Clearmatics Libsnark  0.1
C++ library for zkSNARK proofs
ram_compliance_predicate.tcc
Go to the documentation of this file.
1 /** @file
2  *****************************************************************************
3 
4  Implementation of interfaces for a compliance predicate for RAM.
5 
6  See ram_compliance_predicate.hpp .
7 
8  *****************************************************************************
9  * @author This file is part of libsnark, developed by SCIPR Lab
10  * and contributors (see AUTHORS).
11  * @copyright MIT license (see LICENSE file)
12  *****************************************************************************/
13 
14 #ifndef RAM_COMPLIANCE_PREDICATE_TCC_
15 #define RAM_COMPLIANCE_PREDICATE_TCC_
16 
17 #include <libsnark/gadgetlib1/constraint_profiling.hpp>
18 
19 namespace libsnark
20 {
21 
22 template<typename ramT>
23 ram_pcd_message<ramT>::ram_pcd_message(
24  const size_t type,
25  const ram_architecture_params<ramT> &ap,
26  const size_t timestamp,
27  const libff::bit_vector root_initial,
28  const libff::bit_vector root,
29  const size_t pc_addr,
30  const libff::bit_vector cpu_state,
31  const size_t pc_addr_initial,
32  const libff::bit_vector cpu_state_initial,
33  const bool has_accepted)
34  : r1cs_pcd_message<FieldT>(type)
35  , ap(ap)
36  , timestamp(timestamp)
37  , root_initial(root_initial)
38  , root(root)
39  , pc_addr(pc_addr)
40  , cpu_state(cpu_state)
41  , pc_addr_initial(pc_addr_initial)
42  , cpu_state_initial(cpu_state_initial)
43  , has_accepted(has_accepted)
44 {
45  const size_t digest_size =
46  CRH_with_bit_out_gadget<FieldT>::get_digest_len();
47  assert(libff::log2(timestamp) < ramT::timestamp_length);
48  assert(root_initial.size() == digest_size);
49  assert(root.size() == digest_size);
50  assert(libff::log2(pc_addr) < ap.address_size());
51  assert(cpu_state.size() == ap.cpu_state_size());
52  assert(libff::log2(pc_addr_initial) < ap.address_size());
53  assert(cpu_state_initial.size() == ap.cpu_state_size());
54 }
55 
56 template<typename ramT>
57 libff::bit_vector ram_pcd_message<ramT>::unpacked_payload_as_bits() const
58 {
59  libff::bit_vector result;
60 
61  const libff::bit_vector timestamp_bits =
62  libff::convert_field_element_to_bit_vector<FieldT>(
63  FieldT(timestamp), ramT::timestamp_length);
64  const libff::bit_vector pc_addr_bits =
65  libff::convert_field_element_to_bit_vector<FieldT>(
66  FieldT(pc_addr), ap.address_size());
67  const libff::bit_vector pc_addr_initial_bits =
68  libff::convert_field_element_to_bit_vector<FieldT>(
69  FieldT(pc_addr_initial), ap.address_size());
70 
71  result.insert(result.end(), timestamp_bits.begin(), timestamp_bits.end());
72  result.insert(result.end(), root_initial.begin(), root_initial.end());
73  result.insert(result.end(), root.begin(), root.end());
74  result.insert(result.end(), pc_addr_bits.begin(), pc_addr_bits.end());
75  result.insert(result.end(), cpu_state.begin(), cpu_state.end());
76  result.insert(
77  result.end(), pc_addr_initial_bits.begin(), pc_addr_initial_bits.end());
78  result.insert(
79  result.end(), cpu_state_initial.begin(), cpu_state_initial.end());
80  result.insert(result.end(), has_accepted);
81 
82  assert(result.size() == unpacked_payload_size_in_bits(ap));
83  return result;
84 }
85 
86 template<typename ramT>
87 r1cs_variable_assignment<ram_base_field<ramT>> ram_pcd_message<
88  ramT>::payload_as_r1cs_variable_assignment() const
89 {
90  const libff::bit_vector payload_bits = unpacked_payload_as_bits();
91  const r1cs_variable_assignment<FieldT> result =
92  libff::pack_bit_vector_into_field_element_vector<FieldT>(payload_bits);
93  return result;
94 }
95 
96 template<typename ramT>
97 void ram_pcd_message<ramT>::print_bits(const libff::bit_vector &bv) const
98 {
99  for (bool b : bv) {
100  printf("%d", b ? 1 : 0);
101  }
102  printf("\n");
103 }
104 
105 template<typename ramT> void ram_pcd_message<ramT>::print() const
106 {
107  printf("ram_pcd_message:\n");
108  printf(" type: %zu\n", this->type);
109  printf(" timestamp: %zu\n", timestamp);
110  printf(" root_initial: ");
111  print_bits(root_initial);
112  printf(" root: ");
113  print_bits(root);
114  printf(" pc_addr: %zu\n", pc_addr);
115  printf(" cpu_state: ");
116  print_bits(cpu_state);
117  printf(" pc_addr_initial: %zu\n", pc_addr_initial);
118  printf(" cpu_state_initial: ");
119  print_bits(cpu_state_initial);
120  printf(" has_accepted: %s\n", has_accepted ? "YES" : "no");
121 }
122 
123 template<typename ramT>
124 size_t ram_pcd_message<ramT>::unpacked_payload_size_in_bits(
125  const ram_architecture_params<ramT> &ap)
126 {
127  const size_t digest_size =
128  CRH_with_bit_out_gadget<FieldT>::get_digest_len();
129 
130  return (
131  ramT::timestamp_length + // timestamp
132  2 * digest_size + // root, root_initial
133  2 * ap.address_size() + // pc_addr, pc_addr_initial
134  2 * ap.cpu_state_size() + // cpu_state, cpu_state_initial
135  1); // has_accepted
136 }
137 
138 template<typename ramT>
139 ram_pcd_message_variable<ramT>::ram_pcd_message_variable(
140  protoboard<FieldT> &pb,
141  const ram_architecture_params<ramT> &ap,
142  const std::string &annotation_prefix)
143  : r1cs_pcd_message_variable<ram_base_field<ramT>>(pb, annotation_prefix)
144  , ap(ap)
145 {
146  const size_t unpacked_payload_size_in_bits =
147  ram_pcd_message<ramT>::unpacked_payload_size_in_bits(ap);
148  const size_t packed_payload_size =
149  libff::div_ceil(unpacked_payload_size_in_bits, FieldT::capacity());
150  packed_payload.allocate(
151  pb, packed_payload_size, FMT(annotation_prefix, " packed_payload"));
152 
153  this->update_all_vars();
154 }
155 
156 template<typename ramT>
157 void ram_pcd_message_variable<ramT>::allocate_unpacked_part()
158 {
159  const size_t digest_size =
160  CRH_with_bit_out_gadget<FieldT>::get_digest_len();
161 
162  timestamp.allocate(
163  this->pb,
164  ramT::timestamp_length,
165  FMT(this->annotation_prefix, " timestamp"));
166  root_initial.allocate(
167  this->pb, digest_size, FMT(this->annotation_prefix, " root_initial"));
168  root.allocate(this->pb, digest_size, FMT(this->annotation_prefix, " root"));
169  pc_addr.allocate(
170  this->pb, ap.address_size(), FMT(this->annotation_prefix, " pc_addr"));
171  cpu_state.allocate(
172  this->pb,
173  ap.cpu_state_size(),
174  FMT(this->annotation_prefix, " cpu_state"));
175  pc_addr_initial.allocate(
176  this->pb,
177  ap.address_size(),
178  FMT(this->annotation_prefix, " pc_addr_initial"));
179  cpu_state_initial.allocate(
180  this->pb,
181  ap.cpu_state_size(),
182  FMT(this->annotation_prefix, " cpu_state_initial"));
183  has_accepted.allocate(
184  this->pb, FMT(this->annotation_prefix, " has_accepted"));
185 
186  all_unpacked_vars.insert(
187  all_unpacked_vars.end(), timestamp.begin(), timestamp.end());
188  all_unpacked_vars.insert(
189  all_unpacked_vars.end(), root_initial.begin(), root_initial.end());
190  all_unpacked_vars.insert(all_unpacked_vars.end(), root.begin(), root.end());
191  all_unpacked_vars.insert(
192  all_unpacked_vars.end(), pc_addr.begin(), pc_addr.end());
193  all_unpacked_vars.insert(
194  all_unpacked_vars.end(), cpu_state.begin(), cpu_state.end());
195  all_unpacked_vars.insert(
196  all_unpacked_vars.end(),
197  pc_addr_initial.begin(),
198  pc_addr_initial.end());
199  all_unpacked_vars.insert(
200  all_unpacked_vars.end(),
201  cpu_state_initial.begin(),
202  cpu_state_initial.end());
203  all_unpacked_vars.insert(all_unpacked_vars.end(), has_accepted);
204 
205  unpack_payload.reset(new multipacking_gadget<FieldT>(
206  this->pb,
207  all_unpacked_vars,
208  packed_payload,
209  FieldT::capacity(),
210  FMT(this->annotation_prefix, " unpack_payload")));
211 }
212 
213 template<typename ramT>
214 void ram_pcd_message_variable<ramT>::generate_r1cs_witness_from_bits()
215 {
216  unpack_payload->generate_r1cs_witness_from_bits();
217 }
218 
219 template<typename ramT>
220 void ram_pcd_message_variable<ramT>::generate_r1cs_witness_from_packed()
221 {
222  unpack_payload->generate_r1cs_witness_from_packed();
223 }
224 
225 template<typename ramT>
226 void ram_pcd_message_variable<ramT>::generate_r1cs_constraints()
227 {
228  unpack_payload->generate_r1cs_constraints(true);
229 }
230 
231 template<typename ramT>
232 std::shared_ptr<r1cs_pcd_message<ram_base_field<ramT>>> ram_pcd_message_variable<
233  ramT>::get_message() const
234 {
235  const size_t type_val = this->pb.val(this->type).as_ulong();
236  const size_t timestamp_val =
237  timestamp.get_field_element_from_bits(this->pb).as_ulong();
238  const libff::bit_vector root_initial_val = root_initial.get_bits(this->pb);
239  const libff::bit_vector root_val = root.get_bits(this->pb);
240  const size_t pc_addr_val =
241  pc_addr.get_field_element_from_bits(this->pb).as_ulong();
242  const libff::bit_vector cpu_state_val = cpu_state.get_bits(this->pb);
243  const size_t pc_addr_initial_val =
244  pc_addr_initial.get_field_element_from_bits(this->pb).as_ulong();
245  const libff::bit_vector cpu_state_initial_val =
246  cpu_state_initial.get_bits(this->pb);
247  const bool has_accepted_val = (this->pb.val(has_accepted) == FieldT::one());
248 
249  std::shared_ptr<r1cs_pcd_message<FieldT>> result;
250  result.reset(new ram_pcd_message<ramT>(
251  type_val,
252  ap,
253  timestamp_val,
254  root_initial_val,
255  root_val,
256  pc_addr_val,
257  cpu_state_val,
258  pc_addr_initial_val,
259  cpu_state_initial_val,
260  has_accepted_val));
261  return result;
262 }
263 
264 template<typename ramT>
265 ram_pcd_local_data<ramT>::ram_pcd_local_data(
266  const bool is_halt_case,
267  delegated_ra_memory<CRH_with_bit_out_gadget<FieldT>> &mem,
268  typename ram_input_tape<ramT>::const_iterator &aux_it,
269  const typename ram_input_tape<ramT>::const_iterator &aux_end)
270  : is_halt_case(is_halt_case), mem(mem), aux_it(aux_it), aux_end(aux_end)
271 {
272 }
273 
274 template<typename ramT>
275 r1cs_variable_assignment<ram_base_field<ramT>> ram_pcd_local_data<
276  ramT>::as_r1cs_variable_assignment() const
277 {
278  r1cs_variable_assignment<FieldT> result;
279  result.emplace_back(is_halt_case ? FieldT::one() : FieldT::zero());
280  return result;
281 }
282 
283 template<typename ramT>
284 ram_pcd_local_data_variable<ramT>::ram_pcd_local_data_variable(
285  protoboard<FieldT> &pb, const std::string &annotation_prefix)
286  : r1cs_pcd_local_data_variable<ram_base_field<ramT>>(pb, annotation_prefix)
287 {
288  is_halt_case.allocate(pb, FMT(annotation_prefix, " is_halt_case"));
289 
290  this->update_all_vars();
291 }
292 
293 /*
294  We need to perform the following checks:
295 
296  Always:
297  next.root_initial = cur.root_initial
298  next.pc_addr_init = cur.pc_addr_initial
299  next.cpu_state_initial = cur.cpu_state_initial
300 
301  If is_is_base_case = 1: (base case)
302  that cur.timestamp = 0, cur.cpu_state = cpu_state_init, cur.pc_addr =
303  pc_addr_initial, cur.has_accepted = 0 that cur.root = cur.root_initial
304 
305  If do_halt = 0: (regular case)
306  that instruction fetch was correctly executed
307  next.timestamp = cur.timestamp + 1
308  that CPU accepted on (cur, temp)
309  that load-then-store was correctly handled
310  that next.root = temp.root, next.cpu_state = temp.cpu_state, next.pc_addr =
311  temp.pc_addr
312 
313  If do_halt = 1: (final case)
314  that cur.has_accepted = 1
315  that next.root = 0, next.cpu_state = 0, next.pc_addr = 0
316  that next.timestamp = cur.timestamp and next.has_accepted = cur.has_accepted
317 */
318 
319 template<typename ramT>
320 ram_compliance_predicate_handler<ramT>::ram_compliance_predicate_handler(
321  const ram_architecture_params<ramT> &ap)
322  : compliance_predicate_handler<ram_base_field<ramT>, ram_protoboard<ramT>>(
323  ram_protoboard<ramT>(ap), 100, 1, 1, true, std::set<size_t>{1})
324  , ap(ap)
325  , addr_size(ap.address_size())
326  , value_size(ap.value_size())
327  , digest_size(CRH_with_bit_out_gadget<FieldT>::get_digest_len())
328 {
329  // TODO: assert that message has fields of lengths consistent with
330  // num_addresses/value_size (as a method for ram_message) choose a constant
331  // for timestamp_len check that value_size <= digest_size; digest_size is
332  // not assumed to fit in chunk size (more precisely, it is handled correctly
333  // in the other gadgets). check if others fit (timestamp_length, value_size,
334  // addr_size)
335 
336  // the variables allocated are: next, cur, local data (nil for us),
337  // is_base_case, witness
338 
339  this->outgoing_message.reset(
340  new ram_pcd_message_variable<ramT>(this->pb, ap, "outgoing_message"));
341  this->arity.allocate(this->pb, "arity");
342  this->incoming_messages[0].reset(
343  new ram_pcd_message_variable<ramT>(this->pb, ap, "incoming_message"));
344  this->local_data.reset(
345  new ram_pcd_local_data_variable<ramT>(this->pb, "local_data"));
346 
347  is_base_case.allocate(this->pb, "is_base_case");
348 
349  next = std::dynamic_pointer_cast<ram_pcd_message_variable<ramT>>(
350  this->outgoing_message);
351  cur = std::dynamic_pointer_cast<ram_pcd_message_variable<ramT>>(
352  this->incoming_messages[0]);
353 
354  next->allocate_unpacked_part();
355  cur->allocate_unpacked_part();
356 
357  // work-around for bad linear combination handling
358  zero.allocate(
359  this->pb, "zero"); // will go away when we properly support linear terms
360 
361  temp_next_pc_addr.allocate(this->pb, addr_size, "temp_next_pc_addr");
362  temp_next_cpu_state.allocate(
363  this->pb, ap.cpu_state_size(), "temp_next_cpu_state");
364 
365  const size_t chunk_size = FieldT::capacity();
366 
367  /*
368  Always:
369  next.root_initial = cur.root_initial
370  next.pc_addr_init = cur.pc_addr_initial
371  next.cpu_state_initial = cur.cpu_state_initial
372  */
373  copy_root_initial.reset(new bit_vector_copy_gadget<FieldT>(
374  this->pb,
375  cur->root_initial,
376  next->root_initial,
377  ONE,
378  chunk_size,
379  "copy_root_initial"));
380  copy_pc_addr_initial.reset(new bit_vector_copy_gadget<FieldT>(
381  this->pb,
382  cur->pc_addr_initial,
383  next->pc_addr_initial,
384  ONE,
385  chunk_size,
386  "copy_pc_addr_initial"));
387  copy_cpu_state_initial.reset(new bit_vector_copy_gadget<FieldT>(
388  this->pb,
389  cur->cpu_state_initial,
390  next->cpu_state_initial,
391  ONE,
392  chunk_size,
393  "copy_cpu_state_initial"));
394 
395  /*
396  If is_base_case = 1: (base case)
397  that cur.timestamp = 0, cur.cpu_state = 0, cur.pc_addr = 0,
398  cur.has_accepted = 0 that cur.root = cur.root_initial
399  */
400  packed_cur_timestamp.allocate(this->pb, "packed_cur_timestamp");
401  pack_cur_timestamp.reset(new packing_gadget<FieldT>(
402  this->pb, cur->timestamp, packed_cur_timestamp, "pack_cur_timestamp"));
403 
404  zero_cpu_state = pb_variable_array<FieldT>(cur->cpu_state.size(), zero);
405  zero_pc_addr = pb_variable_array<FieldT>(cur->pc_addr.size(), zero);
406 
407  initialize_cur_cpu_state.reset(new bit_vector_copy_gadget<FieldT>(
408  this->pb,
409  cur->cpu_state_initial,
410  cur->cpu_state,
411  is_base_case,
412  chunk_size,
413  "initialize_cur_cpu_state"));
414  initialize_prev_pc_addr.reset(new bit_vector_copy_gadget<FieldT>(
415  this->pb,
416  cur->pc_addr_initial,
417  cur->pc_addr,
418  is_base_case,
419  chunk_size,
420  "initialize_prev_pc_addr"));
421 
422  initialize_root.reset(new bit_vector_copy_gadget<FieldT>(
423  this->pb,
424  cur->root_initial,
425  cur->root,
426  is_base_case,
427  chunk_size,
428  "initialize_root"));
429  /*
430  If do_halt = 0: (regular case)
431  that instruction fetch was correctly executed
432  next.timestamp = cur.timestamp + 1
433  that CPU accepted on (cur, next)
434  that load-then-store was correctly handled
435  */
436  is_not_halt_case.allocate(this->pb, "is_not_halt_case");
437  // for performing instruction fetch
438  prev_pc_val.allocate(this->pb, value_size, "prev_pc_val");
439  prev_pc_val_digest.reset(new digest_variable<FieldT>(
440  this->pb, digest_size, prev_pc_val, zero, "prev_pc_val_digest"));
441  cur_root_digest.reset(new digest_variable<FieldT>(
442  this->pb, digest_size, cur->root, zero, "cur_root_digest"));
443  instruction_fetch_merkle_proof.reset(
444  new merkle_authentication_path_variable<FieldT, HashT>(
445  this->pb, addr_size, "instruction_fetch_merkle_proof"));
446  instruction_fetch.reset(new memory_load_gadget<FieldT, HashT>(
447  this->pb,
448  addr_size,
449  cur->pc_addr,
450  *prev_pc_val_digest,
451  *cur_root_digest,
452  *instruction_fetch_merkle_proof,
453  ONE,
454  "instruction_fetch"));
455 
456  // for next.timestamp = cur.timestamp + 1
457  packed_next_timestamp.allocate(this->pb, "packed_next_timestamp");
458  pack_next_timestamp.reset(new packing_gadget<FieldT>(
459  this->pb,
460  next->timestamp,
461  packed_next_timestamp,
462  "pack_next_timestamp"));
463 
464  // that CPU accepted on (cur, temp)
465  ls_addr.allocate(this->pb, addr_size, "ls_addr");
466  ls_prev_val.allocate(this->pb, value_size, "ls_prev_val");
467  ls_next_val.allocate(this->pb, value_size, "ls_next_val");
468  cpu_checker.reset(new ram_cpu_checker<ramT>(
469  this->pb,
470  cur->pc_addr,
471  prev_pc_val,
472  cur->cpu_state,
473  ls_addr,
474  ls_prev_val,
475  ls_next_val,
476  temp_next_cpu_state,
477  temp_next_pc_addr,
478  next->has_accepted,
479  "cpu_checker"));
480 
481  // that load-then-store was correctly handled
482  ls_prev_val_digest.reset(new digest_variable<FieldT>(
483  this->pb, digest_size, ls_prev_val, zero, "ls_prev_val_digest"));
484  ls_next_val_digest.reset(new digest_variable<FieldT>(
485  this->pb, digest_size, ls_next_val, zero, "ls_next_val_digest"));
486  next_root_digest.reset(new digest_variable<FieldT>(
487  this->pb, digest_size, next->root, zero, "next_root_digest"));
488  load_merkle_proof.reset(
489  new merkle_authentication_path_variable<FieldT, HashT>(
490  this->pb, addr_size, "load_merkle_proof"));
491  store_merkle_proof.reset(
492  new merkle_authentication_path_variable<FieldT, HashT>(
493  this->pb, addr_size, "store_merkle_proof"));
494  load_store_checker.reset(new memory_load_store_gadget<FieldT, HashT>(
495  this->pb,
496  addr_size,
497  ls_addr,
498  *ls_prev_val_digest,
499  *cur_root_digest,
500  *load_merkle_proof,
501  *ls_next_val_digest,
502  *next_root_digest,
503  *store_merkle_proof,
504  is_not_halt_case,
505  "load_store_checker"));
506  /*
507  If do_halt = 1: (final case)
508  that cur.has_accepted = 1
509  that next.root = 0, next.cpu_state = 0, next.pc_addr = 0
510  that next.timestamp = cur.timestamp and next.has_accepted =
511  cur.has_accepted
512  */
513  do_halt.allocate(this->pb, "do_halt");
514  zero_root = pb_variable_array<FieldT>(next->root.size(), zero);
515  clear_next_root.reset(new bit_vector_copy_gadget<FieldT>(
516  this->pb,
517  zero_root,
518  next->root,
519  do_halt,
520  chunk_size,
521  "clear_next_root"));
522  clear_next_pc_addr.reset(new bit_vector_copy_gadget<FieldT>(
523  this->pb,
524  zero_pc_addr,
525  next->pc_addr,
526  do_halt,
527  chunk_size,
528  "clear_next_pc_addr"));
529  clear_next_cpu_state.reset(new bit_vector_copy_gadget<FieldT>(
530  this->pb,
531  zero_cpu_state,
532  next->cpu_state,
533  do_halt,
534  chunk_size,
535  "clear_cpu_state"));
536 
537  copy_temp_next_pc_addr.reset(new bit_vector_copy_gadget<FieldT>(
538  this->pb,
539  temp_next_pc_addr,
540  next->pc_addr,
541  is_not_halt_case,
542  chunk_size,
543  "copy_temp_next_pc_addr"));
544  copy_temp_next_cpu_state.reset(new bit_vector_copy_gadget<FieldT>(
545  this->pb,
546  temp_next_cpu_state,
547  next->cpu_state,
548  is_not_halt_case,
549  chunk_size,
550  "copy_temp_next_cpu_state"));
551 }
552 
553 template<typename ramT>
554 void ram_compliance_predicate_handler<ramT>::generate_r1cs_constraints()
555 {
556  libff::print_indent();
557  printf("* Message size: %zu\n", next->all_vars.size());
558  libff::print_indent();
559  printf("* Address size: %zu\n", addr_size);
560  libff::print_indent();
561  printf("* CPU state size: %zu\n", ap.cpu_state_size());
562  libff::print_indent();
563  printf("* Digest size: %zu\n", digest_size);
564 
565  PROFILE_CONSTRAINTS(this->pb, "handle next_type, arity and cur_type")
566  {
567  generate_r1cs_equals_const_constraint<FieldT>(
568  this->pb, next->type, FieldT::one(), "next_type");
569  generate_r1cs_equals_const_constraint<FieldT>(
570  this->pb, this->arity, FieldT::one(), "arity");
571  this->pb.add_r1cs_constraint(
572  r1cs_constraint<FieldT>(is_base_case, cur->type, 0),
573  "nonzero_cur_type_implies_base_case_0");
574  generate_boolean_r1cs_constraint<FieldT>(
575  this->pb, cur->type, "cur_type_boolean");
576  generate_boolean_r1cs_constraint<FieldT>(
577  this->pb, is_base_case, "is_base_case_boolean");
578  }
579 
580  PROFILE_CONSTRAINTS(this->pb, "unpack messages")
581  {
582  next->generate_r1cs_constraints();
583  cur->generate_r1cs_constraints();
584  }
585 
586  // work-around for bad linear combination handling
587  generate_r1cs_equals_const_constraint<FieldT>(
588  this->pb, zero, FieldT::zero(), " zero");
589 
590  /* recall that Booleanity of PCD messages has already been enforced by the
591  * PCD machine, which is explains the absence of Booleanity checks */
592  /*
593  We need to perform the following checks:
594 
595  Always:
596  next.root_initial = cur.root_initial
597  next.pc_addr_init = cur.pc_addr_initial
598  next.cpu_state_initial = cur.cpu_state_initial
599  */
600  PROFILE_CONSTRAINTS(this->pb, "copy root_initial")
601  {
602  copy_root_initial->generate_r1cs_constraints(false, false);
603  }
604 
605  PROFILE_CONSTRAINTS(this->pb, "copy pc_addr_initial and cpu_state_initial")
606  {
607  copy_pc_addr_initial->generate_r1cs_constraints(false, false);
608  copy_cpu_state_initial->generate_r1cs_constraints(false, false);
609  }
610 
611  /*
612  If is_base_case = 1: (base case)
613  that cur.timestamp = 0, cur.cpu_state = 0, cur.pc_addr = 0,
614  cur.has_accepted = 0 that cur.root = cur.root_initial
615  */
616  pack_cur_timestamp->generate_r1cs_constraints(false);
617  this->pb.add_r1cs_constraint(
618  r1cs_constraint<FieldT>(is_base_case, packed_cur_timestamp, 0),
619  "clear_ts_on_is_base_case");
620  PROFILE_CONSTRAINTS(this->pb, "copy cur_cpu_state and prev_pc_addr")
621  {
622  initialize_cur_cpu_state->generate_r1cs_constraints(false, false);
623  initialize_prev_pc_addr->generate_r1cs_constraints(false, false);
624  }
625  this->pb.add_r1cs_constraint(
626  r1cs_constraint<FieldT>(is_base_case, cur->has_accepted, 0),
627  "is_base_case_is_not_accepting");
628  PROFILE_CONSTRAINTS(this->pb, "initialize root")
629  {
630  initialize_root->generate_r1cs_constraints(false, false);
631  }
632 
633  /*
634  If do_halt = 0: (regular case)
635  that instruction fetch was correctly executed
636  next.timestamp = cur.timestamp + 1
637  that CPU accepted on (cur, next)
638  that load-then-store was correctly handled
639  that next.root = temp.root, next.cpu_state = temp.cpu_state, next.pc_addr
640  = temp.pc_addr
641  */
642  this->pb.add_r1cs_constraint(
643  r1cs_constraint<FieldT>(1, 1 - do_halt, is_not_halt_case),
644  "is_not_halt_case");
645  PROFILE_CONSTRAINTS(this->pb, "instruction fetch")
646  {
647  instruction_fetch_merkle_proof->generate_r1cs_constraints();
648  instruction_fetch->generate_r1cs_constraints();
649  }
650  pack_next_timestamp->generate_r1cs_constraints(false);
651  this->pb.add_r1cs_constraint(
652  r1cs_constraint<FieldT>(
653  is_not_halt_case,
654  (packed_cur_timestamp + 1) - packed_next_timestamp,
655  0),
656  "increment_timestamp");
657  PROFILE_CONSTRAINTS(this->pb, "CPU checker")
658  {
659  cpu_checker->generate_r1cs_constraints();
660  }
661  PROFILE_CONSTRAINTS(this->pb, "load/store checker")
662  {
663  // See comment in
664  // merkle_tree_check_update_gadget::generate_r1cs_witness() for why we
665  // don't need to call store_merkle_proof->generate_r1cs_constraints()
666  load_merkle_proof->generate_r1cs_constraints();
667  load_store_checker->generate_r1cs_constraints();
668  }
669 
670  PROFILE_CONSTRAINTS(
671  this->pb, "copy temp_next_pc_addr and temp_next_cpu_state")
672  {
673  copy_temp_next_pc_addr->generate_r1cs_constraints(true, false);
674  copy_temp_next_cpu_state->generate_r1cs_constraints(true, false);
675  }
676 
677  /*
678  If do_halt = 1: (final case)
679  that cur.has_accepted = 1
680  that next.root = 0, next.cpu_state = 0, next.pc_addr = 0
681  that next.timestamp = cur.timestamp and next.has_accepted =
682  cur.has_accepted
683  */
684  this->pb.add_r1cs_constraint(
685  r1cs_constraint<FieldT>(do_halt, 1 - cur->has_accepted, 0),
686  "final_case_must_be_accepting");
687 
688  PROFILE_CONSTRAINTS(this->pb, "clear next root")
689  {
690  clear_next_root->generate_r1cs_constraints(false, false);
691  }
692 
693  PROFILE_CONSTRAINTS(this->pb, "clear next_pc_addr and next_cpu_state")
694  {
695  clear_next_pc_addr->generate_r1cs_constraints(false, false);
696  clear_next_cpu_state->generate_r1cs_constraints(false, false);
697  }
698 
699  this->pb.add_r1cs_constraint(
700  r1cs_constraint<FieldT>(
701  do_halt, packed_cur_timestamp - packed_next_timestamp, 0),
702  "equal_ts_on_halt");
703 
704  const size_t accounted = PRINT_CONSTRAINT_PROFILING();
705  const size_t total = this->pb.num_constraints();
706  libff::print_indent();
707  printf("* Unaccounted constraints: %zu\n", total - accounted);
708  libff::print_indent();
709  printf("* Number of constraints in ram_compliance_predicate: %zu\n", total);
710 }
711 
712 template<typename ramT>
713 void ram_compliance_predicate_handler<ramT>::generate_r1cs_witness(
714  const std::vector<std::shared_ptr<r1cs_pcd_message<FieldT>>>
715  &incoming_message_values,
716  const std::shared_ptr<r1cs_pcd_local_data<FieldT>> &local_data_value)
717 {
718  const std::shared_ptr<ram_pcd_local_data<ramT>> ram_local_data_value =
719  std::dynamic_pointer_cast<ram_pcd_local_data<ramT>>(local_data_value);
720  assert(
721  ram_local_data_value->mem.num_addresses ==
722  1ul << addr_size); // check value_size and num_addresses too
723 
724  base_handler::generate_r1cs_witness(
725  incoming_message_values, local_data_value);
726  cur->generate_r1cs_witness_from_packed();
727 
728  this->pb.val(next->type) = FieldT::one();
729  this->pb.val(this->arity) = FieldT::one();
730  this->pb.val(is_base_case) =
731  (this->pb.val(cur->type) == FieldT::zero() ? FieldT::one()
732  : FieldT::zero());
733 
734  this->pb.val(zero) = FieldT::zero();
735  /*
736  Always:
737  next.root_initial = cur.root_initial
738  next.pc_addr_init = cur.pc_addr_initial
739  next.cpu_state_initial = cur.cpu_state_initial
740  */
741  copy_root_initial->generate_r1cs_witness();
742  for (size_t i = 0; i < next->root_initial.size(); ++i) {
743  this->pb.val(cur->root_initial[i]).print();
744  this->pb.val(next->root_initial[i]).print();
745  assert(
746  this->pb.val(cur->root_initial[i]) ==
747  this->pb.val(next->root_initial[i]));
748  }
749 
750  copy_pc_addr_initial->generate_r1cs_witness();
751  copy_cpu_state_initial->generate_r1cs_witness();
752 
753  /*
754  If is_base_case = 1: (base case)
755  that cur.timestamp = 0, cur.cpu_state = 0, cur.pc_addr = 0,
756  cur.has_accepted = 0 that cur.root = cur.root_initial
757  */
758  const bool base_case = (incoming_message_values[0]->type == 0);
759  this->pb.val(is_base_case) = base_case ? FieldT::one() : FieldT::zero();
760 
761  initialize_cur_cpu_state->generate_r1cs_witness();
762  initialize_prev_pc_addr->generate_r1cs_witness();
763 
764  if (base_case) {
765  this->pb.val(packed_cur_timestamp) = FieldT::zero();
766  this->pb.val(cur->has_accepted) = FieldT::zero();
767  pack_cur_timestamp->generate_r1cs_witness_from_packed();
768  } else {
769  pack_cur_timestamp->generate_r1cs_witness_from_bits();
770  }
771 
772  initialize_root->generate_r1cs_witness();
773 
774  /*
775  If do_halt = 0: (regular case)
776  that instruction fetch was correctly executed
777  next.timestamp = cur.timestamp + 1
778  that CPU accepted on (cur, temp)
779  that load-then-store was correctly handled
780  */
781  this->pb.val(do_halt) =
782  ram_local_data_value->is_halt_case ? FieldT::one() : FieldT::zero();
783  this->pb.val(is_not_halt_case) = FieldT::one() - this->pb.val(do_halt);
784 
785  // that instruction fetch was correctly executed
786  const size_t int_pc_addr =
787  libff::convert_bit_vector_to_field_element<FieldT>(
788  cur->pc_addr.get_bits(this->pb))
789  .as_ulong();
790  const size_t int_pc_val = ram_local_data_value->mem.get_value(int_pc_addr);
791 #ifdef DEBUG
792  printf(
793  "pc_addr (in units) = %zu, pc_val = %zu (0x%08zx)\n",
794  int_pc_addr,
795  int_pc_val,
796  int_pc_val);
797 #endif
798  libff::bit_vector pc_val_bv =
799  libff::int_list_to_bits({int_pc_val}, value_size);
800  std::reverse(pc_val_bv.begin(), pc_val_bv.end());
801 
802  prev_pc_val.fill_with_bits(this->pb, pc_val_bv);
803  const merkle_authentication_path pc_path =
804  ram_local_data_value->mem.get_path(int_pc_addr);
805  instruction_fetch_merkle_proof->generate_r1cs_witness(int_pc_addr, pc_path);
806  instruction_fetch->generate_r1cs_witness();
807 
808  // next.timestamp = cur.timestamp + 1 (or cur.timestamp if do_halt)
809  this->pb.val(packed_next_timestamp) =
810  this->pb.val(packed_cur_timestamp) + this->pb.val(is_not_halt_case);
811  pack_next_timestamp->generate_r1cs_witness_from_packed();
812 
813  // that CPU accepted on (cur, temp)
814  // Step 1: Get address and old witnesses for delegated memory.
815  cpu_checker->generate_r1cs_witness_address();
816  const size_t int_ls_addr =
817  ls_addr.get_field_element_from_bits(this->pb).as_ulong();
818  const size_t int_ls_prev_val =
819  ram_local_data_value->mem.get_value(int_ls_addr);
820  const merkle_authentication_path prev_path =
821  ram_local_data_value->mem.get_path(int_ls_addr);
822  ls_prev_val.fill_with_bits_of_ulong(this->pb, int_ls_prev_val);
823  assert(
824  ls_prev_val.get_field_element_from_bits(this->pb) ==
825  FieldT(int_ls_prev_val, true));
826  // Step 2: Execute CPU checker and delegated memory
827  cpu_checker->generate_r1cs_witness_other(
828  ram_local_data_value->aux_it, ram_local_data_value->aux_end);
829 #ifdef DEBUG
830  printf("Debugging information from transition function:\n");
831  cpu_checker->dump();
832 #endif
833  const size_t int_ls_next_val =
834  ls_next_val.get_field_element_from_bits(this->pb).as_ulong();
835  ram_local_data_value->mem.set_value(int_ls_addr, int_ls_next_val);
836 #ifdef DEBUG
837  printf(
838  "Memory location %zu changed from %zu (0x%08zx) to %zu (0x%08zx)\n",
839  int_ls_addr,
840  int_ls_prev_val,
841  int_ls_prev_val,
842  int_ls_next_val,
843  int_ls_next_val);
844 #endif
845  // Step 4: Use both to satisfy load_store_checker
846  load_merkle_proof->generate_r1cs_witness(int_ls_addr, prev_path);
847  load_store_checker->generate_r1cs_witness();
848 
849  /*
850  If do_halt = 1: (final case)
851  that cur.has_accepted = 1
852  that next.root = 0, next.cpu_state = 0, next.pc_addr = 0
853  that next.timestamp = cur.timestamp and next.has_accepted =
854  cur.has_accepted
855  */
856 
857  // Order matters here: both witness maps touch next_root, but the
858  // one that does not set values must be executed the last, so its
859  // auxiliary variables are filled in correctly according to values
860  // actually set by the other witness map.
861  if (this->pb.val(do_halt).is_zero()) {
862  copy_temp_next_pc_addr->generate_r1cs_witness();
863  copy_temp_next_cpu_state->generate_r1cs_witness();
864 
865  clear_next_root->generate_r1cs_witness();
866  clear_next_pc_addr->generate_r1cs_witness();
867  clear_next_cpu_state->generate_r1cs_witness();
868  } else {
869  clear_next_root->generate_r1cs_witness();
870  clear_next_pc_addr->generate_r1cs_witness();
871  clear_next_cpu_state->generate_r1cs_witness();
872 
873  copy_temp_next_pc_addr->generate_r1cs_witness();
874  copy_temp_next_cpu_state->generate_r1cs_witness();
875  }
876 
877 #ifdef DEBUG
878  printf("next.has_accepted: ");
879  this->pb.val(next->has_accepted).print();
880 #endif
881 
882  next->generate_r1cs_witness_from_bits();
883 }
884 
885 template<typename ramT>
886 std::shared_ptr<r1cs_pcd_message<ram_base_field<ramT>>>
887 ram_compliance_predicate_handler<ramT>::get_base_case_message(
888  const ram_architecture_params<ramT> &ap,
889  const ram_boot_trace<ramT> &primary_input)
890 {
891  libff::enter_block(
892  "Call to ram_compliance_predicate_handler::get_base_case_message");
893  const size_t num_addresses = 1ul << ap.address_size();
894  const size_t value_size = ap.value_size();
895  delegated_ra_memory<CRH_with_bit_out_gadget<FieldT>> mem(
896  num_addresses, value_size, primary_input.as_memory_contents());
897 
898  const size_t type = 0;
899 
900  const size_t timestamp = 0;
901 
902  const libff::bit_vector root_initial = mem.get_root();
903  const size_t pc_addr_initial = ap.initial_pc_addr();
904  const libff::bit_vector cpu_state_initial(ap.cpu_state_size(), false);
905 
906  const libff::bit_vector root = root_initial;
907  const size_t pc_addr = pc_addr_initial;
908  const libff::bit_vector cpu_state = cpu_state_initial;
909 
910  const bool has_accepted = false;
911 
912  std::shared_ptr<r1cs_pcd_message<FieldT>> result;
913  result.reset(new ram_pcd_message<ramT>(
914  type,
915  ap,
916  timestamp,
917  root_initial,
918  root,
919  pc_addr,
920  cpu_state,
921  pc_addr_initial,
922  cpu_state_initial,
923  has_accepted));
924  libff::leave_block(
925  "Call to ram_compliance_predicate_handler::get_base_case_message");
926  return result;
927 }
928 
929 template<typename ramT>
930 std::shared_ptr<r1cs_pcd_message<ram_base_field<ramT>>>
931 ram_compliance_predicate_handler<ramT>::get_final_case_msg(
932  const ram_architecture_params<ramT> &ap,
933  const ram_boot_trace<ramT> &primary_input,
934  const size_t time_bound)
935 {
936  libff::enter_block(
937  "Call to ram_compliance_predicate_handler::get_final_case_msg");
938  const size_t num_addresses = 1ul << ap.address_size();
939  const size_t value_size = ap.value_size();
940  delegated_ra_memory<CRH_with_bit_out_gadget<FieldT>> mem(
941  num_addresses, value_size, primary_input.as_memory_contents());
942 
943  const size_t type = 1;
944 
945  const size_t timestamp = time_bound;
946 
947  const libff::bit_vector root_initial = mem.get_root();
948  const size_t pc_addr_initial = ap.initial_pc_addr();
949  const libff::bit_vector cpu_state_initial(ap.cpu_state_size(), false);
950 
951  const libff::bit_vector root(root_initial.size(), false);
952  const size_t pc_addr = 0;
953  const libff::bit_vector cpu_state = cpu_state_initial;
954 
955  const bool has_accepted = true;
956 
957  std::shared_ptr<r1cs_pcd_message<FieldT>> result;
958  result.reset(new ram_pcd_message<ramT>(
959  type,
960  ap,
961  timestamp,
962  root_initial,
963  root,
964  pc_addr,
965  cpu_state,
966  pc_addr_initial,
967  cpu_state_initial,
968  has_accepted));
969  libff::leave_block(
970  "Call to ram_compliance_predicate_handler::get_final_case_msg");
971 
972  return result;
973 }
974 
975 } // namespace libsnark
976 
977 #endif // RAM_COMPLIANCE_PREDICATE_TCC_