From 4d66f8f68af776e1beca988b182ed44baa30a55a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 21 Jul 2016 19:28:58 -0600 Subject: [PATCH] Add test for non-intuitive merkle tree gadget witnessing behavior. --- src/gtest/test_circuit.cpp | 108 +++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/gtest/test_circuit.cpp b/src/gtest/test_circuit.cpp index 612a7288..2039e5b9 100644 --- a/src/gtest/test_circuit.cpp +++ b/src/gtest/test_circuit.cpp @@ -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 void test_value_equals(uint64_t i) { @@ -74,3 +77,108 @@ TEST(circuit, endianness) ASSERT_THROW(swap_endianness_u64(bad), std::length_error); } + +template +bool test_merkle_gadget( + bool enforce_a, + bool enforce_b, + bool write_root_first +) +{ + protoboard pb; + digest_variable root(pb, 256, "root"); + pb.set_input_sizes(256); + + digest_variable commitment1(pb, 256, "commitment1"); + digest_variable commitment2(pb, 256, "commitment2"); + + pb_variable commitment1_read; + commitment1_read.allocate(pb); + pb_variable commitment2_read; + commitment2_read.allocate(pb); + + merkle_tree_gadget mgadget1(pb, commitment1, root, commitment1_read); + merkle_tree_gadget 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 FieldT; + + // Test the normal case + ASSERT_TRUE(test_merkle_gadget(true, true, false)); + ASSERT_TRUE(test_merkle_gadget(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(true, false, false)); + ASSERT_TRUE(test_merkle_gadget(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(false, true, false)); + + // Test the last again, except this time write the root first. + ASSERT_TRUE(test_merkle_gadget(false, true, true)); +}