diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py index 7afde6e54c7..b9906ce72f5 100644 --- a/electrum/bitcoin.py +++ b/electrum/bitcoin.py @@ -539,7 +539,10 @@ class BaseDecodeError(BitcoinException): pass def base_encode(v: bytes, *, base: int) -> str: - """ encode v, which is a string of bytes, to base58.""" + """ encode v, which is a string of bytes, to base58. + + note: time complexity is O(len(v)^2), due to big-int arithmetic. + """ assert_bytes(v) if base not in (58, 43): raise ValueError('not supported base: {}'.format(base)) @@ -552,10 +555,11 @@ def base_encode(v: bytes, *, base: int) -> str: newlen = len(v) num = int.from_bytes(v, byteorder='big') - string = b"" + string_rev = bytearray() while num: num, idx = divmod(num, base) - string = chars[idx:idx + 1] + string + string_rev += chars[idx:idx + 1] + string = string_rev[::-1] result = chars[0:1] * (origlen - newlen) + string return result.decode('ascii') @@ -564,6 +568,8 @@ def base_encode(v: bytes, *, base: int) -> str: def base_decode(v: Union[bytes, str], *, base: int) -> Optional[bytes]: """ decode v into a string of len bytes. + note: time complexity is O(len(v)^2), due to big-int arithmetic. + based on the work of David Keijser in https://github.com/keis/base58 """ # assert_bytes(v) diff --git a/electrum/transaction.py b/electrum/transaction.py index 46b9351c765..3641e639935 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -1228,7 +1228,8 @@ def to_qr_data(self) -> Tuple[str, bool]: tx.convert_all_utxos_to_witness_utxos() is_complete = False tx_bytes = tx.serialize_as_bytes() - return base_encode(tx_bytes, base=43), is_complete + tx_base43 = base_encode(tx_bytes, base=43) # FIXME this takes quadratic time in len(tx) + return tx_base43, is_complete def txid(self) -> Optional[str]: if self._cached_txid is None: @@ -1499,17 +1500,26 @@ def convert_raw_tx_to_hex(raw: Union[str, bytes]) -> str: return binascii.unhexlify(raw).hex() except Exception: pass - # try base43 - try: - return base_decode(raw, base=43).hex() - except Exception: - pass # try base64 if raw[0:6] in ('cHNidP', b'cHNidP'): # base64 psbt try: return base64.b64decode(raw, validate=True).hex() except Exception: pass + # try base43 + try: + # FIXME This takes quadratic time in len(tx). + # We could prefix all txs we base43-serialize with e.g. "BASE43TX:", + # (and break-compat with old versions). Then at least we would not attempt + # the expensive deser here if it's not needed. + if len(raw) > 30_000: + # note: base_decode for this length takes around 0.2 sec on my laptop. + # note: We only use/expect base43 inside QR codes. The max data a QR can fit is around 4 KB, + # serializing that to b43 results in a length of ~5500. 30k is already over 5x that. + raise ValueError("raw tx too large for base43") + return base_decode(raw, base=43).hex() + except Exception: + pass # raw bytes if isinstance(raw, (bytes, bytearray)): return raw.hex()