Skip to content

Commit 1959525

Browse files
committed
Get base58 support working
1 parent faad925 commit 1959525

5 files changed

Lines changed: 130 additions & 36 deletions

File tree

README

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@
22
This python library provides an easy interface to the bitcoin
33
data structures and protocol.
44

5+
6+
Unit tests
7+
----------
8+
9+
python -m unittest discover

bitcoin/base58.py

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,54 +8,92 @@
88
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
99
#
1010

11-
from bitcoin.serialize import Hash
11+
from bitcoin.serialize import Hash, ser_uint256
1212

1313
b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
1414

15-
def encode (n):
16-
l = []
15+
from binascii import hexlify, unhexlify
16+
17+
class Base58Error(Exception):
18+
pass
19+
20+
class InvalidBase58Error(Base58Error):
21+
pass
22+
23+
def encode(b):
24+
"""Encode bytes to a base58-encoded string"""
25+
26+
# Convert big-endian bytes to integer
27+
n = int('0x0' + hexlify(b), 16)
28+
29+
# Divide that integer into bas58
30+
res = []
1731
while n > 0:
1832
n, r = divmod (n, 58)
19-
l.insert (0, (b58_digits[r]))
20-
return ''.join (l)
21-
22-
def decode (s):
23-
n = 0
24-
for ch in s:
25-
n *= 58
26-
digit = b58_digits.index (ch)
27-
n += digit
28-
return n
33+
res.append(b58_digits[r])
34+
res = ''.join(res[::-1])
2935

30-
def encode_padded (s):
31-
res = base58_encode (int ('0x' + s.encode ('hex'), 16))
36+
# Encode leading zeros as base58 zeros
3237
pad = 0
33-
for c in s:
38+
for c in b:
3439
if c == chr(0): pad += 1
3540
else: break
3641
return b58_digits[0] * pad + res
3742

38-
def decode_padded (s):
39-
pad = 0
43+
def decode(s):
44+
"""Decode a base58-encoding string, returning bytes"""
45+
if not s:
46+
return b''
47+
48+
# Convert the string to an integer
49+
n = 0
4050
for c in s:
41-
if c == b58_digits[0]: pad += 1
42-
else: break
43-
h = '%x' % base58_decode (s)
51+
n *= 58
52+
if c not in b58_digits:
53+
raise InvalidBase58Error('Character %r is not a valid base58 character' % c)
54+
digit = b58_digits.index(c)
55+
n += digit
56+
57+
# Convert the integer to bytes
58+
h = '%x' % n
4459
if len(h) % 2:
4560
h = '0' + h
46-
res = h.decode ('hex')
47-
return chr(0) * pad + res
48-
49-
def key_to_address (s):
50-
vs = chr (addrtype) + s
51-
check = Hash(vs)[:4]
52-
return base58_encode_padded (vs + check)
53-
54-
def address_to_key (s):
55-
k = base58_decode_padded (s)
56-
hash160, check0 = k[1:-4], k[-4:]
57-
check1 = Hash (chr (addrtype) + hash160)[:4]
58-
if check0 != check1:
59-
return None
60-
return hash160
61+
res = unhexlify(h)
62+
63+
# Add padding back.
64+
pad = 0
65+
for c in s[:-1]:
66+
if c == b58_digits[0]: pad += 1
67+
else: break
68+
return b'\x00' * pad + res
69+
70+
71+
class Base58ChecksumError(Base58Error):
72+
pass
73+
74+
class CBase58Data(bytes):
75+
def __new__(cls, data, nVersion):
76+
self = super(CBase58Data, cls).__new__(cls, data)
77+
self.nVersion = nVersion
78+
return self
79+
80+
def __str__(self):
81+
vs = chr(self.nVersion) + self
82+
check = ser_uint256(Hash(vs))[0:4]
83+
return encode(vs + check)
84+
85+
@classmethod
86+
def from_str(cls, s):
87+
k = decode(s)
88+
addrbyte, data, check0 = k[0], k[1:-4], k[-4:]
89+
check1 = ser_uint256(Hash(addrbyte + data))[:4]
90+
if check0 != check1:
91+
raise Base58ChecksumError('Checksum mismatch: expected %r, calculated %r' % (check0, check1))
92+
return cls(data, ord(addrbyte))
93+
6194

95+
class CBitcoinAddress(CBase58Data):
96+
PUBKEY_ADDRESS = 0
97+
SCRIPT_ADDRESS = 5
98+
PUBKEY_ADDRESS_TEST = 111
99+
SCRIPT_ADDRESS_TEST = 196

bitcoin/tests/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Distributed under the MIT/X11 software license, see the accompanying
2+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
["", ""],
3+
["61", "2g"],
4+
["626262", "a3gV"],
5+
["636363", "aPEr"],
6+
["73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"],
7+
["00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"],
8+
["516b6fcd0f", "ABnLTmg"],
9+
["bf4f89001e670274dd", "3SEo3LWLoPntC"],
10+
["572e4794", "3EFU7m"],
11+
["ecac89cad93923c02321", "EJDM8drfXA6uyA"],
12+
["10c8511e", "Rt5zm"],
13+
["00000000000000000000", "1111111111"]
14+
]

bitcoin/tests/test_base58.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Distributed under the MIT/X11 software license, see the accompanying
2+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
3+
4+
from __future__ import print_function
5+
6+
import json
7+
import os
8+
import unittest
9+
10+
from binascii import unhexlify
11+
12+
from bitcoin.base58 import *
13+
14+
15+
def load_test_vector(name):
16+
with open(os.path.dirname(__file__) + '/data/' + name, 'r') as fd:
17+
for testcase in json.load(fd):
18+
yield testcase
19+
20+
class Test_base58(unittest.TestCase):
21+
def test_encode_decode(self):
22+
for exp_bin, exp_base58 in load_test_vector('base58_encode_decode.json'):
23+
exp_bin = unhexlify(exp_bin)
24+
25+
act_base58 = encode(exp_bin)
26+
act_bin = decode(exp_base58)
27+
28+
self.assertEqual(act_base58, exp_base58)
29+
self.assertEqual(act_bin, exp_bin)
30+
31+
def test_invalid_base58_exception(self):
32+
with self.assertRaises(InvalidBase58Error):
33+
decode('#')
34+
35+
# FIXME: need to test CBitcoinAddress

0 commit comments

Comments
 (0)