wownero-python/monero/base58.py

152 lines
4.5 KiB
Python
Raw Normal View History

2017-11-24 02:05:16 +00:00
# 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.
2018-07-03 12:43:11 +00:00
#
# Modified by emesik and rooterkyberian:
# + optimized
# + proper exceptions instead of returning errors as results
2017-11-24 02:05:16 +00:00
__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
2018-07-03 12:02:56 +00:00
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)]
2017-11-24 02:05:16 +00:00
2018-07-03 12:02:56 +00:00
def _binToHex(bin_):
return "".join('%02x' % int(b) for b in bin_)
2017-11-24 02:05:16 +00:00
def _uint8be_to_64(data):
if not (1 <= len(data) <= 8):
raise ValueError("Invalid input length: %d" % len(data))
2017-11-24 02:05:16 +00:00
res = 0
for b in data:
res = res << 8 | b
2017-11-24 02:05:16 +00:00
return res
2018-07-03 12:02:56 +00:00
2017-11-24 02:05:16 +00:00
def _uint64_to_8be(num, size):
if size < 1 or size > 8:
raise ValueError("Invalid input length: %d" % size)
res = [0] * size
2017-11-24 02:05:16 +00:00
twopow8 = 2**8
for i in range(size-1,-1,-1):
res[i] = num % twopow8
num = num // twopow8
return res
2018-07-03 12:02:56 +00:00
2017-11-24 02:05:16 +00:00
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)
2017-11-24 02:05:16 +00:00
num = _uint8be_to_64(data)
i = __encodedBlockSizes[l_data] - 1
while num > 0:
remainder = num % __b58base
num = num // __b58base
buf[index+i] = __alphabet[remainder]
2017-11-24 02:05:16 +00:00
i -= 1
return buf
2018-07-03 12:02:56 +00:00
2017-11-24 02:05:16 +00:00
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]
2018-07-03 12:02:56 +00:00
res = bytearray([__alphabet[0]] * res_size)
2017-11-24 02:05:16 +00:00
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)
2018-07-03 12:02:56 +00:00
return bytes(res).decode('ascii')
2017-11-24 02:05:16 +00:00
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)
2017-11-24 02:05:16 +00:00
res_size = __encodedBlockSizes.index(l_data)
if res_size <= 0:
raise ValueError("Invalid block size: %d" % res_size)
2017-11-24 02:05:16 +00:00
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])
2017-11-24 02:05:16 +00:00
product = order * digit + res_num
if product > __UINT64MAX:
raise ValueError("Overflow: %d * %d + %d = %d" % (order, digit, res_num, product))
2017-11-24 02:05:16 +00:00
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))
2017-11-24 02:05:16 +00:00
tmp_buf = _uint64_to_8be(res_num, res_size)
2018-07-03 12:02:56 +00:00
buf[index:index + len(tmp_buf)] = tmp_buf
2017-11-24 02:05:16 +00:00
return buf
2018-07-03 12:02:56 +00:00
2017-11-24 02:05:16 +00:00
def decode(enc):
'''Decode a base58 string (ex: a Monero address) into hexidecimal form.'''
2018-07-03 12:02:56 +00:00
enc = bytearray(enc, encoding='ascii')
2017-11-24 02:05:16 +00:00
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)
2017-11-24 02:05:16 +00:00
data_size = full_block_count * __fullBlockSize + last_block_decoded_size
2018-07-03 12:02:56 +00:00
data = bytearray(data_size)
2017-11-24 02:05:16 +00:00
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)