Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions qa/pull-tester/rpc-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
'p2p-compactblocks.py',
'nulldummy.py',
'importmulti.py',
'sighashlimit.py',
]
if ENABLE_ZMQ:
testScripts.append('zmq_test.py')
Expand Down
157 changes: 157 additions & 0 deletions qa/rpc-tests/sighashlimit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/usr/bin/env python3
# Copyright (c) 2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

from test_framework.test_framework import BitcoinTestFramework
from test_framework.mininode import CTransaction, CTxOut, CTxIn, COutPoint, CTxInWitness
from test_framework.util import *
from test_framework.script import CScript, OP_0, OP_HASH160, OP_EQUAL, OP_CHECKSIG, OP_NOT, OP_2DUP, OP_DROP, OP_2DROP, hash160, sha256

# This is to test the sighash limit policy

class SigHashLimitTest(BitcoinTestFramework):

def __init__(self):
super().__init__()
self.setup_clean_chain = True

def setup_network(self):
# Create 2 nodes. One enforces standardness rules and one does not.
self.nodes = [start_node(0, self.options.tmpdir, ["-acceptnonstdtxn=0"])]
self.nodes.append(start_node(1, self.options.tmpdir))
connect_nodes(self.nodes[0], 1)

def test_preparation(self):
print ("Testing sighash limit policy")
# Generate a block and get the coinbase txid.
self.coinbase_blocks = self.nodes[1].generate(1)
coinbase_txid = int("0x" + self.nodes[1].getblock(self.coinbase_blocks[0])['tx'][0], 0)
self.nodes[1].generate(100)

'''
# By design, it is impossible to create a normal transaction below 400,000 weight,
# while having excessive SigHash size.
#
# Here it creates small script with 4 SigHashOp in P2SH.
# Also create a witness program, which should be ignored in SigHashOp counting.
'''
dummykey = hex_str_to_bytes("0300112233445566778899aabbccddeeff00112233445566778899aabbccddeeff")
self.script = CScript([OP_0, dummykey, OP_2DUP, OP_2DUP, OP_2DUP, OP_CHECKSIG, OP_DROP, OP_CHECKSIG, OP_DROP, OP_CHECKSIG, OP_DROP, OP_CHECKSIG, OP_NOT])
self.p2sh = CScript([OP_HASH160, hash160(self.script), OP_EQUAL])
self.p2wsh = CScript([OP_0, sha256(self.script)])

tx = CTransaction()
tx.vin.append(CTxIn(COutPoint(coinbase_txid)))
for i in range(1000):
tx.vout.append(CTxOut(4500000, self.p2sh))
for i in range(100):
tx.vout.append(CTxOut(4500000, self.p2wsh))

tx.rehash()
signresults = self.nodes[1].signrawtransaction(bytes_to_hex_str(tx.serialize_with_witness()))['hex']
self.txid = int("0x" + self.nodes[1].sendrawtransaction(signresults, True), 0)
self.nodes[1].generate(1)
sync_blocks(self.nodes)

self.p2shcount = 0 # P2SH outputs start from 0
self.p2wshcount = 1000 # P2WSH outputs start from 500

def non_segwit_test(self):
'''
tx1:
Size = 4 + 1 + 88 * 95 + 3 + 2390 * 32 + 4 = 84852
Weight = 84852 * 4 = 339408
Hashable size = 84852 - 47 * 95 = 80387
SigHashOp = 4 * 95 = 380
SigHashSize = 380 * 80387 = 30547060
SigHashSize per Weight = 30547060 / 339408 = 90.001

tx2:
Size = 4 + 1 + 88 * 95 + 3 + 2389 * 32 + 4 = 84820
Weight = 84820 * 4 = 339280
Hashable size = 84820 - 47 * 95 = 80355
SigHashOp = 4 * 95 = 380
SigHashSize = 380 * 80355 = 30534900
SigHashSize per Weight = 30534900 / 339280 = 89.9991
'''
tx1 = CTransaction()
tx2 = CTransaction()
for i in range(95):
tx1.vin.append(CTxIn(COutPoint(self.txid,self.p2shcount),CScript([self.script])))
tx2.vin.append(CTxIn(COutPoint(self.txid,self.p2shcount + 1),CScript([self.script])))
self.p2shcount += 2
for i in range(2389):
tx1.vout.append(CTxOut(1000, self.p2sh))
tx2.vout.append(CTxOut(1000, self.p2sh))
tx1.vout.append(CTxOut(1000, self.p2sh)) # Add one more output to tx1
tx1.rehash()
tx2.rehash()
self.submit_pair(tx1, tx2)

def segwit_test(self):
'''
tx1:
Witness-stripped size = 4 + 1 + 88 * 95 + 41 * 20 + 3 + 2513 * 32 + 4 = 89608
Witness size = 2 + 1 * 95 + 48 * 20 = 1057
Weight = 89608 * 4 + 1057 = 359489
Hashable size = 89608 - 47 * 95 = 85143
SigHashOp = 4 * 95 = 380
SigHashSize = 380 * 85143 = 32354340
SigHashSize per Weight = 32342180 / 359489 = 90.0009

tx2:
Witness-stripped size = 4 + 1 + 88 * 95 + 41 * 20 + 3 + 2512 * 32 + 4 = 89576
Witness size = 2 + 1 * 95 + 48 * 20 = 1057
Weight = 89576 * 4 + 1057 = 359361
Hashable size = 89576 - 47 * 95 = 85111
SigHashOp = 4 * 95 = 380
SigHashSize = 380 * 85111 = 32342180
SigHashSize per Weight = 32342180 / 359361 = 89.9991
'''
tx1 = CTransaction()
tx2 = CTransaction()
for i in range(20):
tx1.vin.append(CTxIn(COutPoint(self.txid,self.p2wshcount)))
tx1.wit.vtxinwit.append(CTxInWitness())
tx1.wit.vtxinwit[i].scriptWitness.stack = [self.script]
tx2.vin.append(CTxIn(COutPoint(self.txid,self.p2wshcount + 1)))
tx2.wit.vtxinwit.append(CTxInWitness())
tx2.wit.vtxinwit[i].scriptWitness.stack = [self.script]
self.p2wshcount += 2
for i in range(95):
tx1.vin.append(CTxIn(COutPoint(self.txid,self.p2shcount),CScript([self.script])))
tx2.vin.append(CTxIn(COutPoint(self.txid,self.p2shcount + 1),CScript([self.script])))
self.p2shcount += 2
for i in range(2512):
tx1.vout.append(CTxOut(1000, self.p2sh))
tx2.vout.append(CTxOut(1000, self.p2sh))
tx1.vout.append(CTxOut(1000, self.p2sh)) # Add one more output to tx1
tx1.rehash()
tx2.rehash()
self.submit_pair(tx1, tx2)

def submit_pair(self, tx1, tx2):
# Non-standard node should accept both tx1 and tx2. Standard node should accept only tx2
try:
self.nodes[0].sendrawtransaction(bytes_to_hex_str(tx1.serialize_with_witness()), True)
except JSONRPCException as exp:
assert_equal(exp.error["message"], "64: bad-txns-nonstandard-too-much-sighashing")
else:
assert(False)
self.nodes[1].sendrawtransaction(bytes_to_hex_str(tx1.serialize_with_witness()), True)
self.nodes[0].sendrawtransaction(bytes_to_hex_str(tx2.serialize_with_witness()), True)
self.nodes[1].sendrawtransaction(bytes_to_hex_str(tx2.serialize_with_witness()), True)
self.nodes[1].generate(1)
sync_blocks(self.nodes)

def run_test(self):
self.test_preparation()
self.non_segwit_test() # Test non-segwit P2SH before segwit activation
self.nodes[0].generate(400) # Activate segwit
sync_blocks(self.nodes)
self.non_segwit_test() # Test non-segwit P2SH after segwit activation
self.segwit_test() # Test non-segwit P2SH mixed with P2WSH

if __name__ == '__main__':
SigHashLimitTest().main()
22 changes: 22 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1062,8 +1062,23 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
return nSigOps;
}

int GetTransactionBaseSigHashOpCount(const CTransaction& tx, const CCoinsViewCache& inputs, int flags)
{
int nSigHashOps = 0;

if (tx.IsCoinBase())
return nSigHashOps;

for (unsigned int i = 0; i < tx.vin.size(); i++) {
nSigHashOps += tx.vin[i].scriptSig.GetSigHashOpCount();
const CTxOut &prevout = inputs.GetOutputFor(tx.vin[i]);
nSigHashOps += prevout.scriptPubKey.GetSigHashOpCount();
if (prevout.scriptPubKey.IsPayToScriptHash() && (flags & SCRIPT_VERIFY_P2SH))
nSigHashOps += prevout.scriptPubKey.GetSigHashOpCount(tx.vin[i].scriptSig);
}

return nSigHashOps;
}


bool CheckTransaction(const CTransaction& tx, CValidationState &state)
Expand Down Expand Up @@ -1285,6 +1300,13 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
if (!tx.wit.IsNull() && fRequireStandard && !IsWitnessStandard(tx, view))
return state.DoS(0, false, REJECT_NONSTANDARD, "bad-witness-nonstandard", true);

// Check for excessive SignatureHash operation
if (fRequireStandard) {
int64_t hashsize = GetTransactionHashableSize(tx) * GetTransactionBaseSigHashOpCount(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS);
if (hashsize > MAX_STANDARD_HASH_PER_WEIGHT * GetTransactionWeight(tx))
return state.Invalid(false, REJECT_NONSTANDARD, "bad-txns-nonstandard-too-much-sighashing");
}

int64_t nSigOpsCost = GetTransactionSigOpCost(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS);

CAmount nValueOut = tx.GetValueOut();
Expand Down
8 changes: 7 additions & 1 deletion src/main.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2015 The Bitcoin Core developers
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

Expand Down Expand Up @@ -325,6 +325,12 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& ma
*/
int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& inputs, int flags);

/**
* Compute total signature hashing operation of a transaction.
* Parameters same as GetTransactionSigOpCost
*/
int GetTransactionBaseSigHashOpCount(const CTransaction& tx, const CCoinsViewCache& inputs, int flags);

/**
* Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts)
* This does not modify the UTXO set. If pvChecks is not NULL, script checks are pushed onto it
Expand Down
6 changes: 6 additions & 0 deletions src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS = 100;
static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEM_SIZE = 80;
/** The maximum size of a standard witnessScript */
static const unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600;
/**
* Maximum standard signature hashing per transaction weight (byte hashed per weight)
* This is equivalent to 36MB for an 100kB non-segwit transaction.
* All transactions below 100kB with legitimate use of CHECK(MULTI)SIG should remain standard with this limit.
*/
static const unsigned int MAX_STANDARD_HASH_PER_WEIGHT = 90;
/**
* Standard script verification flags that standard transactions will comply
* with. However scripts violating these flags may still be present in valid
Expand Down
20 changes: 19 additions & 1 deletion src/primitives/transaction.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2015 The Bitcoin Core developers
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

Expand Down Expand Up @@ -158,3 +158,21 @@ int64_t GetTransactionWeight(const CTransaction& tx)
{
return ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR -1) + ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
}

int64_t GetTransactionHashableSize(const CTransaction& tx)
{
int64_t size = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
for (unsigned int i = 0; i < tx.vin.size(); i++) {
int64_t scriptSigSize = tx.vin[i].scriptSig.size();
size -= scriptSigSize;
// If the scriptSig size is larger than 252, 2 bytes compactSize encoding is deducted.
if (scriptSigSize > 252)
size -= 2;
/*
* Theoretically, 4 bytes should be deducted if the scriptSig is larger than 65535 bytes,
* and 8 bytes should be deducted if it is larger than 4294967295 bytes.
* However, scriptSig larger than 10000 bytes is invalid so it is not needed.
*/
}
return size;
}
5 changes: 4 additions & 1 deletion src/primitives/transaction.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2015 The Bitcoin Core developers
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

Expand Down Expand Up @@ -471,4 +471,7 @@ struct CMutableTransaction
/** Compute the weight of a transaction, as defined by BIP 141 */
int64_t GetTransactionWeight(const CTransaction &tx);

/** Compute the signature hashable size = transaction size - scriptSig size */
int64_t GetTransactionHashableSize(const CTransaction& tx);

#endif // BITCOIN_PRIMITIVES_TRANSACTION_H
20 changes: 16 additions & 4 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,9 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, un
//serror is set
return false;
}
bool fSuccess = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion);
uint256 sighashCopy;
bool cacheSet = false;
bool fSuccess = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion, sighashCopy, cacheSet);

if (!fSuccess && (flags & SCRIPT_VERIFY_NULLFAIL) && vchSig.size())
return set_error(serror, SCRIPT_ERR_SIG_NULLFAIL);
Expand Down Expand Up @@ -961,6 +963,8 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, un
}

bool fSuccess = true;
bool cacheSet = false;
uint256 sighashCopy;
while (fSuccess && nSigsCount > 0)
{
valtype& vchSig = stacktop(-isig);
Expand All @@ -975,11 +979,12 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, un
}

// Check signature
bool fOk = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion);
bool fOk = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion, sighashCopy, cacheSet);

if (fOk) {
isig++;
nSigsCount--;
cacheSet = false;
}
ikey++;
nKeysCount--;
Expand Down Expand Up @@ -1250,7 +1255,7 @@ bool TransactionSignatureChecker::VerifySignature(const std::vector<unsigned cha
return pubkey.Verify(sighash, vchSig);
}

bool TransactionSignatureChecker::CheckSig(const vector<unsigned char>& vchSigIn, const vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
bool TransactionSignatureChecker::CheckSig(const vector<unsigned char>& vchSigIn, const vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion, uint256& sighashCopy, bool& cacheSet) const
{
CPubKey pubkey(vchPubKey);
if (!pubkey.IsValid())
Expand All @@ -1263,7 +1268,14 @@ bool TransactionSignatureChecker::CheckSig(const vector<unsigned char>& vchSigIn
int nHashType = vchSig.back();
vchSig.pop_back();

uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, this->txdata);
uint256 sighash;
if (cacheSet)
sighash = sighashCopy;
else {
sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, this->txdata);
sighashCopy = sighash;
cacheSet = true;
}

if (!VerifySignature(vchSig, pubkey, sighash))
return false;
Expand Down
4 changes: 2 additions & 2 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsig
class BaseSignatureChecker
{
public:
virtual bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
virtual bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion, uint256& sighashCopy, bool& cacheSet) const
{
return false;
}
Expand Down Expand Up @@ -160,7 +160,7 @@ class TransactionSignatureChecker : public BaseSignatureChecker
public:
TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(NULL) {}
TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const;
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion, uint256& sighashCopy, bool& cacheSet) const;
bool CheckLockTime(const CScriptNum& nLockTime) const;
bool CheckSequence(const CScriptNum& nSequence) const;
};
Expand Down
Loading