From 6b010d9bfdef11748d1cba08ef3b288b1ff0341b Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 4 May 2016 18:25:59 -0600 Subject: [PATCH] zkSNARK: Enforce that new output notes have unique `rho` to prevent faerie gold attack. --- src/zcash/circuit/gadget.tcc | 41 ++++++++++++++++++++++++++- src/zcash/circuit/note.tcc | 55 ++++++++++++++++++++++++++++++++++++ src/zcash/circuit/prfs.tcc | 21 ++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/zcash/circuit/gadget.tcc b/src/zcash/circuit/gadget.tcc index 84c718f0b..2f9e4e42c 100644 --- a/src/zcash/circuit/gadget.tcc +++ b/src/zcash/circuit/gadget.tcc @@ -20,16 +20,24 @@ private: // Aux inputs pb_variable ZERO; + std::shared_ptr> zk_phi; // Input note gadgets boost::array>, NumInputs> zk_input_notes; boost::array>, NumInputs> zk_hmac_authentication; + // Output note gadgets + boost::array>, NumOutputs> zk_output_notes; + public: // PRF_pk only has a 1-bit domain separation "nonce" - // for different hmacs. + // for different macs. BOOST_STATIC_ASSERT(NumInputs <= 2); + // PRF_rho only has a 1-bit domain separation "nonce" + // for different output `rho`. + BOOST_STATIC_ASSERT(NumOutputs <= 2); + joinsplit_gadget(protoboard &pb) : gadget(pb) { // Verification { @@ -77,6 +85,8 @@ public: // to be one automatically for us, and is known as `ONE`. ZERO.allocate(pb); + zk_phi.reset(new digest_variable(pb, 252, "")); + for (size_t i = 0; i < NumInputs; i++) { // Input note gadget for commitments, hmacs, nullifiers, // and spend authority. @@ -97,6 +107,16 @@ public: zk_input_hmacs[i] )); } + + for (size_t i = 0; i < NumOutputs; i++) { + zk_output_notes[i].reset(new output_note_gadget( + pb, + ZERO, + zk_phi->bits, + zk_h_sig->bits, + i ? true : false + )); + } } void generate_r1cs_constraints() { @@ -107,6 +127,9 @@ public: // Constrain `ZERO` generate_r1cs_equals_const_constraint(this->pb, ZERO, FieldT::zero(), "ZERO"); + // Constrain bitness of phi + zk_phi->generate_r1cs_constraints(); + for (size_t i = 0; i < NumInputs; i++) { // Constrain the JoinSplit input constraints. zk_input_notes[i]->generate_r1cs_constraints(); @@ -114,6 +137,11 @@ public: // Authenticate h_sig with a_sk zk_hmac_authentication[i]->generate_r1cs_constraints(); } + + for (size_t i = 0; i < NumOutputs; i++) { + // Constrain the JoinSplit output constraints. + zk_output_notes[i]->generate_r1cs_constraints(); + } } void generate_r1cs_witness( @@ -128,6 +156,12 @@ public: // Witness `zero` this->pb.val(ZERO) = FieldT::zero(); + // Witness phi + zk_phi->bits.fill_with_bits( + this->pb, + trailing252(uint256_to_bool_vector(phi)) + ); + // Witness h_sig zk_h_sig->bits.fill_with_bits( this->pb, @@ -142,6 +176,11 @@ public: zk_hmac_authentication[i]->generate_r1cs_witness(); } + for (size_t i = 0; i < NumOutputs; i++) { + // Witness the output information. + zk_output_notes[i]->generate_r1cs_witness(outputs[i]); + } + // 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 index e7ad5b88f..e4efb763b 100644 --- a/src/zcash/circuit/note.tcc +++ b/src/zcash/circuit/note.tcc @@ -105,3 +105,58 @@ public: expose_nullifiers->generate_r1cs_witness(); } }; + +template +class output_note_gadget : public note_gadget { +private: + std::shared_ptr> rho; + + std::shared_ptr> prevent_faerie_gold; + +public: + output_note_gadget( + protoboard& pb, + pb_variable& ZERO, + pb_variable_array& phi, + pb_variable_array& h_sig, + bool nonce + ) : note_gadget(pb) { + rho.reset(new digest_variable(pb, 256, "")); + + // Do not allow the caller to choose the same "rho" + // for any two valid notes in a given view of the + // blockchain. See protocol specification for more + // details. + prevent_faerie_gold.reset(new PRF_rho_gadget( + pb, + ZERO, + phi, + h_sig, + nonce, + rho + )); + } + + void generate_r1cs_constraints() { + note_gadget::generate_r1cs_constraints(); + + // TODO: This constraint may not be necessary if SHA256 + // already boolean constrains its outputs. + rho->generate_r1cs_constraints(); + + prevent_faerie_gold->generate_r1cs_constraints(); + } + + void generate_r1cs_witness(const Note& note) { + note_gadget::generate_r1cs_witness(note); + + prevent_faerie_gold->generate_r1cs_witness(); + + // [SANITY CHECK] Witness rho ourselves with the + // note information. + rho->bits.fill_with_bits( + this->pb, + uint256_to_bool_vector(note.rho) + ); + } +}; diff --git a/src/zcash/circuit/prfs.tcc b/src/zcash/circuit/prfs.tcc index 3c03f7983..982c1fdbd 100644 --- a/src/zcash/circuit/prfs.tcc +++ b/src/zcash/circuit/prfs.tcc @@ -116,3 +116,24 @@ public: PRF_gadget::generate_r1cs_witness(); } }; + +template +class PRF_rho_gadget : public PRF_gadget { +public: + PRF_rho_gadget( + protoboard& pb, + pb_variable& ZERO, + pb_variable_array& phi, + pb_variable_array& h_sig, + bool nonce, + std::shared_ptr> result + ) : PRF_gadget(pb, ZERO, 0, nonce, 1, 0, phi, h_sig, result) {} + + void generate_r1cs_constraints() { + PRF_gadget::generate_r1cs_constraints(); + } + + void generate_r1cs_witness() { + PRF_gadget::generate_r1cs_witness(); + } +};