Skip to content

Commit 66adc8d

Browse files
committed
Cache translate bytes instead of mapping
Apply Black code style Quiet mypy errors
1 parent 1c214fd commit 66adc8d

File tree

5 files changed

+146
-149
lines changed

5 files changed

+146
-149
lines changed

base58/__init__.py

Lines changed: 46 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
'''Base58 encoding
1+
"""Base58 encoding
22
33
Implementations of Base58 and Base58Check encodings that are compatible
44
with the bitcoin network.
5-
'''
5+
"""
66

77
# This module is based upon base58 snippets found scattered over many bitcoin
88
# tools written in python. From what I gather the original source is from a
@@ -11,33 +11,33 @@
1111

1212
from functools import lru_cache
1313
from hashlib import sha256
14-
from typing import Mapping, Union
14+
from typing import Dict, Tuple, Union
1515
from math import log
1616

1717
try:
1818
from gmpy2 import mpz
1919
except ImportError:
2020
mpz = None
2121

22-
__version__ = '2.1.1'
22+
__version__ = "2.1.1"
2323

2424
# 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"
2827
XRP_ALPHABET = RIPPLE_ALPHABET
28+
_MPZ_ALPHABET = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
2929
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]]
3333

3434
# Retro compatibility
3535
alphabet = BITCOIN_ALPHABET
3636

3737

3838
def scrub_input(v: Union[str, bytes]) -> bytes:
3939
if isinstance(v, str):
40-
v = v.encode('ascii')
40+
v = v.encode("ascii")
4141

4242
return v
4343

@@ -52,21 +52,20 @@ def _encode_int(i: int, base: int = 58, alphabet: bytes = BITCOIN_ALPHABET) -> b
5252
while i:
5353
i, idx = divmod(i, base)
5454
string.append(idx)
55-
return string[::-1]
55+
return bytes(string[::-1])
5656
else:
57-
origlen0 = int(log(i, 58))//2
57+
origlen0 = int(log(i, 58)) // 2
5858
try:
5959
split_num = POWERS[base][2**origlen0]
6060
except KeyError:
61-
POWERS[base][2**origlen0] = split_num = base ** origlen0
61+
POWERS[base][2**origlen0] = split_num = base**origlen0
6262
i1, i0 = divmod(i, split_num)
6363

6464
v1 = _encode_int(i1, base, alphabet)
6565
v0 = _encode_int(i0, base, alphabet)
6666
newlen0 = len(v0)
6767
if newlen0 < origlen0:
68-
v0[:0] = b'\0' * (origlen0 - newlen0)
69-
68+
v0 = b"\0" * (origlen0 - newlen0) + v0
7069
return v1 + v0
7170

7271

@@ -77,7 +76,7 @@ def _mpz_encode(i: int, alphabet: bytes) -> bytes:
7776
base = len(alphabet)
7877

7978
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)
8180
encoded: bytes = raw.translate(tr_bytes)
8281

8382
return encoded
@@ -92,49 +91,56 @@ def b58encode_int(
9291
if not i:
9392
if default_one:
9493
return alphabet[0:1]
95-
return b''
94+
return b""
9695
if mpz:
9796
return _mpz_encode(i, alphabet)
9897

9998
base = len(alphabet)
10099
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+
)
102103

103104
return string
104105

105106

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:
109108
"""
110109
Encode a string using Base58
111110
"""
112111
v = scrub_input(v)
113112

114113
origlen = len(v)
115-
v = v.lstrip(b'\0')
114+
v = v.lstrip(b"\0")
116115
newlen = len(v)
117116

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
119118

120119
result = b58encode_int(acc, default_one=False, alphabet=alphabet)
121120
return alphabet[0:1] * (origlen - newlen) + result
122121

123122

124123
@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]:
127125
invmap = {char: index for index, char in enumerate(alphabet)}
128-
126+
base = len(alphabet)
129127
if autofix:
130-
groups = [b'0Oo', b'Il1']
128+
groups = [b"0Oo", b"Il1"]
131129
for group in groups:
132130
pivots = [c for c in group if c in invmap]
133131
if len(pivots) == 1:
134132
for alternative in group:
135133
invmap[alternative] = invmap[pivots[0]]
136134

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
138144

139145

140146
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:
147153
ret_int = base * ret_int + val
148154
return ret_int
149155
else:
150-
split_len = 2**(len(data).bit_length()-2)
156+
split_len = 2 ** (len(data).bit_length() - 2)
151157
try:
152158
base_pow = POWERS[base][split_len]
153159
except KeyError:
154-
POWERS[base] = base_pow = base ** split_len
160+
POWERS[base] = base_pow = base**split_len
155161
return (base_pow * _decode(data[:-split_len])) + _decode(data[-split_len:])
156162

157163

158164
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
161166
) -> int:
162167
"""
163168
Decode a Base58 encoded string as an integer
164169
"""
165-
if b' ' not in alphabet:
170+
if b" " not in alphabet:
166171
v = v.rstrip()
167172
v = scrub_input(v)
168173

169174
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)
177176
cv = v.translate(tr_bytes, delete=del_chars)
178177
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))
180179
raise ValueError("Invalid character {!r}".format(err_char))
181180

182-
if cv == b'':
181+
if cv == b"":
183182
return 0
184183

185184
if mpz:
@@ -192,8 +191,7 @@ def b58decode_int(
192191

193192

194193
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
197195
) -> bytes:
198196
"""
199197
Decode a Base58 encoded string
@@ -210,9 +208,7 @@ def b58decode(
210208
return acc.to_bytes(origlen - newlen + (acc.bit_length() + 7) // 8, "big")
211209

212210

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:
216212
"""
217213
Encode a string using Base58 with a 4 character checksum
218214
"""
@@ -223,10 +219,9 @@ def b58encode_check(
223219

224220

225221
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
228223
) -> bytes:
229-
'''Decode and verify the checksum of a Base58 encoded string'''
224+
"""Decode and verify the checksum of a Base58 encoded string"""
230225

231226
result = b58decode(v, alphabet=alphabet, autofix=autofix)
232227
result, check = result[:-4], result[-4:]

base58/__main__.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,22 @@ def main() -> None:
2020

2121
parser = argparse.ArgumentParser(description=__doc__)
2222
parser.add_argument(
23-
'file',
24-
metavar='FILE',
25-
nargs='?',
26-
type=argparse.FileType('r'),
27-
help=(
28-
"File to encode or decode. If no file is provided standard "
29-
"input is used instead"),
30-
default='-')
23+
"file",
24+
metavar="FILE",
25+
nargs="?",
26+
type=argparse.FileType("r"),
27+
help="File to encode or decode. If no file is provided standard input is used instead",
28+
default="-",
29+
)
3130
parser.add_argument(
32-
'-d', '--decode',
33-
action='store_true',
34-
help="decode data instead of encoding")
31+
"-d", "--decode", action="store_true", help="decode data instead of encoding"
32+
)
3533
parser.add_argument(
36-
'-c', '--check',
37-
action='store_true',
38-
help=(
39-
"calculate a checksum and append to encoded data or verify "
40-
"existing checksum when decoding"))
34+
"-c",
35+
"--check",
36+
action="store_true",
37+
help="calculate a checksum and append to encoded data or verify existing checksum when decoding",
38+
)
4139

4240
args = parser.parse_args()
4341
fun = _fmap[(args.decode, args.check)]
@@ -52,5 +50,5 @@ def main() -> None:
5250
stdout.write(result)
5351

5452

55-
if __name__ == '__main__':
53+
if __name__ == "__main__":
5654
main()

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,5 @@ ignore_missing_imports = True
5757
[mypy-pytest.*]
5858
ignore_missing_imports = True
5959

60+
[mypy-gmpy2.*]
61+
ignore_missing_imports = True

0 commit comments

Comments
 (0)