|
25 | 25 |
|
26 | 26 | import bitcoin |
27 | 27 | import bitcoin.base58 |
| 28 | +import bitcoin.bech32 |
28 | 29 | import bitcoin.core |
29 | 30 | import bitcoin.core.key |
30 | 31 | import bitcoin.core.script as script |
31 | 32 |
|
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): |
33 | 66 | """Raised when an invalid Bitcoin address is encountered""" |
34 | 67 |
|
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""" |
37 | 111 |
|
38 | 112 | @classmethod |
39 | 113 | def from_bytes(cls, data, nVersion): |
40 | | - self = super(CBitcoinAddress, cls).from_bytes(data, nVersion) |
| 114 | + self = super(CBase58BitcoinAddress, cls).from_bytes(data, nVersion) |
41 | 115 |
|
42 | 116 | if nVersion == bitcoin.params.BASE58_PREFIXES['SCRIPT_ADDR']: |
43 | 117 | self.__class__ = P2SHBitcoinAddress |
@@ -68,13 +142,10 @@ def from_scriptPubKey(cls, scriptPubKey): |
68 | 142 | except CBitcoinAddressError: |
69 | 143 | pass |
70 | 144 |
|
71 | | - raise CBitcoinAddressError('scriptPubKey not a valid address') |
| 145 | + raise CBitcoinAddressError('scriptPubKey not a valid base58-encoded address') |
72 | 146 |
|
73 | | - def to_scriptPubKey(self): |
74 | | - """Convert an address to a scriptPubKey""" |
75 | | - raise NotImplementedError |
76 | 147 |
|
77 | | -class P2SHBitcoinAddress(CBitcoinAddress): |
| 148 | +class P2SHBitcoinAddress(CBase58BitcoinAddress): |
78 | 149 | @classmethod |
79 | 150 | def from_bytes(cls, data, nVersion=None): |
80 | 151 | if nVersion is None: |
@@ -112,7 +183,11 @@ def to_scriptPubKey(self): |
112 | 183 | assert self.nVersion == bitcoin.params.BASE58_PREFIXES['SCRIPT_ADDR'] |
113 | 184 | return script.CScript([script.OP_HASH160, self, script.OP_EQUAL]) |
114 | 185 |
|
115 | | -class P2PKHBitcoinAddress(CBitcoinAddress): |
| 186 | + def to_redeemScript(self): |
| 187 | + return self.to_scriptPubKey() |
| 188 | + |
| 189 | + |
| 190 | +class P2PKHBitcoinAddress(CBase58BitcoinAddress): |
116 | 191 | @classmethod |
117 | 192 | def from_bytes(cls, data, nVersion=None): |
118 | 193 | if nVersion is None: |
@@ -204,6 +279,55 @@ def to_scriptPubKey(self, nested=False): |
204 | 279 | assert self.nVersion == bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR'] |
205 | 280 | return script.CScript([script.OP_DUP, script.OP_HASH160, self, script.OP_EQUALVERIFY, script.OP_CHECKSIG]) |
206 | 281 |
|
| 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 | + |
207 | 331 | class CKey(object): |
208 | 332 | """An encapsulated private key |
209 | 333 |
|
@@ -256,6 +380,8 @@ def __init__(self, s): |
256 | 380 | __all__ = ( |
257 | 381 | 'CBitcoinAddressError', |
258 | 382 | 'CBitcoinAddress', |
| 383 | + 'CBase58BitcoinAddress', |
| 384 | + 'CBech32BitcoinAddress', |
259 | 385 | 'P2SHBitcoinAddress', |
260 | 386 | 'P2PKHBitcoinAddress', |
261 | 387 | 'CKey', |
|
0 commit comments