diff --git a/src/zcash/circuit/gadget.tcc b/src/zcash/circuit/gadget.tcc index 7872a54ff..f463600df 100644 --- a/src/zcash/circuit/gadget.tcc +++ b/src/zcash/circuit/gadget.tcc @@ -1,6 +1,7 @@ #include "zcash/circuit/utils.tcc" #include "zcash/circuit/prfs.tcc" #include "zcash/circuit/commitment.tcc" +#include "zcash/circuit/merkle.tcc" #include "zcash/circuit/note.tcc" template @@ -94,7 +95,8 @@ public: zk_input_notes[i].reset(new input_note_gadget( pb, ZERO, - zk_input_nullifiers[i] + zk_input_nullifiers[i], + *zk_merkle_root )); // The input keys authenticate h_sig to prevent @@ -180,6 +182,16 @@ public: // Witness `zero` this->pb.val(ZERO) = FieldT::zero(); + // Witness rt. This is not a sanity check. + // + // This ensures the read gadget constrains + // the intended root in the event that + // both inputs are zero-valued. + zk_merkle_root->bits.fill_with_bits( + this->pb, + uint256_to_bool_vector(rt) + ); + // Witness public balance values zk_vpub_old.fill_with_bits( this->pb, @@ -204,7 +216,12 @@ public: for (size_t i = 0; i < NumInputs; i++) { // Witness the input information. - zk_input_notes[i]->generate_r1cs_witness(inputs[i].key, inputs[i].note); + auto merkle_path = inputs[i].witness.path(); + zk_input_notes[i]->generate_r1cs_witness( + merkle_path, + inputs[i].key, + inputs[i].note + ); // Witness hmacs zk_hmac_authentication[i]->generate_r1cs_witness(); @@ -215,6 +232,17 @@ public: zk_output_notes[i]->generate_r1cs_witness(outputs[i]); } + // [SANITY CHECK] Ensure that the intended root + // was witnessed by the inputs, even if the read + // gadget overwrote it. This allows the prover to + // fail instead of the verifier, in the event that + // the roots of the inputs do not match the + // treestate provided to the proving API. + zk_merkle_root->bits.fill_with_bits( + this->pb, + uint256_to_bool_vector(rt) + ); + // This happens last, because only by now are all the // verifier inputs resolved. unpacker->generate_r1cs_witness_from_bits(); @@ -231,7 +259,7 @@ public: ) { std::vector verify_inputs; - insert_uint256(verify_inputs, uint256()); // TODO: rt + insert_uint256(verify_inputs, rt); insert_uint256(verify_inputs, h_sig); for (size_t i = 0; i < NumInputs; i++) { diff --git a/src/zcash/circuit/merkle.tcc b/src/zcash/circuit/merkle.tcc new file mode 100644 index 000000000..3ec420b91 --- /dev/null +++ b/src/zcash/circuit/merkle.tcc @@ -0,0 +1,60 @@ +template +class merkle_tree_gadget : gadget { +private: + typedef sha256_two_to_one_hash_gadget sha256_gadget; + + pb_variable_array positions; + std::shared_ptr> authvars; + std::shared_ptr> auth; + +public: + merkle_tree_gadget( + protoboard& pb, + digest_variable leaf, + digest_variable root, + pb_variable& enforce + ) : gadget(pb) { + positions.allocate(pb, INCREMENTAL_MERKLE_TREE_DEPTH); + authvars.reset(new merkle_authentication_path_variable( + pb, INCREMENTAL_MERKLE_TREE_DEPTH, "auth" + )); + auth.reset(new merkle_tree_check_read_gadget( + pb, + INCREMENTAL_MERKLE_TREE_DEPTH, + positions, + leaf, + root, + *authvars, + enforce, + "" + )); + } + + void generate_r1cs_constraints() { + for (size_t i = 0; i < INCREMENTAL_MERKLE_TREE_DEPTH; i++) { + // TODO: This might not be necessary, and doesn't + // appear to be done in libsnark's tests, but there + // is no documentation, so let's do it anyway to + // be safe. + generate_boolean_r1cs_constraint( + this->pb, + positions[i], + "boolean_positions" + ); + } + + authvars->generate_r1cs_constraints(); + auth->generate_r1cs_constraints(); + } + + void generate_r1cs_witness(const MerklePath& path) { + // TODO: Change libsnark so that it doesn't require this goofy + // number thing in its API. + size_t path_index = libzerocash::convertVectorToInt(path.index); + + positions.fill_with_bits_of_ulong(this->pb, path_index); + + authvars->generate_r1cs_witness(path_index, path.authentication_path); + auth->generate_r1cs_witness(); + } +}; diff --git a/src/zcash/circuit/note.tcc b/src/zcash/circuit/note.tcc index 418db1d3a..c011db175 100644 --- a/src/zcash/circuit/note.tcc +++ b/src/zcash/circuit/note.tcc @@ -36,6 +36,9 @@ private: std::shared_ptr> commitment; std::shared_ptr> commit_to_inputs; + pb_variable value_enforce; + std::shared_ptr> witness_input; + std::shared_ptr> spend_authority; std::shared_ptr> expose_nullifiers; public: @@ -44,7 +47,8 @@ public: input_note_gadget( protoboard& pb, pb_variable& ZERO, - std::shared_ptr> nullifier + std::shared_ptr> nullifier, + digest_variable rt ) : note_gadget(pb) { a_sk.reset(new digest_variable(pb, 252, "")); a_pk.reset(new digest_variable(pb, 256, "")); @@ -75,6 +79,15 @@ public: this->r->bits, commitment )); + + value_enforce.allocate(pb); + + witness_input.reset(new merkle_tree_gadget( + pb, + *commitment, + rt, + value_enforce + )); } void generate_r1cs_constraints() { @@ -92,9 +105,27 @@ public: expose_nullifiers->generate_r1cs_constraints(); commit_to_inputs->generate_r1cs_constraints(); + + // value * (1 - enforce) = 0 + // Given `enforce` is boolean constrained: + // If `value` is zero, `enforce` _can_ be zero. + // If `value` is nonzero, `enforce` _must_ be one. + generate_boolean_r1cs_constraint(this->pb, value_enforce,""); + + this->pb.add_r1cs_constraint(r1cs_constraint( + packed_addition(this->value), + (1 - value_enforce), + 0 + ), ""); + + witness_input->generate_r1cs_constraints(); } - void generate_r1cs_witness(const SpendingKey& key, const Note& note) { + void generate_r1cs_witness( + const MerklePath& path, + const SpendingKey& key, + const Note& note + ) { note_gadget::generate_r1cs_witness(note); // Witness a_sk for the input @@ -130,6 +161,12 @@ public: this->pb, uint256_to_bool_vector(note.cm()) ); + + // Set enforce flag for nonzero input value + this->pb.val(value_enforce) = (note.value != 0) ? FieldT::one() : FieldT::zero(); + + // Witness merkle tree authentication path + witness_input->generate_r1cs_witness(path); } };