Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Policy: limit witness stack size #52

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
cad77b7
Add segregated witness transaction serialization
sipa Nov 6, 2015
4330cce
Removed ppszTypeName from protocol.cpp
CodeShark Jan 11, 2016
29d7faf
getdata enum issue fix
CodeShark Jan 16, 2016
771b19e
Negotiate witness fetching via 'havewitness'
sipa Nov 16, 2015
5225112
Witness commitment validation
sipa Nov 6, 2015
4bae79c
Script validation logic for witnesses
sipa Nov 8, 2015
aa50817
Enable SCRIPT_VERIFY_WITNESS for mempool transactions
sipa Nov 18, 2015
253129f
Activate script consensus rules in v5 blocks
sipa Nov 18, 2015
5978e02
Only download blocks from witness peers after fork
sipa Dec 29, 2015
784ddf0
Witness script signing
sipa Dec 29, 2015
c9d7ab8
Signing tests
sipa Nov 20, 2015
d679b8f
Make script validation observe input amounts
sipa Dec 26, 2015
52ac543
Add signature version 1 with updated sighash
sipa Dec 27, 2015
a477de6
Testchains: Don't check the genesis block
jtimon Jul 3, 2015
ce86f03
Create segnet
sipa Dec 31, 2015
abf789b
Add segnet seed nodes
sipa Jan 21, 2016
29cdf80
Add witness address RPCs (using P2SH)
sipa Dec 30, 2015
874935e
Return witness data
jl2012 Jan 22, 2016
f308778
Implementing base+wit/4 block size limit rule
sipa Jan 3, 2016
0d66d2d
Add command line options to loosen mempool acceptance rules
morcos Jan 2, 2016
ec3aee8
Add rpc test for segwit
morcos Jan 2, 2016
6d82f24
SigOp counting in witnesses
sipa Jan 6, 2016
d0f6057
Genesis block disk query fix
CodeShark Jan 11, 2016
a660ed1
qt: Work (don't crash) with -segnet
theuni Jan 16, 2016
ed8aff9
Witness stack size limit
jl2012 Jan 26, 2016
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
3 changes: 3 additions & 0 deletions contrib/seeds/generate-seeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ def main():
with open(os.path.join(indir,'nodes_main.txt'),'r') as f:
process_nodes(g, f, 'pnSeed6_main', 8333)
g.write('\n')
with open(os.path.join(indir,'nodes_seg.txt'),'r') as f:
process_nodes(g, f, 'pnSeed6_seg', 28333)
g.write('\n')
with open(os.path.join(indir,'nodes_test.txt'),'r') as f:
process_nodes(g, f, 'pnSeed6_test', 18333)
g.write('#endif // BITCOIN_CHAINPARAMSSEEDS_H\n')
Expand Down
6 changes: 6 additions & 0 deletions contrib/seeds/nodes_seg.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# List of fixed seed nodes for segnet

104.243.38.34
104.155.1.158
119.246.245.241
46.101.235.82
269 changes: 269 additions & 0 deletions qa/rpc-tests/segwit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
#!/usr/bin/env python2
# Copyright (c) 2014 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

#
# Test the SegWit changeover logic
#

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *
import os
import shutil
import hashlib
from binascii import hexlify

NODE_0 = 0
NODE_1 = 1
NODE_2 = 2
WIT_V0 = 0
WIT_V1 = 1

def sha256(s):
return hashlib.new('sha256', s).digest()

def ripemd160(s):
return hashlib.new('ripemd160', s).digest()

def witness_script(version, pubkey):
if (version == 0):
pubkeyhash = hexlify(ripemd160(sha256(pubkey.decode("hex"))))
pkscript = "001976a914" + pubkeyhash + "88ac"
elif (version == 1):
witnessprogram = "21"+pubkey+"ac"
hashwitnessprogram = hexlify(sha256(witnessprogram.decode("hex")))
pkscript = "5120" + hashwitnessprogram
else:
assert("Wrong version" == "0 or 1")
return pkscript

def addlength(script):
scriptlen = format(len(script)/2, 'x')
assert(len(scriptlen) == 2)
return scriptlen + script

def create_witnessprogram(version, node, utxo, pubkey, encode_p2sh, amount):
pkscript = witness_script(version, pubkey);
if (encode_p2sh):
p2sh_hash = hexlify(ripemd160(sha256(pkscript.decode("hex"))))
pkscript = "a914"+p2sh_hash+"87"
inputs = []
outputs = {}
inputs.append({ "txid" : utxo["txid"], "vout" : utxo["vout"]} )
DUMMY_P2SH = "2MySexEGVzZpRgNQ1JdjdP5bRETznm3roQ2" # P2SH of "OP_1 OP_DROP"
outputs[DUMMY_P2SH] = amount
tx_to_witness = node.createrawtransaction(inputs,outputs)
#replace dummy output with our own
tx_to_witness = tx_to_witness[0:110] + addlength(pkscript) + tx_to_witness[-8:]
return tx_to_witness

def send_to_witness(version, node, utxo, pubkey, encode_p2sh, amount, sign=True, insert_redeem_script=""):
tx_to_witness = create_witnessprogram(version, node, utxo, pubkey, encode_p2sh, amount)
if (sign):
signed = node.signrawtransaction(tx_to_witness)
return node.sendrawtransaction(signed["hex"])
else:
if (insert_redeem_script):
tx_to_witness = tx_to_witness[0:82] + addlength(insert_redeem_script) + tx_to_witness[84:]

return node.sendrawtransaction(tx_to_witness)

def getutxo(txid):
utxo = {}
utxo["vout"] = 0
utxo["txid"] = txid
return utxo

class SegWitTest(BitcoinTestFramework):

def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 3)

def setup_network(self):
self.nodes = []
self.nodes.append(start_node(0, self.options.tmpdir, ["-logtimemicros", "-debug"]))
self.nodes.append(start_node(1, self.options.tmpdir, ["-logtimemicros", "-debug", "-blockversion=4", "-promiscuousmempoolflags=517", "-prematurewitness"]))
self.nodes.append(start_node(2, self.options.tmpdir, ["-logtimemicros", "-debug", "-blockversion=5", "-promiscuousmempoolflags=517", "-prematurewitness"]))
connect_nodes(self.nodes[1], 0)
connect_nodes(self.nodes[2], 1)
connect_nodes(self.nodes[0], 2)
self.is_network_split = False
self.sync_all()

def success_mine(self, node, txid, sign, redeem_script=""):
send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script)
block = node.generate(1)
assert_equal(len(node.getblock(block[0])["tx"]), 2)
sync_blocks(self.nodes)

def skip_mine(self, node, txid, sign, redeem_script=""):
send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script)
block = node.generate(1)
assert_equal(len(node.getblock(block[0])["tx"]), 1)
sync_blocks(self.nodes)

def fail_accept(self, node, txid, sign, redeem_script=""):
try:
send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script)
except JSONRPCException as exp:
assert(exp.error["code"] == -26)
else:
raise AssertionError("Tx should not have been accepted")

def fail_mine(self, node, txid, sign, redeem_script=""):
send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script)
try:
node.generate(1)
except JSONRPCException as exp:
assert(exp.error["code"] == -1)
else:
raise AssertionError("Created valid block when TestBlockValidity should have failed")
sync_blocks(self.nodes)

def run_test(self):
self.nodes[0].generate(160) #block 160

self.pubkey = []
p2sh_ids = [] # p2sh_ids[NODE][VER] is an array of txids that spend to a witness version VER pkscript to an address for NODE embedded in p2sh
wit_ids = [] # wit_ids[NODE][VER] is an array of txids that spend to a witness version VER pkscript to an address for NODE via bare witness
for i in xrange(3):
newaddress = self.nodes[i].getnewaddress()
self.nodes[i].addwitnessaddress(newaddress)
self.pubkey.append(self.nodes[i].validateaddress(newaddress)["pubkey"])
p2sh_ids.append([])
wit_ids.append([])
for v in xrange(2):
p2sh_ids[i].append([])
wit_ids[i].append([])

for i in xrange(5):
for n in xrange(3):
for v in xrange(2):
wit_ids[n][v].append(send_to_witness(v, self.nodes[0], self.nodes[0].listunspent()[0], self.pubkey[n], False, Decimal("49.999")))
p2sh_ids[n][v].append(send_to_witness(v, self.nodes[0], self.nodes[0].listunspent()[0], self.pubkey[n], True, Decimal("49.999")))

self.nodes[0].generate(1) #block 161
sync_blocks(self.nodes)

# Make sure all nodes recognize the transactions as theirs
assert_equal(self.nodes[0].getbalance(), 60*50 - 60*50 + 20*Decimal("49.999") + 50)
assert_equal(self.nodes[1].getbalance(), 20*Decimal("49.999"))
assert_equal(self.nodes[2].getbalance(), 20*Decimal("49.999"))

self.nodes[0].generate(581) #block 742
sync_blocks(self.nodes)

print "Verify default node can't accept any witness format txs before fork"
# unsigned, no scriptsig
self.fail_accept(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], False)
self.fail_accept(self.nodes[0], wit_ids[NODE_0][WIT_V1][0], False)
self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], False)
self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], False)
# unsigned with redeem script
self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], False, addlength(witness_script(0, self.pubkey[0])))
self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], False, addlength(witness_script(1, self.pubkey[0])))
# signed
self.fail_accept(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], True)
self.fail_accept(self.nodes[0], wit_ids[NODE_0][WIT_V1][0], True)
self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], True)
self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], True)

print "Verify witness txs are skipped for mining before the fork"
self.skip_mine(self.nodes[2], wit_ids[NODE_2][WIT_V0][0], True) #block 743
self.skip_mine(self.nodes[2], wit_ids[NODE_2][WIT_V1][0], True) #block 744
self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][0], True) #block 745
self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][0], True) #block 746

# TODO: An old node would see these txs without witnesses and be able to mine them

print "Verify unsigned bare witness txs in version 5 blocks are valid before the fork"
self.success_mine(self.nodes[2], wit_ids[NODE_2][WIT_V0][1], False) #block 747
self.success_mine(self.nodes[2], wit_ids[NODE_2][WIT_V1][1], False) #block 748

print "Verify unsigned p2sh witness txs without a redeem script are invalid"
self.fail_accept(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][1], False)
self.fail_accept(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][1], False)

print "Verify unsigned p2sh witness txs with a redeem script in version 5 blocks are valid before the fork"
self.success_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][1], False, addlength(witness_script(0, self.pubkey[2]))) #block 749
self.success_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][1], False, addlength(witness_script(1, self.pubkey[2]))) #block 750

print "Verify previous witness txs skipped for mining can now be mined"
assert_equal(len(self.nodes[2].getrawmempool()), 4)
block = self.nodes[2].generate(1) #block 751
sync_blocks(self.nodes)
assert_equal(len(self.nodes[2].getrawmempool()), 0)
assert_equal(len(self.nodes[2].getblock(block[0])["tx"]), 5)

print "Verify witness txs without witness data in version 5 blocks are invalid after the fork"
self.fail_mine(self.nodes[2], wit_ids[NODE_2][WIT_V0][2], False)
self.fail_mine(self.nodes[2], wit_ids[NODE_2][WIT_V1][2], False)
self.fail_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][2], False, addlength(witness_script(0, self.pubkey[2])))
self.fail_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][2], False, addlength(witness_script(1, self.pubkey[2])))


print "Verify that a version 4 block can still mine those unsigned txs"
assert_equal(len(self.nodes[2].getrawmempool()), 4)
sync_mempools(self.nodes[1:3])
block = self.nodes[1].generate(1) #block 752
sync_blocks(self.nodes)
assert_equal(len(self.nodes[2].getrawmempool()), 0)
assert_equal(len(self.nodes[1].getblock(block[0])["tx"]), 5)

print "Verify all types of witness txs can be submitted signed after the fork to node with -prematurewitness"
self.success_mine(self.nodes[2], wit_ids[NODE_2][WIT_V0][3], True) #block 753
self.success_mine(self.nodes[2], wit_ids[NODE_2][WIT_V1][3], True) #block 754
self.success_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][3], True) #block 755
self.success_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][3], True) #block 756

print "Verify default node can't accept any witness format txs between enforce and reject points of fork"
# unsigned, no scriptsig
self.fail_accept(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], False)
self.fail_accept(self.nodes[0], wit_ids[NODE_0][WIT_V1][0], False)
self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], False)
self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], False)
# unsigned with redeem script
self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], False, addlength(witness_script(0, self.pubkey[0])))
self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], False, addlength(witness_script(1, self.pubkey[0])))
# signed
self.fail_accept(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], True)
self.fail_accept(self.nodes[0], wit_ids[NODE_0][WIT_V1][0], True)
self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], True)
self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], True)

# TODO: verify witness txs are invalid if in a v4 block
print "Verify witness txs aren't mined in a v4 block"
self.skip_mine(self.nodes[1], wit_ids[NODE_1][WIT_V0][0], True) #block 757
self.skip_mine(self.nodes[1], wit_ids[NODE_1][WIT_V1][0], True) #block 758
self.skip_mine(self.nodes[1], p2sh_ids[NODE_1][WIT_V0][0], True) #block 759
self.skip_mine(self.nodes[1], p2sh_ids[NODE_1][WIT_V1][0], True) #block 760

# Mine them from ver 5 node
sync_mempools(self.nodes[1:3])
assert_equal(len(self.nodes[2].getrawmempool()), 4)
block = self.nodes[2].generate(1) #block 761
sync_blocks(self.nodes)
assert_equal(len(self.nodes[2].getrawmempool()), 0)
assert_equal(len(self.nodes[2].getblock(block[0])["tx"]), 5)

self.nodes[0].generate(195) #block 956 (5 of which are v4 blocks)
sync_blocks(self.nodes)

print "Verify that version 4 blocks are invalid period after reject point"
try:
self.nodes[1].generate(1)
except JSONRPCException as exp:
assert(exp.error["code"] == -1)
else:
raise AssertionError("Created valid block when TestBlockValidity should have failed")

print "Verify default node can now use witness txs"
self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], True) #block 957
self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V1][0], True) #block 958
self.success_mine(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], True) #block 959
self.success_mine(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], True) #block 960

if __name__ == '__main__':
SegWitTest().main()
31 changes: 24 additions & 7 deletions src/bitcoin-tx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,18 @@ vector<unsigned char> ParseHexUO(map<string,UniValue>& o, string strKey)
return ParseHexUV(o[strKey], strKey);
}

static CAmount AmountFromValue(const UniValue& value)
{
if (!value.isNum() && !value.isStr())
throw runtime_error("Amount is not a number or string");
CAmount amount;
if (!ParseFixedPoint(value.getValStr(), 8, &amount))
throw runtime_error("Invalid amount");
if (!MoneyRange(amount))
throw runtime_error("Amount out of range");
return amount;
}

static void MutateTxSign(CMutableTransaction& tx, const string& flagStr)
{
int nHashType = SIGHASH_ALL;
Expand Down Expand Up @@ -425,7 +437,10 @@ static void MutateTxSign(CMutableTransaction& tx, const string& flagStr)
if ((unsigned int)nOut >= coins->vout.size())
coins->vout.resize(nOut+1);
coins->vout[nOut].scriptPubKey = scriptPubKey;
coins->vout[nOut].nValue = 0; // we don't know the actual output value
coins->vout[nOut].nValue = 0;
if (prevOut.exists("amount")) {
coins->vout[nOut].nValue = AmountFromValue(prevOut["amount"]);
}
}

// if redeemScript given and private keys given,
Expand Down Expand Up @@ -453,17 +468,19 @@ static void MutateTxSign(CMutableTransaction& tx, const string& flagStr)
continue;
}
const CScript& prevPubKey = coins->vout[txin.prevout.n].scriptPubKey;
const CAmount& amount = coins->vout[txin.prevout.n].nValue;

txin.scriptSig.clear();
SignatureData sigdata;
// Only sign SIGHASH_SINGLE if there's a corresponding output:
if (!fHashSingle || (i < mergedTx.vout.size()))
SignSignature(keystore, prevPubKey, mergedTx, i, nHashType);
ProduceSignature(MutableTransactionSignatureCreator(&keystore, &mergedTx, i, amount, nHashType), prevPubKey, sigdata);

// ... and merge in other signatures:
BOOST_FOREACH(const CTransaction& txv, txVariants) {
txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, txv.vin[i].scriptSig);
}
if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&mergedTx, i)))
BOOST_FOREACH(const CTransaction& txv, txVariants)
sigdata = CombineSignatures(prevPubKey, MutableTransactionSignatureChecker(&mergedTx, i, amount), sigdata, DataFromTransaction(txv, i));
UpdateTransaction(mergedTx, i, sigdata);

if (!VerifyScript(txin.scriptSig, prevPubKey, mergedTx.wit.vtxinwit.size() > i ? &mergedTx.wit.vtxinwit[i].scriptWitness : NULL, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&mergedTx, i, amount)))
fComplete = false;
}

Expand Down
Loading