Skip to content

Commit 70c8ea1

Browse files
committed
Merge #112: Segregated Witness support
5cd3467 fixed issue in is_witness_scriptpubkey where self[0] is interpretted as str instead of the necessary int (Surya Bakshi) 2b50596 Add segwit block (de)serialization. (#9) (the9ull) d8a1a0f Closely match Core's (de-)serialization; remove old test case (Bob McElrath) 0c09892 Encode hashtype as signed (following bitcoind) (Bob McElrath) eca69b7 CTxWitness class, new message types (Bob McElrath) f4d1d2a BIP143 implementation and test vectors (Bob McElrath) 580f466 Initial segwit implementation, works with P2WPKH addresses (Bob McElrath) Pull request description: This is my initial implementation. It correctly serializes and deserializes P2WPKH segwit transactions. I haven't tested the other transaction types. Please don't merge this yet, but I wanted to make it available for others to work on. TODO - ~~BIP 143 tx signing~~ - ~~`CBlock` modifications to support witness data~~ - ~~P2WSH and BIP16 embedded scripts~~ - ~~tests!~~ Tree-SHA512: bc96c263467ae3f27aa86472afb4e29e6c1d81da527832ed153b05faa06ef8152ce518f9c9c1c1fdae45de7f93173c77fa237b160d5cfb7d1068532a8849c9f3
2 parents ab94a31 + 5cd3467 commit 70c8ea1

File tree

9 files changed

+538
-47
lines changed

9 files changed

+538
-47
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
*.sw?
22
*.pyc
33

44
local*.cfg
@@ -8,3 +8,4 @@ local*.cfg
88
build/
99
htmlcov/
1010
python_bitcoinlib.egg-info/
11+
dist/

bitcoin/core/__init__.py

Lines changed: 246 additions & 26 deletions
Large diffs are not rendered by default.

bitcoin/core/script.py

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,17 @@
2424
long = int
2525
_bchr = lambda x: bytes([x])
2626
_bord = lambda x: x
27+
from io import BytesIO as _BytesIO
28+
else:
29+
from cStringIO import StringIO as _BytesIO
2730

2831
import struct
2932

3033
import bitcoin.core
3134
import bitcoin.core._bignum
3235

36+
from .serialize import *
37+
3338
MAX_SCRIPT_SIZE = 10000
3439
MAX_SCRIPT_ELEMENT_SIZE = 520
3540
MAX_SCRIPT_OPCODES = 201
@@ -672,6 +677,31 @@ def is_p2sh(self):
672677
_bord(self[1]) == 0x14 and
673678
_bord(self[22]) == OP_EQUAL)
674679

680+
def is_witness_scriptpubkey(self):
681+
"""Returns true if this is a scriptpubkey signaling segregated witness
682+
data. """
683+
return 3 <= len(self) <= 42 and CScriptOp(struct.unpack('<b',self[0])[0]).is_small_int()
684+
685+
def witness_version(self):
686+
"""Returns the witness version on [0,16]. """
687+
return next(iter(self))
688+
689+
def is_witness_v0_keyhash(self):
690+
"""Returns true if this is a scriptpubkey for V0 P2WPKH. """
691+
return len(self) == 22 and self[0:2] == b'\x00\x14'
692+
693+
def is_witness_v0_nested_keyhash(self):
694+
"""Returns true if this is a scriptpubkey for V0 P2WPKH embedded in P2SH. """
695+
return len(self) == 23 and self[0:3] == b'\x16\x00\x14'
696+
697+
def is_witness_v0_scripthash(self):
698+
"""Returns true if this is a scriptpubkey for V0 P2WSH. """
699+
return len(self) == 34 and self[0:2] == b'\x00\x20'
700+
701+
def is_witness_v0_nested_scripthash(self):
702+
"""Returns true if this is a scriptpubkey for V0 P2WSH embedded in P2SH. """
703+
return len(self) == 23 and self[0:2] == b'\xa9\x14' and self[-1] == b'\x87'
704+
675705
def is_push_only(self):
676706
"""Test if the script only contains pushdata ops
677707
@@ -773,6 +803,38 @@ def GetSigOpCount(self, fAccurate):
773803
lastOpcode = opcode
774804
return n
775805

806+
class CScriptWitness(ImmutableSerializable):
807+
"""An encoding of the data elements on the initial stack for (segregated
808+
witness)
809+
"""
810+
__slots__ = ['stack']
811+
812+
def __init__(self, stack=()):
813+
object.__setattr__(self, 'stack', stack)
814+
815+
def __len__(self):
816+
return len(self.stack)
817+
818+
def __iter__(self):
819+
return iter(self.stack)
820+
821+
def __repr__(self):
822+
return 'CScriptWitness(' + ','.join("x('%s')" % bitcoin.core.b2x(s) for s in self.stack) + ')'
823+
824+
def is_null(self):
825+
return len(self.stack) == 0
826+
827+
@classmethod
828+
def stream_deserialize(cls, f):
829+
n = VarIntSerializer.stream_deserialize(f)
830+
stack = tuple(BytesSerializer.stream_deserialize(f) for i in range(n))
831+
return cls(stack)
832+
833+
def stream_serialize(self, f):
834+
VarIntSerializer.stream_serialize(len(self.stack), f)
835+
for s in self.stack:
836+
BytesSerializer.stream_serialize(s, f)
837+
776838

777839
SIGHASH_ALL = 1
778840
SIGHASH_NONE = 2
@@ -894,21 +956,71 @@ def RawSignatureHash(script, txTo, inIdx, hashtype):
894956
txtmp.vin = []
895957
txtmp.vin.append(tmp)
896958

959+
txtmp.wit = bitcoin.core.CTxWitness()
897960
s = txtmp.serialize()
898-
s += struct.pack(b"<I", hashtype)
961+
s += struct.pack(b"<i", hashtype)
899962

900963
hash = bitcoin.core.Hash(s)
901964

902965
return (hash, None)
903966

967+
SIGVERSION_BASE = 0
968+
SIGVERSION_WITNESS_V0 = 1
904969

905-
def SignatureHash(script, txTo, inIdx, hashtype):
970+
def SignatureHash(script, txTo, inIdx, hashtype, amount=None, sigversion=SIGVERSION_BASE):
906971
"""Calculate a signature hash
907972
908973
'Cooked' version that checks if inIdx is out of bounds - this is *not*
909974
consensus-correct behavior, but is what you probably want for general
910975
wallet use.
911976
"""
977+
978+
if sigversion == SIGVERSION_WITNESS_V0:
979+
hashPrevouts = b'\x00'*32
980+
hashSequence = b'\x00'*32
981+
hashOutputs = b'\x00'*32
982+
983+
if not (hashtype & SIGHASH_ANYONECANPAY):
984+
serialize_prevouts = bytes()
985+
for i in txTo.vin:
986+
serialize_prevouts += i.prevout.serialize()
987+
hashPrevouts = bitcoin.core.Hash(serialize_prevouts)
988+
989+
if (not (hashtype & SIGHASH_ANYONECANPAY) and (hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE):
990+
serialize_sequence = bytes()
991+
for i in txTo.vin:
992+
serialize_sequence += struct.pack("<I", i.nSequence)
993+
hashSequence = bitcoin.core.Hash(serialize_sequence)
994+
995+
if ((hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE):
996+
serialize_outputs = bytes()
997+
for o in txTo.vout:
998+
serialize_outputs += o.serialize()
999+
hashOutputs = bitcoin.core.Hash(serialize_outputs)
1000+
elif ((hashtype & 0x1f) == SIGHASH_SINGLE and inIdx < len(txTo.vout)):
1001+
serialize_outputs = txTo.vout[inIdx].serialize()
1002+
hashOutputs = bitcoin.core.Hash(serialize_outputs)
1003+
1004+
f = _BytesIO()
1005+
f.write(struct.pack("<i", txTo.nVersion))
1006+
f.write(hashPrevouts)
1007+
f.write(hashSequence)
1008+
txTo.vin[inIdx].prevout.stream_serialize(f)
1009+
BytesSerializer.stream_serialize(script, f)
1010+
f.write(struct.pack("<q", amount))
1011+
f.write(struct.pack("<I", txTo.vin[inIdx].nSequence))
1012+
f.write(hashOutputs)
1013+
f.write(struct.pack("<i", txTo.nLockTime))
1014+
f.write(struct.pack("<i", hashtype))
1015+
1016+
return bitcoin.core.Hash(f.getvalue())
1017+
1018+
if script.is_witness_scriptpubkey():
1019+
print("WARNING: You seem to be attempting to sign a scriptPubKey from an")
1020+
print("WARNING: output with segregated witness. This is NOT the correct")
1021+
print("WARNING: thing to sign. You should pass SignatureHash the corresponding")
1022+
print("WARNING: P2WPKH or P2WSH script instead.")
1023+
9121024
(h, err) = RawSignatureHash(script, txTo, inIdx, hashtype)
9131025
if err is not None:
9141026
raise ValueError(err)
@@ -1048,6 +1160,7 @@ def SignatureHash(script, txTo, inIdx, hashtype):
10481160
'CScriptInvalidError',
10491161
'CScriptTruncatedPushDataError',
10501162
'CScript',
1163+
'CScriptWitness',
10511164
'SIGHASH_ALL',
10521165
'SIGHASH_NONE',
10531166
'SIGHASH_SINGLE',
@@ -1056,4 +1169,7 @@ def SignatureHash(script, txTo, inIdx, hashtype):
10561169
'RawSignatureHash',
10571170
'SignatureHash',
10581171
'IsLowDERSignature',
1172+
1173+
'SIGVERSION_BASE',
1174+
'SIGVERSION_WITNESS_V0',
10591175
)

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/messages.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@
3535
from bitcoin.net import *
3636
import bitcoin
3737

38+
MSG_WITNESS_FLAG = 1 << 30
39+
MSG_TYPE_MASK = 0xffffffff >> 2
40+
3841
MSG_TX = 1
3942
MSG_BLOCK = 2
4043
MSG_FILTERED_BLOCK = 3
44+
MSG_CMPCT_BLOCK = 4
45+
MSG_WITNESS_BLOCK = MSG_BLOCK | MSG_WITNESS_FLAG,
46+
MSG_WITNESS_TX = MSG_TX | MSG_WITNESS_FLAG,
47+
MSG_FILTERED_WITNESS_BLOCK = MSG_FILTERED_BLOCK | MSG_WITNESS_FLAG,
4148

4249

4350
class MsgSerializable(Serializable):
@@ -499,6 +506,12 @@ def __repr__(self):
499506
'MSG_TX',
500507
'MSG_BLOCK',
501508
'MSG_FILTERED_BLOCK',
509+
'MSG_CMPCT_BLOCK',
510+
'MSG_TYPE_MASK',
511+
'MSG_WITNESS_TX',
512+
'MSG_WITNESS_BLOCK',
513+
'MSG_WITNESS_FLAG',
514+
'MSG_FILTERED_WITNESS_BLOCK',
502515
'MsgSerializable',
503516
'msg_version',
504517
'msg_verack',

bitcoin/net.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ class CInv(Serializable):
7676
0: "Error",
7777
1: "TX",
7878
2: "Block",
79-
3: "FilteredBlock"}
79+
3: "FilteredBlock",
80+
4: "CompactBlock"}
8081

8182
def __init__(self):
8283
self.type = 0

bitcoin/tests/data/tx_invalid.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@
2323
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7a052c840ba73af26755de42cf01cc9e0a49fef0 EQUAL"]],
2424
"010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000", true],
2525

26-
["Tests for CheckTransaction()"],
27-
["No inputs"],
28-
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7a052c840ba73af26755de42cf01cc9e0a49fef0 EQUAL"]],
29-
"0100000000010000000000000000015100000000", true],
30-
3126
["No outputs"],
3227
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x05ab9e14d983742513f0f451e105ffb4198d1dd4 EQUAL"]],
3328
"01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022100f16703104aab4e4088317c862daec83440242411b039d14280e03dd33b487ab802201318a7be236672c5c56083eb7a5a195bc57a40af7923ff8545016cd3b571e2a601232103c40e5d339df3f30bf753e7e04450ae4ef76c9e45587d1d993bdc4cd06f0651c7acffffffff0000000000", true],

0 commit comments

Comments
 (0)