|
|
@ -11,10 +11,13 @@ |
|
|
|
#include "libsnark/zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark.hpp" |
|
|
|
#include "libsnark/gadgetlib1/gadgets/hashes/sha256/sha256_gadget.hpp" |
|
|
|
#include "libsnark/gadgetlib1/gadgets/merkle_tree/merkle_tree_check_read_gadget.hpp" |
|
|
|
#include "zcash/IncrementalMerkleTree.hpp" |
|
|
|
|
|
|
|
using namespace libsnark; |
|
|
|
using namespace libzcash; |
|
|
|
|
|
|
|
#include "zcash/circuit/utils.tcc" |
|
|
|
#include "zcash/circuit/merkle.tcc" |
|
|
|
|
|
|
|
template<typename FieldT> |
|
|
|
void test_value_equals(uint64_t i) { |
|
|
@ -74,3 +77,108 @@ TEST(circuit, endianness) |
|
|
|
|
|
|
|
ASSERT_THROW(swap_endianness_u64(bad), std::length_error); |
|
|
|
} |
|
|
|
|
|
|
|
template<typename FieldT> |
|
|
|
bool test_merkle_gadget( |
|
|
|
bool enforce_a, |
|
|
|
bool enforce_b, |
|
|
|
bool write_root_first |
|
|
|
) |
|
|
|
{ |
|
|
|
protoboard<FieldT> pb; |
|
|
|
digest_variable<FieldT> root(pb, 256, "root"); |
|
|
|
pb.set_input_sizes(256); |
|
|
|
|
|
|
|
digest_variable<FieldT> commitment1(pb, 256, "commitment1"); |
|
|
|
digest_variable<FieldT> commitment2(pb, 256, "commitment2"); |
|
|
|
|
|
|
|
pb_variable<FieldT> commitment1_read; |
|
|
|
commitment1_read.allocate(pb); |
|
|
|
pb_variable<FieldT> commitment2_read; |
|
|
|
commitment2_read.allocate(pb); |
|
|
|
|
|
|
|
merkle_tree_gadget<FieldT> mgadget1(pb, commitment1, root, commitment1_read); |
|
|
|
merkle_tree_gadget<FieldT> mgadget2(pb, commitment2, root, commitment2_read); |
|
|
|
|
|
|
|
commitment1.generate_r1cs_constraints(); |
|
|
|
commitment2.generate_r1cs_constraints(); |
|
|
|
root.generate_r1cs_constraints(); |
|
|
|
mgadget1.generate_r1cs_constraints(); |
|
|
|
mgadget2.generate_r1cs_constraints(); |
|
|
|
|
|
|
|
ZCIncrementalMerkleTree tree; |
|
|
|
uint256 commitment1_data = uint256S("54d626e08c1c802b305dad30b7e54a82f102390cc92c7d4db112048935236e9c"); |
|
|
|
uint256 commitment2_data = uint256S("59d2cde5e65c1414c32ba54f0fe4bdb3d67618125286e6a191317917c812c6d7"); |
|
|
|
tree.append(commitment1_data); |
|
|
|
auto wit1 = tree.witness(); |
|
|
|
tree.append(commitment2_data); |
|
|
|
wit1.append(commitment2_data); |
|
|
|
auto wit2 = tree.witness(); |
|
|
|
auto expected_root = tree.root(); |
|
|
|
tree.append(uint256S("3e243c8798678570bb8d42616c23a536af44be15c4eef073490c2a44ae5f32c3")); |
|
|
|
auto unexpected_root = tree.root(); |
|
|
|
tree.append(uint256S("26d9b20c7f1c3d2528bbcd43cd63344b0afd3b6a0a8ebd37ec51cba34907bec7")); |
|
|
|
auto badwit1 = tree.witness(); |
|
|
|
tree.append(uint256S("02c2467c9cd15e0d150f74cd636505ed675b0b71b66a719f6f52fdb49a5937bb")); |
|
|
|
auto badwit2 = tree.witness(); |
|
|
|
|
|
|
|
// Perform the test
|
|
|
|
|
|
|
|
pb.val(commitment1_read) = enforce_a ? FieldT::one() : FieldT::zero(); |
|
|
|
pb.val(commitment2_read) = enforce_b ? FieldT::one() : FieldT::zero(); |
|
|
|
|
|
|
|
commitment1.bits.fill_with_bits(pb, uint256_to_bool_vector(commitment1_data)); |
|
|
|
commitment2.bits.fill_with_bits(pb, uint256_to_bool_vector(commitment2_data)); |
|
|
|
|
|
|
|
if (write_root_first) { |
|
|
|
root.bits.fill_with_bits(pb, uint256_to_bool_vector(expected_root)); |
|
|
|
} |
|
|
|
|
|
|
|
mgadget1.generate_r1cs_witness(wit1.path()); |
|
|
|
mgadget2.generate_r1cs_witness(wit2.path()); |
|
|
|
|
|
|
|
// Overwrite with our expected root
|
|
|
|
root.bits.fill_with_bits(pb, uint256_to_bool_vector(expected_root)); |
|
|
|
|
|
|
|
return pb.is_satisfied(); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(circuit, merkle_tree_gadget_weirdness) |
|
|
|
{ |
|
|
|
/*
|
|
|
|
The merkle tree gadget takes a leaf in the merkle tree (the Note commitment), |
|
|
|
a merkle tree authentication path, and a root (anchor). It also takes a parameter |
|
|
|
called read_success, which is used to determine if the commitment actually needs to |
|
|
|
appear in the tree. |
|
|
|
|
|
|
|
If two input notes use the same root (which our protocol does) then if `read_success` |
|
|
|
is disabled on the first note but enabled on the second note (i.e., the first note |
|
|
|
has value of zero and second note has nonzero value) then there is an edge case in |
|
|
|
the witnessing behavior. The first witness will accidentally constrain the root to |
|
|
|
equal null (the default value of the anchor) and the second witness will actually |
|
|
|
copy the bits, violating the constraint system. |
|
|
|
|
|
|
|
Notice that this edge case is not in the constraint system but in the witnessing |
|
|
|
behavior. |
|
|
|
*/ |
|
|
|
|
|
|
|
default_r1cs_ppzksnark_pp::init_public_params(); |
|
|
|
typedef Fr<default_r1cs_ppzksnark_pp> FieldT; |
|
|
|
|
|
|
|
// Test the normal case
|
|
|
|
ASSERT_TRUE(test_merkle_gadget<FieldT>(true, true, false)); |
|
|
|
ASSERT_TRUE(test_merkle_gadget<FieldT>(true, true, true)); |
|
|
|
|
|
|
|
// Test the case where the first commitment is enforced but the second isn't
|
|
|
|
// Works because the first read is performed before the second one
|
|
|
|
ASSERT_TRUE(test_merkle_gadget<FieldT>(true, false, false)); |
|
|
|
ASSERT_TRUE(test_merkle_gadget<FieldT>(true, false, true)); |
|
|
|
|
|
|
|
// Test the case where the first commitment isn't enforced but the second is
|
|
|
|
// Doesn't work because the first multipacker witnesses the existing root (which
|
|
|
|
// is null)
|
|
|
|
ASSERT_TRUE(!test_merkle_gadget<FieldT>(false, true, false)); |
|
|
|
|
|
|
|
// Test the last again, except this time write the root first.
|
|
|
|
ASSERT_TRUE(test_merkle_gadget<FieldT>(false, true, true)); |
|
|
|
} |
|
|
|