Skip to content

Commit 2b50596

Browse files
the9ullmcelrath
authored andcommitted
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.
1 parent d8a1a0f commit 2b50596

File tree

3 files changed

+152
-22
lines changed

3 files changed

+152
-22
lines changed

bitcoin/core/__init__.py

Lines changed: 111 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@
1616
import sys
1717
import time
1818

19-
from .script import CScript, CScriptWitness
19+
from .script import CScript, CScriptWitness, CScriptOp, OP_RETURN
2020

2121
from .serialize import *
2222

23+
if sys.version > '3':
24+
_bytes = bytes
25+
else:
26+
_bytes = lambda x: bytes(bytearray(x))
27+
2328
# Core definitions
2429
COIN = 100000000
2530
MAX_BLOCK_SIZE = 1000000
31+
MAX_BLOCK_WEIGHT = 4000000
2632
MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50
33+
WITNESS_COINBASE_SCRIPTPUBKEY_MAGIC = _bytes([OP_RETURN, 0x24, 0xaa, 0x21, 0xa9, 0xed])
2734

2835
def MoneyRange(nValue, params=None):
2936
global coreparams
@@ -426,9 +433,9 @@ def stream_deserialize(cls, f):
426433
return cls(vin, vout, nLockTime, nVersion)
427434

428435

429-
def stream_serialize(self, f):
436+
def stream_serialize(self, f, include_witness=True):
430437
f.write(struct.pack(b"<i", self.nVersion))
431-
if not self.wit.is_null():
438+
if include_witness and not self.wit.is_null():
432439
assert(len(self.wit.vtxinwit) <= len(self.vin))
433440
f.write(b'\x00') # Marker
434441
f.write(b'\x01') # Flag
@@ -443,6 +450,10 @@ def stream_serialize(self, f):
443450
def is_coinbase(self):
444451
return len(self.vin) == 1 and self.vin[0].prevout.is_null()
445452

453+
def has_witness(self):
454+
"""True if witness"""
455+
return not self.wit.is_null()
456+
446457
def __repr__(self):
447458
return "CTransaction(%r, %r, %i, %i, %r)" % (self.vin, self.vout,
448459
self.nLockTime, self.nVersion, self.wit)
@@ -561,9 +572,12 @@ def __repr__(self):
561572
(self.__class__.__name__, self.nVersion, b2lx(self.hashPrevBlock), b2lx(self.hashMerkleRoot),
562573
self.nTime, self.nBits, self.nNonce)
563574

575+
class NoWitnessData(Exception):
576+
"""The block does not have witness data"""
577+
564578
class CBlock(CBlockHeader):
565579
"""A block including all transactions in it"""
566-
__slots__ = ['vtx', 'vMerkleTree']
580+
__slots__ = ['vtx', 'vMerkleTree', 'vWitnessMerkleTree']
567581

568582
@staticmethod
569583
def build_merkle_tree_from_txids(txids):
@@ -612,12 +626,60 @@ def calc_merkle_root(self):
612626
raise ValueError('Block contains no transactions')
613627
return self.build_merkle_tree_from_txs(self.vtx)[-1]
614628

629+
@staticmethod
630+
def build_witness_merkle_tree_from_txs(txs):
631+
"""Calculate the witness merkle tree from transactions"""
632+
has_witness = False
633+
hashes = []
634+
for tx in txs:
635+
hashes.append(tx.GetHash())
636+
has_witness |= tx.has_witness()
637+
if not has_witness:
638+
raise NoWitnessData
639+
hashes[0] = b'\x00' * 32
640+
return CBlock.build_merkle_tree_from_txids(hashes)
641+
642+
def calc_witness_merkle_root(self):
643+
"""Calculate the witness merkle root
644+
645+
The calculated merkle root is not cached; every invocation
646+
re-calculates it from scratch.
647+
"""
648+
if not len(self.vtx):
649+
raise ValueError('Block contains no transactions')
650+
return self.build_witness_merkle_tree_from_txs(self.vtx)[-1]
651+
652+
def get_witness_commitment_index(self):
653+
"""Return None or an index"""
654+
if not len(self.vtx):
655+
raise ValueError('Block contains no transactions')
656+
commit_pos = None
657+
for index, out in enumerate(self.vtx[0].vout):
658+
script = out.scriptPubKey
659+
if len(script) >= 38 and script[:6] == WITNESS_COINBASE_SCRIPTPUBKEY_MAGIC:
660+
commit_pos = index
661+
if commit_pos is None:
662+
raise ValueError('The witness commitment is missed')
663+
return commit_pos
664+
615665
def __init__(self, nVersion=2, hashPrevBlock=b'\x00'*32, hashMerkleRoot=b'\x00'*32, nTime=0, nBits=0, nNonce=0, vtx=()):
616666
"""Create a new block"""
667+
if vtx:
668+
vMerkleTree = tuple(CBlock.build_merkle_tree_from_txs(vtx))
669+
if hashMerkleRoot == b'\x00'*32:
670+
hashMerkleRoot = vMerkleTree[-1]
671+
elif hashMerkleRoot != vMerkleTree[-1]:
672+
raise CheckBlockError("CBlock : hashMerkleRoot is not compatible with vtx")
673+
else:
674+
vMerkleTree = ()
617675
super(CBlock, self).__init__(nVersion, hashPrevBlock, hashMerkleRoot, nTime, nBits, nNonce)
618676

619-
vMerkleTree = tuple(CBlock.build_merkle_tree_from_txs(vtx))
620677
object.__setattr__(self, 'vMerkleTree', vMerkleTree)
678+
try:
679+
vWitnessMerkleTree = tuple(CBlock.build_witness_merkle_tree_from_txs(vtx))
680+
except NoWitnessData:
681+
vWitnessMerkleTree = ()
682+
object.__setattr__(self, 'vWitnessMerkleTree', vWitnessMerkleTree)
621683
object.__setattr__(self, 'vtx', tuple(CTransaction.from_tx(tx) for tx in vtx))
622684

623685
@classmethod
@@ -627,13 +689,18 @@ def stream_deserialize(cls, f):
627689
vtx = VectorSerializer.stream_deserialize(CTransaction, f)
628690
vMerkleTree = tuple(CBlock.build_merkle_tree_from_txs(vtx))
629691
object.__setattr__(self, 'vMerkleTree', vMerkleTree)
692+
try:
693+
vWitnessMerkleTree = tuple(CBlock.build_witness_merkle_tree_from_txs(vtx))
694+
except NoWitnessData:
695+
vWitnessMerkleTree = ()
696+
object.__setattr__(self, 'vWitnessMerkleTree', vWitnessMerkleTree)
630697
object.__setattr__(self, 'vtx', tuple(vtx))
631698

632699
return self
633700

634-
def stream_serialize(self, f):
701+
def stream_serialize(self, f, include_witness=True):
635702
super(CBlock, self).stream_serialize(f)
636-
VectorSerializer.stream_serialize(CTransaction, self.vtx, f)
703+
VectorSerializer.stream_serialize(CTransaction, self.vtx, f, dict(include_witness=include_witness))
637704

638705
def get_header(self):
639706
"""Return the block header
@@ -660,6 +727,10 @@ def GetHash(self):
660727
object.__setattr__(self, '_cached_GetHash', _cached_GetHash)
661728
return _cached_GetHash
662729

730+
def GetWeight(self):
731+
"""Return the block weight: (stripped_size * 3) + total_size"""
732+
return len(self.serialize(dict(include_witness=False))) * 3 + len(self.serialize())
733+
663734
class CoreChainParams(object):
664735
"""Define consensus-critical parameters of a given instance of the Bitcoin system"""
665736
MAX_MONEY = None
@@ -721,7 +792,8 @@ def CheckTransaction(tx):
721792
raise CheckTransactionError("CheckTransaction() : vout empty")
722793

723794
# Size limits
724-
if len(tx.serialize()) > MAX_BLOCK_SIZE:
795+
base_tx = CTransaction(tx.vin, tx.vout, tx.nLockTime, tx.nVersion)
796+
if len(base_tx.serialize()) > MAX_BLOCK_SIZE:
725797
raise CheckTransactionError("CheckTransaction() : size limits failed")
726798

727799
# Check for negative or overflow output values
@@ -820,7 +892,8 @@ def CheckBlock(block, fCheckPoW = True, fCheckMerkleRoot = True, cur_time=None):
820892
821893
fCheckPoW - Check proof-of-work.
822894
823-
fCheckMerkleRoot - Check merkle root matches transactions.
895+
fCheckMerkleRoot - Check merkle root and witness merkle root matches transactions.
896+
- Check witness commitment in coinbase
824897
825898
cur_time - Current time. Defaults to time.time()
826899
"""
@@ -831,9 +904,12 @@ def CheckBlock(block, fCheckPoW = True, fCheckMerkleRoot = True, cur_time=None):
831904
# Size limits
832905
if not block.vtx:
833906
raise CheckBlockError("CheckBlock() : vtx empty")
834-
if len(block.serialize()) > MAX_BLOCK_SIZE:
907+
if len(block.serialize(dict(include_witness=False))) > MAX_BLOCK_SIZE:
835908
raise CheckBlockError("CheckBlock() : block larger than MAX_BLOCK_SIZE")
836909

910+
if block.GetWeight() > MAX_BLOCK_WEIGHT:
911+
raise CheckBlockError("CheckBlock() : block larger than MAX_BLOCK_WEIGHT")
912+
837913
# First transaction must be coinbase
838914
if not block.vtx[0].is_coinbase():
839915
raise CheckBlockError("CheckBlock() : first tx is not coinbase")
@@ -861,8 +937,29 @@ def CheckBlock(block, fCheckPoW = True, fCheckMerkleRoot = True, cur_time=None):
861937
raise CheckBlockError("CheckBlock() : out-of-bounds SigOpCount")
862938

863939
# Check merkle root
864-
if fCheckMerkleRoot and block.hashMerkleRoot != block.calc_merkle_root():
865-
raise CheckBlockError("CheckBlock() : hashMerkleRoot mismatch")
940+
if fCheckMerkleRoot:
941+
if block.hashMerkleRoot != block.calc_merkle_root():
942+
raise CheckBlockError("CheckBlock() : hashMerkleRoot mismatch")
943+
if len(block.vWitnessMerkleTree):
944+
# At least 1 tx has witness: check witness merkle tree
945+
root = block.vWitnessMerkleTree[-1]
946+
# vtx[0]: coinbase
947+
# vtxinwit[0]: first input
948+
nonce_script = block.vtx[0].wit.vtxinwit[0].scriptWitness
949+
nonce = nonce_script.stack[0]
950+
if len(nonce_script.stack) != 1 or len(nonce) != 32:
951+
raise CheckBlockError("CheckBlock() : invalid coinbase witnessScript")
952+
try:
953+
index = block.get_witness_commitment_index()
954+
except ValueError as e:
955+
raise CheckBlockError("CheckBlock() : " + str(e))
956+
commit_script = block.vtx[0].vout[index].scriptPubKey
957+
if not (6 + 32 <= len(commit_script) <= 6 + 32 + 1):
958+
raise CheckBlockError("CheckBlock() : invalid segwit commitment length")
959+
commitment = commit_script[6:6 + 32]
960+
commit = commit_script[6:6 + 32]
961+
if commit != Hash(root + nonce):
962+
raise CheckBlockError("CheckBlock() : invalid segwit commitment")
866963

867964
__all__ = (
868965
'Hash',
@@ -885,6 +982,8 @@ def CheckBlock(block, fCheckPoW = True, fCheckMerkleRoot = True, cur_time=None):
885982
'CMutableTxOut',
886983
'CTransaction',
887984
'CMutableTransaction',
985+
'CTxWitness',
986+
'CTxInWitness',
888987
'CBlockHeader',
889988
'CBlock',
890989
'CoreChainParams',

bitcoin/core/serialize.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,23 +87,23 @@ class Serializable(object):
8787

8888
__slots__ = []
8989

90-
def stream_serialize(self, f):
90+
def stream_serialize(self, f, **kwargs):
9191
"""Serialize to a stream"""
9292
raise NotImplementedError
9393

9494
@classmethod
95-
def stream_deserialize(cls, f):
95+
def stream_deserialize(cls, f, **kwargs):
9696
"""Deserialize from a stream"""
9797
raise NotImplementedError
9898

99-
def serialize(self):
99+
def serialize(self, params={}):
100100
"""Serialize, returning bytes"""
101101
f = _BytesIO()
102-
self.stream_serialize(f)
102+
self.stream_serialize(f, **params)
103103
return f.getvalue()
104104

105105
@classmethod
106-
def deserialize(cls, buf, allow_padding=False):
106+
def deserialize(cls, buf, allow_padding=False, params={}):
107107
"""Deserialize bytes, returning an instance
108108
109109
allow_padding - Allow buf to include extra padding. (default False)
@@ -112,7 +112,7 @@ def deserialize(cls, buf, allow_padding=False):
112112
deserialization DeserializationExtraDataError will be raised.
113113
"""
114114
fd = _BytesIO(buf)
115-
r = cls.stream_deserialize(fd)
115+
r = cls.stream_deserialize(fd, **params)
116116
if not allow_padding:
117117
padding = fd.read()
118118
if len(padding) != 0:
@@ -242,17 +242,17 @@ class VectorSerializer(Serializer):
242242
# and should be rethought at some point.
243243

244244
@classmethod
245-
def stream_serialize(cls, inner_cls, objs, f):
245+
def stream_serialize(cls, inner_cls, objs, f, inner_params={}):
246246
VarIntSerializer.stream_serialize(len(objs), f)
247247
for obj in objs:
248-
inner_cls.stream_serialize(obj, f)
248+
inner_cls.stream_serialize(obj, f, **inner_params)
249249

250250
@classmethod
251-
def stream_deserialize(cls, inner_cls, f):
251+
def stream_deserialize(cls, inner_cls, f, inner_params={}):
252252
n = VarIntSerializer.stream_deserialize(f)
253253
r = []
254254
for i in range(n):
255-
r.append(inner_cls.stream_deserialize(f))
255+
r.append(inner_cls.stream_deserialize(f, **inner_params))
256256
return r
257257

258258

bitcoin/tests/test_segwit.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,19 @@
1212
from __future__ import absolute_import, division, print_function, unicode_literals
1313

1414
import unittest
15+
import random
16+
import sys
1517

18+
import bitcoin
1619
from bitcoin.core import *
1720
from bitcoin.core.script import *
1821
from bitcoin.wallet import *
1922

23+
if sys.version > '3':
24+
_bytes = bytes
25+
else:
26+
_bytes = lambda x: bytes(bytearray(x))
27+
2028
# Test serialization
2129

2230

@@ -107,4 +115,27 @@ def test_p2sh_p2wsh_signaturehash(self):
107115
0, SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, value, SIGVERSION_WITNESS_V0),
108116
x('511e8e52ed574121fc1b654970395502128263f62662e076dc6baf05c2e6a99b'))
109117

118+
def test_checkblock(self):
119+
# (No witness) coinbase generated by Bitcoin Core
120+
str_coinbase = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03520101ffffffff0100f2052a01000000232102960c90bc04a631cb17922e4f5d80ac75fd590a88b8baaa5a3d5086ac85e4d788ac00000000'
121+
# No witness transaction
122+
str_tx = '0100000001e3d0c1d051a3abe79d5951dcab86f71d8926aff5caed5ff9bd72cb593e4ebaf5010000006b483045022100a28e1c57e160296953e1af85c5034bb1b907c12c50367da75ba547874a8a8c52022040682e888ddce7fd5c72519a9f28f22f5164c8af864d33332bbc7f65596ad4ae012102db30394fd5cc8288bed607b9382338240c014a98c9c0febbfb380db74ceb30a3ffffffff020d920000000000001976a914ad781c8ffcc18b2155433cba2da4601180a66eef88aca3170000000000001976a914bacb1c4b0725e61e385c7093b4260533953c8e1688ac00000000'
123+
# SegWit transaction
124+
str_w_tx = '0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560100000000ffffffff0100b4f505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02483045022100df7b7e5cda14ddf91290e02ea10786e03eb11ee36ec02dd862fe9a326bbcb7fd02203f5b4496b667e6e281cc654a2da9e4f08660c620a1051337fa8965f727eb19190121038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990ac00000000'
125+
witness_nonce = _bytes(random.getrandbits(8) for _ in range(32))
126+
127+
coinbase = CMutableTransaction.deserialize(x(str_coinbase))
128+
coinbase.wit = CTxWitness([CTxInWitness(CScriptWitness([witness_nonce]))])
129+
130+
tx_legacy = CTransaction.deserialize(x(str_tx))
131+
tx_segwit = CTransaction.deserialize(x(str_w_tx))
132+
133+
witness_merkle_root = CBlock.build_witness_merkle_tree_from_txs((coinbase, tx_legacy, tx_segwit))[-1]
134+
135+
commitment = Hash(witness_merkle_root + witness_nonce)
136+
commitment_script = bitcoin.core.WITNESS_COINBASE_SCRIPTPUBKEY_MAGIC + commitment
137+
coinbase.vout.append(CTxOut(0, CScript(commitment_script)))
138+
139+
block = CBlock(2, b'\x00'*32, b'\x00'*32, 0, 0, 0, (coinbase, tx_legacy, tx_segwit))
110140

141+
CheckBlock(block, False, True)

0 commit comments

Comments
 (0)