2 *****************************************************************************
4 Implementation of interfaces for the TinyRAM CPU checker gadget.
6 See tinyram_cpu_checker.hpp .
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 *****************************************************************************/
14 #ifndef TINYRAM_CPU_CHECKER_TCC_
15 #define TINYRAM_CPU_CHECKER_TCC_
17 #include <libff/algebra/fields/field_utils.hpp>
22 template<typename FieldT>
23 tinyram_cpu_checker<FieldT>::tinyram_cpu_checker(
24 tinyram_protoboard<FieldT> &pb,
25 pb_variable_array<FieldT> &prev_pc_addr,
26 pb_variable_array<FieldT> &prev_pc_val,
27 pb_variable_array<FieldT> &prev_state,
28 pb_variable_array<FieldT> &ls_addr,
29 pb_variable_array<FieldT> &ls_prev_val,
30 pb_variable_array<FieldT> &ls_next_val,
31 pb_variable_array<FieldT> &next_state,
32 pb_variable_array<FieldT> &next_pc_addr,
33 pb_variable<FieldT> &next_has_accepted,
34 const std::string &annotation_prefix)
35 : tinyram_standard_gadget<FieldT>(pb, annotation_prefix)
36 , prev_pc_addr(prev_pc_addr)
37 , prev_pc_val(prev_pc_val)
38 , prev_state(prev_state)
40 , ls_prev_val(ls_prev_val)
41 , ls_next_val(ls_next_val)
42 , next_state(next_state)
43 , next_pc_addr(next_pc_addr)
44 , next_has_accepted(next_has_accepted)
46 /* parse previous PC value as an instruction (note that we start
47 parsing from LSB of the instruction doubleword and go to the
49 auto pc_val_it = prev_pc_val.begin();
51 arg2idx = pb_variable_array<FieldT>(
52 pc_val_it, pc_val_it + pb.ap.reg_arg_or_imm_width());
53 std::advance(pc_val_it, pb.ap.reg_arg_or_imm_width());
54 std::advance(pc_val_it, pb.ap.instruction_padding_width());
56 pb_variable_array<FieldT>(pc_val_it, pc_val_it + pb.ap.reg_arg_width());
57 std::advance(pc_val_it, pb.ap.reg_arg_width());
59 pb_variable_array<FieldT>(pc_val_it, pc_val_it + pb.ap.reg_arg_width());
60 std::advance(pc_val_it, pb.ap.reg_arg_width());
61 arg2_is_imm = *pc_val_it;
62 std::advance(pc_val_it, 1);
64 pb_variable_array<FieldT>(pc_val_it, pc_val_it + pb.ap.opcode_width());
65 std::advance(pc_val_it, pb.ap.opcode_width());
67 assert(pc_val_it == prev_pc_val.end());
69 /* parse state as registers + flags */
70 pb_variable_array<FieldT> packed_prev_registers, packed_next_registers;
71 for (size_t i = 0; i < pb.ap.k; ++i) {
72 prev_registers.emplace_back(word_variable_gadget<FieldT>(
74 pb_variable_array<FieldT>(
75 prev_state.begin() + i * pb.ap.w,
76 prev_state.begin() + (i + 1) * pb.ap.w),
77 FMT(annotation_prefix, " prev_registers_%zu", i)));
78 next_registers.emplace_back(word_variable_gadget<FieldT>(
80 pb_variable_array<FieldT>(
81 next_state.begin() + i * pb.ap.w,
82 next_state.begin() + (i + 1) * pb.ap.w),
83 FMT(annotation_prefix, " next_registers_%zu", i)));
85 packed_prev_registers.emplace_back(prev_registers[i].packed);
86 packed_next_registers.emplace_back(next_registers[i].packed);
88 prev_flag = *(++prev_state.rbegin());
89 next_flag = *(++next_state.rbegin());
90 prev_tape1_exhausted = *(prev_state.rbegin());
91 next_tape1_exhausted = *(next_state.rbegin());
93 /* decode arguments */
94 prev_pc_addr_as_word_variable.reset(new word_variable_gadget<FieldT>(
97 FMT(annotation_prefix, " prev_pc_addr_as_word_variable")));
98 desval.reset(new word_variable_gadget<FieldT>(
99 pb, FMT(annotation_prefix, " desval")));
100 arg1val.reset(new word_variable_gadget<FieldT>(
101 pb, FMT(annotation_prefix, " arg1val")));
102 arg2val.reset(new word_variable_gadget<FieldT>(
103 pb, FMT(annotation_prefix, " arg2val")));
105 decode_arguments.reset(new argument_decoder_gadget<FieldT>(
111 packed_prev_registers,
115 FMT(annotation_prefix, " decode_arguments")));
117 /* create indicator variables for opcodes */
118 opcode_indicators.allocate(
120 1ul << pb.ap.opcode_width(),
121 FMT(annotation_prefix, " opcode_indicators"));
123 /* perform the ALU operations */
124 instruction_results.allocate(
126 1ul << pb.ap.opcode_width(),
127 FMT(annotation_prefix, " instruction_results"));
128 instruction_flags.allocate(
130 1ul << pb.ap.opcode_width(),
131 FMT(annotation_prefix, " instruction_flags"));
133 ALU.reset(new ALU_gadget<FieldT>(
136 *prev_pc_addr_as_word_variable,
143 FMT(annotation_prefix, " ALU")));
145 /* check correctness of memory operations */
146 ls_prev_val_as_doubleword_variable.reset(
147 new doubleword_variable_gadget<FieldT>(
150 FMT(annotation_prefix, " ls_prev_val_as_doubleword_variable")));
151 ls_next_val_as_doubleword_variable.reset(
152 new doubleword_variable_gadget<FieldT>(
155 FMT(annotation_prefix, " ls_next_val_as_doubleword_variable")));
156 memory_subaddress.reset(new dual_variable_gadget<FieldT>(
158 pb_variable_array<FieldT>(
159 arg2val->bits.begin(), arg2val->bits.begin() + pb.ap.subaddr_len()),
160 FMT(annotation_prefix, " memory_subaddress")));
162 memory_subcontents.allocate(
163 pb, FMT(annotation_prefix, " memory_subcontents"));
164 memory_access_is_word.assign(
166 1 - (opcode_indicators[tinyram_opcode_LOADB] +
167 opcode_indicators[tinyram_opcode_STOREB]));
168 memory_access_is_byte.assign(
170 opcode_indicators[tinyram_opcode_LOADB] +
171 opcode_indicators[tinyram_opcode_STOREB]);
173 check_memory.reset(new memory_masking_gadget<FieldT>(
175 *ls_prev_val_as_doubleword_variable,
178 memory_access_is_word,
179 memory_access_is_byte,
180 *ls_next_val_as_doubleword_variable,
181 FMT(annotation_prefix, " check_memory")));
184 read_not1.allocate(pb, FMT(annotation_prefix, " read_not1"));
186 /* check consistency of the states according to the ALU results */
187 next_pc_addr_as_word_variable.reset(new word_variable_gadget<FieldT>(
190 FMT(annotation_prefix, " next_pc_addr_as_word_variable")));
192 consistency_enforcer.reset(new consistency_enforcer_gadget<FieldT>(
198 prev_pc_addr_as_word_variable->packed,
199 packed_prev_registers,
202 next_pc_addr_as_word_variable->packed,
203 packed_next_registers,
205 FMT(annotation_prefix, " consistency_enforcer")));
208 template<typename FieldT>
209 void tinyram_cpu_checker<FieldT>::generate_r1cs_constraints()
211 decode_arguments->generate_r1cs_constraints();
213 /* generate indicator variables for opcode */
214 for (size_t i = 0; i < 1ul << this->pb.ap.opcode_width(); ++i) {
215 this->pb.add_r1cs_constraint(
216 r1cs_constraint<FieldT>(
217 opcode_indicators[i], pb_packing_sum<FieldT>(opcode) - i, 0),
218 FMT(this->annotation_prefix, " opcode_indicators_%zu", i));
220 this->pb.add_r1cs_constraint(
221 r1cs_constraint<FieldT>(1, pb_sum<FieldT>(opcode_indicators), 1),
222 FMT(this->annotation_prefix, " opcode_indicators_sum_to_1"));
224 /* consistency checks for repacked variables */
225 for (size_t i = 0; i < this->pb.ap.k; ++i) {
226 prev_registers[i].generate_r1cs_constraints(true);
227 next_registers[i].generate_r1cs_constraints(true);
229 prev_pc_addr_as_word_variable->generate_r1cs_constraints(true);
230 next_pc_addr_as_word_variable->generate_r1cs_constraints(true);
231 ls_prev_val_as_doubleword_variable->generate_r1cs_constraints(true);
232 ls_next_val_as_doubleword_variable->generate_r1cs_constraints(true);
234 /* main consistency checks */
235 decode_arguments->generate_r1cs_constraints();
236 ALU->generate_r1cs_constraints();
237 consistency_enforcer->generate_r1cs_constraints();
239 /* check correct access to memory */
240 ls_prev_val_as_doubleword_variable->generate_r1cs_constraints(false);
241 ls_next_val_as_doubleword_variable->generate_r1cs_constraints(false);
242 memory_subaddress->generate_r1cs_constraints(false);
243 check_memory->generate_r1cs_constraints();
245 this->pb.add_r1cs_constraint(
246 r1cs_constraint<FieldT>(
248 pb_packing_sum<FieldT>(pb_variable_array<FieldT>(
249 arg2val->bits.begin() + this->pb.ap.subaddr_len(),
250 arg2val->bits.end())),
251 pb_packing_sum<FieldT>(ls_addr)),
252 FMT(this->annotation_prefix, " ls_addr_is_arg2val_minus_subaddress"));
254 /* We require that if opcode is one of load.{b,w}, then
255 subcontents is appropriately stored in instruction_results. If
256 opcode is store.b we only take the necessary portion of arg1val
257 (i.e. last byte), and take entire arg1val for store.w.
259 Note that ls_addr is *always* going to be arg2val. If the
260 instruction is a non-memory instruction, we will treat it as a
261 load from that memory location. */
262 this->pb.add_r1cs_constraint(
263 r1cs_constraint<FieldT>(
264 opcode_indicators[tinyram_opcode_LOADB],
265 memory_subcontents - instruction_results[tinyram_opcode_LOADB],
267 FMT(this->annotation_prefix, " handle_loadb"));
268 this->pb.add_r1cs_constraint(
269 r1cs_constraint<FieldT>(
270 opcode_indicators[tinyram_opcode_LOADW],
271 memory_subcontents - instruction_results[tinyram_opcode_LOADW],
273 FMT(this->annotation_prefix, " handle_loadw"));
274 this->pb.add_r1cs_constraint(
275 r1cs_constraint<FieldT>(
276 opcode_indicators[tinyram_opcode_STOREB],
278 pb_packing_sum<FieldT>(pb_variable_array<FieldT>(
279 desval->bits.begin(), desval->bits.begin() + 8)),
281 FMT(this->annotation_prefix, " handle_storeb"));
282 this->pb.add_r1cs_constraint(
283 r1cs_constraint<FieldT>(
284 opcode_indicators[tinyram_opcode_STOREW],
285 memory_subcontents - desval->packed,
287 FMT(this->annotation_prefix, " handle_storew"));
288 this->pb.add_r1cs_constraint(
289 r1cs_constraint<FieldT>(
290 1 - (opcode_indicators[tinyram_opcode_STOREB] +
291 opcode_indicators[tinyram_opcode_STOREW]),
292 ls_prev_val_as_doubleword_variable->packed -
293 ls_next_val_as_doubleword_variable->packed,
295 FMT(this->annotation_prefix,
296 " non_store_instructions_dont_change_memory"));
298 /* specify that accepting state implies opcode = answer && arg2val == 0 */
299 this->pb.add_r1cs_constraint(
300 r1cs_constraint<FieldT>(
301 next_has_accepted, 1 - opcode_indicators[tinyram_opcode_ANSWER], 0),
302 FMT(this->annotation_prefix, " accepting_requires_answer"));
303 this->pb.add_r1cs_constraint(
304 r1cs_constraint<FieldT>(next_has_accepted, arg2val->packed, 0),
305 FMT(this->annotation_prefix, " accepting_requires_arg2val_equal_zero"));
311 prev_tape1_exhausted implies next_tape1_exhausted,
312 prev_tape1_exhausted implies flag to be set
313 reads other than from tape 1 imply flag to be set
314 flag implies result to be 0
316 this->pb.add_r1cs_constraint(
317 r1cs_constraint<FieldT>(
318 prev_tape1_exhausted, 1 - next_tape1_exhausted, 0),
319 FMT(this->annotation_prefix,
320 " prev_tape1_exhausted_implies_next_tape1_exhausted"));
321 this->pb.add_r1cs_constraint(
322 r1cs_constraint<FieldT>(
323 prev_tape1_exhausted,
324 1 - instruction_flags[tinyram_opcode_READ],
326 FMT(this->annotation_prefix, " prev_tape1_exhausted_implies_flag"));
327 this->pb.add_r1cs_constraint(
328 r1cs_constraint<FieldT>(
329 opcode_indicators[tinyram_opcode_READ],
332 FMT(this->annotation_prefix,
333 " read_not1")); /* will be nonzero for read X for X != 1 */
334 this->pb.add_r1cs_constraint(
335 r1cs_constraint<FieldT>(
336 read_not1, 1 - instruction_flags[tinyram_opcode_READ], 0),
337 FMT(this->annotation_prefix, " other_reads_imply_flag"));
338 this->pb.add_r1cs_constraint(
339 r1cs_constraint<FieldT>(
340 instruction_flags[tinyram_opcode_READ],
341 instruction_results[tinyram_opcode_READ],
343 FMT(this->annotation_prefix, " read_flag_implies_result_0"));
346 template<typename FieldT>
347 void tinyram_cpu_checker<FieldT>::generate_r1cs_witness_address()
349 /* decode instruction and arguments */
350 prev_pc_addr_as_word_variable->generate_r1cs_witness_from_bits();
351 for (size_t i = 0; i < this->pb.ap.k; ++i) {
352 prev_registers[i].generate_r1cs_witness_from_bits();
355 decode_arguments->generate_r1cs_witness();
357 desval->generate_r1cs_witness_from_packed();
358 arg1val->generate_r1cs_witness_from_packed();
359 arg2val->generate_r1cs_witness_from_packed();
361 /* clear out ls_addr and fill with everything of arg2val except the
363 ls_addr.fill_with_bits_of_field_element(
365 this->pb.val(arg2val->packed).as_ulong() >> this->pb.ap.subaddr_len());
368 template<typename FieldT>
369 void tinyram_cpu_checker<FieldT>::generate_r1cs_witness_other(
370 tinyram_input_tape_iterator &aux_it,
371 const tinyram_input_tape_iterator &aux_end)
373 /* now ls_prev_val is filled with memory contents at ls_addr. we
374 now ensure consistency with its doubleword representation */
375 ls_prev_val_as_doubleword_variable->generate_r1cs_witness_from_bits();
377 /* fill in the opcode indicators */
378 const size_t opcode_val =
379 opcode.get_field_element_from_bits(this->pb).as_ulong();
380 for (size_t i = 0; i < 1ul << this->pb.ap.opcode_width(); ++i) {
381 this->pb.val(opcode_indicators[i]) =
382 (i == opcode_val ? FieldT::one() : FieldT::zero());
385 /* execute the ALU */
386 ALU->generate_r1cs_witness();
388 /* fill memory_subaddress */
389 memory_subaddress->bits.fill_with_bits(
391 pb_variable_array<FieldT>(
392 arg2val->bits.begin(),
393 arg2val->bits.begin() + +this->pb.ap.subaddr_len())
394 .get_bits(this->pb));
395 memory_subaddress->generate_r1cs_witness_from_bits();
397 /* we distinguish four cases for memory handling:
401 d) load.w or any non-memory instruction */
402 const size_t prev_doubleword =
403 this->pb.val(ls_prev_val_as_doubleword_variable->packed).as_ulong();
404 const size_t subaddress =
405 this->pb.val(memory_subaddress->packed).as_ulong();
407 if (this->pb.val(opcode_indicators[tinyram_opcode_LOADB]) ==
409 const size_t loaded_byte = (prev_doubleword >> (8 * subaddress)) & 0xFF;
410 this->pb.val(instruction_results[tinyram_opcode_LOADB]) =
412 this->pb.val(memory_subcontents) = FieldT(loaded_byte);
414 this->pb.val(opcode_indicators[tinyram_opcode_STOREB]) ==
416 const size_t stored_byte =
417 (this->pb.val(desval->packed).as_ulong()) & 0xFF;
418 this->pb.val(memory_subcontents) = FieldT(stored_byte);
420 this->pb.val(opcode_indicators[tinyram_opcode_STOREW]) ==
422 const size_t stored_word = (this->pb.val(desval->packed).as_ulong());
423 this->pb.val(memory_subcontents) = FieldT(stored_word);
425 const bool access_is_word0 =
426 (this->pb.val(*memory_subaddress->bits.rbegin()) == FieldT::zero());
427 const size_t loaded_word =
428 (prev_doubleword >> (access_is_word0 ? 0 : this->pb.ap.w)) &
429 ((1ul << this->pb.ap.w) - 1);
430 this->pb.val(instruction_results[tinyram_opcode_LOADW]) = FieldT(
431 loaded_word); /* does not hurt even for non-memory instructions */
432 this->pb.val(memory_subcontents) = FieldT(loaded_word);
435 memory_access_is_word.evaluate(this->pb);
436 memory_access_is_byte.evaluate(this->pb);
438 check_memory->generate_r1cs_witness();
441 if (this->pb.val(prev_tape1_exhausted) == FieldT::one()) {
442 /* if tape was exhausted before, it will always be
443 exhausted. we also need to only handle reads from tape 1,
444 so we can safely set flag here */
445 this->pb.val(next_tape1_exhausted) = FieldT::one();
446 this->pb.val(instruction_flags[tinyram_opcode_READ]) = FieldT::one();
449 this->pb.val(read_not1) =
450 this->pb.val(opcode_indicators[tinyram_opcode_READ]) *
451 (FieldT::one() - this->pb.val(arg2val->packed));
452 if (this->pb.val(read_not1) != FieldT::one()) {
453 /* reading from tape other than 0 raises the flag */
454 this->pb.val(instruction_flags[tinyram_opcode_READ]) = FieldT::one();
456 /* otherwise perform the actual read */
457 if (aux_it != aux_end) {
458 this->pb.val(instruction_results[tinyram_opcode_READ]) =
460 if (++aux_it == aux_end) {
461 /* tape has ended! */
462 this->pb.val(next_tape1_exhausted) = FieldT::one();
465 /* handled above, so nothing to do here */
469 /* flag implies result zero */
470 if (this->pb.val(instruction_flags[tinyram_opcode_READ]) == FieldT::one()) {
471 this->pb.val(instruction_results[tinyram_opcode_READ]) = FieldT::zero();
474 /* execute consistency enforcer */
475 consistency_enforcer->generate_r1cs_witness();
476 next_pc_addr_as_word_variable->generate_r1cs_witness_from_packed();
478 for (size_t i = 0; i < this->pb.ap.k; ++i) {
479 next_registers[i].generate_r1cs_witness_from_packed();
482 /* finally set has_accepted to 1 if both the opcode is ANSWER and arg2val is
484 this->pb.val(next_has_accepted) =
485 (this->pb.val(opcode_indicators[tinyram_opcode_ANSWER]) ==
487 this->pb.val(arg2val->packed) == FieldT::zero())
492 template<typename FieldT> void tinyram_cpu_checker<FieldT>::dump() const
495 " pc = %lu, flag = %lu\n",
496 this->pb.val(prev_pc_addr_as_word_variable->packed).as_ulong(),
497 this->pb.val(prev_flag).as_ulong());
500 for (size_t j = 0; j < this->pb.ap.k; ++j) {
504 this->pb.val(prev_registers[j].packed).as_ulong());
508 const size_t opcode_val =
509 opcode.get_field_element_from_bits(this->pb).as_ulong();
511 " %s r%lu, r%lu, %s%lu\n",
512 tinyram_opcode_names[static_cast<tinyram_opcode>(opcode_val)].c_str(),
513 desidx.get_field_element_from_bits(this->pb).as_ulong(),
514 arg1idx.get_field_element_from_bits(this->pb).as_ulong(),
515 (this->pb.val(arg2_is_imm) == FieldT::one() ? "" : "r"),
516 arg2idx.get_field_element_from_bits(this->pb).as_ulong());
519 } // namespace libsnark
521 #endif // TINYRAM_CPU_CHECKER_TCC_