From 85eb5026de26947a4c4d48db3b90af787ea69e38 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Thu, 9 Aug 2018 14:16:30 +0100 Subject: [PATCH] 2WP: Verify number of transactions in SPV proof Use the getblockheader RPC from Bitcoin Core to verify if the number of transactions mentioned in the SPV proof matches the number for the block known by the Core daemon. Inspired by https://github.com/bitcoin/bitcoin/commit/6b9dc8ceaed597d9c539ba6b09c171b258b66ca3 --- src/callrpc.cpp | 19 ++++++++++++++++--- src/callrpc.h | 7 ++++++- src/merkleblock.h | 5 +++++ src/primitives/bitcoin/merkleblock.h | 5 +++++ src/validation.cpp | 9 ++++++++- src/wallet/rpcwallet.cpp | 3 ++- 6 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/callrpc.cpp b/src/callrpc.cpp index 1c07573ce2..357cd2099d 100644 --- a/src/callrpc.cpp +++ b/src/callrpc.cpp @@ -178,7 +178,7 @@ UniValue CallRPC(const std::string& strMethod, const UniValue& params, bool conn return reply; } -bool IsConfirmedBitcoinBlock(const uint256& hash, int nMinConfirmationDepth) +bool IsConfirmedBitcoinBlock(const uint256& hash, const int nMinConfirmationDepth, const int nbTxs) { try { UniValue params(UniValue::VARR); @@ -189,8 +189,21 @@ bool IsConfirmedBitcoinBlock(const uint256& hash, int nMinConfirmationDepth) UniValue result = find_value(reply, "result"); if (!result.isObject()) return false; - result = find_value(result.get_obj(), "confirmations"); - return result.isNum() && result.get_int64() >= nMinConfirmationDepth; + + UniValue confirmations = find_value(result.get_obj(), "confirmations"); + if (!confirmations.isNum() || confirmations.get_int64() < nMinConfirmationDepth) { + return false; + } + + // Only perform extra test if nbTxs has been provided (non-zero). + if (nbTxs != 0) { + UniValue nTx = find_value(result.get_obj(), "nTx"); + if (!nTx.isNum() || nTx.get_int64() != nbTxs) { + LogPrintf("ERROR: Invalid number of transactions in merkle block for %s", + hash.GetHex()); + return false; + } + } } catch (CConnectionFailed& e) { LogPrintf("ERROR: Lost connection to bitcoind RPC, you will want to restart after fixing this!\n"); return false; diff --git a/src/callrpc.h b/src/callrpc.h index 8557aa23ca..3ba091325e 100644 --- a/src/callrpc.h +++ b/src/callrpc.h @@ -35,6 +35,11 @@ public: }; UniValue CallRPC(const std::string& strMethod, const UniValue& params, bool connectToMainchain=false); -bool IsConfirmedBitcoinBlock(const uint256& hash, int nMinConfirmationDepth); + +// Verify if the block with given hash has at least the specified minimum number +// of confirmations. +// For validating merkle blocks, you can provide the nbTxs parameter to verify if +// it equals the number of transactions in the block. +bool IsConfirmedBitcoinBlock(const uint256& hash, int nMinConfirmationDepth, int nbTxs); #endif // BITCOIN_CALLRPC_H diff --git a/src/merkleblock.h b/src/merkleblock.h index 73cbf670ee..5726b6f492 100644 --- a/src/merkleblock.h +++ b/src/merkleblock.h @@ -115,6 +115,11 @@ public: * returns the merkle root, or 0 in case of failure */ uint256 ExtractMatches(std::vector &vMatch, std::vector &vnIndex); + + /** Get number of transactions the merkle proof is indicating for cross-reference with + * local blockchain knowledge. + */ + unsigned int GetNumTransactions() const { return nTransactions; }; }; diff --git a/src/primitives/bitcoin/merkleblock.h b/src/primitives/bitcoin/merkleblock.h index 7dcb4411cb..8ea5120d8d 100644 --- a/src/primitives/bitcoin/merkleblock.h +++ b/src/primitives/bitcoin/merkleblock.h @@ -120,6 +120,11 @@ public: * returns the merkle root, or 0 in case of failure */ uint256 ExtractMatches(std::vector &vMatch, std::vector &vnIndex); + + /** Get number of transactions the merkle proof is indicating for cross-reference with + * local blockchain knowledge. + */ + unsigned int GetNumTransactions() const { return nTransactions; }; }; diff --git a/src/validation.cpp b/src/validation.cpp index c64540bc45..fc6860501f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2531,6 +2531,7 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p uint256 block_hash; uint256 tx_hash; + int num_txs; // Get txout proof if (Params().GetConsensus().ParentChainHasPow()) { @@ -2546,6 +2547,8 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p if (!CheckPeginTx(stack[4], pegtx, prevout, value, claim_script)) { return false; } + + num_txs = merkle_block_pow.txn.GetNumTransactions(); } else { CMerkleBlock merkle_block; @@ -2561,6 +2564,8 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p if (!CheckPeginTx(stack[4], pegtx, prevout, value, claim_script)) { return false; } + + num_txs = merkle_block.txn.GetNumTransactions(); } // Check that the merkle proof corresponds to the txid @@ -2580,7 +2585,9 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p // Finally, validate peg-in via rpc call if (check_depth && GetBoolArg("-validatepegin", DEFAULT_VALIDATE_PEGIN)) { - return IsConfirmedBitcoinBlock(block_hash, Params().GetConsensus().pegin_min_depth); + if (!IsConfirmedBitcoinBlock(block_hash, Params().GetConsensus().pegin_min_depth, num_txs)) { + return false; + } } return true; } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index acfd861315..1adae5c678 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3702,7 +3702,8 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef // Additional block lee-way to avoid bitcoin block races if (GetBoolArg("-validatepegin", DEFAULT_VALIDATE_PEGIN)) { - ret.push_back(Pair("mature", IsConfirmedBitcoinBlock(merkleBlock.header.GetHash(), Params().GetConsensus().pegin_min_depth+2))); + ret.push_back(Pair("mature", IsConfirmedBitcoinBlock(merkleBlock.header.GetHash(), + Params().GetConsensus().pegin_min_depth+2, merkleBlock.txn.GetNumTransactions()))); } return ret;