#include #include "uint256.h" #include "zcash/util.h" #include #include #include #include #include #include #include #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) { protoboard pb; pb_variable_array num; num.allocate(pb, 64, ""); num.fill_with_bits(pb, uint64_to_bool_vector(i)); pb.add_r1cs_constraint(r1cs_constraint( packed_addition(num), FieldT::one(), FieldT::one() * i ), ""); ASSERT_TRUE(pb.is_satisfied()); } TEST(circuit, values) { typedef Fr FieldT; test_value_equals(0); test_value_equals(1); test_value_equals(3); test_value_equals(5391); test_value_equals(883128374); test_value_equals(173419028459); test_value_equals(2205843009213693953); } TEST(circuit, endianness) { std::vector before = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 }; auto result = swap_endianness_u64(before); std::vector after = { 56, 57, 58, 59, 60, 61, 62, 63, 48, 49, 50, 51, 52, 53, 54, 55, 40, 41, 42, 43, 44, 45, 46, 47, 32, 33, 34, 35, 36, 37, 38, 39, 24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 }; EXPECT_EQ(after, result); std::vector bad = {0, 1, 2, 3}; 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(); SproutMerkleTree 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. */ 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)); }