zkSNARK: Enforce merkle authentication path from nonzero-valued public inputs to root.
This commit is contained in:
parent
fcece37f00
commit
59c3d926c6
|
@ -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<typename FieldT, size_t NumInputs, size_t NumOutputs>
|
||||
|
@ -94,7 +95,8 @@ public:
|
|||
zk_input_notes[i].reset(new input_note_gadget<FieldT>(
|
||||
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<bool> 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++) {
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
template<typename FieldT>
|
||||
class merkle_tree_gadget : gadget<FieldT> {
|
||||
private:
|
||||
typedef sha256_two_to_one_hash_gadget<FieldT> sha256_gadget;
|
||||
|
||||
pb_variable_array<FieldT> positions;
|
||||
std::shared_ptr<merkle_authentication_path_variable<FieldT, sha256_gadget>> authvars;
|
||||
std::shared_ptr<merkle_tree_check_read_gadget<FieldT, sha256_gadget>> auth;
|
||||
|
||||
public:
|
||||
merkle_tree_gadget(
|
||||
protoboard<FieldT>& pb,
|
||||
digest_variable<FieldT> leaf,
|
||||
digest_variable<FieldT> root,
|
||||
pb_variable<FieldT>& enforce
|
||||
) : gadget<FieldT>(pb) {
|
||||
positions.allocate(pb, INCREMENTAL_MERKLE_TREE_DEPTH);
|
||||
authvars.reset(new merkle_authentication_path_variable<FieldT, sha256_gadget>(
|
||||
pb, INCREMENTAL_MERKLE_TREE_DEPTH, "auth"
|
||||
));
|
||||
auth.reset(new merkle_tree_check_read_gadget<FieldT, sha256_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<FieldT>(
|
||||
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();
|
||||
}
|
||||
};
|
|
@ -36,6 +36,9 @@ private:
|
|||
std::shared_ptr<digest_variable<FieldT>> commitment;
|
||||
std::shared_ptr<note_commitment_gadget<FieldT>> commit_to_inputs;
|
||||
|
||||
pb_variable<FieldT> value_enforce;
|
||||
std::shared_ptr<merkle_tree_gadget<FieldT>> witness_input;
|
||||
|
||||
std::shared_ptr<PRF_addr_a_pk_gadget<FieldT>> spend_authority;
|
||||
std::shared_ptr<PRF_nf_gadget<FieldT>> expose_nullifiers;
|
||||
public:
|
||||
|
@ -44,7 +47,8 @@ public:
|
|||
input_note_gadget(
|
||||
protoboard<FieldT>& pb,
|
||||
pb_variable<FieldT>& ZERO,
|
||||
std::shared_ptr<digest_variable<FieldT>> nullifier
|
||||
std::shared_ptr<digest_variable<FieldT>> nullifier,
|
||||
digest_variable<FieldT> rt
|
||||
) : note_gadget<FieldT>(pb) {
|
||||
a_sk.reset(new digest_variable<FieldT>(pb, 252, ""));
|
||||
a_pk.reset(new digest_variable<FieldT>(pb, 256, ""));
|
||||
|
@ -75,6 +79,15 @@ public:
|
|||
this->r->bits,
|
||||
commitment
|
||||
));
|
||||
|
||||
value_enforce.allocate(pb);
|
||||
|
||||
witness_input.reset(new merkle_tree_gadget<FieldT>(
|
||||
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<FieldT>(this->pb, value_enforce,"");
|
||||
|
||||
this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(
|
||||
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<FieldT>::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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue