diff --git a/.travis.yml b/.travis.yml index 8d2bfe4ba1d..c08113a3e61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,7 +72,7 @@ script: - if [ "$RUN_TESTS" = "true" ]; then qa/pull-tester/rpc-tests.py --coverage; fi - if [ "$RUN_FEDPEG_BITCOIND" = "true" ]; then BITCOIND_VERSION=0.16.3 && BITCOIND_ARCH=x86_64-linux-gnu; fi - if [ "$RUN_FEDPEG_BITCOIND" = "true" ]; then wget https://bitcoincore.org/bin/bitcoin-core-$BITCOIND_VERSION/bitcoin-$BITCOIND_VERSION-$BITCOIND_ARCH.tar.gz && tar -zxf bitcoin-$BITCOIND_VERSION-$BITCOIND_ARCH.tar.gz && rm bitcoin-$BITCOIND_VERSION-$BITCOIND_ARCH.tar.gz; fi - - if [ "$RUN_FEDPEG_BITCOIND" = "true" ]; then qa/rpc-tests/feature_fedpeg.py --parent_bitcoin --parent_binpath $(pwd)/bitcoin-$BITCOIND_VERSION/bin/bitcoind; fi + - if [ "$RUN_FEDPEG_BITCOIND" = "true" ]; then qa/rpc-tests/feature_fedpeg.py --parent_type=bitcoin --parent_binpath $(pwd)/bitcoin-$BITCOIND_VERSION/bin/bitcoind; fi after_script: - echo $TRAVIS_COMMIT_RANGE - echo $TRAVIS_COMMIT_LOG diff --git a/qa/rpc-tests/feature_fedpeg.py b/qa/rpc-tests/feature_fedpeg.py index 282d8de8c70..8bc0ad783d5 100755 --- a/qa/rpc-tests/feature_fedpeg.py +++ b/qa/rpc-tests/feature_fedpeg.py @@ -33,21 +33,61 @@ def __init__(self): def add_options(self, parser): parser.add_option("--parent_binpath", dest="parent_binpath", default="", help="Use a different binary for launching nodes") - parser.add_option("--parent_bitcoin", dest="parent_bitcoin", default=False, action="store_true", - help="Parent nodes are Bitcoin") + parser.add_option("--parent_type", dest="parent_type", default="elements", + help="Type of parent nodes {elements, bitcoin, signet}") def setup_network(self, split=False): - if self.options.parent_bitcoin and self.options.parent_binpath == "": - raise Exception("Can't run with --parent_bitcoin without specifying --parent_binpath") self.nodes = [] self.extra_args = [] + + if self.options.parent_type not in ['elements', 'bitcoin', 'signet']: + raise Exception("Invalid option --parent_type=%s, valid values: {elements, bitcoin, signet}" % self.options.parent_type) + + if self.options.parent_type == 'bitcoin' and self.options.parent_binpath == "": + raise Exception("Can't run with --parent_type=bitcoin without specifying --parent_binpath") + + self.binary = self.options.parent_binpath if self.options.parent_binpath != "" else None + + if self.options.parent_type == 'signet': + from binascii import hexlify + from test_framework import script, key + from test_framework.util import hex_str_to_bytes + import shutil + temp_args = [ + "-port=" + str(p2p_port(0)), + "-rpcport=" + str(rpc_port(0)), + "-addresstype=legacy", + "-deprecatedrpc=validateaddress", + "-bech32_hrp=sb", + "-pchmessagestart=F0C7706A", + "-pubkeyprefix=125", + "-scriptprefix=87", + "-secretprefix=217", + "-extpubkeyprefix=043587CF", + "-extprvkeyprefix=04358394", + ] + temp_node = start_node(0, self.options.tmpdir, temp_args, binary=self.binary, chain='temp', cookie_auth=True) + addr = temp_node.getnewaddress() + k = key.CECKey() + pub = temp_node.validateaddress(addr)["pubkey"] + k.set_pubkey(hex_str_to_bytes(pub)) + pubkey = key.CPubKey(k.get_pubkey()) + wif = temp_node.dumpprivkey(addr) + stop_node(temp_node, 0) + script = script.CScript([pubkey, script.OP_CHECKSIG]) + blockscript = hexlify(script).decode('ascii') + + print('blockscript', blockscript) + print('wif', wif) + + self.parent_chain = self.options.parent_type # Parent chain args for n in range(2): # We want to test the rpc cookie method so we force the use of a # dummy conf file to avoid loading rpcuser/rpcpassword lines use_cookie_auth = n==1 rpc_u, rpc_p = rpc_auth_pair(n) - if self.options.parent_bitcoin: + if self.options.parent_type == 'bitcoin': self.parent_chain = 'regtest' self.extra_args.append([ "-conf=dummy", @@ -55,10 +95,30 @@ def setup_network(self, split=False): "-addresstype=legacy", # To make sure bitcoind gives back p2pkh no matter version "-deprecatedrpc=validateaddress", "-port="+str(p2p_port(n)), - "-rpcport="+str(rpc_port(n)) + "-rpcport="+str(rpc_port(n)), ]) - else: - self.parent_chain = 'parent' + + elif self.options.parent_type == 'signet': + rpc_u, rpc_p = rpc_auth_pair(n) + self.extra_args.append([ + "-printtoconsole=0", + "-signet_blockscript=%s" % blockscript, + "-signet_siglen=77", + "-port=" + str(p2p_port(n)), + "-rpcport=" + str(rpc_port(n)), + "-addresstype=legacy", # To make sure bitcoind gives back p2pkh no matter version + "-deprecatedrpc=validateaddress", + "-fallbackfee=0.00001", + "-bech32_hrp=sb", + "-pchmessagestart=F0C7706A", + "-pubkeyprefix=125", + "-scriptprefix=87", + "-secretprefix=217", + "-extpubkeyprefix=043587CF", + "-extprvkeyprefix=04358394", + ]) + + elif self.options.parent_type == 'elements': self.extra_args.append([ "-conf=dummy", "-printtoconsole=0", @@ -66,19 +126,20 @@ def setup_network(self, split=False): '-anyonecanspendaremine', '-initialfreecoins=2100000000000000', "-port="+str(p2p_port(n)), - "-rpcport="+str(rpc_port(n)) + "-rpcport="+str(rpc_port(n)), ]) # Only first parent uses name/password, the 2nd uses cookie auth if not use_cookie_auth: self.extra_args[n].extend(["-rpcuser="+rpc_u, "-rpcpassword="+rpc_p]) - self.binary = self.options.parent_binpath if self.options.parent_binpath != "" else None self.nodes.append(start_node(n, self.options.tmpdir, self.extra_args[n], binary=self.binary, chain=self.parent_chain, cookie_auth=use_cookie_auth)) + if self.options.parent_type == 'signet': + self.nodes[n].importprivkey(wif) connect_nodes_bi(self.nodes, 0, 1) self.parentgenesisblockhash = self.nodes[0].getblockhash(0) print('parentgenesisblockhash', self.parentgenesisblockhash) - if not self.options.parent_bitcoin: + if self.options.parent_type == 'elements': parent_pegged_asset = self.nodes[0].getsidechaininfo()['pegged_asset'] # Sidechain args @@ -99,7 +160,8 @@ def setup_network(self, split=False): '-mainchainrpcport=%s' % rpc_port(n), '-recheckpeginblockinterval=15', # Long enough to allow failure and repair before timeout ] - if not self.options.parent_bitcoin: + + if self.options.parent_type == 'elements': args.extend([ '-parentpubkeyprefix=235', '-parentscriptprefix=75', @@ -107,6 +169,15 @@ def setup_network(self, split=False): '-con_parent_pegged_asset=%s' % parent_pegged_asset, ]) + elif self.options.parent_type == 'signet': + args.extend([ + '-con_parent_is_signet=1', + '-con_parent_signet_siglen=77', + '-parentpubkeyprefix=125', + '-parentscriptprefix=87', + '-con_parent_chain_signblockscript=%s' % blockscript, + ]) + if used_cookie_auth: # Need to specify where to find parent cookie file datadir = os.path.join(self.options.tmpdir, "node"+str(n)) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index bb1d0f199bc..974ccb4bddf 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -6,7 +6,7 @@ #include "chainparams.h" #include "consensus/merkle.h" #include "issuance.h" - +#include "primitives/bitcoin/block.h" #include "tinyformat.h" #include "util.h" #include "utilstrencodings.h" @@ -131,6 +131,9 @@ class CCustomParams : public CChainParams { consensus.pegin_min_depth = GetArg("-peginconfirmationdepth", DEFAULT_PEGIN_CONFIRMATION_DEPTH); consensus.mandatory_coinbase_destination = StrHexToScriptWithDefault(GetArg("-con_mandatorycoinbase", ""), CScript()); // Blank script allows any coinbase destination consensus.parent_chain_signblockscript = StrHexToScriptWithDefault(GetArg("-con_parent_chain_signblockscript", ""), CScript()); + consensus.parent_is_signet = GetBoolArg("-con_parent_is_signet", false); + g_solution_blocks = consensus.parent_is_signet; + g_solution_block_len = GetArg("-con_parent_signet_siglen", 77); consensus.parent_pegged_asset.SetHex(GetArg("-con_parent_pegged_asset", "0x00")); // bitcoin regtest is the parent chain by default diff --git a/src/consensus/params.h b/src/consensus/params.h index 5414ad0eb1b..ed29605eadb 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -74,8 +74,10 @@ struct Params { CScript signblockscript; bool has_parent_chain; CScript parent_chain_signblockscript; + bool parent_is_signet; CAsset parent_pegged_asset; - bool ParentChainHasPow() const { return parent_chain_signblockscript == CScript();} + bool ParentChainHasPow() const { return parent_chain_signblockscript == CScript(); } + bool ParentChainIsBitcoinLike() const { return parent_is_signet || ParentChainHasPow(); } }; } // namespace Consensus diff --git a/src/init.cpp b/src/init.cpp index 9ffd2e3cb11..5b58886af51 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -519,6 +519,8 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-parentpubkeyprefix", strprintf(_("The byte prefix, in decimal, of the parent chain's base58 pubkey address. (default: %d)"), 111)); strUsage += HelpMessageOpt("-parentscriptprefix", strprintf(_("The byte prefix, in decimal, of the parent chain's base58 script address. (default: %d)"), 196)); strUsage += HelpMessageOpt("-con_parent_chain_signblockscript", _("Whether parent chain uses pow or signed blocks. If the parent chain uses signed blocks, the challenge (scriptPubKey) script. If not, an empty string. (default: empty script [ie parent uses pow])")); + strUsage += HelpMessageOpt("-con_parent_is_signet", _("If parent chain uses signed blocks, whether it is an elements based chain or one based on signet (default: false)")); + strUsage += HelpMessageOpt("-con_parent_signet_siglen", _("If parent chain uses signed blocks based on signet, The length of the signature must be exactly this long (padded to this length, if shorter). All block headers in this network are of length 80 + this value. (default: 77)")); strUsage += HelpMessageOpt("-con_parent_pegged_asset=", _("Asset ID (hex) for pegged asset for when parent chain has CA. (default: 0x00)")); strUsage += HelpMessageOpt("-recheckpeginblockinterval", strprintf(_("The interval in seconds at which a peg-in witness failing block is re-evaluated in case of intermittant peg-in witness failure. 0 means never. (default: %u)"), 120)); } diff --git a/src/pow.cpp b/src/pow.cpp index 4e591aa094e..6d0cef531f7 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -38,8 +38,30 @@ void ResetChallenge(CBlockHeader& block, const CBlockIndex& indexLast, const Con block.proof.challenge = indexLast.proof.challenge; } -bool CheckBitcoinProof(uint256 hash, unsigned int nBits) +static const unsigned int BLOCK_VERIFY_FLAGS = MANDATORY_SCRIPT_VERIFY_FLAGS | + SCRIPT_VERIFY_DERSIG | + SCRIPT_VERIFY_STRICTENC | + SCRIPT_VERIFY_MINIMALDATA | + SCRIPT_VERIFY_NULLDUMMY | + SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS | + SCRIPT_VERIFY_CLEANSTACK | + SCRIPT_VERIFY_MINIMALIF | + SCRIPT_VERIFY_NULLFAIL | + SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | + SCRIPT_VERIFY_CHECKSEQUENCEVERIFY | + SCRIPT_VERIFY_LOW_S | + SCRIPT_VERIFY_WITNESS | + SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM | + SCRIPT_VERIFY_WITNESS_PUBKEYTYPE; + +bool CheckBitcoinProof(const uint256& hash, unsigned int nBits, const Consensus::Params& params) { + if (g_solution_blocks) { + const auto& payload = g_blockheader_payload_map.at(hash); + CScript solution = CScript(payload.begin(), payload.end()); + return HashVerifyScript(solution, params.parent_chain_signblockscript, BLOCK_VERIFY_FLAGS, hash); + } + bool fNegative; bool fOverflow; arith_uint256 bnTarget; @@ -47,7 +69,7 @@ bool CheckBitcoinProof(uint256 hash, unsigned int nBits) bnTarget.SetCompact(nBits, &fNegative, &fOverflow); // Check range - if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(Params().GetConsensus().parentChainPowLimit)) + if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.parentChainPowLimit)) return false; // Check proof of work matches claimed amount diff --git a/src/pow.h b/src/pow.h index 66327c4c913..dcaa1a0a8fa 100644 --- a/src/pow.h +++ b/src/pow.h @@ -20,7 +20,7 @@ class CWallet; class uint256; /** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */ -bool CheckBitcoinProof(uint256 hash, unsigned int nBits); +bool CheckBitcoinProof(const uint256& hash, unsigned int nBits, const Consensus::Params& params); bool CheckProofSignedParent(const CBlockHeader& block, const Consensus::Params& params); bool CheckProof(const CBlockHeader& block, const Consensus::Params&); /** Scans nonces looking for a hash with at least some zero bits */ diff --git a/src/primitives/bitcoin/block.cpp b/src/primitives/bitcoin/block.cpp index eceb173aa58..25667db9ed8 100644 --- a/src/primitives/bitcoin/block.cpp +++ b/src/primitives/bitcoin/block.cpp @@ -12,6 +12,10 @@ #include "utilstrencodings.h" #include "crypto/common.h" +bool g_solution_blocks = false; +size_t g_solution_block_len = 0; +std::map> g_blockheader_payload_map; + namespace Sidechain { namespace Bitcoin { diff --git a/src/primitives/bitcoin/block.h b/src/primitives/bitcoin/block.h index 2e607ee3dae..45223851749 100644 --- a/src/primitives/bitcoin/block.h +++ b/src/primitives/bitcoin/block.h @@ -12,6 +12,22 @@ #include "serialize.h" #include "uint256.h" +/** + * If true, block headers contain a payload equal to a Bitcoin Script solution + * to a signet challenge as defined in the chain params. + */ +extern bool g_solution_blocks; +/** + * If non-zero, defines an enforced size requirement for block header payloads. + * It requires that all blocks are of size 80 + (this value) bytes. + */ +extern size_t g_solution_block_len; + /** + * Contains a mapping of hash to signature data for each block header + * in signet networks. + */ +extern std::map> g_blockheader_payload_map; + namespace Sidechain { namespace Bitcoin { @@ -48,6 +64,15 @@ class CBlockHeader READWRITE(nTime); READWRITE(nBits); READWRITE(nNonce); + if (g_solution_blocks && !(s.GetType() & SER_GETHASH)) { + READWRITE(g_blockheader_payload_map[GetHash()]); + size_t len = GetSizeOfCompactSize(g_blockheader_payload_map[GetHash()].size()) + g_blockheader_payload_map[GetHash()].size(); + while (len < g_solution_block_len) { + uint8_t padding = 0; + READWRITE(padding); + len++; + } + } } void SetNull() diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index db0e53bfa91..a65539fc124 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1112,6 +1112,8 @@ UniValue getsidechaininfo(const JSONRPCRequest& request) " \"min_peg_diff\" : \"xxxx\", (string) The minimum difficulty parent chain header target. Peg-in headers that have less work will be rejected as an anti-Dos measure.\n" " \"parent_blockhash\" : \"xxxx\", (string) The parent genesis blockhash as source of pegged-in funds.\n" " \"parent_chain_has_pow\": \"xxxx\", (boolean) Whether parent chain has pow or signed blocks.\n" + " \"parent_chain_is_signet\": \"xxxx\", (boolean) If parent chain uses signed blocks, whether it is an elements based chain or one based on signet.\n" + " \"parent_chain_signet_siglen\": \"xxxx\", (numeric) If parent chain uses signed blocks based on signet, The length of the signature must be exactly this long (padded to this length, if shorter). All block headers in this network are of length 80 + this value.\n" " \"parent_chain_signblockscript_asm\": \"xxxx\", (string) If the parent chain has signed blocks, its signblockscript in ASM.\n" " \"parent_chain_signblockscript_hex\": \"xxxx\", (string) If the parent chain has signed blocks, its signblockscript in hex.\n" " \"parent_pegged_asset\": \"xxxx\", (boolean) If the parent chain has Confidential Assets, the asset id of the pegged asset in that chain.\n" @@ -1133,6 +1135,10 @@ UniValue getsidechaininfo(const JSONRPCRequest& request) obj.push_back(Pair("parent_blockhash", parent_blockhash.GetHex())); obj.push_back(Pair("parent_chain_has_pow", consensus.ParentChainHasPow())); if (!consensus.ParentChainHasPow()) { + obj.push_back(Pair("parent_chain_is_signet", consensus.parent_is_signet)); + if (consensus.parent_is_signet) { + obj.push_back(Pair("parent_chain_signet_siglen", (uint64_t)g_solution_block_len)); + } obj.push_back(Pair("parent_chain_signblockscript_asm", ScriptToAsmStr(consensus.parent_chain_signblockscript))); obj.push_back(Pair("parent_chain_signblockscript_hex", HexStr(consensus.parent_chain_signblockscript))); obj.push_back(Pair("parent_pegged_asset", HexStr(consensus.parent_pegged_asset))); diff --git a/src/script/generic.hpp b/src/script/generic.hpp index 533b7b55cab..da826cc68d7 100644 --- a/src/script/generic.hpp +++ b/src/script/generic.hpp @@ -49,6 +49,11 @@ bool GenericVerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, return VerifyScript(scriptSig, scriptPubKey, NULL, flags, SimpleSignatureChecker(SerializeHash(data))); } +bool HashVerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, unsigned int flags, const uint256& hash) +{ + return VerifyScript(scriptSig, scriptPubKey, NULL, flags, SimpleSignatureChecker(hash)); +} + template bool GenericSignScript(const CKeyStore& keystore, const T& data, const CScript& fromPubKey, SignatureData& scriptSig) { diff --git a/src/validation.cpp b/src/validation.cpp index aafb998e8e2..6ae3b90da16 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2546,13 +2546,13 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p uint256 tx_hash; int num_txs; // Get txout proof - if (Params().GetConsensus().ParentChainHasPow()) { + if (Params().GetConsensus().ParentChainIsBitcoinLike()) { Sidechain::Bitcoin::CMerkleBlock merkle_block_pow; if (!GetBlockAndTxFromMerkleBlock(block_hash, tx_hash, merkle_block_pow, stack[5])) { return false; } - if (!CheckBitcoinProof(block_hash, merkle_block_pow.header.nBits)) { + if (!CheckBitcoinProof(block_hash, merkle_block_pow.header.nBits, Params().GetConsensus())) { return false; } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 5930bb2e148..bd51004ee4e 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3716,12 +3716,12 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef UniValue createrawpegin(const JSONRPCRequest& request) { UniValue ret(UniValue::VOBJ); - if (Params().GetConsensus().ParentChainHasPow()) { + if (Params().GetConsensus().ParentChainIsBitcoinLike()) { Sidechain::Bitcoin::CTransactionRef txBTCRef; Sidechain::Bitcoin::CTransaction tx_aux; Sidechain::Bitcoin::CMerkleBlock merkleBlock; ret = createrawpegin(request, txBTCRef, tx_aux, merkleBlock); - if (!CheckBitcoinProof(merkleBlock.header.GetHash(), merkleBlock.header.nBits)) { + if (!CheckBitcoinProof(merkleBlock.header.GetHash(), merkleBlock.header.nBits, Params().GetConsensus())) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof"); } } else {