186 lines
5.2 KiB
Python
186 lines
5.2 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
|
||
|
|
||
|
__alphabet = [
|
||
|
ord(s) for s in "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||
|
]
|
||
|
__b58base = 58
|
||
|
__UINT64MAX = 2 ** 64
|
||
|
__encodedBlockSizes = [0, 2, 3, 5, 6, 7, 9, 10, 11]
|
||
|
__fullBlockSize = 8
|
||
|
__fullEncodedBlockSize = 11
|
||
|
|
||
|
|
||
|
def _hexToBin(hex_):
|
||
|
if len(hex_) % 2 != 0:
|
||
|
raise ValueError("Hex string has invalid length: %d" % len(hex_))
|
||
|
return [int(hex_[i : i + 2], 16) for i in range(0, len(hex_), 2)]
|
||
|
|
||
|
|
||
|
def _binToHex(bin_):
|
||
|
return "".join("%02x" % int(b) for b in bin_)
|
||
|
|
||
|
|
||
|
def _uint8be_to_64(data):
|
||
|
if not (1 <= len(data) <= 8):
|
||
|
raise ValueError("Invalid input length: %d" % len(data))
|
||
|
|
||
|
res = 0
|
||
|
for b in data:
|
||
|
res = res << 8 | b
|
||
|
return res
|
||
|
|
||
|
|
||
|
def _uint64_to_8be(num, size):
|
||
|
if size < 1 or size > 8:
|
||
|
raise ValueError("Invalid input length: %d" % size)
|
||
|
res = [0] * size
|
||
|
|
||
|
twopow8 = 2 ** 8
|
||
|
for i in range(size - 1, -1, -1):
|
||
|
res[i] = num % twopow8
|
||
|
num = num // twopow8
|
||
|
|
||
|
return res
|
||
|
|
||
|
|
||
|
def encode_block(data, buf, index):
|
||
|
l_data = len(data)
|
||
|
|
||
|
if l_data < 1 or l_data > __fullEncodedBlockSize:
|
||
|
raise ValueError("Invalid block length: %d" % l_data)
|
||
|
|
||
|
num = _uint8be_to_64(data)
|
||
|
i = __encodedBlockSizes[l_data] - 1
|
||
|
|
||
|
while num > 0:
|
||
|
remainder = num % __b58base
|
||
|
num = num // __b58base
|
||
|
buf[index + i] = __alphabet[remainder]
|
||
|
i -= 1
|
||
|
|
||
|
return buf
|
||
|
|
||
|
|
||
|
def encode(hex):
|
||
|
"""Encode hexadecimal string as base58 (ex: encoding a Monero address)."""
|
||
|
data = _hexToBin(hex)
|
||
|
l_data = len(data)
|
||
|
|
||
|
if l_data == 0:
|
||
|
return ""
|
||
|
|
||
|
full_block_count = l_data // __fullBlockSize
|
||
|
last_block_size = l_data % __fullBlockSize
|
||
|
res_size = (
|
||
|
full_block_count * __fullEncodedBlockSize + __encodedBlockSizes[last_block_size]
|
||
|
)
|
||
|
|
||
|
res = bytearray([__alphabet[0]] * res_size)
|
||
|
|
||
|
for i in range(full_block_count):
|
||
|
res = encode_block(
|
||
|
data[(i * __fullBlockSize) : (i * __fullBlockSize + __fullBlockSize)],
|
||
|
res,
|
||
|
i * __fullEncodedBlockSize,
|
||
|
)
|
||
|
|
||
|
if last_block_size > 0:
|
||
|
res = encode_block(
|
||
|
data[
|
||
|
(full_block_count * __fullBlockSize) : (
|
||
|
full_block_count * __fullBlockSize + last_block_size
|
||
|
)
|
||
|
],
|
||
|
res,
|
||
|
full_block_count * __fullEncodedBlockSize,
|
||
|
)
|
||
|
|
||
|
return bytes(res).decode("ascii")
|
||
|
|
||
|
|
||
|
def decode_block(data, buf, index):
|
||
|
l_data = len(data)
|
||
|
|
||
|
if l_data < 1 or l_data > __fullEncodedBlockSize:
|
||
|
raise ValueError("Invalid block length: %d" % l_data)
|
||
|
|
||
|
res_size = __encodedBlockSizes.index(l_data)
|
||
|
if res_size <= 0:
|
||
|
raise ValueError("Invalid block size: %d" % res_size)
|
||
|
|
||
|
res_num = 0
|
||
|
order = 1
|
||
|
for i in range(l_data - 1, -1, -1):
|
||
|
digit = __alphabet.index(data[i])
|
||
|
if digit < 0:
|
||
|
raise ValueError("Invalid symbol: %s" % data[i])
|
||
|
|
||
|
product = order * digit + res_num
|
||
|
if product > __UINT64MAX:
|
||
|
raise ValueError(
|
||
|
"Overflow: %d * %d + %d = %d" % (order, digit, res_num, product)
|
||
|
)
|
||
|
|
||
|
res_num = product
|
||
|
order = order * __b58base
|
||
|
|
||
|
if res_size < __fullBlockSize and 2 ** (8 * res_size) <= res_num:
|
||
|
raise ValueError("Overflow: %d doesn't fit in %d bit(s)" % (res_num, res_size))
|
||
|
|
||
|
tmp_buf = _uint64_to_8be(res_num, res_size)
|
||
|
buf[index : index + len(tmp_buf)] = tmp_buf
|
||
|
|
||
|
return buf
|
||
|
|
||
|
|
||
|
def decode(enc):
|
||
|
"""Decode a base58 string (ex: a Monero address) into hexidecimal form."""
|
||
|
enc = bytearray(enc, encoding="ascii")
|
||
|
l_enc = len(enc)
|
||
|
|
||
|
if l_enc == 0:
|
||
|
return ""
|
||
|
|
||
|
full_block_count = l_enc // __fullEncodedBlockSize
|
||
|
last_block_size = l_enc % __fullEncodedBlockSize
|
||
|
try:
|
||
|
last_block_decoded_size = __encodedBlockSizes.index(last_block_size)
|
||
|
except ValueError:
|
||
|
raise ValueError("Invalid encoded length: %d" % l_enc)
|
||
|
|
||
|
data_size = full_block_count * __fullBlockSize + last_block_decoded_size
|
||
|
|
||
|
data = bytearray(data_size)
|
||
|
for i in range(full_block_count):
|
||
|
data = decode_block(
|
||
|
enc[
|
||
|
(i * __fullEncodedBlockSize) : (
|
||
|
i * __fullEncodedBlockSize + __fullEncodedBlockSize
|
||
|
)
|
||
|
],
|
||
|
data,
|
||
|
i * __fullBlockSize,
|
||
|
)
|
||
|
|
||
|
if last_block_size > 0:
|
||
|
data = decode_block(
|
||
|
enc[
|
||
|
(full_block_count * __fullEncodedBlockSize) : (
|
||
|
full_block_count * __fullEncodedBlockSize + last_block_size
|
||
|
)
|
||
|
],
|
||
|
data,
|
||
|
full_block_count * __fullBlockSize,
|
||
|
)
|
||
|
|
||
|
return _binToHex(data)
|