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_