1 // Copyright (c) 2015-2022 Clearmatics Technologies Ltd
 
    3 // SPDX-License-Identifier: LGPL-3.0+
 
    5 #ifndef __ZETH_CIRCUITS_JOINSPLIT_TCC__
 
    6 #define __ZETH_CIRCUITS_JOINSPLIT_TCC__
 
    8 #include "libzeth/circuits/notes/note.hpp"
 
    9 #include "libzeth/circuits/safe_arithmetic.hpp"
 
   10 #include "libzeth/core/joinsplit_input.hpp"
 
   11 #include "libzeth/core/merkle_tree_field.hpp"
 
   12 #include "libzeth/zeth_constants.hpp"
 
   14 #include <boost/static_assert.hpp>
 
   26 class joinsplit_gadget : public libsnark::gadget<FieldT>
 
   29     const size_t digest_len_minus_field_cap =
 
   30         subtract_with_clamp(HashT::get_digest_len(), FieldT::capacity());
 
   32     // Number of residual bits from packing of hash digests into smaller
 
   33     // field elements to which are added the public value of size 64 bits
 
   34     const size_t length_bit_residual =
 
   35         2 * ZETH_V_SIZE + digest_len_minus_field_cap * (1 + 2 * NumInputs);
 
   36     // Number of field elements needed to pack this number of bits
 
   37     const size_t nb_field_residual =
 
   38         libff::div_ceil(length_bit_residual, FieldT::capacity());
 
   40     // Multipacking gadgets for the inputs (nullifierS, hsig, message
 
   41     // authentication tags (h_is) and the residual bits (comprising the
 
   42     // previous variables' bits not containable in a single field element as
 
   43     // well as the public values) (the root and cms are field elements)
 
   44     // because we pack the nullifiers (Inputs of JS = NumInputs),
 
   45     // AND the signature hash h_sig (+1) AND the message authentication tags
 
   46     // h_iS (+ NumInputs) AND the residual field elements
 
   47     // which aggregate the extra bits and public values (+1)
 
   49         libsnark::pb_variable_array<FieldT>,
 
   50         NumInputs + 1 + NumInputs + 1>
 
   53         libsnark::pb_variable_array<FieldT>,
 
   54         NumInputs + 1 + NumInputs + 1>
 
   57     // We use an array of multipackers here instead of a single packer that
 
   60         std::shared_ptr<libsnark::multipacking_gadget<FieldT>>,
 
   61         NumInputs + 1 + NumInputs + 1>
 
   64     libsnark::pb_variable<FieldT> ZERO;
 
   66     // PUBLIC DATA: to be made available to the mixer
 
   69     std::shared_ptr<libsnark::pb_variable<FieldT>> merkle_root;
 
   70     // List of nullifiers of the notes to spend
 
   71     std::array<std::shared_ptr<libsnark::digest_variable<FieldT>>, NumInputs>
 
   73     // List of commitments generated for the new notes
 
   74     libsnark::pb_variable_array<FieldT> output_commitments;
 
   75     // Public value that is put into the mix
 
   76     libsnark::pb_variable_array<FieldT> zk_vpub_in;
 
   77     // Value that is taken out of the mix
 
   78     libsnark::pb_variable_array<FieldT> zk_vpub_out;
 
   79     // Sighash h_sig := hSigCRH(randomSeed, {nf_old},
 
   80     // joinSplitPubKey) (p.53 ZCash proto. spec.)
 
   81     std::shared_ptr<libsnark::digest_variable<FieldT>> h_sig;
 
   82     // List of message authentication tags
 
   83     std::array<std::shared_ptr<libsnark::digest_variable<FieldT>>, NumInputs>
 
   86     // PRIVATE DATA: must be auxiliary (private) inputs to the statement.
 
   87     // Protoboard owner is responsible for ensuring this. (Note that the PUBLIC
 
   88     // inputs above are allocated first, so only the first
 
   89     // get_num_public_elements allocated by this gadget are "public").
 
   91     // Total amount transferred in the transaction
 
   92     libsnark::pb_variable_array<FieldT> zk_total_uint64;
 
   93     // List of all spending keys
 
   94     std::array<std::shared_ptr<libsnark::digest_variable<FieldT>>, NumInputs>
 
   96     // List of all output rhos
 
   97     std::array<std::shared_ptr<libsnark::digest_variable<FieldT>>, NumOutputs>
 
   99     // random seed for uniqueness of the new rho
 
  100     std::shared_ptr<libsnark::digest_variable<FieldT>> phi;
 
  102     // Input note gadgets
 
  104         std::shared_ptr<input_note_gadget<FieldT, HashT, HashTreeT, TreeDepth>>,
 
  107     // Message authentication tag gadgets
 
  108     std::array<std::shared_ptr<PRF_pk_gadget<FieldT, HashT>>, NumInputs>
 
  112     std::array<std::shared_ptr<PRF_rho_gadget<FieldT, HashT>>, NumOutputs>
 
  114     // Output note gadgets
 
  115     std::array<std::shared_ptr<output_note_gadget<FieldT, HashT>>, NumOutputs>
 
  119     // Make sure that we do not exceed the number of inputs/outputs
 
  120     // specified in zeth's configuration file (see: zeth.h file)
 
  121     BOOST_STATIC_ASSERT(NumInputs <= ZETH_NUM_JS_INPUTS);
 
  122     BOOST_STATIC_ASSERT(NumOutputs <= ZETH_NUM_JS_OUTPUTS);
 
  124     // Primary inputs are packed to be added to the extended proof and given to
 
  125     // the verifier on-chain
 
  126     explicit joinsplit_gadget(
 
  127         libsnark::protoboard<FieldT> &pb,
 
  128         const std::string &annotation_prefix = "joinsplit_gadget")
 
  129         : libsnark::gadget<FieldT>(pb, annotation_prefix)
 
  131         // Block dedicated to generate the verifier inputs
 
  133             // PUBLIC DATA: allocated first so that the protoboard has access.
 
  135             // Allocation is currently performed here in the following order
 
  136             // (with the protoboard owner determining whether these are primary
 
  137             // or auxiliary inputs to the circuit):
 
  143             // - Residual field element(S)
 
  145             // This yields the following index mappings:
 
  147             //  1, ...                            : Nullifiers (NumInputs)
 
  148             //  1 + NumInputs, ...                : Commitments (Num Outputs)
 
  149             //  1 + NumInputs + NumOutputs        : h_sig
 
  150             //  2 + NumInputs + NumOutputs, ...   : h_iS (NumInputs)
 
  151             //  2 + 2xNumInputs + NumOutputs, ... : v_in, v_out, residual
 
  152             //                                            (nb_field_residual)
 
  154             // We first allocate the root
 
  155             merkle_root.reset(new libsnark::pb_variable<FieldT>);
 
  156             merkle_root->allocate(
 
  157                 pb, FMT(this->annotation_prefix, " merkle_root"));
 
  159             output_commitments.allocate(pb, NumOutputs, " output_commitments");
 
  161             // We allocate a field element for each of the input nullifiers
 
  162             // to pack their first FieldT::capacity() bits
 
  163             for (size_t i = 0; i < NumInputs; i++) {
 
  164                 packed_inputs[i].allocate(
 
  167                     FMT(this->annotation_prefix, " in_nullifier[%zu]", i));
 
  170             // We allocate a field element for h_sig to pack its first
 
  171             // FieldT::capacity() bits
 
  172             packed_inputs[NumInputs].allocate(
 
  173                 pb, 1, FMT(this->annotation_prefix, " h_sig"));
 
  175             // We allocate a field element for each message authentication tags
 
  176             // h_iS to pack their first FieldT::capacity() bits
 
  177             for (size_t i = NumInputs + 1; i < NumInputs + 1 + NumInputs; i++) {
 
  178                 packed_inputs[i].allocate(
 
  179                     pb, 1, FMT(this->annotation_prefix, " h_i[%zu]", i));
 
  182             // We allocate as many field elements as needed to pack the public
 
  183             // values and the hash digests' residual bits
 
  184             packed_inputs[NumInputs + 1 + NumInputs].allocate(
 
  187                 FMT(this->annotation_prefix, " residual_bits"));
 
  189             // Compute the number of packed public elements, and the total
 
  190             // number of public elements (see table above). The "packed" inputs
 
  191             // (those represented as a field element and some residual bits)
 
  193             //   H_sig, nullifier, commitments and h_iS
 
  194             const size_t num_packed_public_elements =
 
  195                 2 * NumInputs + 1 + nb_field_residual;
 
  196             const size_t num_public_elements =
 
  197                 1 + NumOutputs + num_packed_public_elements;
 
  201             // Allocate a ZERO variable
 
  202             // TODO: check whether/why this is actually needed
 
  203             ZERO.allocate(pb, FMT(this->annotation_prefix, " ZERO"));
 
  205             // Initialize the digest_variables
 
  206             phi.reset(new libsnark::digest_variable<FieldT>(
 
  207                 pb, ZETH_PHI_SIZE, FMT(this->annotation_prefix, " phi")));
 
  208             h_sig.reset(new libsnark::digest_variable<FieldT>(
 
  209                 pb, ZETH_HSIG_SIZE, FMT(this->annotation_prefix, " h_sig")));
 
  210             for (size_t i = 0; i < NumInputs; i++) {
 
  211                 input_nullifiers[i].reset(new libsnark::digest_variable<FieldT>(
 
  213                     HashT::get_digest_len(),
 
  214                     FMT(this->annotation_prefix, " input_nullifiers[%zu]", i)));
 
  215                 a_sks[i].reset(new libsnark::digest_variable<FieldT>(
 
  218                     FMT(this->annotation_prefix, " a_sks[%zu]", i)));
 
  219                 h_is[i].reset(new libsnark::digest_variable<FieldT>(
 
  221                     HashT::get_digest_len(),
 
  222                     FMT(this->annotation_prefix, " h_is[%zu]", i)));
 
  224             for (size_t i = 0; i < NumOutputs; i++) {
 
  225                 rho_is[i].reset(new libsnark::digest_variable<FieldT>(
 
  227                     HashT::get_digest_len(),
 
  228                     FMT(this->annotation_prefix, " rho_is[%zu]", i)));
 
  231             // Allocate the zk_vpub_in and zk_vpub_out
 
  233                 pb, ZETH_V_SIZE, FMT(this->annotation_prefix, " zk_vpub_in"));
 
  234             zk_vpub_out.allocate(
 
  235                 pb, ZETH_V_SIZE, FMT(this->annotation_prefix, " zk_vpub_out"));
 
  237             // Assign digests to unpacked field elements and residual bits.
 
  238             // Note that the order here dictates the layout of residual bits
 
  239             // (from lowest order to highest order):
 
  243             //   h_0, ..., h_{num_inputs},
 
  244             //   nf_0, ..., nf_{num_inputs},
 
  247             // where vpub_out and vpub_in are each 64 bits.
 
  248             libsnark::pb_variable_array<FieldT> &residual_bits =
 
  249                 unpacked_inputs[NumInputs + 1 + NumInputs];
 
  251             // Assign the public output and input values to the first residual
 
  252             // bits (in this way, they will always appear in the same place in
 
  253             // the field element).
 
  254             assign_public_value_to_residual_bits(zk_vpub_out, residual_bits);
 
  255             assign_public_value_to_residual_bits(zk_vpub_in, residual_bits);
 
  257             // Initialize the unpacked input corresponding to the h_is
 
  258             for (size_t i = NumInputs + 1, j = 0;
 
  259                  i < NumInputs + 1 + NumInputs && j < NumInputs;
 
  261                 digest_variable_assign_to_field_element_and_residual(
 
  262                     *h_is[j], unpacked_inputs[i], residual_bits);
 
  265             // Initialize the unpacked input corresponding to the input
 
  267             for (size_t i = 0; i < NumInputs; i++) {
 
  268                 digest_variable_assign_to_field_element_and_residual(
 
  269                     *input_nullifiers[i], unpacked_inputs[i], residual_bits);
 
  272             // Initialize the unpacked input corresponding to the h_sig
 
  273             digest_variable_assign_to_field_element_and_residual(
 
  274                 *h_sig, unpacked_inputs[NumInputs], residual_bits);
 
  277             // The root is a FieldT, hence is not packed, likewise for the cms.
 
  278             // The size of the packed inputs should be 2*NumInputs + 1 + 1
 
  279             // since we are packing all the inputs nullifiers + the h_is +
 
  280             // + the h_sig + the residual bits
 
  281             assert(packed_inputs.size() == NumInputs + 1 + NumInputs + 1);
 
  282             assert(num_packed_public_elements == [this]() {
 
  284                 for (const auto &i : packed_inputs) {
 
  285                     sum = sum + i.size();
 
  289             assert(num_public_elements == get_num_public_elements());
 
  290             (void)num_public_elements;
 
  292             // [SANITY CHECK] Total size of unpacked inputs
 
  293             size_t total_size_unpacked_inputs = 0;
 
  294             for (size_t i = 0; i < NumInputs + 1 + NumInputs + 1; i++) {
 
  295                 total_size_unpacked_inputs += unpacked_inputs[i].size();
 
  298                 total_size_unpacked_inputs == get_unpacked_inputs_bit_size());
 
  300             // These gadgets will ensure that all of the inputs we provide are
 
  301             // boolean constrained, and and correctly packed into field elements
 
  302             // We basically build the public inputs here
 
  304             // 1. Pack the nullifiers
 
  305             for (size_t i = 0; i < NumInputs; i++) {
 
  306                 packers[i].reset(new libsnark::multipacking_gadget<FieldT>(
 
  311                     FMT(this->annotation_prefix,
 
  312                         " packer_nullifiers[%zu]",
 
  317             packers[NumInputs].reset(new libsnark::multipacking_gadget<FieldT>(
 
  319                 unpacked_inputs[NumInputs],
 
  320                 packed_inputs[NumInputs],
 
  322                 FMT(this->annotation_prefix, " packer_h_sig")));
 
  325             for (size_t i = NumInputs + 1; i < NumInputs + 1 + NumInputs; i++) {
 
  326                 packers[i].reset(new libsnark::multipacking_gadget<FieldT>(
 
  331                     FMT(this->annotation_prefix, " packer_h_i[%zu]", i)));
 
  334             // 4. Pack the other values and residual bits
 
  335             packers[NumInputs + 1 + NumInputs].reset(
 
  336                 new libsnark::multipacking_gadget<FieldT>(
 
  339                     packed_inputs[NumInputs + 1 + NumInputs],
 
  341                     FMT(this->annotation_prefix, " packer_residual_bits")));
 
  343         } // End of the block dedicated to generate the verifier inputs
 
  345         zk_total_uint64.allocate(
 
  346             pb, ZETH_V_SIZE, FMT(this->annotation_prefix, " zk_total"));
 
  348         // Input note gadgets for commitments, nullifiers, and spend authority
 
  349         // as well as PRF gadgets for the h_iS
 
  350         for (size_t i = 0; i < NumInputs; i++) {
 
  351             input_notes[i].reset(
 
  352                 new input_note_gadget<FieldT, HashT, HashTreeT, TreeDepth>(
 
  353                     pb, ZERO, a_sks[i], input_nullifiers[i], *merkle_root));
 
  355             h_i_gadgets[i].reset(new PRF_pk_gadget<FieldT, HashT>(
 
  356                 pb, ZERO, a_sks[i]->bits, h_sig->bits, i, h_is[i]));
 
  359         // Output note gadgets for commitments as well as PRF gadgets for the
 
  361         for (size_t i = 0; i < NumOutputs; i++) {
 
  362             rho_i_gadgets[i].reset(new PRF_rho_gadget<FieldT, HashT>(
 
  363                 pb, ZERO, phi->bits, h_sig->bits, i, rho_is[i]));
 
  365             output_notes[i].reset(new output_note_gadget<FieldT, HashT>(
 
  366                 pb, rho_is[i], output_commitments[i]));
 
  370     // Check the booleaness of packing variables
 
  371     // Check the booleaness of phi and the a_sks
 
  372     // Check value of ZERO (i.e. that ZERO = FieldT::zero())
 
  373     // Check input notes, output notes, h_iS and rhoS are correctly computed
 
  374     // Check the joinsplit is balanced
 
  375     // N.B. note_gadget checks the booleaness of v and r_trap
 
  376     // N.B. input_note_gadget checks the booleaness of rho^old
 
  377     // N.B. output_note_gadget checks the booleaness of of a_pk^new
 
  378     void generate_r1cs_constraints()
 
  380         // The `true` passed to `generate_r1cs_constraints` ensures that all
 
  381         // inputs are boolean strings
 
  382         for (size_t i = 0; i < packers.size(); i++) {
 
  383             packers[i]->generate_r1cs_constraints(true);
 
  386         // Constrain the not-packed digest variables, ensure there are 256 bit
 
  387         // long boolean arrays
 
  388         phi->generate_r1cs_constraints();
 
  389         for (size_t i = 0; i < NumInputs; i++) {
 
  390             a_sks[i]->generate_r1cs_constraints();
 
  393         // Constrain `ZERO`: Make sure that the ZERO variable is the zero of the
 
  395         libsnark::generate_r1cs_equals_const_constraint<FieldT>(
 
  399             FMT(this->annotation_prefix, " ZERO"));
 
  401         // Constrain the JoinSplit inputs and the h_iS
 
  402         for (size_t i = 0; i < NumInputs; i++) {
 
  403             input_notes[i]->generate_r1cs_constraints();
 
  404             h_i_gadgets[i]->generate_r1cs_constraints();
 
  407         // Constrain the JoinSplit outputs and the output rho_iS
 
  408         for (size_t i = 0; i < NumOutputs; i++) {
 
  409             rho_i_gadgets[i]->generate_r1cs_constraints();
 
  410             output_notes[i]->generate_r1cs_constraints();
 
  413         // Generate the constraints to ensure that the condition of the
 
  414         // joinsplit holds (ie: LHS = RHS)
 
  417             libsnark::linear_combination<FieldT> left_side =
 
  418                 packed_addition(zk_vpub_in);
 
  419             for (size_t i = 0; i < NumInputs; i++) {
 
  420                 left_side = left_side + packed_addition(input_notes[i]->value);
 
  424             libsnark::linear_combination<FieldT> right_side =
 
  425                 packed_addition(zk_vpub_out);
 
  426             for (size_t i = 0; i < NumOutputs; i++) {
 
  428                     right_side + packed_addition(output_notes[i]->value);
 
  431             // Ensure that both sides are equal (ie: 1 * left_side = right_side)
 
  432             this->pb.add_r1cs_constraint(
 
  433                 libsnark::r1cs_constraint<FieldT>(1, left_side, right_side),
 
  434                 FMT(this->annotation_prefix, " lhs_rhs_equality_constraint"));
 
  436             // See: https://github.com/zcash/zcash/issues/854
 
  437             // Ensure that `left_side` is a 64-bit integer
 
  438             for (size_t i = 0; i < ZETH_V_SIZE; i++) {
 
  439                 libsnark::generate_boolean_r1cs_constraint<FieldT>(
 
  442                     FMT(this->annotation_prefix, " zk_total_uint64[%zu]", i));
 
  445             this->pb.add_r1cs_constraint(
 
  446                 libsnark::r1cs_constraint<FieldT>(
 
  447                     1, left_side, packed_addition(zk_total_uint64)),
 
  448                 FMT(this->annotation_prefix, " lhs_equal_zk_total_constraint"));
 
  452     void generate_r1cs_witness(
 
  454         const std::array<joinsplit_input<FieldT, TreeDepth>, NumInputs> &inputs,
 
  455         const std::array<zeth_note, NumOutputs> &outputs,
 
  458         const bits256 h_sig_in,
 
  459         const bits256 phi_in)
 
  462         this->pb.val(ZERO) = FieldT::zero();
 
  464         // Witness the merkle root
 
  465         this->pb.val(*merkle_root) = rt;
 
  467         // Witness public values
 
  469         // Witness LHS public value
 
  470         vpub_in.fill_pb_variable_array(this->pb, zk_vpub_in);
 
  472         // Witness RHS public value
 
  473         vpub_out.fill_pb_variable_array(this->pb, zk_vpub_out);
 
  476         h_sig->generate_r1cs_witness(h_sig_in.to_vector());
 
  478         // Witness the h_iS, a_sk and rho_iS
 
  479         for (size_t i = 0; i < NumInputs; i++) {
 
  480             a_sks[i]->generate_r1cs_witness(
 
  481                 inputs[i].spending_key_a_sk.to_vector());
 
  485         phi->generate_r1cs_witness(phi_in.to_vector());
 
  488             // Witness total_uint64 bits
 
  489             // We add binary numbers here see:
 
  490             // https://stackoverflow.com/questions/13282825/adding-binary-numbers-in-c
 
  491             // To check left_side_acc < 2^64, we set the function's bool to true
 
  492             bits64 left_side_acc = vpub_in;
 
  493             for (size_t i = 0; i < NumInputs; i++) {
 
  494                 left_side_acc = bits_add<ZETH_V_SIZE>(
 
  495                     left_side_acc, inputs[i].note.value, true);
 
  498             left_side_acc.fill_pb_variable_array(this->pb, zk_total_uint64);
 
  501         // Witness the JoinSplit inputs and the h_is
 
  502         for (size_t i = 0; i < NumInputs; i++) {
 
  503             input_notes[i]->generate_r1cs_witness(
 
  504                 inputs[i].witness_merkle_path,
 
  505                 inputs[i].address_bits,
 
  508             h_i_gadgets[i]->generate_r1cs_witness();
 
  511         // Witness the JoinSplit outputs
 
  512         for (size_t i = 0; i < NumOutputs; i++) {
 
  513             rho_i_gadgets[i]->generate_r1cs_witness();
 
  514             output_notes[i]->generate_r1cs_witness(outputs[i]);
 
  517         // This happens last, because only by now are all the
 
  518         // verifier inputs resolved.
 
  519         for (size_t i = 0; i < packers.size(); i++) {
 
  520             packers[i]->generate_r1cs_witness_from_bits();
 
  524     // Given a digest variable, assign to an unpacked field element
 
  525     // `unpacked_element` and unpacked element holding residual bits.
 
  526     void digest_variable_assign_to_field_element_and_residual(
 
  527         const libsnark::digest_variable<FieldT> &digest_var,
 
  528         libsnark::pb_variable_array<FieldT> &unpacked_element,
 
  529         libsnark::pb_variable_array<FieldT> &unpacked_residual_bits)
 
  531         const size_t field_capacity = FieldT::capacity();
 
  533         // Digest_var holds bits high-order first. pb_variable_array will be
 
  534         // packed with low-order bit first to match the evm.
 
  536         // The field element holds the lowest order bits ordered 256 -
 
  537         // digest_len_minus_field_cap bits.
 
  538         unpacked_element.insert(
 
  539             unpacked_element.end(),
 
  540             digest_var.bits.rbegin(),
 
  541             digest_var.bits.rbegin() + field_capacity);
 
  543         // The remaining high order bits are appended to
 
  544         // unpacked_residual_bits.
 
  545         unpacked_residual_bits.insert(
 
  546             unpacked_residual_bits.end(),
 
  547             digest_var.bits.rbegin() + field_capacity,
 
  548             digest_var.bits.rend());
 
  551     static void assign_public_value_to_residual_bits(
 
  552         const libsnark::pb_variable_array<FieldT> &unpacked_public_value,
 
  553         libsnark::pb_variable_array<FieldT> &unpacked_residual_bits)
 
  555         unpacked_residual_bits.insert(
 
  556             unpacked_residual_bits.end(),
 
  557             unpacked_public_value.rbegin(),
 
  558             unpacked_public_value.rend());
 
  561     // Computes the total bit-length of the primary inputs
 
  562     static size_t get_inputs_bit_size()
 
  566         // Bit-length of the Merkle Root
 
  567         acc += FieldT::capacity();
 
  569         // Bit-length of the CommitmentS
 
  570         for (size_t i = 0; i < NumOutputs; i++) {
 
  571             acc += FieldT::capacity();
 
  574         // Bit-length of the NullifierS
 
  575         for (size_t i = 0; i < NumInputs; i++) {
 
  576             acc += HashT::get_digest_len();
 
  579         // Bit-length of vpub_in
 
  582         // Bit-length of vpub_out
 
  585         // Bit-length of h_sig
 
  586         acc += HashT::get_digest_len();
 
  588         // Bit-length of the h_iS
 
  589         for (size_t i = 0; i < NumInputs; i++) {
 
  590             acc += HashT::get_digest_len();
 
  596     // Computes the total bit-length of the unpacked primary inputs
 
  597     static size_t get_unpacked_inputs_bit_size()
 
  599         // The Merkle root and commitments are not in the `unpacked_inputs`
 
  600         // so we subtract their bit-length to get the total bit-length of
 
  601         // the primary inputs in `unpacked_inputs`
 
  602         return get_inputs_bit_size() - (1 + NumOutputs) * FieldT::capacity();
 
  605     // Computes the number of field elements in the public data
 
  606     static size_t get_num_public_elements()
 
  608         size_t nb_elements = 0;
 
  610         // The merkle root is represented by 1 field element (bit_length(root) =
 
  611         // FieldT::capacity())
 
  614         // Each commitment is represented by 1 field element (bit_length(cm) =
 
  615         // FieldT::capacity())
 
  616         for (size_t i = 0; i < NumOutputs; i++) {
 
  620         // Each nullifier is represented by 1 field element and
 
  621         // (HashT::get_digest_len() - FieldT::capacity()) bits we aggregate in
 
  622         // the residual field element(s) later on (c.f. last incrementation)
 
  623         for (size_t i = 0; i < NumInputs; i++) {
 
  627         // The h_sig is represented 1 field element and (HashT::get_digest_len()
 
  628         // - FieldT::capacity()) bits we aggregate in the residual field
 
  629         // element(s) later on (c.f. last incrementation)
 
  632         // Each authentication tag is represented by 1 field element and
 
  633         // (HashT::get_digest_len() - FieldT::capacity()) bits we aggregate in
 
  634         // the residual field element(s) later on (c.f. last incrementation)
 
  635         for (size_t i = 0; i < NumInputs; i++) {
 
  639         // Residual bits and public values (in and out) aggregated in
 
  640         // `nb_field_residual` field elements
 
  641         nb_elements += libff::div_ceil(
 
  642             2 * ZETH_V_SIZE + subtract_with_clamp(
 
  643                                   HashT::get_digest_len(), FieldT::capacity()) *
 
  651 } // namespace libzeth
 
  653 #endif // __ZETH_CIRCUITS_JOINSPLIT_TCC__