diff --git a/src/Makefile.test.include b/src/Makefile.test.include --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -98,6 +98,7 @@ test/undo_tests.cpp \ test/univalue_tests.cpp \ test/util_tests.cpp \ + test/utxocommit_tests.cpp \ test/validation_tests.cpp if ENABLE_WALLET diff --git a/src/coins.h b/src/coins.h --- a/src/coins.h +++ b/src/coins.h @@ -56,12 +56,21 @@ template void Serialize(Stream &s) const { assert(!IsSpent()); ::Serialize(s, VARINT(nHeightAndIsCoinBase)); - ::Serialize(s, CTxOutCompressor(REF(out))); + // only compress for disk format + if (s.GetType() & SER_DISK) { + ::Serialize(s, CTxOutCompressor(REF(out))); + } else { + ::Serialize(s, REF(out)); + } } template void Unserialize(Stream &s) { ::Unserialize(s, VARINT(nHeightAndIsCoinBase)); - ::Unserialize(s, REF(CTxOutCompressor(out))); + if (s.GetType() & SER_DISK) { + ::Unserialize(s, REF(CTxOutCompressor(out))); + } else { + ::Unserialize(s, REF(out)); + } } size_t DynamicMemoryUsage() const { diff --git a/src/test/utxocommit_tests.cpp b/src/test/utxocommit_tests.cpp new file mode 100644 --- /dev/null +++ b/src/test/utxocommit_tests.cpp @@ -0,0 +1,124 @@ +// Copyright (c) 2014-2016 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// Tests for CUtxoCommit wrapper. +// Mostly redundant with libsecp256k1_multiset tests + +#include "coins.h" +#include "test/test_bitcoin.h" +#include "util.h" +#include + +#include + +#include "secp256k1/include/secp256k1_multiset.h" +#include "utxocommit.h" + +static COutPoint RandomOutpoint() { + const COutPoint op(InsecureRand256(), insecure_rand()); + return op; +} + +static Coin RandomCoin() { + const Coin c(CTxOut(Amount(InsecureRandRange(1000)), + CScript(InsecureRandBytes(insecure_rand() % 0x3f))), + insecure_rand(), InsecureRandBool()); + return c; +} + +BOOST_FIXTURE_TEST_SUITE(utxocommit_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(utxo_commit_order) { + + // Test order independence + + const COutPoint op1 = RandomOutpoint(); + const COutPoint op2 = RandomOutpoint(); + const COutPoint op3 = RandomOutpoint(); + const Coin c1 = RandomCoin(); + const Coin c2 = RandomCoin(); + const Coin c3 = RandomCoin(); + + CUtxoCommit uc1, uc2, uc3; + BOOST_CHECK(uc1 == uc2); + uc1.Add(op1, c1); + uc1.Add(op2, c2); + uc1.Add(op3, c3); + + uc2.Add(op2, c2); + BOOST_CHECK(uc1 != uc2); + uc2.Add(op3, c3); + uc2.Add(op1, c1); + BOOST_CHECK(uc1 == uc2); + + // remove ordering + uc2.Remove(op2, c2); + uc2.Remove(op3, c3); + + uc1.Remove(op2, c2); + uc1.Remove(op3, c3); + + BOOST_CHECK(uc1 == uc2); + + // odd but allowed + uc3.Remove(op2, c2); + uc3.Add(op2, c2); + uc3.Add(op1, c1); + BOOST_CHECK(uc1 == uc3); +} + +BOOST_AUTO_TEST_CASE(utxo_commit_serialize) { + + // Test whether the serialization is as expected + + // some coin & output + const std::vector txid = ParseHex( + "38115d014104c6ec27cffce0823c3fecb162dbd576c88dd7cda0b7b32b096118"); + const uint32_t output = 2; + const uint32_t height = 7; + const uint64_t amount = 100; + + const auto script = + CScript(ParseHex("76A9148ABCDEFABBAABBAABBAABBAABBAABBAABBAABBA88AC")); + + const COutPoint op(uint256(txid), output); + const Coin coin = Coin(CTxOut(Amount(amount), script), height, false); + CScript s; + + // find commit + CUtxoCommit commit; + commit.Add(op, coin); + uint256 hash = commit.GetHash(); + + // try the same manually + std::vector expected; + + // txid + expected.insert(expected.end(), txid.begin(), txid.end()); + + // output + auto outputbytes = ParseHex("02000000"); + expected.insert(expected.end(), outputbytes.begin(), outputbytes.end()); + + // height and coinbase => height * 2 + expected.push_back(14); + + // amount & script + auto amountbytes = ParseHex("6400000000000000"); + expected.insert(expected.end(), amountbytes.begin(), amountbytes.end()); + expected.push_back(uint8_t(script.size())); + expected.insert(expected.end(), script.begin(), script.end()); + + secp256k1_multiset multiset; + secp256k1_multiset_init(context, &multiset); + secp256k1_multiset_add(context, &multiset, expected.data(), + expected.size()); + + std::vector expectedhash(32); + secp256k1_multiset_finalize(context, expectedhash.data(), &multiset); + + BOOST_ASSERT(uint256(expectedhash) == hash); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/utxocommit.h b/src/utxocommit.h new file mode 100644 --- /dev/null +++ b/src/utxocommit.h @@ -0,0 +1,80 @@ +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTXOCOMMIT_H +#define BITCOIN_UTXOCOMMIT_H + +#include "coins.h" +#include "hash.h" +#include "secp256k1/include/secp256k1_multiset.h" +#include "streams.h" +#include + +secp256k1_context *context = nullptr; + +/* A Utxo Commitment + * + * This is maintained as 96-byte multiset value that uniquely defines a UTXO set + * + * It wraps the secp256k1 multiset + * + * Note that a CUtxoCommit allows "negative sets". That is + * + * CUtxoCommit set; // set is an empty set + * set.Remove(X); // set is empty set "minus" X + * set.Add(X); // set is an empty set + * + * This means a CUtxoCommit can both represent the total UTXO set, or a delta to + * the UTXO set +*/ +class CUtxoCommit { +private: + secp256k1_multiset multiset; + +public: + // Constructs empty CUtxoCommit + CUtxoCommit() { secp256k1_multiset_init(context, &multiset); } + + // Construct by combining two other CUtxoCommits + CUtxoCommit(const CUtxoCommit &commit1, const CUtxoCommit &commit2) + : CUtxoCommit() { + secp256k1_multiset_combine(context, &this->multiset, &commit1.multiset); + secp256k1_multiset_combine(context, &this->multiset, &commit2.multiset); + } + + // Adds a TXO from multiset + void Add(const COutPoint &out, const Coin &element) { + + CDataStream txo(SER_NETWORK, PROTOCOL_VERSION); + txo << out << element; + secp256k1_multiset_add(context, &multiset, (const uint8_t *)&txo[0], + txo.size()); + } + + // Removes a TXO from multiset + void Remove(const COutPoint &out, const Coin &element) { + + CDataStream txo(SER_NETWORK, PROTOCOL_VERSION); + txo << out << element; + secp256k1_multiset_remove(context, &multiset, (const uint8_t *)&txo[0], + txo.size()); + } + + uint256 GetHash() const { + + std::vector hash(32); + secp256k1_multiset_finalize(context, hash.data(), &multiset); + return uint256(hash); + } + + // Comparison + friend bool operator==(const CUtxoCommit &a, const CUtxoCommit &b) { + return a.GetHash() == b.GetHash(); + } + friend bool operator!=(const CUtxoCommit &a, const CUtxoCommit &b) { + return a.GetHash() != b.GetHash(); + } +}; + +#endif // MULTISET_H