ledger-app-monero/tests/monero_client/utils/base58.py

192 lines
5.5 KiB
Python

"""MoneroPy - A python toolbox for Monero
Copyright (C) 2016 The MoneroPy Developers.
MoneroPy is released under the BSD 3-Clause license. Use and redistribution of
this software is subject to the license terms in the LICENSE file found in the
top-level directory of this distribution.
Modified by emesik and rooterkyberian:
+ optimized
+ proper exceptions instead of returning errors as results
"""
from typing import List
ALPHABET: List[int] = [
ord(s) for s in ("123456789ABCDEFGHJKLMNPQRSTUVWXYZ"
"abcdefghijkmnopqrstuvwxyz")
]
B58_BASE: int = 58
UINT64_MAX: int = 2 ** 64
ENCODED_BLOCK_SIZES: List[int] = [0, 2, 3, 5, 6, 7, 9, 10, 11]
FULL_BLOCK_SIZE: int = 8
FULL_ENCODED_BLOCK_SIZE: int = 11
def hex_to_bin(h: str) -> List[int]:
if len(h) % 2 != 0:
raise ValueError(f"Hex string has invalid length: {len(h)}")
return [int(h[i:i + 2], 16) for i in range(0, len(h), 2)]
def bin_to_hex(b: bytes) -> str:
return "".join("%02x" % int(b) for b in b)
def uint8be_to_64(data: bytes) -> int:
if not 1 <= len(data) <= 8:
raise ValueError(f"Invalid input length: {len(data)}")
res: int = 0
for b in data: # type: int
res = res << 8 | b
return res
def uint64_to_8be(num: int, size: int) -> List[int]:
if size < 1 or size > 8:
raise ValueError(f"Invalid input length: {size}")
res: List[int] = [0] * size
two_pow8: int = 2 ** 8
for i in range(size - 1, -1, -1): # type: int
res[i] = num % two_pow8
num = num // two_pow8
return res
def encode_block(data: bytes, buf: bytearray, index: int) -> bytearray:
l_data: int = len(data)
if l_data < 1 or l_data > FULL_ENCODED_BLOCK_SIZE:
raise ValueError(f"Invalid block length: {l_data}")
remainder: int
num: int = uint8be_to_64(data)
i: int = ENCODED_BLOCK_SIZES[l_data] - 1
while num > 0:
remainder = num % B58_BASE
num = num // B58_BASE
buf[index + i] = ALPHABET[remainder]
i -= 1
return buf
def encode(data: bytes) -> str:
"""Encode data bytes as base58 (ex: encoding a Monero address)."""
l_data: int = len(data)
if l_data == 0:
return ""
full_block_count: int = l_data // FULL_BLOCK_SIZE
last_block_size: int = l_data % FULL_BLOCK_SIZE
res_size: int = (full_block_count *
FULL_ENCODED_BLOCK_SIZE +
ENCODED_BLOCK_SIZES[last_block_size])
res: bytearray = bytearray([ALPHABET[0]] * res_size)
for i in range(full_block_count): # type: int
res = encode_block(
data[(i * FULL_BLOCK_SIZE):(i * FULL_BLOCK_SIZE + FULL_BLOCK_SIZE)],
res,
i * FULL_ENCODED_BLOCK_SIZE
)
if last_block_size > 0:
begin: int = full_block_count * FULL_BLOCK_SIZE
end: int = full_block_count * FULL_BLOCK_SIZE + last_block_size
res = encode_block(data[begin:end],
res,
full_block_count * FULL_ENCODED_BLOCK_SIZE)
return bytes(res).decode("ascii")
def decode_block(data: bytes, buf: bytearray, index: int) -> bytearray:
l_data: int = len(data)
if l_data < 1 or l_data > FULL_ENCODED_BLOCK_SIZE:
raise ValueError(f"Invalid block length: {l_data}")
res_size: int = ENCODED_BLOCK_SIZES.index(l_data)
if res_size <= 0:
raise ValueError(f"Invalid block size: {res_size}")
res_num: int = 0
order: int = 1
for i in range(l_data - 1, -1, -1): # type: int
digit: int = ALPHABET.index(data[i])
if digit < 0:
raise ValueError(f"Invalid symbol: {data[i]}")
product: int = order * digit + res_num
if product > UINT64_MAX:
raise ValueError(
f"Overflow: {order} * {digit} + {res_num} = {product}"
)
res_num = product
order = order * B58_BASE
if res_size < FULL_BLOCK_SIZE and 2 ** (8 * res_size) <= res_num:
raise ValueError(f"Overflow: {res_num} doesn't fit in {res_size} bit(s)")
tmp_buf: List[int] = uint64_to_8be(res_num, res_size)
buf[index:index + len(tmp_buf)] = tmp_buf
return buf
def decode(encoded_value: str) -> str:
"""Decode a base58 string (ex: a Monero address) into hexidecimal form."""
data_encoded: bytearray = bytearray(encoded_value, encoding="ascii")
l_enc: int = len(data_encoded)
if l_enc == 0:
return ""
full_block_count: int = l_enc // FULL_ENCODED_BLOCK_SIZE
last_block_size: int = l_enc % FULL_ENCODED_BLOCK_SIZE
try:
last_block_decoded_size: int = ENCODED_BLOCK_SIZES.index(last_block_size)
except ValueError:
raise ValueError(f"Invalid encoded length: {l_enc}")
data_size: int = (full_block_count *
FULL_BLOCK_SIZE +
last_block_decoded_size)
data: bytearray = bytearray(data_size)
begin: int
end: int
for i in range(full_block_count): # type: int
begin = i * FULL_ENCODED_BLOCK_SIZE
end = i * FULL_ENCODED_BLOCK_SIZE + FULL_ENCODED_BLOCK_SIZE
data = decode_block(data_encoded[begin:end],
data,
i * FULL_BLOCK_SIZE)
if last_block_size > 0:
begin = full_block_count * FULL_ENCODED_BLOCK_SIZE
end = full_block_count * FULL_ENCODED_BLOCK_SIZE + last_block_size
data = decode_block(data_encoded[begin:end],
data,
full_block_count * FULL_BLOCK_SIZE)
return bin_to_hex(data)