Skip to content

Commit 7c2b4e1

Browse files
committed
add Bech32-encoded address support
introduce CBase58Address and CBech32Address make CBitcoinAddress a wrapper that returns CBase58Address or CBech32Address depending on input data introduce to_redeemScript() method, because for P2WPKHBitcoinAddress redeem script is not the same as scriptPubKey. unfortunately, for P2WSHBitcoinAddress to_redeemScript() cannot be implemented, as we need all pubkeys for that, and not only the hash.
1 parent 737380e commit 7c2b4e1

File tree

4 files changed

+147
-14
lines changed

4 files changed

+147
-14
lines changed

bitcoin/bech32.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,9 @@ def __str__(self):
7373

7474
def __repr__(self):
7575
return '%s(%r)' % (self.__class__.__name__, str(self))
76+
77+
__all__ = (
78+
'Bech32Error',
79+
'Bech32ChecksumError',
80+
'CBech32Data',
81+
)

bitcoin/tests/test_bech32.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
from bitcoin.core.script import CScript, OP_0, OP_1, OP_16
3131
from bitcoin.bech32 import *
32+
from bitcoin.segwit_addr import encode, decode
3233

3334

3435
def load_test_vectors(name):

bitcoin/tests/test_segwit.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ def test_p2wpkh_signaturehash(self):
3636
scriptpubkey = CScript(x('00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1'))
3737
value = int(6*COIN)
3838

39-
address = CBitcoinAddress.from_scriptPubKey(scriptpubkey)
40-
self.assertEqual(SignatureHash(address.to_scriptPubKey(), CTransaction.deserialize(unsigned_tx),
39+
address = CBech32BitcoinAddress.from_scriptPubKey(scriptpubkey)
40+
self.assertEqual(SignatureHash(address.to_redeemScript(), CTransaction.deserialize(unsigned_tx),
4141
1, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0),
4242
x('c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670'))
4343

@@ -47,8 +47,8 @@ def test_p2sh_p2wpkh_signaturehash(self):
4747
redeemscript = CScript(x('001479091972186c449eb1ded22b78e40d009bdf0089'))
4848
value = int(10*COIN)
4949

50-
address = CBitcoinAddress.from_scriptPubKey(redeemscript)
51-
self.assertEqual(SignatureHash(address.to_scriptPubKey(), CTransaction.deserialize(unsigned_tx),
50+
address = CBase58BitcoinAddress.from_scriptPubKey(redeemscript)
51+
self.assertEqual(SignatureHash(address.to_redeemScript(), CTransaction.deserialize(unsigned_tx),
5252
0, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0),
5353
x('64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6'))
5454

bitcoin/wallet.py

Lines changed: 136 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,93 @@
2525

2626
import bitcoin
2727
import bitcoin.base58
28+
import bitcoin.bech32
2829
import bitcoin.core
2930
import bitcoin.core.key
3031
import bitcoin.core.script as script
3132

32-
class CBitcoinAddressError(bitcoin.base58.Base58Error):
33+
34+
class CBitcoinAddress(object):
35+
36+
def __new__(cls, s):
37+
try:
38+
return CBech32BitcoinAddress(s)
39+
except bitcoin.bech32.Bech32Error:
40+
pass
41+
42+
try:
43+
return CBase58BitcoinAddress(s)
44+
except bitcoin.base58.Base58Error:
45+
pass
46+
47+
raise CBitcoinAddressError('Unrecognized encoding for bitcoin address')
48+
49+
@classmethod
50+
def from_scriptPubKey(cls, scriptPubKey):
51+
"""Convert a scriptPubKey to a subclass of CBitcoinAddress"""
52+
try:
53+
return CBech32BitcoinAddress.from_scriptPubKey(scriptPubKey)
54+
except CBitcoinAddressError:
55+
pass
56+
57+
try:
58+
return CBase58BitcoinAddress.from_scriptPubKey(scriptPubKey)
59+
except CBitcoinAddressError:
60+
pass
61+
62+
raise CBitcoinAddressError('scriptPubKey is not in a recognized address format')
63+
64+
65+
class CBitcoinAddressError(Exception):
3366
"""Raised when an invalid Bitcoin address is encountered"""
3467

35-
class CBitcoinAddress(bitcoin.base58.CBase58Data):
36-
"""A Bitcoin address"""
68+
69+
class CBech32BitcoinAddress(bitcoin.bech32.CBech32Data, CBitcoinAddress):
70+
"""A Bech32-encoded Bitcoin address"""
71+
72+
@classmethod
73+
def from_bytes(cls, witver, witprog):
74+
75+
assert(witver == 0)
76+
77+
self = super(CBech32BitcoinAddress, cls).from_bytes(witver, witprog)
78+
79+
if len(self) == 32:
80+
self.__class__ = P2WSHBitcoinAddress
81+
elif len(self) == 20:
82+
self.__class__ = P2WPKHBitcoinAddress
83+
else:
84+
raise CBitcoinAddressError('witness program does not match any known segwit address format')
85+
86+
return self
87+
88+
@classmethod
89+
def from_scriptPubKey(cls, scriptPubKey):
90+
"""Convert a scriptPubKey to a CBech32BitcoinAddress
91+
92+
Returns a CBech32BitcoinAddress subclass, either P2WSHBitcoinAddress or
93+
P2WPKHBitcoinAddress. If the scriptPubKey is not recognized
94+
CBitcoinAddressError will be raised.
95+
"""
96+
try:
97+
return P2WSHBitcoinAddress.from_scriptPubKey(scriptPubKey)
98+
except CBitcoinAddressError:
99+
pass
100+
101+
try:
102+
return P2WPKHBitcoinAddress.from_scriptPubKey(scriptPubKey)
103+
except CBitcoinAddressError:
104+
pass
105+
106+
raise CBitcoinAddressError('scriptPubKey not a valid bech32-encoded address')
107+
108+
109+
class CBase58BitcoinAddress(bitcoin.base58.CBase58Data, CBitcoinAddress):
110+
"""A Base58-encoded Bitcoin address"""
37111

38112
@classmethod
39113
def from_bytes(cls, data, nVersion):
40-
self = super(CBitcoinAddress, cls).from_bytes(data, nVersion)
114+
self = super(CBase58BitcoinAddress, cls).from_bytes(data, nVersion)
41115

42116
if nVersion == bitcoin.params.BASE58_PREFIXES['SCRIPT_ADDR']:
43117
self.__class__ = P2SHBitcoinAddress
@@ -68,13 +142,10 @@ def from_scriptPubKey(cls, scriptPubKey):
68142
except CBitcoinAddressError:
69143
pass
70144

71-
raise CBitcoinAddressError('scriptPubKey not a valid address')
145+
raise CBitcoinAddressError('scriptPubKey not a valid base58-encoded address')
72146

73-
def to_scriptPubKey(self):
74-
"""Convert an address to a scriptPubKey"""
75-
raise NotImplementedError
76147

77-
class P2SHBitcoinAddress(CBitcoinAddress):
148+
class P2SHBitcoinAddress(CBase58BitcoinAddress):
78149
@classmethod
79150
def from_bytes(cls, data, nVersion=None):
80151
if nVersion is None:
@@ -112,7 +183,11 @@ def to_scriptPubKey(self):
112183
assert self.nVersion == bitcoin.params.BASE58_PREFIXES['SCRIPT_ADDR']
113184
return script.CScript([script.OP_HASH160, self, script.OP_EQUAL])
114185

115-
class P2PKHBitcoinAddress(CBitcoinAddress):
186+
def to_redeemScript(self):
187+
return self.to_scriptPubKey()
188+
189+
190+
class P2PKHBitcoinAddress(CBase58BitcoinAddress):
116191
@classmethod
117192
def from_bytes(cls, data, nVersion=None):
118193
if nVersion is None:
@@ -204,6 +279,55 @@ def to_scriptPubKey(self, nested=False):
204279
assert self.nVersion == bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR']
205280
return script.CScript([script.OP_DUP, script.OP_HASH160, self, script.OP_EQUALVERIFY, script.OP_CHECKSIG])
206281

282+
def to_redeemScript(self):
283+
return self.to_scriptPubKey()
284+
285+
286+
class P2WSHBitcoinAddress(CBech32BitcoinAddress):
287+
288+
@classmethod
289+
def from_scriptPubKey(cls, scriptPubKey):
290+
"""Convert a scriptPubKey to a P2WSH address
291+
292+
Raises CBitcoinAddressError if the scriptPubKey isn't of the correct
293+
form.
294+
"""
295+
if scriptPubKey.is_witness_v0_scripthash():
296+
return cls.from_bytes(0, scriptPubKey[2:34])
297+
else:
298+
raise CBitcoinAddressError('not a P2WSH scriptPubKey')
299+
300+
def to_scriptPubKey(self):
301+
"""Convert an address to a scriptPubKey"""
302+
assert self.witver == 0
303+
return script.CScript([0, self])
304+
305+
def to_redeemScript(self):
306+
return NotImplementedError("not enough data in p2wsh address to reconstruct redeem script")
307+
308+
309+
class P2WPKHBitcoinAddress(CBech32BitcoinAddress):
310+
311+
@classmethod
312+
def from_scriptPubKey(cls, scriptPubKey):
313+
"""Convert a scriptPubKey to a P2WSH address
314+
315+
Raises CBitcoinAddressError if the scriptPubKey isn't of the correct
316+
form.
317+
"""
318+
if scriptPubKey.is_witness_v0_keyhash():
319+
return cls.from_bytes(0, scriptPubKey[2:22])
320+
else:
321+
raise CBitcoinAddressError('not a P2WSH scriptPubKey')
322+
323+
def to_scriptPubKey(self):
324+
"""Convert an address to a scriptPubKey"""
325+
assert self.witver == 0
326+
return script.CScript([0, self])
327+
328+
def to_redeemScript(self):
329+
return script.CScript([script.OP_DUP, script.OP_HASH160, self, script.OP_EQUALVERIFY, script.OP_CHECKSIG])
330+
207331
class CKey(object):
208332
"""An encapsulated private key
209333
@@ -256,6 +380,8 @@ def __init__(self, s):
256380
__all__ = (
257381
'CBitcoinAddressError',
258382
'CBitcoinAddress',
383+
'CBase58BitcoinAddress',
384+
'CBech32BitcoinAddress',
259385
'P2SHBitcoinAddress',
260386
'P2PKHBitcoinAddress',
261387
'CKey',

0 commit comments

Comments
 (0)