diff --git a/src/test/merkle_tests.cpp b/src/test/merkle_tests.cpp index 883cda13a..cfdd9d1b2 100644 --- a/src/test/merkle_tests.cpp +++ b/src/test/merkle_tests.cpp @@ -18,6 +18,7 @@ using namespace json_spirit; extern Array read_json(const std::string& jsondata); #include "zcash/IncrementalMerkleTree.hpp" +#include "zerocash/IncrementalMerkleTree.h" //#define PRINT_JSON 1 @@ -41,15 +42,109 @@ void expect_test_vector(T& it, const U& expected) #endif } -BOOST_FIXTURE_TEST_SUITE(merkle_tests, BasicTestingSetup) +/* +This is a wrapper around the old incremental merkle tree which +attempts to mimic the new API as much as possible so that its +behavior can be compared with the test vectors we use. +*/ +class OldIncrementalMerkleTree { +private: + libzerocash::IncrementalMerkleTree* tree; + boost::optional> index; + bool witnessed; -BOOST_AUTO_TEST_CASE(tree_test_vectors) -{ - Array root_tests = read_json(std::string(json_tests::merkle_roots, json_tests::merkle_roots + sizeof(json_tests::merkle_roots))); - Array ser_tests = read_json(std::string(json_tests::merkle_serialization, json_tests::merkle_serialization + sizeof(json_tests::merkle_serialization))); - Array witness_ser_tests = read_json(std::string(json_tests::merkle_witness_serialization, json_tests::merkle_witness_serialization + sizeof(json_tests::merkle_witness_serialization))); - Array path_tests = read_json(std::string(json_tests::merkle_path, json_tests::merkle_path + sizeof(json_tests::merkle_path))); +public: + OldIncrementalMerkleTree() : index(boost::none), witnessed(false) { + this->tree = new IncrementalMerkleTree(INCREMENTAL_MERKLE_TREE_DEPTH_TESTING); + } + ~OldIncrementalMerkleTree() + { + delete tree; + } + + OldIncrementalMerkleTree (const OldIncrementalMerkleTree& other) : index(boost::none), witnessed(false) + { + this->tree = new IncrementalMerkleTree(INCREMENTAL_MERKLE_TREE_DEPTH_TESTING); + this->tree->setTo(*other.tree); + index = other.index; + witnessed = other.witnessed; + } + + OldIncrementalMerkleTree& operator= (const OldIncrementalMerkleTree& other) + { + OldIncrementalMerkleTree tmp(other); // re-use copy-constructor + *this = std::move(tmp); // re-use move-assignment + return *this; + } + + OldIncrementalMerkleTree& operator= (OldIncrementalMerkleTree&& other) + { + tree->setTo(*other.tree); + index = other.index; + witnessed = other.witnessed; + return *this; + } + + libzcash::MerklePath path() { + assert(witnessed); + + if (!index) { + throw std::runtime_error("can't create an authentication path for the beginning of the tree"); + } + + merkle_authentication_path path(INCREMENTAL_MERKLE_TREE_DEPTH_TESTING); + tree->getWitness(*index, path); + + libzcash::MerklePath ret; + ret.authentication_path = path; + ret.index = *index; + + return ret; + } + + uint256 root() { + std::vector newrt_v(32); + tree->getRootValue(newrt_v); + return uint256(newrt_v); + } + + void append(uint256 obj) { + std::vector new_index; + std::vector obj_bv(obj.begin(), obj.end()); + + std::vector commitment_bv(256); + libzerocash::convertBytesVectorToVector(obj_bv, commitment_bv); + + tree->insertElement(commitment_bv, new_index); + + if (!witnessed) { + index = new_index; + } + } + + OldIncrementalMerkleTree witness() { + OldIncrementalMerkleTree ret; + ret.tree->setTo(*tree); + ret.index = index; + ret.witnessed = true; + + return ret; + } +}; + +template +void expect_ser_test_vector(B& b, const C& c, const A& tree) { + expect_test_vector(b, c); +} + +template +void expect_ser_test_vector(B& b, const C& c, const OldIncrementalMerkleTree& tree) { + // Don't perform serialization tests on the old tree. +} + +template +void test_tree(Array root_tests, Array ser_tests, Array witness_ser_tests, Array path_tests) { Array::iterator root_iterator = root_tests.begin(); Array::iterator ser_iterator = ser_tests.begin(); Array::iterator witness_ser_iterator = witness_ser_tests.begin(); @@ -57,7 +152,7 @@ BOOST_AUTO_TEST_CASE(tree_test_vectors) uint256 test_commitment = uint256S("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - ZCTestingIncrementalMerkleTree tree; + Tree tree; // The root of the tree at this point is expected to be null. BOOST_CHECK(tree.root().IsNull()); @@ -65,7 +160,7 @@ BOOST_AUTO_TEST_CASE(tree_test_vectors) // We need to witness at every single point in the tree, so // that the consistency of the tree and the merkle paths can // be checked. - vector witnesses; + vector witnesses; for (size_t i = 0; i < 16; i++) { // Witness here @@ -78,10 +173,10 @@ BOOST_AUTO_TEST_CASE(tree_test_vectors) expect_test_vector(root_iterator, tree.root()); // Check serialization of tree - expect_test_vector(ser_iterator, tree); + expect_ser_test_vector(ser_iterator, tree, tree); bool first = true; // The first witness can never form a path - BOOST_FOREACH(ZCTestingIncrementalWitness& wit, witnesses) + BOOST_FOREACH(Witness& wit, witnesses) { // Append the same commitment to all the witnesses wit.append(test_commitment); @@ -91,11 +186,65 @@ BOOST_AUTO_TEST_CASE(tree_test_vectors) } else { auto path = wit.path(); - expect_test_vector(path_iterator, path); + // The old tree has some serious bugs which make it + // fail some of these test vectors. + // + // The new tree is strictly more correct in its + // behavior, as we demonstrate by constructing and + // evaluating the tree over a dummy circuit. + if (typeid(Tree) != typeid(OldIncrementalMerkleTree)) { + expect_test_vector(path_iterator, path); + + typedef Fr FieldT; + + protoboard pb; + pb_variable_array positions; + digest_variable commitment(pb, 256, "commitment"); + digest_variable root(pb, 256, "root"); + positions.allocate(pb, INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, "pos"); + merkle_authentication_path_variable> authvars(pb, INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, "auth"); + merkle_tree_check_read_gadget> auth( + pb, INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, positions, commitment, root, authvars, ONE, "path" + ); + commitment.generate_r1cs_constraints(); + root.generate_r1cs_constraints(); + authvars.generate_r1cs_constraints(); + auth.generate_r1cs_constraints(); + + std::vector commitment_bv; + { + std::vector commitment_v(test_commitment.begin(), test_commitment.end()); + convertBytesVectorToVector(commitment_v, commitment_bv); + } + + size_t path_index = libzerocash::convertVectorToInt(path.index); + + commitment.bits.fill_with_bits(pb, bit_vector(commitment_bv)); + positions.fill_with_bits_of_ulong(pb, path_index); + + authvars.generate_r1cs_witness(path_index, path.authentication_path); + auth.generate_r1cs_witness(); + + std::vector root_bv; + { + uint256 witroot = wit.root(); + std::vector root_v(witroot.begin(), witroot.end()); + convertBytesVectorToVector(root_v, root_bv); + } + + root.bits.fill_with_bits(pb, bit_vector(root_bv)); + + BOOST_CHECK(pb.is_satisfied()); + + root_bv[0] = !root_bv[0]; + root.bits.fill_with_bits(pb, bit_vector(root_bv)); + + BOOST_CHECK(!pb.is_satisfied()); + } } // Check witness serialization - expect_test_vector(witness_ser_iterator, wit); + expect_ser_test_vector(witness_ser_iterator, wit, tree); BOOST_CHECK(wit.root() == tree.root()); @@ -103,15 +252,31 @@ BOOST_AUTO_TEST_CASE(tree_test_vectors) } } - // Tree should be full now - BOOST_CHECK_THROW(tree.append(uint256()), std::runtime_error); + // The old tree would silently ignore appending when it was full. + if (typeid(Tree) != typeid(OldIncrementalMerkleTree)) { + // Tree should be full now + BOOST_CHECK_THROW(tree.append(uint256()), std::runtime_error); - BOOST_FOREACH(ZCTestingIncrementalWitness& wit, witnesses) - { - BOOST_CHECK_THROW(wit.append(uint256()), std::runtime_error); + BOOST_FOREACH(Witness& wit, witnesses) + { + BOOST_CHECK_THROW(wit.append(uint256()), std::runtime_error); + } } } +BOOST_FIXTURE_TEST_SUITE(merkle_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(tree_test_vectors) +{ + Array root_tests = read_json(std::string(json_tests::merkle_roots, json_tests::merkle_roots + sizeof(json_tests::merkle_roots))); + Array ser_tests = read_json(std::string(json_tests::merkle_serialization, json_tests::merkle_serialization + sizeof(json_tests::merkle_serialization))); + Array witness_ser_tests = read_json(std::string(json_tests::merkle_witness_serialization, json_tests::merkle_witness_serialization + sizeof(json_tests::merkle_witness_serialization))); + Array path_tests = read_json(std::string(json_tests::merkle_path, json_tests::merkle_path + sizeof(json_tests::merkle_path))); + + test_tree(root_tests, ser_tests, witness_ser_tests, path_tests); + test_tree(root_tests, ser_tests, witness_ser_tests, path_tests); +} + BOOST_AUTO_TEST_CASE( deserializeInvalid ) { ZCIncrementalMerkleTree newTree; diff --git a/src/zcash/IncrementalMerkleTree.hpp b/src/zcash/IncrementalMerkleTree.hpp index 28cb81ea8..f170ffffa 100644 --- a/src/zcash/IncrementalMerkleTree.hpp +++ b/src/zcash/IncrementalMerkleTree.hpp @@ -26,6 +26,8 @@ public: READWRITE(index); } + MerklePath() { } + MerklePath(std::vector> authentication_path, std::vector index) : authentication_path(authentication_path), index(index) { } };