Zeth - Zerocash on Ethereum  0.8
Reference implementation of the Zeth protocol by Clearmatics
joinsplit.tcc
Go to the documentation of this file.
1 // Copyright (c) 2015-2022 Clearmatics Technologies Ltd
2 //
3 // SPDX-License-Identifier: LGPL-3.0+
4 
5 #ifndef __ZETH_CIRCUITS_JOINSPLIT_TCC__
6 #define __ZETH_CIRCUITS_JOINSPLIT_TCC__
7 
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"
13 
14 #include <boost/static_assert.hpp>
15 
16 namespace libzeth
17 {
18 
19 template<
20  typename FieldT,
21  typename HashT,
22  typename HashTreeT,
23  size_t NumInputs,
24  size_t NumOutputs,
25  size_t TreeDepth>
26 class joinsplit_gadget : public libsnark::gadget<FieldT>
27 {
28 private:
29  const size_t digest_len_minus_field_cap =
30  subtract_with_clamp(HashT::get_digest_len(), FieldT::capacity());
31 
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());
39 
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)
48  std::array<
49  libsnark::pb_variable_array<FieldT>,
50  NumInputs + 1 + NumInputs + 1>
51  packed_inputs;
52  std::array<
53  libsnark::pb_variable_array<FieldT>,
54  NumInputs + 1 + NumInputs + 1>
55  unpacked_inputs;
56 
57  // We use an array of multipackers here instead of a single packer that
58  // packs everything.
59  std::array<
60  std::shared_ptr<libsnark::multipacking_gadget<FieldT>>,
61  NumInputs + 1 + NumInputs + 1>
62  packers;
63 
64  libsnark::pb_variable<FieldT> ZERO;
65 
66  // PUBLIC DATA: to be made available to the mixer
67 
68  // Merkle Root
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>
72  input_nullifiers;
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>
84  h_is;
85 
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").
90 
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>
95  a_sks;
96  // List of all output rhos
97  std::array<std::shared_ptr<libsnark::digest_variable<FieldT>>, NumOutputs>
98  rho_is;
99  // random seed for uniqueness of the new rho
100  std::shared_ptr<libsnark::digest_variable<FieldT>> phi;
101 
102  // Input note gadgets
103  std::array<
104  std::shared_ptr<input_note_gadget<FieldT, HashT, HashTreeT, TreeDepth>>,
105  NumInputs>
106  input_notes;
107  // Message authentication tag gadgets
108  std::array<std::shared_ptr<PRF_pk_gadget<FieldT, HashT>>, NumInputs>
109  h_i_gadgets;
110 
111  // Rho PRF gadgets
112  std::array<std::shared_ptr<PRF_rho_gadget<FieldT, HashT>>, NumOutputs>
113  rho_i_gadgets;
114  // Output note gadgets
115  std::array<std::shared_ptr<output_note_gadget<FieldT, HashT>>, NumOutputs>
116  output_notes;
117 
118 public:
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);
123 
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)
130  {
131  // Block dedicated to generate the verifier inputs
132  {
133  // PUBLIC DATA: allocated first so that the protoboard has access.
134  //
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):
138  // - Root
139  // - NullifierS
140  // - CommitmentS
141  // - h_sig
142  // - h_iS
143  // - Residual field element(S)
144  //
145  // This yields the following index mappings:
146  // 0 : "Root"
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)
153 
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"));
158 
159  output_commitments.allocate(pb, NumOutputs, " output_commitments");
160 
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(
165  pb,
166  1,
167  FMT(this->annotation_prefix, " in_nullifier[%zu]", i));
168  }
169 
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"));
174 
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));
180  }
181 
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(
185  pb,
186  nb_field_residual,
187  FMT(this->annotation_prefix, " residual_bits"));
188 
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)
192  // are:
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;
198 
199  // PRIVATE DATA:
200 
201  // Allocate a ZERO variable
202  // TODO: check whether/why this is actually needed
203  ZERO.allocate(pb, FMT(this->annotation_prefix, " ZERO"));
204 
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>(
212  pb,
213  HashT::get_digest_len(),
214  FMT(this->annotation_prefix, " input_nullifiers[%zu]", i)));
215  a_sks[i].reset(new libsnark::digest_variable<FieldT>(
216  pb,
217  ZETH_A_SK_SIZE,
218  FMT(this->annotation_prefix, " a_sks[%zu]", i)));
219  h_is[i].reset(new libsnark::digest_variable<FieldT>(
220  pb,
221  HashT::get_digest_len(),
222  FMT(this->annotation_prefix, " h_is[%zu]", i)));
223  }
224  for (size_t i = 0; i < NumOutputs; i++) {
225  rho_is[i].reset(new libsnark::digest_variable<FieldT>(
226  pb,
227  HashT::get_digest_len(),
228  FMT(this->annotation_prefix, " rho_is[%zu]", i)));
229  }
230 
231  // Allocate the zk_vpub_in and zk_vpub_out
232  zk_vpub_in.allocate(
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"));
236 
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):
240  //
241  // vpub_out,
242  // vpub_in
243  // h_0, ..., h_{num_inputs},
244  // nf_0, ..., nf_{num_inputs},
245  // h_sig,
246  //
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];
250 
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);
256 
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;
260  i++, j++) {
261  digest_variable_assign_to_field_element_and_residual(
262  *h_is[j], unpacked_inputs[i], residual_bits);
263  }
264 
265  // Initialize the unpacked input corresponding to the input
266  // NullifierS
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);
270  }
271 
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);
275 
276  // [SANITY CHECK]
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]() {
283  size_t sum = 0;
284  for (const auto &i : packed_inputs) {
285  sum = sum + i.size();
286  }
287  return sum;
288  }());
289  assert(num_public_elements == get_num_public_elements());
290  (void)num_public_elements;
291 
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();
296  }
297  assert(
298  total_size_unpacked_inputs == get_unpacked_inputs_bit_size());
299 
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
303  //
304  // 1. Pack the nullifiers
305  for (size_t i = 0; i < NumInputs; i++) {
306  packers[i].reset(new libsnark::multipacking_gadget<FieldT>(
307  pb,
308  unpacked_inputs[i],
309  packed_inputs[i],
310  FieldT::capacity(),
311  FMT(this->annotation_prefix,
312  " packer_nullifiers[%zu]",
313  i)));
314  }
315 
316  // 2. Pack the h_sig
317  packers[NumInputs].reset(new libsnark::multipacking_gadget<FieldT>(
318  pb,
319  unpacked_inputs[NumInputs],
320  packed_inputs[NumInputs],
321  FieldT::capacity(),
322  FMT(this->annotation_prefix, " packer_h_sig")));
323 
324  // 3. Pack the h_iS
325  for (size_t i = NumInputs + 1; i < NumInputs + 1 + NumInputs; i++) {
326  packers[i].reset(new libsnark::multipacking_gadget<FieldT>(
327  pb,
328  unpacked_inputs[i],
329  packed_inputs[i],
330  FieldT::capacity(),
331  FMT(this->annotation_prefix, " packer_h_i[%zu]", i)));
332  }
333 
334  // 4. Pack the other values and residual bits
335  packers[NumInputs + 1 + NumInputs].reset(
336  new libsnark::multipacking_gadget<FieldT>(
337  pb,
338  residual_bits,
339  packed_inputs[NumInputs + 1 + NumInputs],
340  FieldT::capacity(),
341  FMT(this->annotation_prefix, " packer_residual_bits")));
342 
343  } // End of the block dedicated to generate the verifier inputs
344 
345  zk_total_uint64.allocate(
346  pb, ZETH_V_SIZE, FMT(this->annotation_prefix, " zk_total"));
347 
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));
354 
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]));
357  }
358 
359  // Output note gadgets for commitments as well as PRF gadgets for the
360  // rho_is
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]));
364 
365  output_notes[i].reset(new output_note_gadget<FieldT, HashT>(
366  pb, rho_is[i], output_commitments[i]));
367  }
368  }
369 
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()
379  {
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);
384  }
385 
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();
391  }
392 
393  // Constrain `ZERO`: Make sure that the ZERO variable is the zero of the
394  // field
395  libsnark::generate_r1cs_equals_const_constraint<FieldT>(
396  this->pb,
397  ZERO,
398  FieldT::zero(),
399  FMT(this->annotation_prefix, " ZERO"));
400 
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();
405  }
406 
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();
411  }
412 
413  // Generate the constraints to ensure that the condition of the
414  // joinsplit holds (ie: LHS = RHS)
415  {
416  // Compute the LHS
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);
421  }
422 
423  // Compute the RHS
424  libsnark::linear_combination<FieldT> right_side =
425  packed_addition(zk_vpub_out);
426  for (size_t i = 0; i < NumOutputs; i++) {
427  right_side =
428  right_side + packed_addition(output_notes[i]->value);
429  }
430 
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"));
435 
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>(
440  this->pb,
441  zk_total_uint64[i],
442  FMT(this->annotation_prefix, " zk_total_uint64[%zu]", i));
443  }
444 
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"));
449  }
450  }
451 
452  void generate_r1cs_witness(
453  const FieldT &rt,
454  const std::array<joinsplit_input<FieldT, TreeDepth>, NumInputs> &inputs,
455  const std::array<zeth_note, NumOutputs> &outputs,
456  bits64 vpub_in,
457  bits64 vpub_out,
458  const bits256 h_sig_in,
459  const bits256 phi_in)
460  {
461  // Witness `zero`
462  this->pb.val(ZERO) = FieldT::zero();
463 
464  // Witness the merkle root
465  this->pb.val(*merkle_root) = rt;
466 
467  // Witness public values
468  //
469  // Witness LHS public value
470  vpub_in.fill_pb_variable_array(this->pb, zk_vpub_in);
471 
472  // Witness RHS public value
473  vpub_out.fill_pb_variable_array(this->pb, zk_vpub_out);
474 
475  // Witness h_sig
476  h_sig->generate_r1cs_witness(h_sig_in.to_vector());
477 
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());
482  }
483 
484  // Witness phi
485  phi->generate_r1cs_witness(phi_in.to_vector());
486 
487  {
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);
496  }
497 
498  left_side_acc.fill_pb_variable_array(this->pb, zk_total_uint64);
499  }
500 
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,
506  inputs[i].note);
507 
508  h_i_gadgets[i]->generate_r1cs_witness();
509  }
510 
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]);
515  }
516 
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();
521  }
522  }
523 
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)
530  {
531  const size_t field_capacity = FieldT::capacity();
532 
533  // Digest_var holds bits high-order first. pb_variable_array will be
534  // packed with low-order bit first to match the evm.
535 
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);
542 
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());
549  }
550 
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)
554  {
555  unpacked_residual_bits.insert(
556  unpacked_residual_bits.end(),
557  unpacked_public_value.rbegin(),
558  unpacked_public_value.rend());
559  }
560 
561  // Computes the total bit-length of the primary inputs
562  static size_t get_inputs_bit_size()
563  {
564  size_t acc = 0;
565 
566  // Bit-length of the Merkle Root
567  acc += FieldT::capacity();
568 
569  // Bit-length of the CommitmentS
570  for (size_t i = 0; i < NumOutputs; i++) {
571  acc += FieldT::capacity();
572  }
573 
574  // Bit-length of the NullifierS
575  for (size_t i = 0; i < NumInputs; i++) {
576  acc += HashT::get_digest_len();
577  }
578 
579  // Bit-length of vpub_in
580  acc += ZETH_V_SIZE;
581 
582  // Bit-length of vpub_out
583  acc += ZETH_V_SIZE;
584 
585  // Bit-length of h_sig
586  acc += HashT::get_digest_len();
587 
588  // Bit-length of the h_iS
589  for (size_t i = 0; i < NumInputs; i++) {
590  acc += HashT::get_digest_len();
591  }
592 
593  return acc;
594  }
595 
596  // Computes the total bit-length of the unpacked primary inputs
597  static size_t get_unpacked_inputs_bit_size()
598  {
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();
603  }
604 
605  // Computes the number of field elements in the public data
606  static size_t get_num_public_elements()
607  {
608  size_t nb_elements = 0;
609 
610  // The merkle root is represented by 1 field element (bit_length(root) =
611  // FieldT::capacity())
612  nb_elements += 1;
613 
614  // Each commitment is represented by 1 field element (bit_length(cm) =
615  // FieldT::capacity())
616  for (size_t i = 0; i < NumOutputs; i++) {
617  nb_elements += 1;
618  }
619 
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++) {
624  nb_elements += 1;
625  }
626 
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)
630  nb_elements += 1;
631 
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++) {
636  nb_elements += 1;
637  }
638 
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()) *
644  (1 + 2 * NumInputs),
645  FieldT::capacity());
646 
647  return nb_elements;
648  }
649 };
650 
651 } // namespace libzeth
652 
653 #endif // __ZETH_CIRCUITS_JOINSPLIT_TCC__