Skip to content

Commit f4d1d2a

Browse files
author
Bob McElrath
committed
BIP143 implementation and test vectors
1 parent 580f466 commit f4d1d2a

File tree

5 files changed

+193
-4
lines changed

5 files changed

+193
-4
lines changed

bitcoin/core/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ class CMutableTransaction(CTransaction):
439439
"""A mutable transaction"""
440440
__slots__ = []
441441

442-
def __init__(self, vin=None, vout=None, nLockTime=0, nVersion=1, witness=CScriptWitness([])):
442+
def __init__(self, vin=None, vout=None, nLockTime=0, nVersion=1, witness=None):
443443
if not (0 <= nLockTime <= 0xffffffff):
444444
raise ValueError('CTransaction: nLockTime must be in range 0x0 to 0xffffffff; got %x' % nLockTime)
445445
self.nLockTime = nLockTime

bitcoin/core/script.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
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

@@ -674,6 +677,24 @@ def is_p2sh(self):
674677
_bord(self[1]) == 0x14 and
675678
_bord(self[22]) == OP_EQUAL)
676679

680+
def is_witness_scriptpubkey(self):
681+
return 3 <= len(self) <= 42 and CScriptOp(self[0]).is_small_int()
682+
683+
def witness_version(self):
684+
return next(iter(self))
685+
686+
def is_witness_v0_keyhash(self):
687+
return len(self) == 22 and self[0:2] == b'\x00\x14'
688+
689+
def is_witness_v0_nested_keyhash(self):
690+
return len(self) == 23 and self[0:3] == b'\x16\x00\x14'
691+
692+
def is_witness_v0_scripthash(self):
693+
return len(self) == 34 and self[0:2] == b'\x00\x20'
694+
695+
def is_witness_v0_nested_scripthash(self):
696+
return len(self) == 23 and self[0:2] == b'\xa9\x14' and self[-1] == b'\x87'
697+
677698
def is_push_only(self):
678699
"""Test if the script only contains pushdata ops
679700
@@ -935,14 +956,63 @@ def RawSignatureHash(script, txTo, inIdx, hashtype):
935956

936957
return (hash, None)
937958

959+
SIGVERSION_BASE = 0
960+
SIGVERSION_WITNESS_V0 = 1
938961

939-
def SignatureHash(script, txTo, inIdx, hashtype):
962+
def SignatureHash(script, txTo, inIdx, hashtype, amount=None, sigversion=SIGVERSION_BASE):
940963
"""Calculate a signature hash
941964
942965
'Cooked' version that checks if inIdx is out of bounds - this is *not*
943966
consensus-correct behavior, but is what you probably want for general
944967
wallet use.
945968
"""
969+
970+
if sigversion == SIGVERSION_WITNESS_V0:
971+
hashPrevouts = b'\x00'*32
972+
hashSequence = b'\x00'*32
973+
hashOutputs = b'\x00'*32
974+
975+
if not (hashtype & SIGHASH_ANYONECANPAY):
976+
serialize_prevouts = bytes()
977+
for i in txTo.vin:
978+
serialize_prevouts += i.prevout.serialize()
979+
hashPrevouts = bitcoin.core.Hash(serialize_prevouts)
980+
981+
if (not (hashtype & SIGHASH_ANYONECANPAY) and (hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE):
982+
serialize_sequence = bytes()
983+
for i in txTo.vin:
984+
serialize_sequence += struct.pack("<I", i.nSequence)
985+
hashSequence = bitcoin.core.Hash(serialize_sequence)
986+
987+
if ((hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE):
988+
serialize_outputs = bytes()
989+
for o in txTo.vout:
990+
serialize_outputs += o.serialize()
991+
hashOutputs = bitcoin.core.Hash(serialize_outputs)
992+
elif ((hashtype & 0x1f) == SIGHASH_SINGLE and inIdx < len(txTo.vout)):
993+
serialize_outputs = txTo.vout[inIdx].serialize()
994+
hashOutputs = bitcoin.core.Hash(serialize_outputs)
995+
996+
f = _BytesIO()
997+
f.write(struct.pack("<i", txTo.nVersion))
998+
f.write(hashPrevouts)
999+
f.write(hashSequence)
1000+
txTo.vin[inIdx].prevout.stream_serialize(f)
1001+
BytesSerializer.stream_serialize(script, f)
1002+
f.write(struct.pack("<q", amount))
1003+
f.write(struct.pack("<I", txTo.vin[inIdx].nSequence))
1004+
f.write(hashOutputs)
1005+
f.write(struct.pack("<i", txTo.nLockTime))
1006+
f.write(struct.pack("<I", hashtype))
1007+
1008+
return bitcoin.core.Hash(f.getvalue())
1009+
1010+
if script.is_witness_scriptpubkey():
1011+
print("WARNING: You seem to be attempting to sign a scriptPubKey from an")
1012+
print("WARNING: output with segregated witness. This is NOT the correct")
1013+
print("WARNING: thing to sign. You should pass SignatureHash the corresponding")
1014+
print("WARNING: P2WPKH or P2WSH script instead.")
1015+
9461016
(h, err) = RawSignatureHash(script, txTo, inIdx, hashtype)
9471017
if err is not None:
9481018
raise ValueError(err)
@@ -1091,4 +1161,7 @@ def SignatureHash(script, txTo, inIdx, hashtype):
10911161
'RawSignatureHash',
10921162
'SignatureHash',
10931163
'IsLowDERSignature',
1164+
1165+
'SIGVERSION_BASE',
1166+
'SIGVERSION_WITNESS_V0',
10941167
)

bitcoin/core/serialize.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,9 @@ def uint256_to_shortstr(u):
371371
'Hash160',
372372
'SerializationError',
373373
'SerializationTruncationError',
374+
'SerializationMissingWitnessError',
374375
'DeserializationExtraDataError',
376+
'DeserializationFormatError',
375377
'ser_read',
376378
'Serializable',
377379
'ImmutableSerializable',

bitcoin/tests/test_segwit.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Copyright (C) 2016 The python-bitcoinlib developers
2+
#
3+
# This file is part of python-bitcoinlib.
4+
#
5+
# It is subject to the license terms in the LICENSE file found in the top-level
6+
# directory of this distribution.
7+
#
8+
# No part of python-bitcoinlib, including this file, may be copied, modified,
9+
# propagated, or distributed except according to the terms contained in the
10+
# LICENSE file.
11+
12+
from __future__ import absolute_import, division, print_function, unicode_literals
13+
14+
import unittest
15+
16+
from bitcoin.core import *
17+
from bitcoin.core.script import *
18+
from bitcoin.wallet import *
19+
20+
# Test serialization
21+
22+
23+
class Test_Segwit(unittest.TestCase):
24+
25+
# Test BIP 143 vectors
26+
def test_p2wpkh_signaturehash(self):
27+
unsigned_tx = x('0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac11000000')
28+
scriptpubkey = CScript(x('00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1'))
29+
value = int(6*COIN)
30+
31+
address = CBitcoinAddress.from_scriptPubKey(scriptpubkey)
32+
self.assertEqual(SignatureHash(address.to_scriptPubKey(), CTransaction.deserialize(unsigned_tx),
33+
1, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0),
34+
x('c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670'))
35+
36+
def test_p2sh_p2wpkh_signaturehash(self):
37+
unsigned_tx = x('0100000001db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a54770100000000feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac92040000')
38+
scriptpubkey = CScript(x('a9144733f37cf4db86fbc2efed2500b4f4e49f31202387'))
39+
redeemscript = CScript(x('001479091972186c449eb1ded22b78e40d009bdf0089'))
40+
value = int(10*COIN)
41+
42+
address = CBitcoinAddress.from_scriptPubKey(redeemscript)
43+
self.assertEqual(SignatureHash(address.to_scriptPubKey(), CTransaction.deserialize(unsigned_tx),
44+
0, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0),
45+
x('64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6'))
46+
47+
def test_p2wsh_signaturehash1(self):
48+
unsigned_tx = x('0100000002fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e0000000000ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac00000000')
49+
scriptpubkey1 = CScript(x('21036d5c20fa14fb2f635474c1dc4ef5909d4568e5569b79fc94d3448486e14685f8ac'))
50+
value1 = int(1.5625*COIN)
51+
scriptpubkey2 = CScript(x('00205d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0'))
52+
value2 = int(49*COIN)
53+
scriptcode1 = CScript(x('21026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac'))
54+
# This is the same script with everything up to the last executed OP_CODESEPARATOR, including that
55+
# OP_CODESEPARATOR removed
56+
scriptcode2 = CScript(x('210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac'))
57+
58+
self.assertEqual(SignatureHash(scriptcode1, CTransaction.deserialize(unsigned_tx),
59+
1, SIGHASH_SINGLE, value2, SIGVERSION_WITNESS_V0),
60+
x('82dde6e4f1e94d02c2b7ad03d2115d691f48d064e9d52f58194a6637e4194391'))
61+
self.assertEqual(SignatureHash(scriptcode2, CTransaction.deserialize(unsigned_tx),
62+
1, SIGHASH_SINGLE, value2, SIGVERSION_WITNESS_V0),
63+
x('fef7bd749cce710c5c052bd796df1af0d935e59cea63736268bcbe2d2134fc47'))
64+
65+
def test_p2wsh_signaturehash2(self):
66+
unsigned_tx = x('0100000002e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac00000000')
67+
scriptpubkey1 = CScript(x('0020ba468eea561b26301e4cf69fa34bde4ad60c81e70f059f045ca9a79931004a4d'))
68+
value1 = int(0.16777215*COIN)
69+
witnessscript1= CScript(x('0063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac'))
70+
scriptcode1 = CScript(x('0063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac'))
71+
scriptpubkey2 = CScript(x('0020d9bbfbe56af7c4b7f960a70d7ea107156913d9e5a26b0a71429df5e097ca6537'))
72+
value2 = int(0.16777215*COIN)
73+
witnessscript2= CScript(x('5163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac'))
74+
scriptcode2 = CScript(x('68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac'))
75+
76+
self.assertEqual(SignatureHash(scriptcode1, CTransaction.deserialize(unsigned_tx),
77+
0, SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, value1, SIGVERSION_WITNESS_V0),
78+
x('e9071e75e25b8a1e298a72f0d2e9f4f95a0f5cdf86a533cda597eb402ed13b3a'))
79+
self.assertEqual(SignatureHash(scriptcode2, CTransaction.deserialize(unsigned_tx),
80+
1, SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, value2, SIGVERSION_WITNESS_V0),
81+
x('cd72f1f1a433ee9df816857fad88d8ebd97e09a75cd481583eb841c330275e54'))
82+
83+
def test_p2sh_p2wsh_signaturehash(self):
84+
unsigned_tx = x('010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac00000000')
85+
86+
scriptPubKey = CScript(x('a9149993a429037b5d912407a71c252019287b8d27a587'))
87+
value = int(9.87654321*COIN)
88+
redeemScript = CScript(x('0020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54'))
89+
witnessscript= CScript(x('56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae'))
90+
91+
self.assertEqual(SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx),
92+
0, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0),
93+
x('185c0be5263dce5b4bb50a047973c1b6272bfbd0103a89444597dc40b248ee7c'))
94+
self.assertEqual(SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx),
95+
0, SIGHASH_NONE, value, SIGVERSION_WITNESS_V0),
96+
x('e9733bc60ea13c95c6527066bb975a2ff29a925e80aa14c213f686cbae5d2f36'))
97+
self.assertEqual(SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx),
98+
0, SIGHASH_SINGLE, value, SIGVERSION_WITNESS_V0),
99+
x('1e1f1c303dc025bd664acb72e583e933fae4cff9148bf78c157d1e8f78530aea'))
100+
self.assertEqual(SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx),
101+
0, SIGHASH_ALL|SIGHASH_ANYONECANPAY, value, SIGVERSION_WITNESS_V0),
102+
x('2a67f03e63a6a422125878b40b82da593be8d4efaafe88ee528af6e5a9955c6e'))
103+
self.assertEqual(SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx),
104+
0, SIGHASH_NONE|SIGHASH_ANYONECANPAY, value, SIGVERSION_WITNESS_V0),
105+
x('781ba15f3779d5542ce8ecb5c18716733a5ee42a6f51488ec96154934e2c890a'))
106+
self.assertEqual(SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx),
107+
0, SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, value, SIGVERSION_WITNESS_V0),
108+
x('511e8e52ed574121fc1b654970395502128263f62662e076dc6baf05c2e6a99b'))
109+
110+

bitcoin/wallet.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,11 @@ def from_scriptPubKey(cls, scriptPubKey, accept_non_canonical_pushdata=True, acc
165165
except bitcoin.core.script.CScriptInvalidError:
166166
raise CBitcoinAddressError('not a P2PKH scriptPubKey: script is invalid')
167167

168-
if (len(scriptPubKey) == 25
168+
if scriptPubKey.is_witness_v0_keyhash():
169+
return cls.from_bytes(scriptPubKey[2:22], bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR'])
170+
elif scriptPubKey.is_witness_v0_nested_keyhash():
171+
return cls.from_bytes(scriptPubKey[3:23], bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR'])
172+
elif (len(scriptPubKey) == 25
169173
and _bord(scriptPubKey[0]) == script.OP_DUP
170174
and _bord(scriptPubKey[1]) == script.OP_HASH160
171175
and _bord(scriptPubKey[2]) == 0x14
@@ -195,7 +199,7 @@ def from_scriptPubKey(cls, scriptPubKey, accept_non_canonical_pushdata=True, acc
195199

196200
raise CBitcoinAddressError('not a P2PKH scriptPubKey')
197201

198-
def to_scriptPubKey(self):
202+
def to_scriptPubKey(self, nested=False):
199203
"""Convert an address to a scriptPubKey"""
200204
assert self.nVersion == bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR']
201205
return script.CScript([script.OP_DUP, script.OP_HASH160, self, script.OP_EQUALVERIFY, script.OP_CHECKSIG])

0 commit comments

Comments
 (0)