From 580f466a684679a4b7803aef248b12b7d5883610 Mon Sep 17 00:00:00 2001 From: Bob McElrath Date: Fri, 8 Jul 2016 14:11:59 -0400 Subject: [PATCH 1/7] Initial segwit implementation, works with P2WPKH addresses --- .gitignore | 3 +- bitcoin/core/__init__.py | 112 ++++++++++++++++++++++++++++++++------ bitcoin/core/script.py | 35 ++++++++++++ bitcoin/core/serialize.py | 9 +++ 4 files changed, 141 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 97fa5c7..d8994c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ - +*.sw? *.pyc local*.cfg @@ -8,3 +8,4 @@ local*.cfg build/ htmlcov/ python_bitcoinlib.egg-info/ +dist/ diff --git a/bitcoin/core/__init__.py b/bitcoin/core/__init__.py index a3d5580..a55ac73 100644 --- a/bitcoin/core/__init__.py +++ b/bitcoin/core/__init__.py @@ -16,7 +16,7 @@ import sys import time -from .script import CScript +from .script import CScript, CScriptWitness from .serialize import * @@ -304,11 +304,44 @@ def from_txout(cls, txout): """Create a fullly mutable copy of an existing TxOut""" return cls(txout.nValue, txout.scriptPubKey) + +class CTxInWitness(ImmutableSerializable): + """Witness data for a transaction input. """ + __slots__ = ['scriptWitness'] + + def __init__(self, scriptWitness=CScriptWitness()): + object.__setattr__(self, 'scriptWitness', scriptWitness) + + @classmethod + def stream_deserialize(cls, f): + scriptWitness = CScriptWitness.stream_deserialize(f) + return cls(scriptWitness) + + def stream_serialize(self, f): + self.scriptWitness.stream_serialize(f) + + def __repr__(self): + return "CTxInWitness(%s)" % (repr(self.scriptWitness)) + + @classmethod + def from_txinwitness(cls, txinwitness): + """Create an immutable copy of an existing TxInWitness + + If txin is already immutable (txin.__class__ is CTxIn) it is returned + directly. + """ + if txinwitness.__class__ is CTxInWitness: + return txinwitness + + else: + return cls(txinwitness.scriptWitness) + + class CTransaction(ImmutableSerializable): """A transaction""" - __slots__ = ['nVersion', 'vin', 'vout', 'nLockTime'] + __slots__ = ['nVersion', 'vin', 'vout', 'nLockTime', 'wit'] - def __init__(self, vin=(), vout=(), nLockTime=0, nVersion=1): + def __init__(self, vin=(), vout=(), nLockTime=0, nVersion=1, witness=()): """Create a new transaction vin and vout are iterables of transaction inputs and outputs @@ -322,26 +355,55 @@ def __init__(self, vin=(), vout=(), nLockTime=0, nVersion=1): object.__setattr__(self, 'nVersion', nVersion) object.__setattr__(self, 'vin', tuple(CTxIn.from_txin(txin) for txin in vin)) object.__setattr__(self, 'vout', tuple(CTxOut.from_txout(txout) for txout in vout)) + object.__setattr__(self, 'wit', + tuple(CTxInWitness.from_txinwitness(witness) for txinwitness in + witness)) @classmethod def stream_deserialize(cls, f): nVersion = struct.unpack(b" Date: Thu, 14 Jul 2016 13:58:28 -0400 Subject: [PATCH 2/7] BIP143 implementation and test vectors --- bitcoin/core/__init__.py | 2 +- bitcoin/core/script.py | 75 +++++++++++++++++++++++- bitcoin/core/serialize.py | 2 + bitcoin/tests/test_segwit.py | 110 +++++++++++++++++++++++++++++++++++ bitcoin/wallet.py | 8 ++- 5 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 bitcoin/tests/test_segwit.py diff --git a/bitcoin/core/__init__.py b/bitcoin/core/__init__.py index a55ac73..c5c47c7 100644 --- a/bitcoin/core/__init__.py +++ b/bitcoin/core/__init__.py @@ -439,7 +439,7 @@ class CMutableTransaction(CTransaction): """A mutable transaction""" __slots__ = [] - def __init__(self, vin=None, vout=None, nLockTime=0, nVersion=1, witness=CScriptWitness([])): + def __init__(self, vin=None, vout=None, nLockTime=0, nVersion=1, witness=None): if not (0 <= nLockTime <= 0xffffffff): raise ValueError('CTransaction: nLockTime must be in range 0x0 to 0xffffffff; got %x' % nLockTime) self.nLockTime = nLockTime diff --git a/bitcoin/core/script.py b/bitcoin/core/script.py index cec498d..baf0d40 100644 --- a/bitcoin/core/script.py +++ b/bitcoin/core/script.py @@ -24,6 +24,9 @@ long = int _bchr = lambda x: bytes([x]) _bord = lambda x: x + from io import BytesIO as _BytesIO +else: + from cStringIO import StringIO as _BytesIO import struct @@ -674,6 +677,24 @@ def is_p2sh(self): _bord(self[1]) == 0x14 and _bord(self[22]) == OP_EQUAL) + def is_witness_scriptpubkey(self): + return 3 <= len(self) <= 42 and CScriptOp(self[0]).is_small_int() + + def witness_version(self): + return next(iter(self)) + + def is_witness_v0_keyhash(self): + return len(self) == 22 and self[0:2] == b'\x00\x14' + + def is_witness_v0_nested_keyhash(self): + return len(self) == 23 and self[0:3] == b'\x16\x00\x14' + + def is_witness_v0_scripthash(self): + return len(self) == 34 and self[0:2] == b'\x00\x20' + + def is_witness_v0_nested_scripthash(self): + return len(self) == 23 and self[0:2] == b'\xa9\x14' and self[-1] == b'\x87' + def is_push_only(self): """Test if the script only contains pushdata ops @@ -935,14 +956,63 @@ def RawSignatureHash(script, txTo, inIdx, hashtype): return (hash, None) +SIGVERSION_BASE = 0 +SIGVERSION_WITNESS_V0 = 1 -def SignatureHash(script, txTo, inIdx, hashtype): +def SignatureHash(script, txTo, inIdx, hashtype, amount=None, sigversion=SIGVERSION_BASE): """Calculate a signature hash 'Cooked' version that checks if inIdx is out of bounds - this is *not* consensus-correct behavior, but is what you probably want for general wallet use. """ + + if sigversion == SIGVERSION_WITNESS_V0: + hashPrevouts = b'\x00'*32 + hashSequence = b'\x00'*32 + hashOutputs = b'\x00'*32 + + if not (hashtype & SIGHASH_ANYONECANPAY): + serialize_prevouts = bytes() + for i in txTo.vin: + serialize_prevouts += i.prevout.serialize() + hashPrevouts = bitcoin.core.Hash(serialize_prevouts) + + if (not (hashtype & SIGHASH_ANYONECANPAY) and (hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE): + serialize_sequence = bytes() + for i in txTo.vin: + serialize_sequence += struct.pack(" Date: Tue, 19 Jul 2016 19:31:59 -0400 Subject: [PATCH 3/7] CTxWitness class, new message types --- bitcoin/core/__init__.py | 70 +++++++++++++++++++++++++++++++--------- bitcoin/core/script.py | 18 ++++++++--- bitcoin/messages.py | 13 ++++++++ bitcoin/net.py | 3 +- 4 files changed, 83 insertions(+), 21 deletions(-) diff --git a/bitcoin/core/__init__.py b/bitcoin/core/__init__.py index c5c47c7..d4d428c 100644 --- a/bitcoin/core/__init__.py +++ b/bitcoin/core/__init__.py @@ -306,12 +306,15 @@ def from_txout(cls, txout): class CTxInWitness(ImmutableSerializable): - """Witness data for a transaction input. """ + """Witness data for a single transaction input. """ __slots__ = ['scriptWitness'] def __init__(self, scriptWitness=CScriptWitness()): object.__setattr__(self, 'scriptWitness', scriptWitness) + def is_null(self): + return self.scriptWitness.is_null() + @classmethod def stream_deserialize(cls, f): scriptWitness = CScriptWitness.stream_deserialize(f) @@ -336,12 +339,50 @@ def from_txinwitness(cls, txinwitness): else: return cls(txinwitness.scriptWitness) +class CTxWitness(ImmutableSerializable): + """Witness data for all inputs to a transaction.""" + __slots__ = ['vtxinwit'] + + def __init__(self, vtxinwit=()): + object.__setattr__(self, 'vtxinwit', vtxinwit) + + def is_null(self): + for n in range(len(self.vtxinwit)): + if not self.vtxinwit[n].is_null(): return False + return True + + # FIXME this cannot be a @classmethod like the others because we need to + # know how many items to deserialize, which comes from len(vin) + def stream_deserialize(self, f): + vtxinwit = tuple(CTxInWitness.stream_deserialize(f) for dummy in + range(len(self.vtxinwit))) + return CTxWitness(vtxinwit) + + def stream_serialize(self, f): + for i in range(len(self.vtxinwit)): + self.vtxinwit[i].stream_serialize(f) + + def __repr__(self): + return "CTxWitness(%s)" % (','.join(repr(w) for w in self.vtxinwit)) + + @classmethod + def from_txwitness(cls, txwitness): + """Create an immutable copy of an existing TxWitness + + If txwitness is already immutable (txwitness.__class__ is CTxWitness) it is returned + directly. + """ + if txwitness.__class__ is CTxWitness: + return txwitness + else: + return cls(txwitness.vtxinwit) + class CTransaction(ImmutableSerializable): """A transaction""" __slots__ = ['nVersion', 'vin', 'vout', 'nLockTime', 'wit'] - def __init__(self, vin=(), vout=(), nLockTime=0, nVersion=1, witness=()): + def __init__(self, vin=(), vout=(), nLockTime=0, nVersion=1, witness=CTxWitness()): """Create a new transaction vin and vout are iterables of transaction inputs and outputs @@ -351,13 +392,10 @@ def __init__(self, vin=(), vout=(), nLockTime=0, nVersion=1, witness=()): if not (0 <= nLockTime <= 0xffffffff): raise ValueError('CTransaction: nLockTime must be in range 0x0 to 0xffffffff; got %x' % nLockTime) object.__setattr__(self, 'nLockTime', nLockTime) - object.__setattr__(self, 'nVersion', nVersion) object.__setattr__(self, 'vin', tuple(CTxIn.from_txin(txin) for txin in vin)) object.__setattr__(self, 'vout', tuple(CTxOut.from_txout(txout) for txout in vout)) - object.__setattr__(self, 'wit', - tuple(CTxInWitness.from_txinwitness(witness) for txinwitness in - witness)) + object.__setattr__(self, 'wit', CTxWitness.from_txwitness(witness)) @classmethod def stream_deserialize(cls, f): @@ -370,7 +408,8 @@ def stream_deserialize(cls, f): raise DeserializationFormatError vin = VectorSerializer.stream_deserialize(CTxIn, f) vout = VectorSerializer.stream_deserialize(CTxOut, f) - wit = VectorSerializer.stream_deserialize(CTxInWitness, f) + wit = CTxWitness(tuple(0 for dummy in range(len(vin)))) + wit = wit.stream_deserialize(f) nLockTime = struct.unpack(b"> 2 + MSG_TX = 1 MSG_BLOCK = 2 MSG_FILTERED_BLOCK = 3 +MSG_CMPCT_BLOCK = 4 +MSG_WITNESS_BLOCK = MSG_BLOCK | MSG_WITNESS_FLAG, +MSG_WITNESS_TX = MSG_TX | MSG_WITNESS_FLAG, +MSG_FILTERED_WITNESS_BLOCK = MSG_FILTERED_BLOCK | MSG_WITNESS_FLAG, class MsgSerializable(Serializable): @@ -499,6 +506,12 @@ def __repr__(self): 'MSG_TX', 'MSG_BLOCK', 'MSG_FILTERED_BLOCK', + 'MSG_CMPCT_BLOCK', + 'MSG_TYPE_MASK', + 'MSG_WITNESS_TX', + 'MSG_WITNESS_BLOCK', + 'MSG_WITNESS_FLAG', + 'MSG_FILTERED_WITNESS_BLOCK', 'MsgSerializable', 'msg_version', 'msg_verack', diff --git a/bitcoin/net.py b/bitcoin/net.py index aa49f5c..c0ddef9 100644 --- a/bitcoin/net.py +++ b/bitcoin/net.py @@ -76,7 +76,8 @@ class CInv(Serializable): 0: "Error", 1: "TX", 2: "Block", - 3: "FilteredBlock"} + 3: "FilteredBlock", + 4: "CompactBlock"} def __init__(self): self.type = 0 From 0c09892b002afdb8cd918d47ad31bc93f2400c8c Mon Sep 17 00:00:00 2001 From: Bob McElrath Date: Wed, 20 Jul 2016 16:25:08 -0400 Subject: [PATCH 4/7] Encode hashtype as signed (following bitcoind) --- bitcoin/core/script.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitcoin/core/script.py b/bitcoin/core/script.py index f98b484..b5ffdfa 100644 --- a/bitcoin/core/script.py +++ b/bitcoin/core/script.py @@ -958,7 +958,7 @@ def RawSignatureHash(script, txTo, inIdx, hashtype): txtmp.wit = bitcoin.core.CTxWitness() s = txtmp.serialize() - s += struct.pack(b" Date: Wed, 20 Jul 2016 18:30:29 -0400 Subject: [PATCH 5/7] Closely match Core's (de-)serialization; remove old test case --- bitcoin/core/__init__.py | 23 +++++++++++++---------- bitcoin/core/serialize.py | 11 ----------- bitcoin/tests/data/tx_invalid.json | 5 ----- 3 files changed, 13 insertions(+), 26 deletions(-) diff --git a/bitcoin/core/__init__.py b/bitcoin/core/__init__.py index d4d428c..5360cb3 100644 --- a/bitcoin/core/__init__.py +++ b/bitcoin/core/__init__.py @@ -399,13 +399,19 @@ def __init__(self, vin=(), vout=(), nLockTime=0, nVersion=1, witness=CTxWitness( @classmethod def stream_deserialize(cls, f): + """Deserialize a transaction. This implementation corresponds to + Bitcoin's SerializeTransaction() and consensus behavior. Note that + Bitcoin's DecodeHexTx() also has the option to attempt deserializing + as a non-witness transaction first, falling back to the consensus + behavior if it fails. The difference lies in transactions which + have zero inputs: they are invalid but may be (de-)serialized anyway + for the purpose of signing them and adding inputs. If the behavior + of DecodeHexTx() is needed it could be added, but not here. """ nVersion = struct.unpack(b" Date: Thu, 2 Feb 2017 17:28:49 +0100 Subject: [PATCH 6/7] Add segwit block (de)serialization. (#9) Add witness merkle tree; Add parameters to stream_(de)serialize and (de)serialize methods; Check witness merkle tree. CheckBlock: - check MAX_BLOCK_WEIGHT; - check witness witnessScript; - check witness commitment. --- bitcoin/core/__init__.py | 123 +++++++++++++++++++++++++++++++---- bitcoin/core/serialize.py | 20 +++--- bitcoin/tests/test_segwit.py | 31 +++++++++ 3 files changed, 152 insertions(+), 22 deletions(-) diff --git a/bitcoin/core/__init__.py b/bitcoin/core/__init__.py index 5360cb3..2b1d0dc 100644 --- a/bitcoin/core/__init__.py +++ b/bitcoin/core/__init__.py @@ -16,14 +16,21 @@ import sys import time -from .script import CScript, CScriptWitness +from .script import CScript, CScriptWitness, CScriptOp, OP_RETURN from .serialize import * +if sys.version > '3': + _bytes = bytes +else: + _bytes = lambda x: bytes(bytearray(x)) + # Core definitions COIN = 100000000 MAX_BLOCK_SIZE = 1000000 +MAX_BLOCK_WEIGHT = 4000000 MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50 +WITNESS_COINBASE_SCRIPTPUBKEY_MAGIC = _bytes([OP_RETURN, 0x24, 0xaa, 0x21, 0xa9, 0xed]) def MoneyRange(nValue, params=None): global coreparams @@ -426,9 +433,9 @@ def stream_deserialize(cls, f): return cls(vin, vout, nLockTime, nVersion) - def stream_serialize(self, f): + def stream_serialize(self, f, include_witness=True): f.write(struct.pack(b"= 38 and script[:6] == WITNESS_COINBASE_SCRIPTPUBKEY_MAGIC: + commit_pos = index + if commit_pos is None: + raise ValueError('The witness commitment is missed') + return commit_pos + def __init__(self, nVersion=2, hashPrevBlock=b'\x00'*32, hashMerkleRoot=b'\x00'*32, nTime=0, nBits=0, nNonce=0, vtx=()): """Create a new block""" + if vtx: + vMerkleTree = tuple(CBlock.build_merkle_tree_from_txs(vtx)) + if hashMerkleRoot == b'\x00'*32: + hashMerkleRoot = vMerkleTree[-1] + elif hashMerkleRoot != vMerkleTree[-1]: + raise CheckBlockError("CBlock : hashMerkleRoot is not compatible with vtx") + else: + vMerkleTree = () super(CBlock, self).__init__(nVersion, hashPrevBlock, hashMerkleRoot, nTime, nBits, nNonce) - vMerkleTree = tuple(CBlock.build_merkle_tree_from_txs(vtx)) object.__setattr__(self, 'vMerkleTree', vMerkleTree) + try: + vWitnessMerkleTree = tuple(CBlock.build_witness_merkle_tree_from_txs(vtx)) + except NoWitnessData: + vWitnessMerkleTree = () + object.__setattr__(self, 'vWitnessMerkleTree', vWitnessMerkleTree) object.__setattr__(self, 'vtx', tuple(CTransaction.from_tx(tx) for tx in vtx)) @classmethod @@ -627,13 +689,18 @@ def stream_deserialize(cls, f): vtx = VectorSerializer.stream_deserialize(CTransaction, f) vMerkleTree = tuple(CBlock.build_merkle_tree_from_txs(vtx)) object.__setattr__(self, 'vMerkleTree', vMerkleTree) + try: + vWitnessMerkleTree = tuple(CBlock.build_witness_merkle_tree_from_txs(vtx)) + except NoWitnessData: + vWitnessMerkleTree = () + object.__setattr__(self, 'vWitnessMerkleTree', vWitnessMerkleTree) object.__setattr__(self, 'vtx', tuple(vtx)) return self - def stream_serialize(self, f): + def stream_serialize(self, f, include_witness=True): super(CBlock, self).stream_serialize(f) - VectorSerializer.stream_serialize(CTransaction, self.vtx, f) + VectorSerializer.stream_serialize(CTransaction, self.vtx, f, dict(include_witness=include_witness)) def get_header(self): """Return the block header @@ -660,6 +727,10 @@ def GetHash(self): object.__setattr__(self, '_cached_GetHash', _cached_GetHash) return _cached_GetHash + def GetWeight(self): + """Return the block weight: (stripped_size * 3) + total_size""" + return len(self.serialize(dict(include_witness=False))) * 3 + len(self.serialize()) + class CoreChainParams(object): """Define consensus-critical parameters of a given instance of the Bitcoin system""" MAX_MONEY = None @@ -721,7 +792,8 @@ def CheckTransaction(tx): raise CheckTransactionError("CheckTransaction() : vout empty") # Size limits - if len(tx.serialize()) > MAX_BLOCK_SIZE: + base_tx = CTransaction(tx.vin, tx.vout, tx.nLockTime, tx.nVersion) + if len(base_tx.serialize()) > MAX_BLOCK_SIZE: raise CheckTransactionError("CheckTransaction() : size limits failed") # Check for negative or overflow output values @@ -820,7 +892,8 @@ def CheckBlock(block, fCheckPoW = True, fCheckMerkleRoot = True, cur_time=None): fCheckPoW - Check proof-of-work. - fCheckMerkleRoot - Check merkle root matches transactions. + fCheckMerkleRoot - Check merkle root and witness merkle root matches transactions. + - Check witness commitment in coinbase cur_time - Current time. Defaults to time.time() """ @@ -831,9 +904,12 @@ def CheckBlock(block, fCheckPoW = True, fCheckMerkleRoot = True, cur_time=None): # Size limits if not block.vtx: raise CheckBlockError("CheckBlock() : vtx empty") - if len(block.serialize()) > MAX_BLOCK_SIZE: + if len(block.serialize(dict(include_witness=False))) > MAX_BLOCK_SIZE: raise CheckBlockError("CheckBlock() : block larger than MAX_BLOCK_SIZE") + if block.GetWeight() > MAX_BLOCK_WEIGHT: + raise CheckBlockError("CheckBlock() : block larger than MAX_BLOCK_WEIGHT") + # First transaction must be coinbase if not block.vtx[0].is_coinbase(): raise CheckBlockError("CheckBlock() : first tx is not coinbase") @@ -861,8 +937,29 @@ def CheckBlock(block, fCheckPoW = True, fCheckMerkleRoot = True, cur_time=None): raise CheckBlockError("CheckBlock() : out-of-bounds SigOpCount") # Check merkle root - if fCheckMerkleRoot and block.hashMerkleRoot != block.calc_merkle_root(): - raise CheckBlockError("CheckBlock() : hashMerkleRoot mismatch") + if fCheckMerkleRoot: + if block.hashMerkleRoot != block.calc_merkle_root(): + raise CheckBlockError("CheckBlock() : hashMerkleRoot mismatch") + if len(block.vWitnessMerkleTree): + # At least 1 tx has witness: check witness merkle tree + root = block.vWitnessMerkleTree[-1] + # vtx[0]: coinbase + # vtxinwit[0]: first input + nonce_script = block.vtx[0].wit.vtxinwit[0].scriptWitness + nonce = nonce_script.stack[0] + if len(nonce_script.stack) != 1 or len(nonce) != 32: + raise CheckBlockError("CheckBlock() : invalid coinbase witnessScript") + try: + index = block.get_witness_commitment_index() + except ValueError as e: + raise CheckBlockError("CheckBlock() : " + str(e)) + commit_script = block.vtx[0].vout[index].scriptPubKey + if not (6 + 32 <= len(commit_script) <= 6 + 32 + 1): + raise CheckBlockError("CheckBlock() : invalid segwit commitment length") + commitment = commit_script[6:6 + 32] + commit = commit_script[6:6 + 32] + if commit != Hash(root + nonce): + raise CheckBlockError("CheckBlock() : invalid segwit commitment") __all__ = ( 'Hash', @@ -885,6 +982,8 @@ def CheckBlock(block, fCheckPoW = True, fCheckMerkleRoot = True, cur_time=None): 'CMutableTxOut', 'CTransaction', 'CMutableTransaction', + 'CTxWitness', + 'CTxInWitness', 'CBlockHeader', 'CBlock', 'CoreChainParams', diff --git a/bitcoin/core/serialize.py b/bitcoin/core/serialize.py index 425d7ba..78f3315 100644 --- a/bitcoin/core/serialize.py +++ b/bitcoin/core/serialize.py @@ -87,23 +87,23 @@ class Serializable(object): __slots__ = [] - def stream_serialize(self, f): + def stream_serialize(self, f, **kwargs): """Serialize to a stream""" raise NotImplementedError @classmethod - def stream_deserialize(cls, f): + def stream_deserialize(cls, f, **kwargs): """Deserialize from a stream""" raise NotImplementedError - def serialize(self): + def serialize(self, params={}): """Serialize, returning bytes""" f = _BytesIO() - self.stream_serialize(f) + self.stream_serialize(f, **params) return f.getvalue() @classmethod - def deserialize(cls, buf, allow_padding=False): + def deserialize(cls, buf, allow_padding=False, params={}): """Deserialize bytes, returning an instance allow_padding - Allow buf to include extra padding. (default False) @@ -112,7 +112,7 @@ def deserialize(cls, buf, allow_padding=False): deserialization DeserializationExtraDataError will be raised. """ fd = _BytesIO(buf) - r = cls.stream_deserialize(fd) + r = cls.stream_deserialize(fd, **params) if not allow_padding: padding = fd.read() if len(padding) != 0: @@ -242,17 +242,17 @@ class VectorSerializer(Serializer): # and should be rethought at some point. @classmethod - def stream_serialize(cls, inner_cls, objs, f): + def stream_serialize(cls, inner_cls, objs, f, inner_params={}): VarIntSerializer.stream_serialize(len(objs), f) for obj in objs: - inner_cls.stream_serialize(obj, f) + inner_cls.stream_serialize(obj, f, **inner_params) @classmethod - def stream_deserialize(cls, inner_cls, f): + def stream_deserialize(cls, inner_cls, f, inner_params={}): n = VarIntSerializer.stream_deserialize(f) r = [] for i in range(n): - r.append(inner_cls.stream_deserialize(f)) + r.append(inner_cls.stream_deserialize(f, **inner_params)) return r diff --git a/bitcoin/tests/test_segwit.py b/bitcoin/tests/test_segwit.py index f10e2ae..f00b01d 100644 --- a/bitcoin/tests/test_segwit.py +++ b/bitcoin/tests/test_segwit.py @@ -12,11 +12,19 @@ from __future__ import absolute_import, division, print_function, unicode_literals import unittest +import random +import sys +import bitcoin from bitcoin.core import * from bitcoin.core.script import * from bitcoin.wallet import * +if sys.version > '3': + _bytes = bytes +else: + _bytes = lambda x: bytes(bytearray(x)) + # Test serialization @@ -107,4 +115,27 @@ def test_p2sh_p2wsh_signaturehash(self): 0, SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, value, SIGVERSION_WITNESS_V0), x('511e8e52ed574121fc1b654970395502128263f62662e076dc6baf05c2e6a99b')) + def test_checkblock(self): + # (No witness) coinbase generated by Bitcoin Core + str_coinbase = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03520101ffffffff0100f2052a01000000232102960c90bc04a631cb17922e4f5d80ac75fd590a88b8baaa5a3d5086ac85e4d788ac00000000' + # No witness transaction + str_tx = '0100000001e3d0c1d051a3abe79d5951dcab86f71d8926aff5caed5ff9bd72cb593e4ebaf5010000006b483045022100a28e1c57e160296953e1af85c5034bb1b907c12c50367da75ba547874a8a8c52022040682e888ddce7fd5c72519a9f28f22f5164c8af864d33332bbc7f65596ad4ae012102db30394fd5cc8288bed607b9382338240c014a98c9c0febbfb380db74ceb30a3ffffffff020d920000000000001976a914ad781c8ffcc18b2155433cba2da4601180a66eef88aca3170000000000001976a914bacb1c4b0725e61e385c7093b4260533953c8e1688ac00000000' + # SegWit transaction + str_w_tx = '0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560100000000ffffffff0100b4f505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02483045022100df7b7e5cda14ddf91290e02ea10786e03eb11ee36ec02dd862fe9a326bbcb7fd02203f5b4496b667e6e281cc654a2da9e4f08660c620a1051337fa8965f727eb19190121038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990ac00000000' + witness_nonce = _bytes(random.getrandbits(8) for _ in range(32)) + + coinbase = CMutableTransaction.deserialize(x(str_coinbase)) + coinbase.wit = CTxWitness([CTxInWitness(CScriptWitness([witness_nonce]))]) + + tx_legacy = CTransaction.deserialize(x(str_tx)) + tx_segwit = CTransaction.deserialize(x(str_w_tx)) + + witness_merkle_root = CBlock.build_witness_merkle_tree_from_txs((coinbase, tx_legacy, tx_segwit))[-1] + + commitment = Hash(witness_merkle_root + witness_nonce) + commitment_script = bitcoin.core.WITNESS_COINBASE_SCRIPTPUBKEY_MAGIC + commitment + coinbase.vout.append(CTxOut(0, CScript(commitment_script))) + + block = CBlock(2, b'\x00'*32, b'\x00'*32, 0, 0, 0, (coinbase, tx_legacy, tx_segwit)) + CheckBlock(block, False, True) From 5cd34672d7085ca4d55693a1008101c02ceeb7e9 Mon Sep 17 00:00:00 2001 From: Surya Bakshi Date: Sun, 1 Jan 2017 18:34:21 -0600 Subject: [PATCH 7/7] fixed issue in is_witness_scriptpubkey where self[0] is interpretted as str instead of the necessary int --- bitcoin/core/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/core/script.py b/bitcoin/core/script.py index b5ffdfa..baca69b 100644 --- a/bitcoin/core/script.py +++ b/bitcoin/core/script.py @@ -680,7 +680,7 @@ def is_p2sh(self): def is_witness_scriptpubkey(self): """Returns true if this is a scriptpubkey signaling segregated witness data. """ - return 3 <= len(self) <= 42 and CScriptOp(self[0]).is_small_int() + return 3 <= len(self) <= 42 and CScriptOp(struct.unpack('