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/utils.tcc"
|
||||||
#include "zcash/circuit/prfs.tcc"
|
#include "zcash/circuit/prfs.tcc"
|
||||||
#include "zcash/circuit/commitment.tcc"
|
#include "zcash/circuit/commitment.tcc"
|
||||||
|
#include "zcash/circuit/merkle.tcc"
|
||||||
#include "zcash/circuit/note.tcc"
|
#include "zcash/circuit/note.tcc"
|
||||||
|
|
||||||
template<typename FieldT, size_t NumInputs, size_t NumOutputs>
|
template<typename FieldT, size_t NumInputs, size_t NumOutputs>
|
||||||
|
@ -94,7 +95,8 @@ public:
|
||||||
zk_input_notes[i].reset(new input_note_gadget<FieldT>(
|
zk_input_notes[i].reset(new input_note_gadget<FieldT>(
|
||||||
pb,
|
pb,
|
||||||
ZERO,
|
ZERO,
|
||||||
zk_input_nullifiers[i]
|
zk_input_nullifiers[i],
|
||||||
|
*zk_merkle_root
|
||||||
));
|
));
|
||||||
|
|
||||||
// The input keys authenticate h_sig to prevent
|
// The input keys authenticate h_sig to prevent
|
||||||
|
@ -180,6 +182,16 @@ public:
|
||||||
// Witness `zero`
|
// Witness `zero`
|
||||||
this->pb.val(ZERO) = FieldT::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
|
// Witness public balance values
|
||||||
zk_vpub_old.fill_with_bits(
|
zk_vpub_old.fill_with_bits(
|
||||||
this->pb,
|
this->pb,
|
||||||
|
@ -204,7 +216,12 @@ public:
|
||||||
|
|
||||||
for (size_t i = 0; i < NumInputs; i++) {
|
for (size_t i = 0; i < NumInputs; i++) {
|
||||||
// Witness the input information.
|
// 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
|
// Witness hmacs
|
||||||
zk_hmac_authentication[i]->generate_r1cs_witness();
|
zk_hmac_authentication[i]->generate_r1cs_witness();
|
||||||
|
@ -215,6 +232,17 @@ public:
|
||||||
zk_output_notes[i]->generate_r1cs_witness(outputs[i]);
|
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
|
// This happens last, because only by now are all the
|
||||||
// verifier inputs resolved.
|
// verifier inputs resolved.
|
||||||
unpacker->generate_r1cs_witness_from_bits();
|
unpacker->generate_r1cs_witness_from_bits();
|
||||||
|
@ -231,7 +259,7 @@ public:
|
||||||
) {
|
) {
|
||||||
std::vector<bool> verify_inputs;
|
std::vector<bool> verify_inputs;
|
||||||
|
|
||||||
insert_uint256(verify_inputs, uint256()); // TODO: rt
|
insert_uint256(verify_inputs, rt);
|
||||||
insert_uint256(verify_inputs, h_sig);
|
insert_uint256(verify_inputs, h_sig);
|
||||||
|
|
||||||
for (size_t i = 0; i < NumInputs; i++) {
|
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<digest_variable<FieldT>> commitment;
|
||||||
std::shared_ptr<note_commitment_gadget<FieldT>> commit_to_inputs;
|
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_addr_a_pk_gadget<FieldT>> spend_authority;
|
||||||
std::shared_ptr<PRF_nf_gadget<FieldT>> expose_nullifiers;
|
std::shared_ptr<PRF_nf_gadget<FieldT>> expose_nullifiers;
|
||||||
public:
|
public:
|
||||||
|
@ -44,7 +47,8 @@ public:
|
||||||
input_note_gadget(
|
input_note_gadget(
|
||||||
protoboard<FieldT>& pb,
|
protoboard<FieldT>& pb,
|
||||||
pb_variable<FieldT>& ZERO,
|
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) {
|
) : note_gadget<FieldT>(pb) {
|
||||||
a_sk.reset(new digest_variable<FieldT>(pb, 252, ""));
|
a_sk.reset(new digest_variable<FieldT>(pb, 252, ""));
|
||||||
a_pk.reset(new digest_variable<FieldT>(pb, 256, ""));
|
a_pk.reset(new digest_variable<FieldT>(pb, 256, ""));
|
||||||
|
@ -75,6 +79,15 @@ public:
|
||||||
this->r->bits,
|
this->r->bits,
|
||||||
commitment
|
commitment
|
||||||
));
|
));
|
||||||
|
|
||||||
|
value_enforce.allocate(pb);
|
||||||
|
|
||||||
|
witness_input.reset(new merkle_tree_gadget<FieldT>(
|
||||||
|
pb,
|
||||||
|
*commitment,
|
||||||
|
rt,
|
||||||
|
value_enforce
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void generate_r1cs_constraints() {
|
void generate_r1cs_constraints() {
|
||||||
|
@ -92,9 +105,27 @@ public:
|
||||||
expose_nullifiers->generate_r1cs_constraints();
|
expose_nullifiers->generate_r1cs_constraints();
|
||||||
|
|
||||||
commit_to_inputs->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);
|
note_gadget<FieldT>::generate_r1cs_witness(note);
|
||||||
|
|
||||||
// Witness a_sk for the input
|
// Witness a_sk for the input
|
||||||
|
@ -130,6 +161,12 @@ public:
|
||||||
this->pb,
|
this->pb,
|
||||||
uint256_to_bool_vector(note.cm())
|
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