From 4aad5139397a3586a01e7b19eecd50f40c222b6b Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Mon, 18 Sep 2017 13:32:24 -0400 Subject: [PATCH] Add unit tests for peg-in authorization --- src/Makefile.test.include | 1 + src/test/pegin_witness_tests.cpp | 131 +++++++++++++++++++++++++++++++ src/test/test_bitcoin.cpp | 8 +- src/test/test_bitcoin.h | 4 +- src/validation.cpp | 3 + 5 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 src/test/pegin_witness_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 095ee288fa..bc27116188 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -109,6 +109,7 @@ BITCOIN_TESTS =\ test/multisig_tests.cpp \ test/net_tests.cpp \ test/netbase_tests.cpp \ + test/pegin_witness_tests.cpp \ test/pmt_tests.cpp \ test/policyestimator_tests.cpp \ test/pow_tests.cpp \ diff --git a/src/test/pegin_witness_tests.cpp b/src/test/pegin_witness_tests.cpp new file mode 100644 index 0000000000..6e3c9ffc9a --- /dev/null +++ b/src/test/pegin_witness_tests.cpp @@ -0,0 +1,131 @@ +// Copyright (c) 2017-2017 Blockstream +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "clientversion.h" +#include "chainparams.h" +#include "checkqueue.h" +#include "consensus/validation.h" +#include "core_io.h" +#include "validation.h" // For CheckTransaction +#include "policy/policy.h" +#include "script/script.h" +#include "script/script_error.h" +#include "utilstrencodings.h" +#include "validation.h" +#include "streams.h" +#include "test/test_bitcoin.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include + +std::vector > witness_stack = { + ParseHex("00ca9a3b"), + ParseHex("e48a1a02a8f799892fda58347c2d794144311d4307dbfd10f77ffe28088c60be"), + ParseHex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f"), + ParseHex("0014542a5a8eea9945cc3d7e24468f108b8e0c721a81"), + ParseHex("020000000365d984d2d0323bbab4807c12c533c014491596929082b1e684205c3fa80397f4010000006b483045022100dfc4741b5dadb6c4c9bd8774d037450e1a281dc2fd73d84115d1d68863d7fc6f02201cfb193bb929ef0e84dfcea5b37bea6484b7a54e79d5a74968a7f3baa79d67880121035b218cb6172451fec3fc467a71938d7e78f425cd76a771da9f193f952ca8c812feffffff6f17f18a671f8e67bdb666e36d8941c1d8ee29b85d9bddbeedbc2973f44fd6bb0000000049483045022100a0848deef222213765ce67f41528e0df6b3877a5f2810d4800759ea8616479ac022058fa148daa1ed7daab46ec0cfe373c17d82d6281cb8e9c7a4a7334136c5e4f0501feffffff9e6a22f9045ec732bad1c0f98d81bd2c9a1b4d4adbfabefb09a9d49e354e80350000000049483045022100ff7c9f9e145c5572d4b21a65a1fbc3331ee3a6757f378554223d963f0f2821d2022048d4c12e7bc4333a2393778645f385f7266176f42e3e86d6af3f0508cc1eed5c01feffffff025892dc01000000001976a914d85a6ba69df7c74e53ec7b99e424c24e2db6d8c988ac00ca9a3b0000000017a914a8105ab6cc3d9aa6ecd9736989d3388c933250e28728030000"), + ParseHex("0000002088912d3d4e77e8b023382aad412b06a56db195666744eb6d37f9a0c40539380b927fd4b53a3bf94e4df21ae6ea8e407a46df057c43477e2f7b27c67c3751d03e8b31c959ffff7f20010000000200000002b96824c26e89b2fa9989c682412fe3a16372fcdf6c0dce872a7e2d2728c6d69f6e441b8424dfddfcae9954c3a603210ef1336ba8a30e63b4fb0a25c6b8103dcd0105") +}; + +std::vector pegin_transaction = ParseHex("020000000001016e441b8424dfddfcae9954c3a603210ef1336ba8a30e63b4fb0a25c6b8103dcd0100004000ffffffff0201e48a1a02a8f799892fda58347c2d794144311d4307dbfd10f77ffe28088c60be01000000003b9aade0001976a914a4c05e4e702cec80df26074843a189cfb05bebdb88ac01e48a1a02a8f799892fda58347c2d794144311d4307dbfd10f77ffe28088c60be010000000000001c200000000002483045022100b2d5f19291a3297f28802a6a2bfef2bc0c440660c55a399bfa89533277bcfdcc022042a003eadd85bcc7fb568f9025d6d869ee5e52084613c859cfae6c929766f75e012103ced1fe72e7e33f37747782a505c94f26c1451e8ceceaef8c75f175f87fdb2e87060400ca9a3b20e48a1a02a8f799892fda58347c2d794144311d4307dbfd10f77ffe28088c60be2006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f160014542a5a8eea9945cc3d7e24468f108b8e0c721a81fdc401020000000365d984d2d0323bbab4807c12c533c014491596929082b1e684205c3fa80397f4010000006b483045022100dfc4741b5dadb6c4c9bd8774d037450e1a281dc2fd73d84115d1d68863d7fc6f02201cfb193bb929ef0e84dfcea5b37bea6484b7a54e79d5a74968a7f3baa79d67880121035b218cb6172451fec3fc467a71938d7e78f425cd76a771da9f193f952ca8c812feffffff6f17f18a671f8e67bdb666e36d8941c1d8ee29b85d9bddbeedbc2973f44fd6bb0000000049483045022100a0848deef222213765ce67f41528e0df6b3877a5f2810d4800759ea8616479ac022058fa148daa1ed7daab46ec0cfe373c17d82d6281cb8e9c7a4a7334136c5e4f0501feffffff9e6a22f9045ec732bad1c0f98d81bd2c9a1b4d4adbfabefb09a9d49e354e80350000000049483045022100ff7c9f9e145c5572d4b21a65a1fbc3331ee3a6757f378554223d963f0f2821d2022048d4c12e7bc4333a2393778645f385f7266176f42e3e86d6af3f0508cc1eed5c01feffffff025892dc01000000001976a914d85a6ba69df7c74e53ec7b99e424c24e2db6d8c988ac00ca9a3b0000000017a914a8105ab6cc3d9aa6ecd9736989d3388c933250e28728030000970000002088912d3d4e77e8b023382aad412b06a56db195666744eb6d37f9a0c40539380b927fd4b53a3bf94e4df21ae6ea8e407a46df057c43477e2f7b27c67c3751d03e8b31c959ffff7f20010000000200000002b96824c26e89b2fa9989c682412fe3a16372fcdf6c0dce872a7e2d2728c6d69f6e441b8424dfddfcae9954c3a603210ef1336ba8a30e63b4fb0a25c6b8103dcd01050000000000000000"); + +COutPoint prevout(uint256S("cd3d10b8c6250afbb4630ea3a86b33f10e2103a6c35499aefcdddf24841b446e"), 1); + +// Needed for easier parent PoW check, and setting fedpegscript +struct RegtestingSetup : public TestingSetup { + RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST, "512103dff4923d778550cc13ce0d887d737553b4b58f4e8e886507fc39f5e447b2186451ae") {} +}; + +BOOST_FIXTURE_TEST_SUITE(pegin_witness_tests, RegtestingSetup) + +BOOST_AUTO_TEST_CASE(witness_valid) +{ + + CScriptWitness witness; + witness.stack = witness_stack; + + BOOST_CHECK(IsValidPeginWitness(witness, prevout)); + + // Missing byte on each field to make claim ill-formatted + // This will break deserialization and other data-matching checks + for (unsigned int i = 0; i < witness.stack.size(); i++) { + witness.stack[i].pop_back(); + BOOST_CHECK(!IsValidPeginWitness(witness, prevout)); + witness.stack = witness_stack; + BOOST_CHECK(IsValidPeginWitness(witness, prevout)); + } + + // Test mismatched but valid nOut to proof + COutPoint fake_prevout = prevout; + fake_prevout.n = 0; + BOOST_CHECK(!IsValidPeginWitness(witness, fake_prevout)); + + // Test mistmatched but valid txid + fake_prevout = prevout; + fake_prevout.hash = uint256S("2f103ee04a5649eecb932b4da4ca9977f53a12bbe04d9d1eb5ccc0f4a06334"); + BOOST_CHECK(!IsValidPeginWitness(witness, fake_prevout)); + + // Ensure that all witness stack sizes are handled + BOOST_CHECK(IsValidPeginWitness(witness, prevout)); + for (unsigned int i = 0; i < witness.stack.size(); i++) { + witness.stack.pop_back(); + BOOST_CHECK(!IsValidPeginWitness(witness, prevout)); + } + witness.stack = witness_stack; + + // Extra element causes failure + witness.stack.push_back(witness.stack.back()); + BOOST_CHECK(!IsValidPeginWitness(witness, prevout)); + witness.stack = witness_stack; + + // Check validation of peg-in transaction's inputs and balance + CDataStream ssTx(pegin_transaction, SER_NETWORK, PROTOCOL_VERSION); + CTransactionRef txRef; + ssTx >> txRef; + CTransaction tx(*txRef); + + // Only one(valid) input witness should exist, and should match + BOOST_CHECK(tx.wit.vtxinwit.size() == 1); + BOOST_CHECK(tx.wit.vtxinwit[0].m_pegin_witness.stack == witness_stack); + BOOST_CHECK(tx.vin[0].m_is_pegin); + // Check that serialization doesn't cause issuance to become non-null + BOOST_CHECK(tx.vin[0].assetIssuance.IsNull()); + BOOST_CHECK(IsValidPeginWitness(tx.wit.vtxinwit[0].m_pegin_witness, prevout)); + + std::set > setPeginsSpent; + CValidationState state; + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + BOOST_CHECK(Consensus::CheckTxInputs(tx, state, coins, 0, setPeginsSpent, nullptr, false)); + BOOST_CHECK(setPeginsSpent.size() == 1); + setPeginsSpent.clear(); + + // Strip pegin_witness + CMutableTransaction mtxn(tx); + mtxn.wit.vtxinwit[0].m_pegin_witness.SetNull(); + CTransaction tx2(mtxn); + BOOST_CHECK(!Consensus::CheckTxInputs(tx2, state, coins, 0, setPeginsSpent, nullptr, false)); + BOOST_CHECK(setPeginsSpent.empty()); + + // Invalidate peg-in (and spending) authorization by pegin marker. + // This only checks for peg-in authorization, with the only input marked + // as m_is_pegin + CMutableTransaction mtxn2(tx); + mtxn2.vin[0].m_is_pegin = false; + CTransaction tx3(mtxn2); + BOOST_CHECK(!Consensus::CheckTxInputs(tx3, state, coins, 0, setPeginsSpent, nullptr, false)); + BOOST_CHECK(setPeginsSpent.empty()); + + + // TODO Test mixed pegin/non-pegin input case + // TODO Test spending authorization in conjunction with valid witness program in pegin auth + +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index d36be06eb2..378761a5db 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -37,7 +37,7 @@ FastRandomContext insecure_rand_ctx(true); extern bool fPrintToConsole; extern void noui_connect(); -BasicTestingSetup::BasicTestingSetup(const std::string& chainName) +BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::string& fedpegscript) { ECC_Start(); SetupEnvironment(); @@ -47,6 +47,10 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName) InitSurjectionproofCache(); fPrintToDebugLog = false; // don't want to write to debug.log file fCheckBlockIndex = true; + // Hack to allow testing of fedpeg args + if (!fedpegscript.empty()) { + SoftSetArg("-fedpegscript", fedpegscript); + } SelectParams(chainName); noui_connect(); } @@ -57,7 +61,7 @@ BasicTestingSetup::~BasicTestingSetup() g_connman.reset(); } -TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(chainName) +TestingSetup::TestingSetup(const std::string& chainName, const std::string& fedpegscript) : BasicTestingSetup(chainName, fedpegscript) { const CChainParams& chainparams = Params(); // Ideally we'd move all the RPC tests to the functional testing framework diff --git a/src/test/test_bitcoin.h b/src/test/test_bitcoin.h index e6a618a71c..8d2b90d7f3 100644 --- a/src/test/test_bitcoin.h +++ b/src/test/test_bitcoin.h @@ -20,7 +20,7 @@ struct BasicTestingSetup { ECCVerifyHandle globalVerifyHandle; - BasicTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); + BasicTestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::string& fedpegscript = ""); ~BasicTestingSetup(); }; @@ -35,7 +35,7 @@ struct TestingSetup: public BasicTestingSetup { CConnman* connman; CKey coinbaseKey; // private/public key needed to spend coinbase transactions - TestingSetup(const std::string& chainName = CBaseChainParams::MAIN); + TestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::string& fedpegscript = ""); ~TestingSetup(); }; diff --git a/src/validation.cpp b/src/validation.cpp index 7e4c12501c..8bdb9f28dd 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2399,6 +2399,9 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p CScript fedpegscript = Params().GetConsensus().fedpegScript; // fedpegscript should be multisig or OP_TRUE + // When set to OP_TRUE, the witness program can be any valid witness program + // as there are no pubkeys to tweak. This means typically in regtest there + // is no spending authorization for peg-in inputs. txnouttype type; std::vector > solutions; if (!Solver(fedpegscript, type, solutions) || (type != TX_MULTISIG && type != TX_TRUE)) {