diff --git a/depends/packages/libzerocash.mk b/depends/packages/libzerocash.mk index 4a4dbd5a..0d7ee781 100644 --- a/depends/packages/libzerocash.mk +++ b/depends/packages/libzerocash.mk @@ -2,8 +2,8 @@ package=libzerocash $(package)_download_path=https://github.com/Electric-Coin-Company/$(package)/archive/ $(package)_file_name=$(package)-$($(package)_git_commit).tar.gz $(package)_download_file=$($(package)_git_commit).tar.gz -$(package)_sha256_hash=c758b1f2b3372fb0e228442745668d0498a183cd0a4bcc423271e4ff3ddde85e -$(package)_git_commit=69df6c95d97a1f1ee1fece0a6a7eef7d6a577dbc +$(package)_sha256_hash=ef9cd53db6eedea3a5d24551d16d9f23dd52277e91296a14539faa027770ad23 +$(package)_git_commit=e79cd2dfee8213d49b6c2a8b2353a38d7563c965 $(package)_dependencies=libsnark crypto++ openssl boost libgmp $(package)_patches= diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index aa76ae16..4bf1faea 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -9,6 +9,87 @@ #include "tinyformat.h" #include "utilstrencodings.h" +#include "libzerocash/PourProver.h" +#include "libzerocash/PourTransaction.h" + +template +boost::array, N> uint256_to_array(const boost::array& in) { + boost::array, N> result; + for (size_t i = 0; i < N; i++) { + result[i] = std::vector(in[i].begin(), in[i].end()); + } + + return result; +} + +template +boost::array unsigned_char_vector_array_to_uint256_array(const boost::array, N>& in) { + boost::array result; + for (size_t i = 0; i < N; i++) { + result[i] = uint256(in[i]); + } + + return result; +} + +CPourTx::CPourTx(ZerocashParams& params, + const CScript& scriptPubKey, + const uint256& anchor, + const boost::array& inputs, + const boost::array& outputs, + CAmount vpub_old, + CAmount vpub_new) : scriptSig(), scriptPubKey(scriptPubKey), vpub_old(vpub_old), vpub_new(vpub_new), anchor(anchor) +{ + uint256 scriptPubKeyHash; + { + CHashWriter ss(SER_GETHASH, 0); + ss << scriptPubKey; + scriptPubKeyHash = ss.GetHash(); + } + + PourTransaction pourtx(params, + std::vector(scriptPubKeyHash.begin(), scriptPubKeyHash.end()), + std::vector(anchor.begin(), anchor.end()), + std::vector(inputs.begin(), inputs.end()), + std::vector(outputs.begin(), outputs.end()), + vpub_old, + vpub_new); + + boost::array, NUM_POUR_INPUTS> serials_bv; + boost::array, NUM_POUR_OUTPUTS> commitments_bv; + boost::array, NUM_POUR_INPUTS> macs_bv; + boost::array ciphertexts_bv; + + proof = pourtx.unpack(serials_bv, commitments_bv, macs_bv, ciphertexts_bv); + serials = unsigned_char_vector_array_to_uint256_array(serials_bv); + commitments = unsigned_char_vector_array_to_uint256_array(commitments_bv); + macs = unsigned_char_vector_array_to_uint256_array(macs_bv); + + ciphertexts = ciphertexts_bv; +} + +bool CPourTx::Verify(ZerocashParams& params) const { + // Compute the hash of the scriptPubKey. + uint256 scriptPubKeyHash; + { + CHashWriter ss(SER_GETHASH, 0); + ss << scriptPubKey; + scriptPubKeyHash = ss.GetHash(); + } + + return PourProver::VerifyProof( + params, + std::vector(scriptPubKeyHash.begin(), scriptPubKeyHash.end()), + std::vector(anchor.begin(), anchor.end()), + vpub_old, + vpub_new, + uint256_to_array(serials), + uint256_to_array(commitments), + uint256_to_array(macs), + proof + ); +} + std::string COutPoint::ToString() const { return strprintf("COutPoint(%s, %u)", hash.ToString().substr(0,10), n); diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 34d340b0..9927d85b 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -13,6 +13,15 @@ #include +#include "libzerocash/ZerocashParams.h" +#include "libzerocash/PourInput.h" +#include "libzerocash/PourOutput.h" + +using namespace libzerocash; + +static const unsigned int NUM_POUR_INPUTS = 2; +static const unsigned int NUM_POUR_OUTPUTS = 2; + class CPourTx { public: @@ -39,25 +48,25 @@ public: // are derived from the secrets placed in the bucket // and the secret spend-authority key known by the // spender. - boost::array serials; + boost::array serials; // Bucket commitments are introduced into the commitment // tree, blinding the public about the values and // destinations involved in the Pour. The presence of a // commitment in the bucket commitment tree is required // to spend it. - boost::array commitments; + boost::array commitments; // Ciphertexts // These are encrypted using ECIES. They are used to // transfer metadata and seeds to generate trapdoors // for the recipient to spend the value. - boost::array, 2> ciphertexts; + boost::array ciphertexts; // MACs // The verification of the pour requires these MACs // to be provided as an input. - boost::array macs; + boost::array macs; // Pour proof // This is a zk-SNARK which ensures that this pour is valid. @@ -67,6 +76,18 @@ public: } + CPourTx(ZerocashParams& params, + const CScript& scriptPubKey, + const uint256& rt, + const boost::array& inputs, + const boost::array& outputs, + CAmount vpub_old, + CAmount vpub_new + ); + + // Verifies that the pour proof is correct. + bool Verify(ZerocashParams& params) const; + ADD_SERIALIZE_METHODS; template diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 34a9372a..7ecbd97d 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -24,8 +24,16 @@ #include #include "json/json_spirit_writer_template.h" +#include "libzerocash/ZerocashParams.h" +#include "libzerocash/IncrementalMerkleTree.h" +#include "libzerocash/PourInput.h" +#include "libzerocash/PourOutput.h" +#include "libzerocash/Address.h" +#include "libzerocash/Coin.h" + using namespace std; using namespace json_spirit; +using namespace libzerocash; // In script_tests.cpp extern Array read_json(const std::string& jsondata); @@ -285,6 +293,95 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) return dummyTransactions; } +BOOST_AUTO_TEST_CASE(test_basic_pour_verification) +{ + // We only check that pours are constructed properly + // and verify properly here. libsnark tends to segfault + // when our snarks or what-have-you are invalid, so + // we can't really catch everything here. + // + // See #471, #520, #459 and probably others. + // + // There may be ways to use boost tests to catch failing + // threads or processes (?) but they appear to not work + // on all platforms and would gently push us down an ugly + // path. We should just fix the assertions. + // + // Also, it's generally libzerocash's job to ensure + // the integrity of the scheme through its own tests. + + static const unsigned int TEST_TREE_DEPTH = 3; + + // construct the r1cs keypair + auto keypair = ZerocashParams::GenerateNewKeyPair(TEST_TREE_DEPTH); + ZerocashParams p( + TEST_TREE_DEPTH, + &keypair + ); + + // construct a merkle tree + IncrementalMerkleTree merkleTree(TEST_TREE_DEPTH); + Address addr = Address::CreateNewRandomAddress(); + Coin coin(addr.getPublicAddress(), 100); + + // commitment from coin + std::vector commitment(ZC_CM_SIZE * 8); + convertBytesVectorToVector(coin.getCoinCommitment().getCommitmentValue(), commitment); + + // insert commitment into the merkle tree + std::vector index; + merkleTree.insertElement(commitment, index); + + // compute the merkle root we will be working with + vector rt(ZC_ROOT_SIZE); + { + vector root_bv(ZC_ROOT_SIZE * 8); + merkleTree.getRootValue(root_bv); + convertVectorToBytesVector(root_bv, rt); + } + + merkle_authentication_path path(TEST_TREE_DEPTH); + merkleTree.getWitness(index, path); + + // create CPourTx + CScript scriptPubKey; + boost::array inputs = { + PourInput(coin, addr, convertVectorToInt(index), path), + PourInput(TEST_TREE_DEPTH) // dummy input of zero value + }; + boost::array outputs = { + PourOutput(50), + PourOutput(50) + }; + + { + CPourTx pourtx(p, scriptPubKey, uint256(rt), inputs, outputs, 0, 0); + BOOST_CHECK(pourtx.Verify(p)); + + CDataStream ss(SER_DISK, CLIENT_VERSION); + ss << pourtx; + + CPourTx pourtx_deserialized; + ss >> pourtx_deserialized; + + BOOST_CHECK(pourtx_deserialized == pourtx); + BOOST_CHECK(pourtx_deserialized.Verify(p)); + } + + { + // Ensure that the balance equation is working. + BOOST_CHECK_THROW(CPourTx(p, scriptPubKey, uint256(rt), inputs, outputs, 10, 0), std::invalid_argument); + BOOST_CHECK_THROW(CPourTx(p, scriptPubKey, uint256(rt), inputs, outputs, 0, 10), std::invalid_argument); + } + + { + // Ensure that it won't verify if the root is changed. + auto test = CPourTx(p, scriptPubKey, uint256(rt), inputs, outputs, 0, 0); + test.anchor = GetRandHash(); + BOOST_CHECK(!test.Verify(p)); + } +} + BOOST_AUTO_TEST_CASE(test_simple_pour_invalidity) { CMutableTransaction tx;