1
- ''' Base58 encoding
1
+ """ Base58 encoding
2
2
3
3
Implementations of Base58 and Base58Check encodings that are compatible
4
4
with the bitcoin network.
5
- '''
5
+ """
6
6
7
7
# This module is based upon base58 snippets found scattered over many bitcoin
8
8
# tools written in python. From what I gather the original source is from a
11
11
12
12
from functools import lru_cache
13
13
from hashlib import sha256
14
- from typing import Mapping , Union
14
+ from typing import Dict , Tuple , Union
15
15
from math import log
16
16
17
17
try :
18
18
from gmpy2 import mpz
19
19
except ImportError :
20
20
mpz = None
21
21
22
- __version__ = ' 2.1.1'
22
+ __version__ = " 2.1.1"
23
23
24
24
# 58 character alphabet used
25
- BITCOIN_ALPHABET = \
26
- b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
27
- RIPPLE_ALPHABET = b'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz'
25
+ BITCOIN_ALPHABET = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
26
+ RIPPLE_ALPHABET = b"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"
28
27
XRP_ALPHABET = RIPPLE_ALPHABET
28
+ _MPZ_ALPHABET = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
29
29
POWERS = {
30
- 45 : {2 ** i : 45 ** (2 ** i ) for i in range (4 , 20 )},
31
- 58 : {2 ** i : 58 ** (2 ** i ) for i in range (4 , 20 )}
32
- }
30
+ 45 : {2 ** i : 45 ** (2 ** i ) for i in range (4 , 20 )},
31
+ 58 : {2 ** i : 58 ** (2 ** i ) for i in range (4 , 20 )},
32
+ } # type: Dict[int, Dict[int, int]]
33
33
34
34
# Retro compatibility
35
35
alphabet = BITCOIN_ALPHABET
36
36
37
37
38
38
def scrub_input (v : Union [str , bytes ]) -> bytes :
39
39
if isinstance (v , str ):
40
- v = v .encode (' ascii' )
40
+ v = v .encode (" ascii" )
41
41
42
42
return v
43
43
@@ -52,21 +52,20 @@ def _encode_int(i: int, base: int = 58, alphabet: bytes = BITCOIN_ALPHABET) -> b
52
52
while i :
53
53
i , idx = divmod (i , base )
54
54
string .append (idx )
55
- return string [::- 1 ]
55
+ return bytes ( string [::- 1 ])
56
56
else :
57
- origlen0 = int (log (i , 58 ))// 2
57
+ origlen0 = int (log (i , 58 )) // 2
58
58
try :
59
59
split_num = POWERS [base ][2 ** origlen0 ]
60
60
except KeyError :
61
- POWERS [base ][2 ** origlen0 ] = split_num = base ** origlen0
61
+ POWERS [base ][2 ** origlen0 ] = split_num = base ** origlen0
62
62
i1 , i0 = divmod (i , split_num )
63
63
64
64
v1 = _encode_int (i1 , base , alphabet )
65
65
v0 = _encode_int (i0 , base , alphabet )
66
66
newlen0 = len (v0 )
67
67
if newlen0 < origlen0 :
68
- v0 [:0 ] = b'\0 ' * (origlen0 - newlen0 )
69
-
68
+ v0 = b"\0 " * (origlen0 - newlen0 ) + v0
70
69
return v1 + v0
71
70
72
71
@@ -77,7 +76,7 @@ def _mpz_encode(i: int, alphabet: bytes) -> bytes:
77
76
base = len (alphabet )
78
77
79
78
raw : bytes = mpz (i ).digits (base ).encode ()
80
- tr_bytes = bytes .maketrans ('' . join ([ mpz ( x ). digits ( base ) for x in range ( base )]). encode () , alphabet )
79
+ tr_bytes = bytes .maketrans (_MPZ_ALPHABET [: base ] , alphabet )
81
80
encoded : bytes = raw .translate (tr_bytes )
82
81
83
82
return encoded
@@ -92,49 +91,56 @@ def b58encode_int(
92
91
if not i :
93
92
if default_one :
94
93
return alphabet [0 :1 ]
95
- return b''
94
+ return b""
96
95
if mpz :
97
96
return _mpz_encode (i , alphabet )
98
97
99
98
base = len (alphabet )
100
99
raw_string = _encode_int (i , base , alphabet )
101
- string = raw_string .translate (bytes .maketrans (bytearray (range (len (alphabet ))), alphabet ))
100
+ string = raw_string .translate (
101
+ bytes .maketrans (bytearray (range (len (alphabet ))), alphabet )
102
+ )
102
103
103
104
return string
104
105
105
106
106
- def b58encode (
107
- v : Union [str , bytes ], alphabet : bytes = BITCOIN_ALPHABET
108
- ) -> bytes :
107
+ def b58encode (v : Union [str , bytes ], alphabet : bytes = BITCOIN_ALPHABET ) -> bytes :
109
108
"""
110
109
Encode a string using Base58
111
110
"""
112
111
v = scrub_input (v )
113
112
114
113
origlen = len (v )
115
- v = v .lstrip (b' \0 ' )
114
+ v = v .lstrip (b" \0 " )
116
115
newlen = len (v )
117
116
118
- acc = int .from_bytes (v , byteorder = ' big' ) # first byte is most significant
117
+ acc = int .from_bytes (v , byteorder = " big" ) # first byte is most significant
119
118
120
119
result = b58encode_int (acc , default_one = False , alphabet = alphabet )
121
120
return alphabet [0 :1 ] * (origlen - newlen ) + result
122
121
123
122
124
123
@lru_cache ()
125
- def _get_base58_decode_map (alphabet : bytes ,
126
- autofix : bool ) -> Mapping [int , int ]:
124
+ def _get_base58_decode_map (alphabet : bytes , autofix : bool ) -> Tuple [bytes , bytes ]:
127
125
invmap = {char : index for index , char in enumerate (alphabet )}
128
-
126
+ base = len ( alphabet )
129
127
if autofix :
130
- groups = [b' 0Oo' , b' Il1' ]
128
+ groups = [b" 0Oo" , b" Il1" ]
131
129
for group in groups :
132
130
pivots = [c for c in group if c in invmap ]
133
131
if len (pivots ) == 1 :
134
132
for alternative in group :
135
133
invmap [alternative ] = invmap [pivots [0 ]]
136
134
137
- return invmap
135
+ del_chars = bytes (bytearray (x for x in range (256 ) if x not in invmap ))
136
+
137
+ if mpz is not None :
138
+ mpz_alphabet = "" .join ([mpz (x ).digits (base ) for x in invmap .values ()]).encode ()
139
+ tr_bytes = bytes .maketrans (bytearray (invmap .keys ()), mpz_alphabet )
140
+ return tr_bytes , del_chars
141
+
142
+ tr_bytes = bytes .maketrans (bytearray (invmap .keys ()), bytearray (invmap .values ()))
143
+ return tr_bytes , del_chars
138
144
139
145
140
146
def _decode (data : bytes , min_split : int = 256 , base : int = 58 ) -> int :
@@ -147,39 +153,32 @@ def _decode(data: bytes, min_split: int = 256, base: int = 58) -> int:
147
153
ret_int = base * ret_int + val
148
154
return ret_int
149
155
else :
150
- split_len = 2 ** (len (data ).bit_length ()- 2 )
156
+ split_len = 2 ** (len (data ).bit_length () - 2 )
151
157
try :
152
158
base_pow = POWERS [base ][split_len ]
153
159
except KeyError :
154
- POWERS [base ] = base_pow = base ** split_len
160
+ POWERS [base ] = base_pow = base ** split_len
155
161
return (base_pow * _decode (data [:- split_len ])) + _decode (data [- split_len :])
156
162
157
163
158
164
def b58decode_int (
159
- v : Union [str , bytes ], alphabet : bytes = BITCOIN_ALPHABET , * ,
160
- autofix : bool = False
165
+ v : Union [str , bytes ], alphabet : bytes = BITCOIN_ALPHABET , * , autofix : bool = False
161
166
) -> int :
162
167
"""
163
168
Decode a Base58 encoded string as an integer
164
169
"""
165
- if b' ' not in alphabet :
170
+ if b" " not in alphabet :
166
171
v = v .rstrip ()
167
172
v = scrub_input (v )
168
173
169
174
base = len (alphabet )
170
- map = _get_base58_decode_map (alphabet , autofix = autofix )
171
- if mpz :
172
- tr_bytes = bytes .maketrans (bytearray (map .keys ()), '' .join ([mpz (x ).digits (base ) for x in map .values ()]).encode ())
173
- else :
174
- tr_bytes = bytes .maketrans (bytearray (map .keys ()), bytearray (map .values ()))
175
- del_chars = bytes (bytearray (x for x in range (256 ) if x not in map ))
176
-
175
+ tr_bytes , del_chars = _get_base58_decode_map (alphabet , autofix = autofix )
177
176
cv = v .translate (tr_bytes , delete = del_chars )
178
177
if len (v ) != len (cv ):
179
- err_char = chr (next (c for c in v if c not in map ))
178
+ err_char = chr (next (c for c in v if c in del_chars ))
180
179
raise ValueError ("Invalid character {!r}" .format (err_char ))
181
180
182
- if cv == b'' :
181
+ if cv == b"" :
183
182
return 0
184
183
185
184
if mpz :
@@ -192,8 +191,7 @@ def b58decode_int(
192
191
193
192
194
193
def b58decode (
195
- v : Union [str , bytes ], alphabet : bytes = BITCOIN_ALPHABET , * ,
196
- autofix : bool = False
194
+ v : Union [str , bytes ], alphabet : bytes = BITCOIN_ALPHABET , * , autofix : bool = False
197
195
) -> bytes :
198
196
"""
199
197
Decode a Base58 encoded string
@@ -210,9 +208,7 @@ def b58decode(
210
208
return acc .to_bytes (origlen - newlen + (acc .bit_length () + 7 ) // 8 , "big" )
211
209
212
210
213
- def b58encode_check (
214
- v : Union [str , bytes ], alphabet : bytes = BITCOIN_ALPHABET
215
- ) -> bytes :
211
+ def b58encode_check (v : Union [str , bytes ], alphabet : bytes = BITCOIN_ALPHABET ) -> bytes :
216
212
"""
217
213
Encode a string using Base58 with a 4 character checksum
218
214
"""
@@ -223,10 +219,9 @@ def b58encode_check(
223
219
224
220
225
221
def b58decode_check (
226
- v : Union [str , bytes ], alphabet : bytes = BITCOIN_ALPHABET , * ,
227
- autofix : bool = False
222
+ v : Union [str , bytes ], alphabet : bytes = BITCOIN_ALPHABET , * , autofix : bool = False
228
223
) -> bytes :
229
- ''' Decode and verify the checksum of a Base58 encoded string'''
224
+ """ Decode and verify the checksum of a Base58 encoded string"""
230
225
231
226
result = b58decode (v , alphabet = alphabet , autofix = autofix )
232
227
result , check = result [:- 4 ], result [- 4 :]
0 commit comments