Zeth - Zerocash on Ethereum  0.8
Reference implementation of the Zeth protocol by Clearmatics
note.tcc
Go to the documentation of this file.
1 #ifndef __ZETH_CIRCUITS_NOTE_TCC__
2 #define __ZETH_CIRCUITS_NOTE_TCC__
3 
4 // DISCLAIMER:
5 // Content Taken and adapted from Zcash
6 // https://github.com/zcash/zcash/blob/master/src/zcash/circuit/note.tcc
7 
8 #include "libzeth/circuits/notes/note.hpp"
9 
10 namespace libzeth
11 {
12 
13 template<typename FieldT>
14 note_gadget<FieldT>::note_gadget(
15  libsnark::protoboard<FieldT> &pb, const std::string &annotation_prefix)
16  : libsnark::gadget<FieldT>(pb, annotation_prefix)
17 {
18  value.allocate(
19  pb,
20  ZETH_V_SIZE,
21  FMT(this->annotation_prefix, " value")); // ZETH_V_SIZE = 64
22  r.allocate(
23  pb,
24  ZETH_R_SIZE,
25  FMT(this->annotation_prefix, " r")); // ZETH_R_SIZE = 256
26 }
27 
28 template<typename FieldT> void note_gadget<FieldT>::generate_r1cs_constraints()
29 {
30  for (size_t i = 0; i < ZETH_V_SIZE; i++) {
31  libsnark::generate_boolean_r1cs_constraint<FieldT>(
32  this->pb, value[i], FMT(this->annotation_prefix, " value[%zu]", i));
33  }
34 
35  for (size_t i = 0; i < ZETH_R_SIZE; i++) {
36  libsnark::generate_boolean_r1cs_constraint<FieldT>(
37  this->pb, r[i], FMT(this->annotation_prefix, " r[%zu]", i));
38  }
39 }
40 
41 template<typename FieldT>
42 void note_gadget<FieldT>::generate_r1cs_witness(const zeth_note &note)
43 {
44  note.r.fill_pb_variable_array(this->pb, r);
45  note.value.fill_pb_variable_array(this->pb, value);
46 }
47 
48 // Gadget that makes sure that all conditions are met in order to spend a note:
49 // - The nullifier is correctly computed from a_sk and rho
50 // - The commitment cm is correctly computed from the coin's data
51 // - commitment cm is in the tree of merkle root rt
52 template<typename FieldT, typename HashT, typename HashTreeT, size_t TreeDepth>
53 input_note_gadget<FieldT, HashT, HashTreeT, TreeDepth>::input_note_gadget(
54  libsnark::protoboard<FieldT> &pb,
55  const libsnark::pb_variable<FieldT> &ZERO,
56  std::shared_ptr<libsnark::digest_variable<FieldT>> a_sk,
57  std::shared_ptr<libsnark::digest_variable<FieldT>> nullifier,
58  const libsnark::pb_variable<FieldT> &rt,
59  const std::string &annotation_prefix)
60  : note_gadget<FieldT>(pb, annotation_prefix)
61 {
62  // ZETH_RHO_SIZE = 256
63  rho.allocate(pb, ZETH_RHO_SIZE, " rho");
64  address_bits_va.allocate(
65  pb, TreeDepth, FMT(this->annotation_prefix, " merkle_tree_depth"));
66  a_pk.reset(new libsnark::digest_variable<FieldT>(
67  pb, HashT::get_digest_len(), FMT(this->annotation_prefix, " a_pk")));
68  commitment.allocate(pb, FMT(this->annotation_prefix, " commitment"));
69 
70  libsnark::pb_variable_array<FieldT> *pb_auth_path =
71  new libsnark::pb_variable_array<FieldT>();
72  pb_auth_path->allocate(
73  pb, TreeDepth, FMT(this->annotation_prefix, " authentication_path"));
74  auth_path.reset(pb_auth_path);
75 
76  // Call to the "PRF_addr_a_pk_gadget" to make sure a_pk is correctly
77  // computed from a_sk
78  spend_authority.reset(
79  new PRF_addr_a_pk_gadget<FieldT, HashT>(pb, ZERO, a_sk->bits, a_pk));
80 
81  // Call to the "PRF_nf_gadget" to make sure the nullifier is correctly
82  // computed from a_sk and rho
83  expose_nullifiers.reset(
84  new PRF_nf_gadget<FieldT, HashT>(pb, ZERO, a_sk->bits, rho, nullifier));
85 
86  // Below this point, we need to do several calls
87  // to the commitment gagdets.
88  //
89  // Call to the "note_commitment_gadget" to make sure that the
90  // commitment cm is computed correctly from the coin data
91  // ie: a_pk, value, rho, and trap_r#include "circuits/notes/note.tcc"
92 
93  // This gadget compute the commitment cm (coin commitment)
94  //
95  // Note: In our case it can be useful to retrieve the commitment k if we
96  // want to implement the mint function the same way as it is done in
97  // Zerocash. That way we only need to provide k along with the value when we
98  // deposit onto the mixer. Doing so removes the need to generate a proof
99  // when we deposit However, this comes with a drawback of introducing
100  // different types of function calls on the smart contract and also requires
101  // additional steps/function calls to "pour"/split the newly created
102  // commitment corresponding to a coin of value V, into a set of commitments
103  // corresponding to coins of value v_i such that Sum_i coins.value = V (ie:
104  // this step provides an additional layer of obfuscation and minimizes the
105  // interactions with the mixer (that we know affect the public state and
106  // leak data)).
107  commit_to_inputs_cm.reset(new COMM_cm_gadget<FieldT, HashT>(
108  pb, a_pk->bits, rho, this->r, this->value, commitment));
109 
110  // We do not forget to allocate the `value_enforce` variable
111  // since it is submitted to boolean constraints
112  value_enforce.allocate(pb, FMT(this->annotation_prefix, " value_enforce"));
113 
114  // This gadget makes sure that the computed
115  // commitment is in the merkle tree of root rt
116  // We finally compute a root from the (field) commitment and the
117  // authentication path We furthermore check, depending on value_enforce, if
118  // the computed root is equal to the current one
119  check_membership.reset(new merkle_path_authenticator<FieldT, HashTreeT>(
120  pb,
121  TreeDepth,
122  address_bits_va,
123  commitment,
124  rt,
125  *auth_path,
126  value_enforce, // boolean that is set to ONE if the cm needs to be in
127  // the tree of root rt (and if the given path needs to be
128  // correct), ZERO otherwise
129  FMT(this->annotation_prefix, " auth_path")));
130 }
131 
132 template<typename FieldT, typename HashT, typename HashTreeT, size_t TreeDepth>
133 void input_note_gadget<FieldT, HashT, HashTreeT, TreeDepth>::
134  generate_r1cs_constraints()
135 {
136  // Generate constraints of parent gadget
137  note_gadget<FieldT>::generate_r1cs_constraints();
138 
139  // Generate the constraints for the rho 256-bit string
140  for (size_t i = 0; i < ZETH_RHO_SIZE; i++) { // ZETH_RHO_SIZE = 256
141  libsnark::generate_boolean_r1cs_constraint<FieldT>(
142  this->pb, rho[i], FMT(this->annotation_prefix, " rho"));
143  }
144  spend_authority->generate_r1cs_constraints();
145  expose_nullifiers->generate_r1cs_constraints();
146  commit_to_inputs_cm->generate_r1cs_constraints();
147  // value * (1 - enforce) = 0
148  // Given `enforce` is boolean constrained:
149  // If `value` is zero, `enforce` _can_ be zero.
150  // If `value` is nonzero, `enforce` _must_ be one.
151  libsnark::generate_boolean_r1cs_constraint<FieldT>(
152  this->pb,
153  value_enforce,
154  FMT(this->annotation_prefix, " value_enforce"));
155 
156  this->pb.add_r1cs_constraint(
157  libsnark::r1cs_constraint<FieldT>(
158  packed_addition(this->value), (1 - value_enforce), 0),
159  FMT(this->annotation_prefix, " wrap_constraint_mkpath_dummy_inputs"));
160 
161  check_membership->generate_r1cs_constraints();
162 }
163 
164 template<typename FieldT, typename HashT, typename HashTreeT, size_t TreeDepth>
165 void input_note_gadget<FieldT, HashT, HashTreeT, TreeDepth>::
166  generate_r1cs_witness(
167  const std::vector<FieldT> &merkle_path,
168  const bits_addr<TreeDepth> &address_bits,
169  const zeth_note &note)
170 {
171  // Generate witness of parent gadget
172  note_gadget<FieldT>::generate_r1cs_witness(note);
173 
174  // Witness a_pk for a_sk with PRF_addr
175  spend_authority->generate_r1cs_witness();
176 
177  // Witness rho for the input note
178  note.rho.fill_pb_variable_array(this->pb, rho);
179  // Witness the nullifier for the input note
180  expose_nullifiers->generate_r1cs_witness();
181 
182  // Witness the commitment of the input note
183  commit_to_inputs_cm->generate_r1cs_witness();
184 
185  // Set enforce flag for nonzero input value
186  // Set the enforce flag according to the value of the note
187  // Remember that if the note has a value of 0, we do not enforce the
188  // corresponding commitment to be in the tree. If the value is > 0 though,
189  // we enforce the corresponding commitment to be in the merkle tree of
190  // commitment
191  //
192  // Note: We need to set the value of `value_enforce`, because this bit is
193  // used in the merkle_tree_check_read_gadget which uses a
194  // `field_vector_copy_gadget` that does a check with the computed root and
195  // the root given to the `merkle_tree_check_read_gadget`
196  //
197  // This check is in the form of constraints like:
198  // ```
199  // template<typename FieldT>
200  // void field_vector_copy_gadget<FieldT>::generate_r1cs_constraints(){
201  // for (size_t i = 0; i < source.size(); ++i)
202  // {
203  // this->pb.add_r1cs_constraint(
204  // r1cs_constraint<FieldT>(
205  // do_copy,
206  // source[i] - target[i],
207  // 0
208  // ),
209  // FMT(
210  // this->annotation_prefix, "
211  // copying_check_%zu", i));
212  // }
213  // }
214  // ```
215  // If `do_copy` is set to 0, we basically do NOT compare the computed root
216  // and the given root. This is useful in our case as we can use any random
217  // merkle path with our dummy/0-valued coin and make sure that the computed
218  // root is never compared with the actual root (because they will never be
219  // equal as we used a random merkle path to witness the 0-valued coin)
220  //
221  // Basically `value_enforce` makes sure that we give a VALID merkle auth
222  // path to our commitment or, in other words, that the commitment is in the
223  // tree.
224  //
225  // The `value_enforce` is NOT set by the gagdet, it is set by us to tell
226  // whether we want to verify the commitment is in the tree or whether we
227  // want to render this check useless by having a tautology (ie: 0 = 0 for
228  // each constraints of the field_vector_copy_gadget)
229  //
230  // UPDATE:
231  // The way the variable `value_enforce` works here is that: we give a root
232  // as input to the `merkle_tree_check_read_gadget`. This gadget computes the
233  // root obtained by the verification of the merkle authentication path and
234  // stores the result in `computed_root` which is a digest variable. Then,
235  // the value of the `value_enforce` or `read_successful` variable is checked
236  // to copy the result of the `computed_root` IN the variable `root` which is
237  // the given root If the value of `value_enforce` is FieldT::one() => the
238  // content of `root` is replaced by the content of `computed_root`. Else (if
239  // `value_enforce == FieldT::zero()`), then the value of `root` remains the
240  // same.
241  //
242  // Note that if the given path is not an auth path to the given commitment,
243  // and if the value of `value_enforce` is set to FieldT::one(), then the
244  // value of `root` is changed to the value of the computed root. But because
245  // the merkle root is a public input, it is sent to the verifier. Thus, if
246  // the auth path is not valid, the verifier gets the root value `root`, but
247  // this value is replaced by `computed_root` in the circuit, which should
248  // lead the verification of the proof to fail.
249  //
250  // WARNING: Because we decide to use a single root for ALL the inputs of the
251  // JoinSplit here, we need to be extra careful. Note that if one of the
252  // input oes not have a valid auth path (is not correctly authenticated),
253  // the root (shared by all inputs) will be changed and the proof should be
254  // rejected.
255  this->pb.val(value_enforce) =
256  (note.is_zero_valued()) ? FieldT::zero() : FieldT::one();
257  std::cout << "[DEBUG] Value of `value_enforce`: "
258  << (this->pb.val(value_enforce)).as_ulong() << std::endl;
259 
260  // Witness merkle tree authentication path
261  address_bits.fill_pb_variable_array(this->pb, address_bits_va);
262 
263  // Set auth_path values
264  auth_path->fill_with_field_elements(this->pb, merkle_path);
265 
266  check_membership->generate_r1cs_witness();
267 }
268 
269 // Commit to the output notes of the JS
270 template<typename FieldT, typename HashT>
271 output_note_gadget<FieldT, HashT>::output_note_gadget(
272  libsnark::protoboard<FieldT> &pb,
273  std::shared_ptr<libsnark::digest_variable<FieldT>> rho,
274  const libsnark::pb_variable<FieldT> &commitment,
275  const std::string &annotation_prefix)
276  : note_gadget<FieldT>(pb, annotation_prefix)
277 {
278  a_pk.reset(new libsnark::digest_variable<FieldT>(
279  pb, HashT::get_digest_len(), FMT(this->annotation_prefix, " a_pk")));
280 
281  // Commit to the output notes publicly without disclosing them.
282  commit_to_outputs_cm.reset(new COMM_cm_gadget<FieldT, HashT>(
283  pb, a_pk->bits, rho->bits, this->r, this->value, commitment));
284 }
285 
286 template<typename FieldT, typename HashT>
287 void output_note_gadget<FieldT, HashT>::generate_r1cs_constraints()
288 {
289  // Generate constraints of the parent gadget
290  note_gadget<FieldT>::generate_r1cs_constraints();
291 
292  a_pk->generate_r1cs_constraints();
293  commit_to_outputs_cm->generate_r1cs_constraints();
294 }
295 
296 template<typename FieldT, typename HashT>
297 void output_note_gadget<FieldT, HashT>::generate_r1cs_witness(
298  const zeth_note &note)
299 {
300  // Generate witness of the parent gadget
301  note_gadget<FieldT>::generate_r1cs_witness(note);
302 
303  // Witness a_pk with note information
304  note.a_pk.fill_pb_variable_array(this->pb, a_pk->bits);
305 
306  commit_to_outputs_cm->generate_r1cs_witness();
307 }
308 
309 } // namespace libzeth
310 
311 #endif // __ZETH_CIRCUITS_NOTE_TCC__