diff --git a/src/zcash/circuit/gadget.tcc b/src/zcash/circuit/gadget.tcc index bb266864a..dc0fad918 100644 --- a/src/zcash/circuit/gadget.tcc +++ b/src/zcash/circuit/gadget.tcc @@ -1,4 +1,6 @@ #include "zcash/circuit/utils.tcc" +#include "zcash/circuit/prfs.tcc" +#include "zcash/circuit/note.tcc" template class joinsplit_gadget : gadget { @@ -19,6 +21,9 @@ private: // Aux inputs pb_variable ZERO; + // Input note gadgets + boost::array>, NumInputs> zk_input_notes; + public: joinsplit_gadget(protoboard &pb) : gadget(pb) { // Verification @@ -67,7 +72,14 @@ public: // to be one automatically for us, and is known as `ONE`. ZERO.allocate(pb); - + for (size_t i = 0; i < NumInputs; i++) { + // Input note gadget for commitments, hmacs, nullifiers, + // and spend authority. + zk_input_notes[i].reset(new input_note_gadget( + pb, + ZERO + )); + } } void generate_r1cs_constraints() { @@ -77,6 +89,11 @@ public: // Constrain `ZERO` generate_r1cs_equals_const_constraint(this->pb, ZERO, FieldT::zero(), "ZERO"); + + for (size_t i = 0; i < NumInputs; i++) { + // Constrain the JoinSplit input constraints. + zk_input_notes[i]->generate_r1cs_constraints(); + } } void generate_r1cs_witness( @@ -91,6 +108,11 @@ public: // Witness `zero` this->pb.val(ZERO) = FieldT::zero(); + 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); + } + // This happens last, because only by now are all the // verifier inputs resolved. unpacker->generate_r1cs_witness_from_bits(); diff --git a/src/zcash/circuit/note.tcc b/src/zcash/circuit/note.tcc new file mode 100644 index 000000000..ab29f4098 --- /dev/null +++ b/src/zcash/circuit/note.tcc @@ -0,0 +1,82 @@ +template +class note_gadget : public gadget { +public: + pb_variable_array value; + std::shared_ptr> r; + + note_gadget(protoboard &pb) : gadget(pb) { + value.allocate(pb, 64); + r.reset(new digest_variable(pb, 256, "")); + } + + void generate_r1cs_constraints() { + for (size_t i = 0; i < 64; i++) { + generate_boolean_r1cs_constraint( + this->pb, + value[i], + "boolean_value" + ); + } + + r->generate_r1cs_constraints(); + } + + void generate_r1cs_witness(const Note& note) { + r->bits.fill_with_bits(this->pb, uint256_to_bool_vector(note.r)); + value.fill_with_bits(this->pb, uint64_to_bool_vector(note.value)); + } +}; + +template +class input_note_gadget : public note_gadget { +public: + std::shared_ptr> a_sk; + std::shared_ptr> a_pk; + + std::shared_ptr> spend_authority; + + input_note_gadget( + protoboard& pb, + pb_variable& ZERO + ) : note_gadget(pb) { + a_sk.reset(new digest_variable(pb, 252, "")); + a_pk.reset(new digest_variable(pb, 256, "")); + spend_authority.reset(new PRF_addr_a_pk_gadget( + pb, + ZERO, + a_sk->bits, + a_pk + )); + } + + void generate_r1cs_constraints() { + note_gadget::generate_r1cs_constraints(); + + a_sk->generate_r1cs_constraints(); + + // TODO: This constraint may not be necessary if SHA256 + // already boolean constrains its outputs. + a_pk->generate_r1cs_constraints(); + + spend_authority->generate_r1cs_constraints(); + } + + void generate_r1cs_witness(const SpendingKey& key, const Note& note) { + note_gadget::generate_r1cs_witness(note); + + // Witness a_sk for the input + a_sk->bits.fill_with_bits( + this->pb, + trailing252(uint256_to_bool_vector(key)) + ); + + // Witness a_pk for a_sk with PRF_addr + spend_authority->generate_r1cs_witness(); + + // [SANITY CHECK] Witness a_pk with note information + a_pk->bits.fill_with_bits( + this->pb, + uint256_to_bool_vector(note.a_pk) + ); + } +}; diff --git a/src/zcash/circuit/prfs.tcc b/src/zcash/circuit/prfs.tcc new file mode 100644 index 000000000..64cd7619d --- /dev/null +++ b/src/zcash/circuit/prfs.tcc @@ -0,0 +1,77 @@ +template +class PRF_gadget : gadget { +private: + std::shared_ptr> block; + std::shared_ptr> hasher; + std::shared_ptr> result; + +public: + PRF_gadget( + protoboard& pb, + pb_variable& ZERO, + bool a, + bool b, + bool c, + bool d, + pb_variable_array x, + boost::optional> y, + std::shared_ptr> result + ) : gadget(pb), result(result) { + + pb_linear_combination_array IV = SHA256_default_IV(pb); + + pb_variable_array discriminants; + discriminants.emplace_back(a ? ONE : ZERO); + discriminants.emplace_back(b ? ONE : ZERO); + discriminants.emplace_back(c ? ONE : ZERO); + discriminants.emplace_back(d ? ONE : ZERO); + + if (!y) { + // Create y and pad it with zeroes. + y = pb_variable_array(); + while (y->size() < 256) { + y->emplace_back(ZERO); + } + } + + block.reset(new block_variable(pb, { + discriminants, + x, + *y + }, "PRF_block")); + + hasher.reset(new sha256_compression_function_gadget( + pb, + IV, + block->bits, + *result, + "PRF_hasher")); + } + + void generate_r1cs_constraints() { + hasher->generate_r1cs_constraints(); + } + + void generate_r1cs_witness() { + hasher->generate_r1cs_witness(); + } +}; + +template +class PRF_addr_a_pk_gadget : public PRF_gadget { +public: + PRF_addr_a_pk_gadget( + protoboard& pb, + pb_variable& ZERO, + pb_variable_array& a_sk, + std::shared_ptr> result + ) : PRF_gadget(pb, ZERO, 1, 1, 0, 0, a_sk, boost::none, result) {} + + void generate_r1cs_constraints() { + PRF_gadget::generate_r1cs_constraints(); + } + + void generate_r1cs_witness() { + PRF_gadget::generate_r1cs_witness(); + } +}; diff --git a/src/zcash/circuit/utils.tcc b/src/zcash/circuit/utils.tcc index 64018a3b4..1cc6bb9e9 100644 --- a/src/zcash/circuit/utils.tcc +++ b/src/zcash/circuit/utils.tcc @@ -1,3 +1,11 @@ +std::vector trailing252(std::vector input) { + if (input.size() != 256) { + throw std::length_error("trailing252 input invalid length"); + } + + return std::vector(input.begin() + 4, input.end()); +} + std::vector uint256_to_bool_vector(uint256 input) { std::vector input_v(input.begin(), input.end()); std::vector output_bv(256, 0);