From 0037053d73746d8cc535fe07c8fb483ed87a4b91 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 23 Dec 2022 09:12:36 +0000 Subject: [PATCH] bitcoin: 3x speedup for DecodeBase58Check (compared to HEAD~2) rewrite base_{decode,encode} based on https://github.com/keis/base58/blob/2fae7065e3446e69e5d748c767011f05eee8400e/base58/__init__.py --- electrum/bitcoin.py | 81 +++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 51 deletions(-) diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py index 82c1d9137..abbf0fea2 100644 --- a/electrum/bitcoin.py +++ b/electrum/bitcoin.py @@ -559,33 +559,26 @@ def base_encode(v: bytes, *, base: int) -> str: chars = __b58chars if base == 43: chars = __b43chars - long_value = 0 - power_of_base = 1 - for c in v[::-1]: - # naive but slow variant: long_value += (256**i) * c - long_value += power_of_base * c - power_of_base <<= 8 - result = bytearray() - while long_value >= base: - div, mod = divmod(long_value, base) - result.append(chars[mod]) - long_value = div - result.append(chars[long_value]) - # Bitcoin does a little leading-zero-compression: - # leading 0-bytes in the input become leading-1s - nPad = 0 - for c in v: - if c == 0x00: - nPad += 1 - else: - break - result.extend([chars[0]] * nPad) - result.reverse() + + origlen = len(v) + v = v.lstrip(b'\x00') + newlen = len(v) + + num = int.from_bytes(v, byteorder='big') + string = b"" + while num: + num, idx = divmod(num, base) + string = chars[idx:idx + 1] + string + + result = chars[0:1] * (origlen - newlen) + string return result.decode('ascii') -def base_decode(v: Union[bytes, str], *, base: int, length: int = None) -> Optional[bytes]: - """ decode v into a string of len bytes.""" +def base_decode(v: Union[bytes, str], *, base: int) -> Optional[bytes]: + """ decode v into a string of len bytes. + + based on the work of David Keijser in https://github.com/keis/base58 + """ # assert_bytes(v) v = to_bytes(v, 'ascii') if base not in (58, 43): @@ -595,33 +588,19 @@ def base_decode(v: Union[bytes, str], *, base: int, length: int = None) -> Optio if base == 43: chars = __b43chars chars_inv = __b43chars_inv - long_value = 0 - power_of_base = 1 - for c in v[::-1]: - try: - digit = chars_inv[c] - except KeyError: - raise BaseDecodeError('Forbidden character {} for base {}'.format(c, base)) - # naive but slow variant: long_value += digit * (base**i) - long_value += digit * power_of_base - power_of_base *= base - result = bytearray() - while long_value >= 256: - div, mod = divmod(long_value, 256) - result.append(mod) - long_value = div - result.append(long_value) - nPad = 0 - for c in v: - if c == chars[0]: - nPad += 1 - else: - break - result.extend(b'\x00' * nPad) - if length is not None and len(result) != length: - return None - result.reverse() - return bytes(result) + + origlen = len(v) + v = v.lstrip(chars[0:1]) + newlen = len(v) + + num = 0 + try: + for char in v: + num = num * base + chars_inv[char] + except KeyError: + raise BaseDecodeError('Forbidden character {} for base {}'.format(char, base)) + + return num.to_bytes(origlen - newlen + (num.bit_length() + 7) // 8, 'big') class InvalidChecksum(BaseDecodeError):