aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorWladimir J. van der Laan <laanwj@protonmail.com>2020-09-21 22:32:05 +0200
committerWladimir J. van der Laan <laanwj@protonmail.com>2020-09-21 22:33:00 +0200
commit8c5f68118cd03ff0b9babee351fee83117fb7afa (patch)
treee38b0f179c5440fcb835b6408d05690949d57172 /src
parentc0c409dcd330e8b837291b1d331894aeb0015ead (diff)
parent8258c4c0076bb5f27efdc117a04b27fcd6dd00b2 (diff)
downloadbitcoin-8c5f68118cd03ff0b9babee351fee83117fb7afa.tar.xz
Merge #18267: BIP-325: Signet [consensus]
8258c4c0076bb5f27efdc117a04b27fcd6dd00b2 test: some sanity checks for consensus logic (Anthony Towns) e47ad375bf17557f805bd206e789b8db78c6338a test: basic signet tests (Karl-Johan Alm) 4c189abdc452f08dfa758564b5381bc78c42d481 test: add small signet fuzzer (practicalswift) ec9b25d046793be50da1c11ba61d1b4b13b295b0 test: signet network selection tests (Karl-Johan Alm) 3efe298dccb248f25d6b01ab6a80b1cd6c9e1a1e signet: hard-coded parameters for Signet Global Network VI (2020-09-07) (Karl-Johan Alm) c7898bca4e1ccbc6edafd3b72eaf80df38e3af32 qt: update QT to support signet network (Karl-Johan Alm) a8de47a1c9033fac3355590f1fe2158a95011bb3 consensus: add signet validation (Karl-Johan Alm) e8990f121405af8cd539b904ef082439261e6c93 add signet chain and accompanying parameters (Karl-Johan Alm) 404682b7cdb54494e7c98f0ba0cac8b51f379750 add signet basic support (signet.cpp) (Karl-Johan Alm) a2147d7dadec1febcd9c2b8ebbbf78dce6d0556b validation: move GetWitnessCommitmentIndex to consensus/validation (Karl-Johan Alm) Pull request description: This PR is a part of BIP-325 (https://github.com/bitcoin/bips/blob/master/bip-0325.mediawiki), and is a sub-PR of #16411. * Signet consensus (this) * Signet RPC tools (pending) * Signet utility scripts (contrib/signet) (pending) ACKs for top commit: jonatack: re-ACK 8258c4c0076bb5f27efdc117a04b27fcd6dd00b per `git diff dbeea65 8258c4c`, only change since last review is updated `-signet*` config option naming. fjahr: re-ACK 8258c4c laanwj: ACK 8258c4c0076bb5f27efdc117a04b27fcd6dd00b2 MarcoFalke: Approach ACK 8258c4c007 🌵 Tree-SHA512: 5d158add96755910837feafa8214e13695b769a6aec3a2da753cf672618bef377fac43b0f4b772a87b25dd9f0c1c9b29f2789785d7a7d47a155cdcf48f7c975d
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/Makefile.test.include7
-rw-r--r--src/chainparams.cpp105
-rw-r--r--src/chainparamsbase.cpp16
-rw-r--r--src/chainparamsbase.h1
-rw-r--r--src/consensus/params.h7
-rw-r--r--src/consensus/validation.h27
-rw-r--r--src/qt/guiconstants.h1
-rw-r--r--src/qt/networkstyle.cpp3
-rw-r--r--src/signet.cpp149
-rw-r--r--src/signet.h42
-rw-r--r--src/test/fuzz/signet.cpp34
-rw-r--r--src/test/key_io_tests.cpp2
-rw-r--r--src/test/pow_tests.cpp47
-rw-r--r--src/test/util_tests.cpp8
-rw-r--r--src/util/system.cpp10
-rw-r--r--src/validation.cpp37
-rw-r--r--src/validation.h5
18 files changed, 459 insertions, 44 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index b23bf062c5..aa63b5f516 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -202,6 +202,7 @@ BITCOIN_CORE_H = \
script/signingprovider.h \
script/standard.h \
shutdown.h \
+ signet.h \
streams.h \
support/allocators/secure.h \
support/allocators/zeroafterfree.h \
@@ -322,6 +323,7 @@ libbitcoin_server_a_SOURCES = \
rpc/server.cpp \
script/sigcache.cpp \
shutdown.cpp \
+ signet.cpp \
timedata.cpp \
torcontrol.cpp \
txdb.cpp \
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 77e50cf22c..06dde87ddd 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -137,6 +137,7 @@ FUZZ_TARGETS = \
test/fuzz/secp256k1_ecdsa_signature_parse_der_lax \
test/fuzz/service_deserialize \
test/fuzz/signature_checker \
+ test/fuzz/signet \
test/fuzz/snapshotmetadata_deserialize \
test/fuzz/span \
test/fuzz/spanparsing \
@@ -1129,6 +1130,12 @@ test_fuzz_signature_checker_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_signature_checker_LDFLAGS = $(FUZZ_SUITE_LDFLAGS_COMMON)
test_fuzz_signature_checker_SOURCES = test/fuzz/signature_checker.cpp
+test_fuzz_signet_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_signet_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_signet_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_signet_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_signet_SOURCES = test/fuzz/signet.cpp
+
test_fuzz_snapshotmetadata_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DSNAPSHOTMETADATA_DESERIALIZE=1
test_fuzz_snapshotmetadata_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_snapshotmetadata_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON)
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index ffd2076c9a..d7f7888ef3 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -7,6 +7,7 @@
#include <chainparamsseeds.h>
#include <consensus/merkle.h>
+#include <hash.h> // for signet block challenge hash
#include <tinyformat.h>
#include <util/system.h>
#include <util/strencodings.h>
@@ -63,6 +64,8 @@ class CMainParams : public CChainParams {
public:
CMainParams() {
strNetworkID = CBaseChainParams::MAIN;
+ consensus.signet_blocks = false;
+ consensus.signet_challenge.clear();
consensus.nSubsidyHalvingInterval = 210000;
consensus.BIP16Exception = uint256S("0x00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22");
consensus.BIP34Height = 227931;
@@ -172,6 +175,8 @@ class CTestNetParams : public CChainParams {
public:
CTestNetParams() {
strNetworkID = CBaseChainParams::TESTNET;
+ consensus.signet_blocks = false;
+ consensus.signet_challenge.clear();
consensus.nSubsidyHalvingInterval = 210000;
consensus.BIP16Exception = uint256S("0x00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105");
consensus.BIP34Height = 21111;
@@ -251,12 +256,103 @@ public:
};
/**
+ * Signet
+ */
+class SigNetParams : public CChainParams {
+public:
+ explicit SigNetParams(const ArgsManager& args) {
+ std::vector<uint8_t> bin;
+ vSeeds.clear();
+
+ if (!args.IsArgSet("-signetchallenge")) {
+ LogPrintf("Using default signet network\n");
+ bin = ParseHex("512103ad5e0edad18cb1f0fc0d28a3d4f1f3e445640337489abb10404f2d1e086be430210359ef5021964fe22d6f8e05b2463c9540ce96883fe3b278760f048f5189f2e6c452ae");
+ vSeeds.emplace_back("178.128.221.177");
+ vSeeds.emplace_back("2a01:7c8:d005:390::5");
+ vSeeds.emplace_back("ntv3mtqw5wt63red.onion:38333");
+ } else {
+ const auto signet_challenge = args.GetArgs("-signetchallenge");
+ if (signet_challenge.size() != 1) {
+ throw std::runtime_error(strprintf("%s: -signetchallenge cannot be multiple values.", __func__));
+ }
+ bin = ParseHex(signet_challenge[0]);
+
+ LogPrintf("Signet with challenge %s\n", signet_challenge[0]);
+ }
+
+ if (args.IsArgSet("-signetseednode")) {
+ vSeeds = args.GetArgs("-signetseednode");
+ }
+
+ strNetworkID = CBaseChainParams::SIGNET;
+ consensus.signet_blocks = true;
+ consensus.signet_challenge.assign(bin.begin(), bin.end());
+ consensus.nSubsidyHalvingInterval = 210000;
+ consensus.BIP34Height = 1;
+ consensus.BIP65Height = 1;
+ consensus.BIP66Height = 1;
+ consensus.CSVHeight = 1;
+ consensus.SegwitHeight = 1;
+ consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
+ consensus.nPowTargetSpacing = 10 * 60;
+ consensus.fPowAllowMinDifficultyBlocks = false;
+ consensus.fPowNoRetargeting = false;
+ consensus.nRuleChangeActivationThreshold = 1916;
+ consensus.nMinerConfirmationWindow = 2016;
+ consensus.powLimit = uint256S("00000377ae000000000000000000000000000000000000000000000000000000");
+ consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28;
+ consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008
+ consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008
+
+ // message start is defined as the first 4 bytes of the sha256d of the block script
+ CHashWriter h(SER_DISK, 0);
+ h << consensus.signet_challenge;
+ uint256 hash = h.GetHash();
+ memcpy(pchMessageStart, hash.begin(), 4);
+ LogPrintf("Signet derived magic (message start): %s\n", HexStr({pchMessageStart, pchMessageStart + 4}));
+
+ nDefaultPort = 38333;
+ nPruneAfterHeight = 1000;
+ m_assumed_blockchain_size = 0;
+ m_assumed_chain_state_size = 0;
+
+ genesis = CreateGenesisBlock(1598918400, 52613770, 0x1e0377ae, 1, 50 * COIN);
+ consensus.hashGenesisBlock = genesis.GetHash();
+ assert(consensus.hashGenesisBlock == uint256S("0x00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6"));
+ assert(genesis.hashMerkleRoot == uint256S("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"));
+
+ vFixedSeeds.clear();
+
+ base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,111);
+ base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,196);
+ base58Prefixes[SECRET_KEY] = std::vector<unsigned char>(1,239);
+ base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF};
+ base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
+
+ bech32_hrp = "tb";
+
+ fDefaultConsistencyChecks = false;
+ fRequireStandard = true;
+ m_is_test_chain = true;
+ m_is_mockable_chain = false;
+
+ chainTxData = ChainTxData{
+ 0,
+ 0,
+ 0
+ };
+ }
+};
+
+/**
* Regression test
*/
class CRegTestParams : public CChainParams {
public:
explicit CRegTestParams(const ArgsManager& args) {
strNetworkID = CBaseChainParams::REGTEST;
+ consensus.signet_blocks = false;
+ consensus.signet_challenge.clear();
consensus.nSubsidyHalvingInterval = 150;
consensus.BIP16Exception = uint256();
consensus.BIP34Height = 500; // BIP34 activated on regtest (Used in functional tests)
@@ -391,12 +487,15 @@ const CChainParams &Params() {
std::unique_ptr<const CChainParams> CreateChainParams(const std::string& chain)
{
- if (chain == CBaseChainParams::MAIN)
+ if (chain == CBaseChainParams::MAIN) {
return std::unique_ptr<CChainParams>(new CMainParams());
- else if (chain == CBaseChainParams::TESTNET)
+ } else if (chain == CBaseChainParams::TESTNET) {
return std::unique_ptr<CChainParams>(new CTestNetParams());
- else if (chain == CBaseChainParams::REGTEST)
+ } else if (chain == CBaseChainParams::SIGNET) {
+ return std::unique_ptr<CChainParams>(new SigNetParams(gArgs));
+ } else if (chain == CBaseChainParams::REGTEST) {
return std::unique_ptr<CChainParams>(new CRegTestParams(gArgs));
+ }
throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain));
}
diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp
index 1825ced640..034e897ca6 100644
--- a/src/chainparamsbase.cpp
+++ b/src/chainparamsbase.cpp
@@ -13,6 +13,7 @@
const std::string CBaseChainParams::MAIN = "main";
const std::string CBaseChainParams::TESTNET = "test";
+const std::string CBaseChainParams::SIGNET = "signet";
const std::string CBaseChainParams::REGTEST = "regtest";
void SetupChainParamsBaseOptions(ArgsManager& argsman)
@@ -23,6 +24,9 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman)
argsman.AddArg("-segwitheight=<n>", "Set the activation height of segwit. -1 to disable. (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-testnet", "Use the test chain. Equivalent to -chain=test.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
+ argsman.AddArg("-signet", "Use the signet chain. Note that the network is defined by the -signetchallenge parameter", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
+ argsman.AddArg("-signetchallenge", "Blocks must satisfy the given script to be considered valid (only for signet networks; defaults to the global default signet test network challenge)", ArgsManager::ALLOW_STRING, OptionsCategory::CHAINPARAMS);
+ argsman.AddArg("-signetseednode", "Specify a seed node for the signet network, in the hostname[:port] format, e.g. sig.net:1234 (may be used multiple times to specify multiple seed nodes; defaults to the global default signet test network seed node(s))", ArgsManager::ALLOW_STRING, OptionsCategory::CHAINPARAMS);
}
static std::unique_ptr<CBaseChainParams> globalChainBaseParams;
@@ -35,14 +39,16 @@ const CBaseChainParams& BaseParams()
std::unique_ptr<CBaseChainParams> CreateBaseChainParams(const std::string& chain)
{
- if (chain == CBaseChainParams::MAIN)
+ if (chain == CBaseChainParams::MAIN) {
return MakeUnique<CBaseChainParams>("", 8332);
- else if (chain == CBaseChainParams::TESTNET)
+ } else if (chain == CBaseChainParams::TESTNET) {
return MakeUnique<CBaseChainParams>("testnet3", 18332);
- else if (chain == CBaseChainParams::REGTEST)
+ } else if (chain == CBaseChainParams::SIGNET) {
+ return MakeUnique<CBaseChainParams>("signet", 38332);
+ } else if (chain == CBaseChainParams::REGTEST) {
return MakeUnique<CBaseChainParams>("regtest", 18443);
- else
- throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain));
+ }
+ throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain));
}
void SelectBaseParams(const std::string& chain)
diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h
index 1c52d0ea97..9852446b3c 100644
--- a/src/chainparamsbase.h
+++ b/src/chainparamsbase.h
@@ -21,6 +21,7 @@ public:
/** Chain name strings */
static const std::string MAIN;
static const std::string TESTNET;
+ static const std::string SIGNET;
static const std::string REGTEST;
///@}
diff --git a/src/consensus/params.h b/src/consensus/params.h
index 61b1fbc2e5..85ab3f61ef 100644
--- a/src/consensus/params.h
+++ b/src/consensus/params.h
@@ -80,6 +80,13 @@ struct Params {
int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; }
uint256 nMinimumChainWork;
uint256 defaultAssumeValid;
+
+ /**
+ * If true, witness commitments contain a payload equal to a Bitcoin Script solution
+ * to the signet challenge. See BIP325.
+ */
+ bool signet_blocks{false};
+ std::vector<uint8_t> signet_challenge;
};
} // namespace Consensus
diff --git a/src/consensus/validation.h b/src/consensus/validation.h
index 2a93a090d6..e007c481df 100644
--- a/src/consensus/validation.h
+++ b/src/consensus/validation.h
@@ -12,6 +12,12 @@
#include <primitives/transaction.h>
#include <primitives/block.h>
+/** Index marker for when no witness commitment is present in a coinbase transaction. */
+static constexpr int NO_WITNESS_COMMITMENT{-1};
+
+/** Minimum size of a witness commitment structure. Defined in BIP 141. **/
+static constexpr size_t MINIMUM_WITNESS_COMMITMENT{38};
+
/** A "reason" why a transaction was invalid, suitable for determining whether the
* provider of the transaction should be banned/ignored/disconnected/etc.
*/
@@ -151,4 +157,25 @@ static inline int64_t GetTransactionInputWeight(const CTxIn& txin)
return ::GetSerializeSize(txin, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(txin, PROTOCOL_VERSION) + ::GetSerializeSize(txin.scriptWitness.stack, PROTOCOL_VERSION);
}
+/** Compute at which vout of the block's coinbase transaction the witness commitment occurs, or -1 if not found */
+inline int GetWitnessCommitmentIndex(const CBlock& block)
+{
+ int commitpos = NO_WITNESS_COMMITMENT;
+ if (!block.vtx.empty()) {
+ for (size_t o = 0; o < block.vtx[0]->vout.size(); o++) {
+ const CTxOut& vout = block.vtx[0]->vout[o];
+ if (vout.scriptPubKey.size() >= MINIMUM_WITNESS_COMMITMENT &&
+ vout.scriptPubKey[0] == OP_RETURN &&
+ vout.scriptPubKey[1] == 0x24 &&
+ vout.scriptPubKey[2] == 0xaa &&
+ vout.scriptPubKey[3] == 0x21 &&
+ vout.scriptPubKey[4] == 0xa9 &&
+ vout.scriptPubKey[5] == 0xed) {
+ commitpos = o;
+ }
+ }
+ }
+ return commitpos;
+}
+
#endif // BITCOIN_CONSENSUS_VALIDATION_H
diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h
index 9457ea37d6..882d2c8f52 100644
--- a/src/qt/guiconstants.h
+++ b/src/qt/guiconstants.h
@@ -46,6 +46,7 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80;
#define QAPP_ORG_DOMAIN "bitcoin.org"
#define QAPP_APP_NAME_DEFAULT "Bitcoin-Qt"
#define QAPP_APP_NAME_TESTNET "Bitcoin-Qt-testnet"
+#define QAPP_APP_NAME_SIGNET "Bitcoin-Qt-signet"
#define QAPP_APP_NAME_REGTEST "Bitcoin-Qt-regtest"
/* One gigabyte (GB) in bytes */
diff --git a/src/qt/networkstyle.cpp b/src/qt/networkstyle.cpp
index 3a251e0573..b1081f6aee 100644
--- a/src/qt/networkstyle.cpp
+++ b/src/qt/networkstyle.cpp
@@ -19,7 +19,8 @@ static const struct {
} network_styles[] = {
{"main", QAPP_APP_NAME_DEFAULT, 0, 0},
{"test", QAPP_APP_NAME_TESTNET, 70, 30},
- {"regtest", QAPP_APP_NAME_REGTEST, 160, 30}
+ {"signet", QAPP_APP_NAME_SIGNET, 35, 15},
+ {"regtest", QAPP_APP_NAME_REGTEST, 160, 30},
};
static const unsigned network_styles_count = sizeof(network_styles)/sizeof(*network_styles);
diff --git a/src/signet.cpp b/src/signet.cpp
new file mode 100644
index 0000000000..a29f89b58e
--- /dev/null
+++ b/src/signet.cpp
@@ -0,0 +1,149 @@
+// Copyright (c) 2019-2020 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <signet.h>
+
+#include <array>
+#include <cstdint>
+#include <vector>
+
+#include <consensus/merkle.h>
+#include <consensus/params.h>
+#include <consensus/validation.h>
+#include <core_io.h>
+#include <hash.h>
+#include <primitives/block.h>
+#include <primitives/transaction.h>
+#include <span.h>
+#include <script/interpreter.h>
+#include <script/standard.h>
+#include <streams.h>
+#include <util/strencodings.h>
+#include <util/system.h>
+#include <uint256.h>
+
+static constexpr uint8_t SIGNET_HEADER[4] = {0xec, 0xc7, 0xda, 0xa2};
+
+static constexpr unsigned int BLOCK_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_NULLDUMMY;
+
+static bool FetchAndClearCommitmentSection(const Span<const uint8_t> header, CScript& witness_commitment, std::vector<uint8_t>& result)
+{
+ CScript replacement;
+ bool found_header = false;
+ result.clear();
+
+ opcodetype opcode;
+ CScript::const_iterator pc = witness_commitment.begin();
+ std::vector<uint8_t> pushdata;
+ while (witness_commitment.GetOp(pc, opcode, pushdata)) {
+ if (pushdata.size() > 0) {
+ if (!found_header && pushdata.size() > (size_t) header.size() && Span<const uint8_t>(pushdata.data(), header.size()) == header) {
+ // pushdata only counts if it has the header _and_ some data
+ result.insert(result.end(), pushdata.begin() + header.size(), pushdata.end());
+ pushdata.erase(pushdata.begin() + header.size(), pushdata.end());
+ found_header = true;
+ }
+ replacement << pushdata;
+ } else {
+ replacement << opcode;
+ }
+ }
+
+ if (found_header) witness_commitment = replacement;
+ return found_header;
+}
+
+static uint256 ComputeModifiedMerkleRoot(const CMutableTransaction& cb, const CBlock& block)
+{
+ std::vector<uint256> leaves;
+ leaves.resize(block.vtx.size());
+ leaves[0] = cb.GetHash();
+ for (size_t s = 1; s < block.vtx.size(); ++s) {
+ leaves[s] = block.vtx[s]->GetHash();
+ }
+ return ComputeMerkleRoot(std::move(leaves));
+}
+
+SignetTxs SignetTxs::Create(const CBlock& block, const CScript& challenge)
+{
+ CMutableTransaction tx_to_spend;
+ tx_to_spend.nVersion = 0;
+ tx_to_spend.nLockTime = 0;
+ tx_to_spend.vin.emplace_back(COutPoint(), CScript(OP_0), 0);
+ tx_to_spend.vout.emplace_back(0, challenge);
+
+ CMutableTransaction tx_spending;
+ tx_spending.nVersion = 0;
+ tx_spending.nLockTime = 0;
+ tx_spending.vin.emplace_back(COutPoint(), CScript(), 0);
+ tx_spending.vout.emplace_back(0, CScript(OP_RETURN));
+
+ // can't fill any other fields before extracting signet
+ // responses from block coinbase tx
+
+ // find and delete signet signature
+ if (block.vtx.empty()) return invalid(); // no coinbase tx in block; invalid
+ CMutableTransaction modified_cb(*block.vtx.at(0));
+
+ const int cidx = GetWitnessCommitmentIndex(block);
+ if (cidx == NO_WITNESS_COMMITMENT) {
+ return invalid(); // require a witness commitment
+ }
+
+ CScript& witness_commitment = modified_cb.vout.at(cidx).scriptPubKey;
+
+ std::vector<uint8_t> signet_solution;
+ if (!FetchAndClearCommitmentSection(SIGNET_HEADER, witness_commitment, signet_solution)) {
+ // no signet solution -- allow this to support OP_TRUE as trivial block challenge
+ } else {
+ try {
+ VectorReader v(SER_NETWORK, INIT_PROTO_VERSION, signet_solution, 0);
+ v >> tx_spending.vin[0].scriptSig;
+ v >> tx_spending.vin[0].scriptWitness.stack;
+ if (!v.empty()) return invalid(); // extraneous data encountered
+ } catch (const std::exception&) {
+ return invalid(); // parsing error
+ }
+ }
+ uint256 signet_merkle = ComputeModifiedMerkleRoot(modified_cb, block);
+
+ std::vector<uint8_t> block_data;
+ CVectorWriter writer(SER_NETWORK, INIT_PROTO_VERSION, block_data, 0);
+ writer << block.nVersion;
+ writer << block.hashPrevBlock;
+ writer << signet_merkle;
+ writer << block.nTime;
+ tx_to_spend.vin[0].scriptSig << block_data;
+ tx_spending.vin[0].prevout = COutPoint(tx_to_spend.GetHash(), 0);
+
+ return {tx_to_spend, tx_spending};
+}
+
+// Signet block solution checker
+bool CheckSignetBlockSolution(const CBlock& block, const Consensus::Params& consensusParams)
+{
+ if (block.GetHash() == consensusParams.hashGenesisBlock) {
+ // genesis block solution is always valid
+ return true;
+ }
+
+ const CScript challenge(consensusParams.signet_challenge.begin(), consensusParams.signet_challenge.end());
+ const SignetTxs signet_txs(block, challenge);
+
+ if (!signet_txs.m_valid) {
+ LogPrint(BCLog::VALIDATION, "CheckSignetBlockSolution: Errors in block (block solution parse failure)\n");
+ return false;
+ }
+
+ const CScript& scriptSig = signet_txs.m_to_sign.vin[0].scriptSig;
+ const CScriptWitness& witness = signet_txs.m_to_sign.vin[0].scriptWitness;
+
+ TransactionSignatureChecker sigcheck(&signet_txs.m_to_sign, /*nIn=*/ 0, /*amount=*/ signet_txs.m_to_spend.vout[0].nValue);
+
+ if (!VerifyScript(scriptSig, signet_txs.m_to_spend.vout[0].scriptPubKey, &witness, BLOCK_SCRIPT_VERIFY_FLAGS, sigcheck)) {
+ LogPrint(BCLog::VALIDATION, "CheckSignetBlockSolution: Errors in block (block solution invalid)\n");
+ return false;
+ }
+ return true;
+}
diff --git a/src/signet.h b/src/signet.h
new file mode 100644
index 0000000000..5694716fb6
--- /dev/null
+++ b/src/signet.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019-2020 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_SIGNET_H
+#define BITCOIN_SIGNET_H
+
+#include <consensus/params.h>
+#include <primitives/block.h>
+#include <primitives/transaction.h>
+
+/**
+ * Extract signature and check whether a block has a valid solution
+ */
+bool CheckSignetBlockSolution(const CBlock& block, const Consensus::Params& consensusParams);
+
+/**
+ * Generate the signet tx corresponding to the given block
+ *
+ * The signet tx commits to everything in the block except:
+ * 1. It hashes a modified merkle root with the signet signature removed.
+ * 2. It skips the nonce.
+ */
+class SignetTxs {
+private:
+ struct invalid {};
+ SignetTxs(invalid i) : m_to_spend(), m_to_sign(), m_valid(false) { }
+
+ template<class T1, class T2>
+ SignetTxs(const T1& to_spend, const T2& to_sign) : m_to_spend{to_spend}, m_to_sign{to_sign}, m_valid(true) { }
+
+ static SignetTxs Create(const CBlock& block, const CScript& challenge);
+
+public:
+ SignetTxs(const CBlock& block, const CScript& challenge) : SignetTxs(Create(block, challenge)) { }
+
+ const CTransaction m_to_spend;
+ const CTransaction m_to_sign;
+ const bool m_valid;
+};
+
+#endif // BITCOIN_SIGNET_H
diff --git a/src/test/fuzz/signet.cpp b/src/test/fuzz/signet.cpp
new file mode 100644
index 0000000000..4736ae27f5
--- /dev/null
+++ b/src/test/fuzz/signet.cpp
@@ -0,0 +1,34 @@
+// Copyright (c) 2020 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <chainparams.h>
+#include <consensus/validation.h>
+#include <primitives/block.h>
+#include <signet.h>
+#include <streams.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/util.h>
+
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+void initialize()
+{
+ InitializeFuzzingContext(CBaseChainParams::SIGNET);
+}
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+ const std::optional<CBlock> block = ConsumeDeserializable<CBlock>(fuzzed_data_provider);
+ if (!block) {
+ return;
+ }
+ (void)CheckSignetBlockSolution(*block, Params().GetConsensus());
+ if (GetWitnessCommitmentIndex(*block) != NO_WITNESS_COMMITMENT) {
+ (void)SignetTxs(*block, ConsumeScript(fuzzed_data_provider));
+ }
+}
diff --git a/src/test/key_io_tests.cpp b/src/test/key_io_tests.cpp
index d465ee6759..611e9f2623 100644
--- a/src/test/key_io_tests.cpp
+++ b/src/test/key_io_tests.cpp
@@ -136,7 +136,7 @@ BOOST_AUTO_TEST_CASE(key_io_invalid)
std::string exp_base58string = test[0].get_str();
// must be invalid as public and as private key
- for (const auto& chain : { CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::REGTEST }) {
+ for (const auto& chain : { CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET, CBaseChainParams::REGTEST }) {
SelectParams(chain);
destination = DecodeDestination(exp_base58string);
BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid pubkey in mainnet:" + strTest);
diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp
index 0f9872f434..ca49b89ad8 100644
--- a/src/test/pow_tests.cpp
+++ b/src/test/pow_tests.cpp
@@ -135,4 +135,51 @@ BOOST_AUTO_TEST_CASE(GetBlockProofEquivalentTime_test)
}
}
+void sanity_check_chainparams(std::string chainName)
+{
+ const auto chainParams = CreateChainParams(chainName);
+ const auto consensus = chainParams->GetConsensus();
+
+ // hash genesis is correct
+ BOOST_CHECK_EQUAL(consensus.hashGenesisBlock, chainParams->GenesisBlock().GetHash());
+
+ // target timespan is an even multiple of spacing
+ BOOST_CHECK_EQUAL(consensus.nPowTargetTimespan % consensus.nPowTargetSpacing, 0);
+
+ // genesis nBits is positive, doesn't overflow and is lower than powLimit
+ arith_uint256 pow_compact;
+ bool neg, over;
+ pow_compact.SetCompact(chainParams->GenesisBlock().nBits, &neg, &over);
+ BOOST_CHECK(!neg && pow_compact != 0);
+ BOOST_CHECK(!over);
+ BOOST_CHECK(UintToArith256(consensus.powLimit) >= pow_compact);
+
+ // check max target * 4*nPowTargetTimespan doesn't overflow -- see pow.cpp:CalculateNextWorkRequired()
+ if (!consensus.fPowNoRetargeting) {
+ arith_uint256 targ_max("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
+ targ_max /= consensus.nPowTargetTimespan*4;
+ BOOST_CHECK(UintToArith256(consensus.powLimit) < targ_max);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(ChainParams_MAIN_sanity)
+{
+ sanity_check_chainparams(CBaseChainParams::MAIN);
+}
+
+BOOST_AUTO_TEST_CASE(ChainParams_REGTEST_sanity)
+{
+ sanity_check_chainparams(CBaseChainParams::REGTEST);
+}
+
+BOOST_AUTO_TEST_CASE(ChainParams_TESTNET_sanity)
+{
+ sanity_check_chainparams(CBaseChainParams::TESTNET);
+}
+
+BOOST_AUTO_TEST_CASE(ChainParams_SIGNET_sanity)
+{
+ sanity_check_chainparams(CBaseChainParams::SIGNET);
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index bf7c6c3e3e..241c56934e 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -848,8 +848,8 @@ struct ArgsMergeTestingSetup : public BasicTestingSetup {
ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&] {
for (bool soft_set : {false, true}) {
for (bool force_set : {false, true}) {
- for (const std::string& section : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET}) {
- for (const std::string& network : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET}) {
+ for (const std::string& section : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET}) {
+ for (const std::string& network : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET}) {
for (bool net_specific : {false, true}) {
fn(arg_actions, conf_actions, soft_set, force_set, section, network, net_specific);
}
@@ -1003,7 +1003,7 @@ BOOST_FIXTURE_TEST_CASE(util_ArgsMerge, ArgsMergeTestingSetup)
// Results file is formatted like:
//
// <input> || <IsArgSet/IsArgNegated/GetArg output> | <GetArgs output> | <GetUnsuitable output>
- BOOST_CHECK_EQUAL(out_sha_hex, "8fd4877bb8bf337badca950ede6c917441901962f160e52514e06a60dea46cde");
+ BOOST_CHECK_EQUAL(out_sha_hex, "d1e436c1cd510d0ec44d5205d4b4e3bee6387d316e0075c58206cb16603f3d82");
}
// Similar test as above, but for ArgsManager::GetChainName function.
@@ -1106,7 +1106,7 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup)
// Results file is formatted like:
//
// <input> || <output>
- BOOST_CHECK_EQUAL(out_sha_hex, "f0b3a3c29869edc765d579c928f7f1690a71fbb673b49ccf39cbc4de18156a0d");
+ BOOST_CHECK_EQUAL(out_sha_hex, "f263493e300023b6509963887444c41386f44b63bc30047eb8402e8c1144854c");
}
BOOST_AUTO_TEST_CASE(util_ReadWriteSettings)
diff --git a/src/util/system.cpp b/src/util/system.cpp
index 999937d906..41715aac1a 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -263,6 +263,7 @@ const std::list<SectionInfo> ArgsManager::GetUnrecognizedSections() const
// Section names to be recognized in the config file.
static const std::set<std::string> available_sections{
CBaseChainParams::REGTEST,
+ CBaseChainParams::SIGNET,
CBaseChainParams::TESTNET,
CBaseChainParams::MAIN
};
@@ -916,16 +917,21 @@ std::string ArgsManager::GetChainName() const
};
const bool fRegTest = get_net("-regtest");
+ const bool fSigNet = get_net("-signet");
const bool fTestNet = get_net("-testnet");
const bool is_chain_arg_set = IsArgSet("-chain");
- if ((int)is_chain_arg_set + (int)fRegTest + (int)fTestNet > 1) {
- throw std::runtime_error("Invalid combination of -regtest, -testnet and -chain. Can use at most one.");
+ if ((int)is_chain_arg_set + (int)fRegTest + (int)fSigNet + (int)fTestNet > 1) {
+ throw std::runtime_error("Invalid combination of -regtest, -signet, -testnet and -chain. Can use at most one.");
}
if (fRegTest)
return CBaseChainParams::REGTEST;
+ if (fSigNet) {
+ return CBaseChainParams::SIGNET;
+ }
if (fTestNet)
return CBaseChainParams::TESTNET;
+
return GetArg("-chain", CBaseChainParams::MAIN);
}
diff --git a/src/validation.cpp b/src/validation.cpp
index 0823c6b124..a96913e3a0 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -33,6 +33,7 @@
#include <script/script.h>
#include <script/sigcache.h>
#include <shutdown.h>
+#include <signet.h>
#include <timedata.h>
#include <tinyformat.h>
#include <txdb.h>
@@ -1169,6 +1170,11 @@ bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::P
if (!CheckProofOfWork(block.GetHash(), block.nBits, consensusParams))
return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString());
+ // Signet only: check block solution
+ if (consensusParams.signet_blocks && !CheckSignetBlockSolution(block, consensusParams)) {
+ return error("ReadBlockFromDisk: Errors in block solution at %s", pos.ToString());
+ }
+
return true;
}
@@ -3346,6 +3352,11 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu
if (!CheckBlockHeader(block, state, consensusParams, fCheckPOW))
return false;
+ // Signet only: check block solution
+ if (consensusParams.signet_blocks && fCheckPOW && !CheckSignetBlockSolution(block, consensusParams)) {
+ return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-signet-blksig", "signet block signature validation failure");
+ }
+
// Check the merkle root.
if (fCheckMerkleRoot) {
bool mutated;
@@ -3409,31 +3420,11 @@ bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& pa
return (height >= params.SegwitHeight);
}
-int GetWitnessCommitmentIndex(const CBlock& block)
-{
- int commitpos = -1;
- if (!block.vtx.empty()) {
- for (size_t o = 0; o < block.vtx[0]->vout.size(); o++) {
- const CTxOut& vout = block.vtx[0]->vout[o];
- if (vout.scriptPubKey.size() >= MINIMUM_WITNESS_COMMITMENT &&
- vout.scriptPubKey[0] == OP_RETURN &&
- vout.scriptPubKey[1] == 0x24 &&
- vout.scriptPubKey[2] == 0xaa &&
- vout.scriptPubKey[3] == 0x21 &&
- vout.scriptPubKey[4] == 0xa9 &&
- vout.scriptPubKey[5] == 0xed) {
- commitpos = o;
- }
- }
- }
- return commitpos;
-}
-
void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams)
{
int commitpos = GetWitnessCommitmentIndex(block);
static const std::vector<unsigned char> nonce(32, 0x00);
- if (commitpos != -1 && IsWitnessEnabled(pindexPrev, consensusParams) && !block.vtx[0]->HasWitness()) {
+ if (commitpos != NO_WITNESS_COMMITMENT && IsWitnessEnabled(pindexPrev, consensusParams) && !block.vtx[0]->HasWitness()) {
CMutableTransaction tx(*block.vtx[0]);
tx.vin[0].scriptWitness.stack.resize(1);
tx.vin[0].scriptWitness.stack[0] = nonce;
@@ -3447,7 +3438,7 @@ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBloc
int commitpos = GetWitnessCommitmentIndex(block);
std::vector<unsigned char> ret(32, 0x00);
if (consensusParams.SegwitHeight != std::numeric_limits<int>::max()) {
- if (commitpos == -1) {
+ if (commitpos == NO_WITNESS_COMMITMENT) {
uint256 witnessroot = BlockWitnessMerkleRoot(block, nullptr);
CHash256().Write(witnessroot).Write(ret).Finalize(witnessroot);
CTxOut out;
@@ -3585,7 +3576,7 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat
bool fHaveWitness = false;
if (nHeight >= consensusParams.SegwitHeight) {
int commitpos = GetWitnessCommitmentIndex(block);
- if (commitpos != -1) {
+ if (commitpos != NO_WITNESS_COMMITMENT) {
bool malleated = false;
uint256 hashWitness = BlockWitnessMerkleRoot(block, &malleated);
// The malleation check is ignored; as the transaction tree itself
diff --git a/src/validation.h b/src/validation.h
index 0bc80e1cee..0da62093a3 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -93,8 +93,6 @@ static const unsigned int DEFAULT_CHECKLEVEL = 3;
// one 128MB block file + added 15% undo data = 147MB greater for a total of 545MB
// Setting the target to >= 550 MiB will make it likely we can respect the target.
static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 550 * 1024 * 1024;
-/** Minimum size of a witness commitment structure. Defined in BIP 141. **/
-static constexpr size_t MINIMUM_WITNESS_COMMITMENT{38};
struct BlockHasher
{
@@ -306,9 +304,6 @@ bool TestBlockValidity(BlockValidationState& state, const CChainParams& chainpar
* Note that transaction witness validation rules are always enforced when P2SH is enforced. */
bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params);
-/** Compute at which vout of the block's coinbase transaction the witness commitment occurs, or -1 if not found */
-int GetWitnessCommitmentIndex(const CBlock& block);
-
/** Update uncommitted block structures (currently: only the witness reserved value). This is safe for submitted blocks. */
void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams);