diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -133,6 +133,7 @@ scheduler.cpp script/sign.cpp script/standard.cpp + utxocommit.cpp warnings.cpp ) diff --git a/src/Makefile.am b/src/Makefile.am --- a/src/Makefile.am +++ b/src/Makefile.am @@ -233,6 +233,7 @@ txdb.cpp \ txmempool.cpp \ ui_interface.cpp \ + utxocommit.cpp \ validation.cpp \ validationinterface.cpp \ versionbits.cpp \ 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/CMakeLists.txt b/src/test/CMakeLists.txt --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -71,6 +71,7 @@ undo_tests.cpp univalue_tests.cpp util_tests.cpp + utxocommit_tests.cpp validation_tests.cpp ) 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,126 @@ +// 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_context *ctx; + ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + secp256k1_multiset multiset; + secp256k1_multiset_init(ctx, &multiset); + secp256k1_multiset_add(ctx, &multiset, expected.data(), expected.size()); + + std::vector expectedhash(32); + secp256k1_multiset_finalize(ctx, expectedhash.data(), &multiset); + + secp256k1_context_destroy(ctx); + 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,63 @@ +// 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 + +class Coin; + +/* 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(); + + // Construct by combining two other CUtxoCommits + CUtxoCommit(const CUtxoCommit &commit1, const CUtxoCommit &commit2); + + // Adds a TXO from multiset + void Add(const COutPoint &out, const Coin &element); + + // Removes a TXO from multiset + void Remove(const COutPoint &out, const Coin &element); + + void Clear(); + + uint256 GetHash() const; + + // 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(); + } + + ~CUtxoCommit(); +}; + +#endif // MULTISET_H diff --git a/src/utxocommit.cpp b/src/utxocommit.cpp new file mode 100644 --- /dev/null +++ b/src/utxocommit.cpp @@ -0,0 +1,66 @@ +// 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. + +#include "utxocommit.h" + +namespace { +secp256k1_context *secp256k1_context_multiset; +int secp256k1_context_refcount = 0; +} + +// Constructs empty CUtxoCommit +CUtxoCommit::CUtxoCommit() { + if (secp256k1_context_refcount == 0) { + secp256k1_context_multiset = + secp256k1_context_create(SECP256K1_CONTEXT_NONE); + } + secp256k1_context_refcount++; + secp256k1_multiset_init(secp256k1_context_multiset, &multiset); +} + +CUtxoCommit::~CUtxoCommit() { + secp256k1_context_refcount--; + if (secp256k1_context_refcount == 0) { + secp256k1_context_destroy(secp256k1_context_multiset); + } +} + +// Construct by combining two other CUtxoCommits +CUtxoCommit::CUtxoCommit(const CUtxoCommit &commit1, const CUtxoCommit &commit2) + : CUtxoCommit() { + secp256k1_multiset_combine(secp256k1_context_multiset, &this->multiset, + &commit1.multiset); + secp256k1_multiset_combine(secp256k1_context_multiset, &this->multiset, + &commit2.multiset); +} + +// Adds a TXO from multiset +void CUtxoCommit::Add(const COutPoint &out, const Coin &element) { + + CDataStream txo(SER_NETWORK, PROTOCOL_VERSION); + txo << out << element; + secp256k1_multiset_add(secp256k1_context_multiset, &multiset, + (const uint8_t *)&txo[0], txo.size()); +} + +// Removes a TXO from multiset +void CUtxoCommit::Remove(const COutPoint &out, const Coin &element) { + + CDataStream txo(SER_NETWORK, PROTOCOL_VERSION); + txo << out << element; + secp256k1_multiset_remove(secp256k1_context_multiset, &multiset, + (const uint8_t *)&txo[0], txo.size()); +} + +void CUtxoCommit::Clear() { + secp256k1_multiset_init(secp256k1_context_multiset, &multiset); +} + +uint256 CUtxoCommit::GetHash() const { + + std::vector hash(32); + secp256k1_multiset_finalize(secp256k1_context_multiset, hash.data(), + &multiset); + return uint256(hash); +}