Skip to content

Commit 3def138

Browse files
authored
Merge pull request #85 from alecalve/bech32
Add support for bech32 addresses
2 parents 7fa4f50 + d6665a4 commit 3def138

File tree

8 files changed

+69
-12
lines changed

8 files changed

+69
-12
lines changed

blockchain_parser/address.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,39 @@
1010
# in the LICENSE file.
1111

1212
from bitcoin import base58
13+
from bitcoin.bech32 import CBech32Data
1314
from .utils import btc_ripemd160, double_sha256
1415

1516

1617
class Address(object):
1718
"""Represents a bitcoin address"""
1819

19-
def __init__(self, hash, public_key, address, type):
20+
def __init__(self, hash, public_key, address, type, segwit_version):
2021
self._hash = hash
2122
self.public_key = public_key
2223
self._address = address
2324
self.type = type
25+
self._segwit_version = segwit_version
2426

2527
def __repr__(self):
2628
return "Address(addr=%s)" % self.address
2729

2830
@classmethod
2931
def from_public_key(cls, public_key):
3032
"""Constructs an Address object from a public key"""
31-
return cls(None, public_key, None, "normal")
33+
return cls(None, public_key, None, "normal", None)
3234

3335
@classmethod
3436
def from_ripemd160(cls, hash, type="normal"):
3537
"""Constructs an Address object from a RIPEMD-160 hash, it may be a
3638
normal address or a P2SH address, the latter is indicated by setting
3739
type to 'p2sh'"""
38-
return cls(hash, None, None, type)
40+
return cls(hash, None, None, type, None)
41+
42+
@classmethod
43+
def from_bech32(cls, hash, segwit_version):
44+
"""Constructs an Address object from a bech32 hash."""
45+
return cls(hash, None, None, "bech32", segwit_version)
3946

4047
@property
4148
def hash(self):
@@ -47,12 +54,18 @@ def hash(self):
4754

4855
@property
4956
def address(self):
50-
"""Returns the base58 encoded representation of this address"""
57+
"""Returns the encoded representation of this address.
58+
If SegWit, it's encoded using bech32, otherwise using base58
59+
"""
5160
if self._address is None:
52-
version = b'\x00' if self.type == "normal" else b'\x05'
53-
checksum = double_sha256(version + self.hash)
61+
if self.type != "bech32":
62+
version = b'\x00' if self.type == "normal" else b'\x05'
63+
checksum = double_sha256(version + self.hash)
5464

55-
self._address = base58.encode(version + self.hash + checksum[:4])
65+
self._address = base58.encode(version + self.hash + checksum[:4])
66+
else:
67+
bech_encoded = CBech32Data.from_bytes(self._segwit_version, self._hash)
68+
self._address = str(bech_encoded)
5669
return self._address
5770

5871
def is_p2sh(self):

blockchain_parser/output.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ def addresses(self):
7171
n = self.script.operations[-2]
7272
for operation in self.script.operations[1:1+n]:
7373
self._addresses.append(Address.from_public_key(operation))
74+
elif self.type == "p2wpkh":
75+
address = Address.from_bech32(self.script.operations[1], 0)
76+
self._addresses.append(address)
77+
elif self.type == "p2wsh":
78+
address = Address.from_bech32(self.script.operations[1], 0)
79+
self._addresses.append(address)
7480

7581
return self._addresses
7682

@@ -92,6 +98,12 @@ def is_multisig(self):
9298
def is_unknown(self):
9399
return self.script.is_unknown()
94100

101+
def is_p2wpkh(self):
102+
return self.script.is_p2wpkh()
103+
104+
def is_p2wsh(self):
105+
return self.script.is_p2wsh()
106+
95107
@property
96108
def type(self):
97109
"""Returns the output's script type as a string"""
@@ -114,4 +126,10 @@ def type(self):
114126
if self.is_return():
115127
return "OP_RETURN"
116128

129+
if self.is_p2wpkh():
130+
return "p2wpkh"
131+
132+
if self.is_p2wsh():
133+
return "p2wsh"
134+
117135
return "unknown"

blockchain_parser/script.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ def is_return(self):
9898
def is_p2sh(self):
9999
return self.script.is_p2sh()
100100

101+
def is_p2wsh(self):
102+
return self.script.is_witness_v0_scripthash()
103+
104+
def is_p2wpkh(self):
105+
return self.script.is_witness_v0_keyhash()
106+
101107
def is_pubkey(self):
102108
return len(self.operations) == 2 \
103109
and self.operations[-1] == OP_CHECKSIG \
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
020000000001016dee1ae581bf13134aa6aea8051b974c064bc2227094dc971561aac44a02ec500000000000ffffffff023cad17000000000017a914682ca28dc5e71f0139ad95a71c639c9e61c9c82e8773a33a0400000000160014a89e7f54db49b31364169f3e8982fcb5d3275e8b024830450221009424d2dcee3e8a00261cd3df8c7a103b14a8f0853075118329a21856f9cef36802207b9a2d51347bd2f73359375260e29fb67c67fa87825a625362a16b4a3c38f2350121030731907d43214f178bd4c9e279ac22043858001e220a935fff94f5fd3e8606a300000000
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
020000000001019425b8169d86af46b96afb6d8e1f40af44fedbb27fab196a88d1ae95b23230fd0200000000000000000260c084000000000017a914a0d015cec889e4cce8b359c6d607346e3985d66587fca67410000000002200206f49e7412b232a3d02c199caff40e9246d935a64799c369db577ecfd0929113804004730440220706ee05f17d6dee8ad42f3bf714a7e4911fed31325bad04284bc4ed7f3261e6b02203d72b089ee893164b203db722517a5bde37c791a3a256976c0379c73ad9f30110147304402207e36fcea59efb1b371dcabbe6766fb6334a498fe4769b263c00c7774df74b7f502200aea2ec5bab47ac16ed67cfc709665cfdcbd4f48d187a6a043267fe96789e1ce0169522102820ade490833f7c3e1abf944caed54b43f88f255f781a393ac4a6eedf04ff4862103c31b3287a9d6820a67c00af26c4955957ddf5fd21312c0ac8f11021b3f6192af2103ae579b44326ac0ba725038a982e3873f338c107b9d169172ca49e5bc7178469153ae00000000

blockchain_parser/tests/test_transaction.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,24 @@ def test_bip69(self):
5151
tx = Transaction(data)
5252
self.assertTrue(tx.uses_bip69())
5353

54+
def test_bech32_p2wpkh(self):
55+
example_tx = "bech32_p2wpkh.txt"
56+
with open(os.path.join(dir_path, example_tx)) as f:
57+
data = a2b_hex(f.read().strip())
58+
59+
tx = Transaction(data)
60+
self.assertEqual(["3BBqfnaPbgi5KWECWdFpvryUfw7QatWy37"], [a.address for a in tx.outputs[0].addresses])
61+
self.assertEqual(["bc1q4z0874xmfxe3xeqknulgnqhukhfjwh5tvjrr2x"], [a.address for a in tx.outputs[1].addresses])
62+
63+
def test_bech32_p2wsh(self):
64+
example_tx = "bech32_p2wsh.txt"
65+
with open(os.path.join(dir_path, example_tx)) as f:
66+
data = a2b_hex(f.read().strip())
67+
68+
tx = Transaction(data)
69+
self.assertEqual(["3GMKKFPNUg13VktgihUD8QfXVQRBdoDNDf"], [a.address for a in tx.outputs[0].addresses])
70+
self.assertEqual(["bc1qday7wsftyv4r6qkpn8907s8fy3kexkny0xwrd8d4wlk06zffzyuqpp629n"], [a.address for a in tx.outputs[1].addresses])
71+
5472
def test_segwit(self):
5573
example_tx = "segwit.txt"
5674
with open(os.path.join(dir_path, example_tx)) as f:

requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
python-bitcoinlib==0.5.0
2-
plyvel==1.0.4
3-
coverage==4.0.2
1+
python-bitcoinlib==0.11.0
2+
plyvel==1.2.0
3+
coverage==4.0.2

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
'Topic :: Software Development :: Libraries',
2020
],
2121
install_requires=[
22-
'python-bitcoinlib==0.5.0',
23-
'plyvel==1.0.4'
22+
'python-bitcoinlib==0.11.0',
23+
'plyvel==1.2.0'
2424
]
2525
)

0 commit comments

Comments
 (0)