Skip to content

Commit a15b4d9

Browse files
committed
Drop base58 dependency, copy over David Keijser's code directly
There is no need to be remotely fetching code for a simple 100 lines file of code which should never change anymore.
1 parent 1492d39 commit a15b4d9

3 files changed

Lines changed: 182 additions & 8 deletions

File tree

bip32/base58.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
'''Base58 encoding
2+
3+
Implementations of Base58 and Base58Check encodings that are compatible
4+
with the bitcoin network.
5+
6+
This file was copied over and added to the bip32 project from David Keijser's https://github.com/keis/base58 (https://pypi.org/project/base58/). This
7+
package is released under an MIT licensed. The code was copied in this file and left untouched. Here is a copy of the MIT license accompanying the
8+
code:
9+
Copyright (c) 2015 David Keijser
10+
11+
Permission is hereby granted, free of charge, to any person obtaining a copy
12+
of this software and associated documentation files (the "Software"), to deal
13+
in the Software without restriction, including without limitation the rights
14+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15+
copies of the Software, and to permit persons to whom the Software is
16+
furnished to do so, subject to the following conditions:
17+
18+
The above copyright notice and this permission notice shall be included in
19+
all copies or substantial portions of the Software.
20+
21+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27+
THE SOFTWARE.
28+
'''
29+
30+
# This module is based upon base58 snippets found scattered over many bitcoin
31+
# tools written in python. From what I gather the original source is from a
32+
# forum post by Gavin Andresen, so direct your praise to him.
33+
# This module adds shiny packaging and support for python3.
34+
35+
from functools import lru_cache
36+
from hashlib import sha256
37+
from typing import Mapping, Union
38+
39+
# 58 character alphabet used
40+
BITCOIN_ALPHABET = \
41+
b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
42+
RIPPLE_ALPHABET = b'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz'
43+
XRP_ALPHABET = RIPPLE_ALPHABET
44+
45+
# Retro compatibility
46+
alphabet = BITCOIN_ALPHABET
47+
48+
49+
def scrub_input(v: Union[str, bytes]) -> bytes:
50+
if isinstance(v, str):
51+
v = v.encode('ascii')
52+
53+
return v
54+
55+
56+
def b58encode_int(
57+
i: int, default_one: bool = True, alphabet: bytes = BITCOIN_ALPHABET
58+
) -> bytes:
59+
"""
60+
Encode an integer using Base58
61+
"""
62+
if not i and default_one:
63+
return alphabet[0:1]
64+
string = b""
65+
base = len(alphabet)
66+
while i:
67+
i, idx = divmod(i, base)
68+
string = alphabet[idx:idx+1] + string
69+
return string
70+
71+
72+
def b58encode(
73+
v: Union[str, bytes], alphabet: bytes = BITCOIN_ALPHABET
74+
) -> bytes:
75+
"""
76+
Encode a string using Base58
77+
"""
78+
v = scrub_input(v)
79+
80+
origlen = len(v)
81+
v = v.lstrip(b'\0')
82+
newlen = len(v)
83+
84+
acc = int.from_bytes(v, byteorder='big') # first byte is most significant
85+
86+
result = b58encode_int(acc, default_one=False, alphabet=alphabet)
87+
return alphabet[0:1] * (origlen - newlen) + result
88+
89+
90+
@lru_cache()
91+
def _get_base58_decode_map(alphabet: bytes,
92+
autofix: bool) -> Mapping[int, int]:
93+
invmap = {char: index for index, char in enumerate(alphabet)}
94+
95+
if autofix:
96+
groups = [b'0Oo', b'Il1']
97+
for group in groups:
98+
pivots = [c for c in group if c in invmap]
99+
if len(pivots) == 1:
100+
for alternative in group:
101+
invmap[alternative] = invmap[pivots[0]]
102+
103+
return invmap
104+
105+
106+
def b58decode_int(
107+
v: Union[str, bytes], alphabet: bytes = BITCOIN_ALPHABET, *,
108+
autofix: bool = False
109+
) -> int:
110+
"""
111+
Decode a Base58 encoded string as an integer
112+
"""
113+
if b' ' not in alphabet:
114+
v = v.rstrip()
115+
v = scrub_input(v)
116+
117+
map = _get_base58_decode_map(alphabet, autofix=autofix)
118+
119+
decimal = 0
120+
base = len(alphabet)
121+
try:
122+
for char in v:
123+
decimal = decimal * base + map[char]
124+
except KeyError as e:
125+
raise ValueError(
126+
"Invalid character {!r}".format(chr(e.args[0]))
127+
) from None
128+
return decimal
129+
130+
131+
def b58decode(
132+
v: Union[str, bytes], alphabet: bytes = BITCOIN_ALPHABET, *,
133+
autofix: bool = False
134+
) -> bytes:
135+
"""
136+
Decode a Base58 encoded string
137+
"""
138+
v = v.rstrip()
139+
v = scrub_input(v)
140+
141+
origlen = len(v)
142+
v = v.lstrip(alphabet[0:1])
143+
newlen = len(v)
144+
145+
acc = b58decode_int(v, alphabet=alphabet, autofix=autofix)
146+
147+
return acc.to_bytes(origlen - newlen + (acc.bit_length() + 7) // 8, 'big')
148+
149+
150+
def b58encode_check(
151+
v: Union[str, bytes], alphabet: bytes = BITCOIN_ALPHABET
152+
) -> bytes:
153+
"""
154+
Encode a string using Base58 with a 4 character checksum
155+
"""
156+
v = scrub_input(v)
157+
158+
digest = sha256(sha256(v).digest()).digest()
159+
return b58encode(v + digest[:4], alphabet=alphabet)
160+
161+
162+
def b58decode_check(
163+
v: Union[str, bytes], alphabet: bytes = BITCOIN_ALPHABET, *,
164+
autofix: bool = False
165+
) -> bytes:
166+
'''Decode and verify the checksum of a Base58 encoded string'''
167+
168+
result = b58decode(v, alphabet=alphabet, autofix=autofix)
169+
result, check = result[:-4], result[-4:]
170+
digest = sha256(sha256(result).digest()).digest()
171+
172+
if check != digest[:4]:
173+
raise ValueError("Invalid checksum")
174+
175+
return result

bip32/bip32.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import base58
21
import hashlib
32
import hmac
43

4+
from .base58 import b58encode_check, b58decode_check
55
from .utils import (
66
HARDENED_INDEX,
77
_derive_hardened_private_child,
@@ -211,7 +211,7 @@ def get_xpriv_from_path(self, path):
211211
self.network,
212212
)
213213

214-
return base58.b58encode_check(extended_key).decode()
214+
return b58encode_check(extended_key).decode()
215215

216216
def get_xpub_from_path(self, path):
217217
"""Get an encoded extended pubkey from a derivation path.
@@ -242,11 +242,11 @@ def get_xpub_from_path(self, path):
242242
self.network,
243243
)
244244

245-
return base58.b58encode_check(extended_key).decode()
245+
return b58encode_check(extended_key).decode()
246246

247247
def get_xpriv(self):
248248
"""Get the base58 encoded extended private key."""
249-
return base58.b58encode_check(self.get_xpriv_bytes()).decode()
249+
return b58encode_check(self.get_xpriv_bytes()).decode()
250250

251251
def get_xpriv_bytes(self):
252252
"""Get the encoded extended private key."""
@@ -263,7 +263,7 @@ def get_xpriv_bytes(self):
263263

264264
def get_xpub(self):
265265
"""Get the encoded extended public key."""
266-
return base58.b58encode_check(self.get_xpub_bytes()).decode()
266+
return b58encode_check(self.get_xpub_bytes()).decode()
267267

268268
def get_xpub_bytes(self):
269269
"""Get the encoded extended public key."""
@@ -285,7 +285,7 @@ def from_xpriv(cls, xpriv):
285285
if not isinstance(xpriv, str):
286286
raise InvalidInputError("'xpriv' must be a string")
287287

288-
extended_key = base58.b58decode_check(xpriv)
288+
extended_key = b58decode_check(xpriv)
289289
(
290290
network,
291291
depth,
@@ -313,7 +313,7 @@ def from_xpub(cls, xpub):
313313
if not isinstance(xpub, str):
314314
raise InvalidInputError("'xpub' must be a string")
315315

316-
extended_key = base58.b58decode_check(xpub)
316+
extended_key = b58decode_check(xpub)
317317
(
318318
network,
319319
depth,

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
coincurve>=15.0,<19
2-
base58~=2.0

0 commit comments

Comments
 (0)