This repository has been archived on 2024-12-05. You can view files and clone it, but cannot push or open issues or pull requests.
PyBitmessage-2024-12-05/src/addresses.py

282 lines
9.2 KiB
Python
Raw Normal View History

"""
Operations with addresses
"""
# pylint: disable=inconsistent-return-statements
2012-11-19 20:45:05 +01:00
import hashlib
import logging
2016-03-23 23:26:57 +01:00
from binascii import hexlify, unhexlify
from struct import pack, unpack
2012-11-19 20:45:05 +01:00
logger = logging.getLogger('default')
2012-11-19 20:45:05 +01:00
ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
2017-09-21 17:24:51 +02:00
2012-11-19 20:45:05 +01:00
def encodeBase58(num, alphabet=ALPHABET):
"""Encode a number in Base X
Args:
num: The number to encode
alphabet: The alphabet to use for encoding
2012-11-19 20:45:05 +01:00
"""
if num == 0:
2012-11-19 20:45:05 +01:00
return alphabet[0]
arr = []
base = len(alphabet)
while num:
num, rem = divmod(num, base)
2012-11-19 20:45:05 +01:00
arr.append(alphabet[rem])
arr.reverse()
return ''.join(arr)
2017-09-21 17:24:51 +02:00
2012-11-19 20:45:05 +01:00
def decodeBase58(string, alphabet=ALPHABET):
"""Decode a Base X encoded string into the number
Args:
string: The encoded string
alphabet: The alphabet to use for encoding
2012-11-19 20:45:05 +01:00
"""
base = len(alphabet)
num = 0
2017-09-21 17:24:51 +02:00
2012-11-19 20:45:05 +01:00
try:
for char in string:
2014-07-27 03:31:45 +02:00
num *= base
num += alphabet.index(char)
except: # noqa:E722 # ValueError
2017-09-21 17:24:51 +02:00
# character not found (like a space character or a 0)
2012-11-19 20:45:05 +01:00
return 0
return num
2017-09-21 17:24:51 +02:00
class varintEncodeError(Exception):
"""Exception class for encoding varint"""
pass
class varintDecodeError(Exception):
"""Exception class for decoding varint data"""
pass
2012-11-19 20:45:05 +01:00
def encodeVarint(integer):
"""Convert integer into varint bytes"""
2012-11-19 20:45:05 +01:00
if integer < 0:
raise varintEncodeError('varint cannot be < 0')
2012-11-19 20:45:05 +01:00
if integer < 253:
2017-09-21 17:24:51 +02:00
return pack('>B', integer)
2012-11-19 20:45:05 +01:00
if integer >= 253 and integer < 65536:
2017-09-21 17:24:51 +02:00
return pack('>B', 253) + pack('>H', integer)
2012-11-19 20:45:05 +01:00
if integer >= 65536 and integer < 4294967296:
2017-09-21 17:24:51 +02:00
return pack('>B', 254) + pack('>I', integer)
2012-11-19 20:45:05 +01:00
if integer >= 4294967296 and integer < 18446744073709551616:
2017-09-21 17:24:51 +02:00
return pack('>B', 255) + pack('>Q', integer)
2012-11-19 20:45:05 +01:00
if integer >= 18446744073709551616:
raise varintEncodeError('varint cannot be >= 18446744073709551616')
2012-11-19 20:45:05 +01:00
2017-09-21 17:24:51 +02:00
2012-11-19 20:45:05 +01:00
def decodeVarint(data):
2014-08-27 09:14:32 +02:00
"""
2017-09-21 17:24:51 +02:00
Decodes an encoded varint to an integer and returns it.
Per protocol v3, the encoded value must be encoded with
the minimum amount of data possible or else it is malformed.
Returns a tuple: (theEncodedValue, theSizeOfTheVarintInBytes)
2014-08-27 09:14:32 +02:00
"""
2017-09-21 17:24:51 +02:00
if not data:
2017-09-21 17:24:51 +02:00
return (0, 0)
firstByte, = unpack('>B', data[0:1])
2012-11-19 20:45:05 +01:00
if firstByte < 253:
2014-08-27 09:14:32 +02:00
# encodes 0 to 252
2017-09-21 17:24:51 +02:00
return (firstByte, 1) # the 1 is the length of the varint
2012-11-19 20:45:05 +01:00
if firstByte == 253:
2014-08-27 09:14:32 +02:00
# encodes 253 to 65535
if len(data) < 3:
2017-09-21 17:24:51 +02:00
raise varintDecodeError(
'The first byte of this varint as an integer is %s'
' but the total length is only %s. It needs to be'
' at least 3.' % (firstByte, len(data)))
encodedValue, = unpack('>H', data[1:3])
2014-08-27 09:14:32 +02:00
if encodedValue < 253:
2017-09-21 17:24:51 +02:00
raise varintDecodeError(
'This varint does not encode the value with the lowest'
' possible number of bytes.')
return (encodedValue, 3)
2012-11-19 20:45:05 +01:00
if firstByte == 254:
2014-08-27 09:14:32 +02:00
# encodes 65536 to 4294967295
if len(data) < 5:
2017-09-21 17:24:51 +02:00
raise varintDecodeError(
'The first byte of this varint as an integer is %s'
' but the total length is only %s. It needs to be'
' at least 5.' % (firstByte, len(data)))
encodedValue, = unpack('>I', data[1:5])
2014-08-27 09:14:32 +02:00
if encodedValue < 65536:
2017-09-21 17:24:51 +02:00
raise varintDecodeError(
'This varint does not encode the value with the lowest'
' possible number of bytes.')
return (encodedValue, 5)
2012-11-19 20:45:05 +01:00
if firstByte == 255:
2014-08-27 09:14:32 +02:00
# encodes 4294967296 to 18446744073709551615
if len(data) < 9:
2017-09-21 17:24:51 +02:00
raise varintDecodeError(
'The first byte of this varint as an integer is %s'
' but the total length is only %s. It needs to be'
' at least 9.' % (firstByte, len(data)))
encodedValue, = unpack('>Q', data[1:9])
2014-08-27 09:14:32 +02:00
if encodedValue < 4294967296:
2017-09-21 17:24:51 +02:00
raise varintDecodeError(
'This varint does not encode the value with the lowest'
' possible number of bytes.')
return (encodedValue, 9)
2012-11-19 20:45:05 +01:00
def calculateInventoryHash(data):
"""Calculate inventory hash from object data"""
2012-11-19 20:45:05 +01:00
sha = hashlib.new('sha512')
sha2 = hashlib.new('sha512')
sha.update(data)
sha2.update(sha.digest())
return sha2.digest()[0:32]
2017-09-21 17:24:51 +02:00
def encodeAddress(version, stream, ripe):
"""Convert ripe to address"""
if version >= 2 and version < 4:
if len(ripe) != 20:
2017-09-21 17:24:51 +02:00
raise Exception(
'Programming error in encodeAddress: The length of'
' a given ripe hash was not 20.'
)
2021-03-04 15:15:41 +01:00
if ripe[:2] == b'\x00\x00':
ripe = ripe[2:]
elif ripe[:1] == b'\x00':
ripe = ripe[1:]
elif version == 4:
if len(ripe) != 20:
2017-09-21 17:24:51 +02:00
raise Exception(
'Programming error in encodeAddress: The length of'
' a given ripe hash was not 20.')
ripe = ripe.lstrip(b'\x00')
2013-08-13 03:59:38 +02:00
storedBinaryData = encodeVarint(version) + encodeVarint(stream) + ripe
2017-09-21 17:24:51 +02:00
# Generate the checksum
2012-11-19 20:45:05 +01:00
sha = hashlib.new('sha512')
sha.update(storedBinaryData)
2012-11-19 20:45:05 +01:00
currentHash = sha.digest()
sha = hashlib.new('sha512')
sha.update(currentHash)
checksum = sha.digest()[0:4]
# FIXME: encodeBase58 should take binary data, to reduce conversions
# encodeBase58(storedBinaryData + checksum)
2017-09-21 17:24:51 +02:00
asInt = int(hexlify(storedBinaryData) + hexlify(checksum), 16)
# should it be str? If yes, it should be everywhere in the code
2017-09-21 17:24:51 +02:00
return 'BM-' + encodeBase58(asInt)
2012-11-19 20:45:05 +01:00
def decodeAddress(address):
"""
returns (status, address version number, stream number,
data (almost certainly a ripe hash))
"""
2020-01-17 14:27:36 +01:00
# pylint: disable=too-many-return-statements,too-many-statements
# pylint: disable=too-many-branches
2012-11-19 20:45:05 +01:00
address = str(address).strip()
if address[:3] == 'BM-':
integer = decodeBase58(address[3:])
else:
integer = decodeBase58(address)
2012-11-19 20:45:05 +01:00
if integer == 0:
status = 'invalidcharacters'
2017-09-21 17:24:51 +02:00
return status, 0, 0, ''
# after converting to hex, the string will be prepended
# with a 0x and appended with a L in python2
hexdata = hex(integer)[2:].rstrip('L')
2012-11-19 20:45:05 +01:00
if len(hexdata) % 2 != 0:
hexdata = '0' + hexdata
2016-03-23 23:26:57 +01:00
data = unhexlify(hexdata)
2012-11-19 20:45:05 +01:00
checksum = data[-4:]
sha = hashlib.new('sha512')
sha.update(data[:-4])
currentHash = sha.digest()
sha = hashlib.new('sha512')
sha.update(currentHash)
if checksum != sha.digest()[0:4]:
status = 'checksumfailed'
2017-09-21 17:24:51 +02:00
return status, 0, 0, ''
2012-11-19 20:45:05 +01:00
2014-08-27 09:14:32 +02:00
try:
addressVersionNumber, bytesUsedByVersionNumber = decodeVarint(data[:9])
except varintDecodeError as e:
logger.error(str(e))
2014-08-27 09:14:32 +02:00
status = 'varintmalformed'
2017-09-21 17:24:51 +02:00
return status, 0, 0, ''
2012-11-19 20:45:05 +01:00
if addressVersionNumber > 4:
logger.error('cannot decode address version numbers this high')
2013-01-16 17:52:52 +01:00
status = 'versiontoohigh'
2017-09-21 17:24:51 +02:00
return status, 0, 0, ''
2013-01-16 17:52:52 +01:00
elif addressVersionNumber == 0:
logger.error('cannot decode address version numbers of zero.')
2012-11-19 20:45:05 +01:00
status = 'versiontoohigh'
2017-09-21 17:24:51 +02:00
return status, 0, 0, ''
2012-11-19 20:45:05 +01:00
2014-08-27 09:14:32 +02:00
try:
2017-09-21 17:24:51 +02:00
streamNumber, bytesUsedByStreamNumber = \
decodeVarint(data[bytesUsedByVersionNumber:])
2014-08-27 09:14:32 +02:00
except varintDecodeError as e:
logger.error(str(e))
2014-08-27 09:14:32 +02:00
status = 'varintmalformed'
2017-09-21 17:24:51 +02:00
return status, 0, 0, ''
2012-11-19 20:45:05 +01:00
status = 'success'
2013-01-16 17:52:52 +01:00
if addressVersionNumber == 1:
2017-09-21 17:24:51 +02:00
return status, addressVersionNumber, streamNumber, data[-24:-4]
elif addressVersionNumber == 2 or addressVersionNumber == 3:
2017-09-21 17:24:51 +02:00
embeddedRipeData = \
data[bytesUsedByVersionNumber + bytesUsedByStreamNumber:-4]
2014-08-27 09:14:32 +02:00
if len(embeddedRipeData) == 19:
2017-09-21 17:24:51 +02:00
return status, addressVersionNumber, streamNumber, \
b'\x00' + embeddedRipeData
2014-08-27 09:14:32 +02:00
elif len(embeddedRipeData) == 20:
2017-09-21 17:24:51 +02:00
return status, addressVersionNumber, streamNumber, \
embeddedRipeData
2014-08-27 09:14:32 +02:00
elif len(embeddedRipeData) == 18:
2017-09-21 17:24:51 +02:00
return status, addressVersionNumber, streamNumber, \
b'\x00\x00' + embeddedRipeData
2014-08-27 09:14:32 +02:00
elif len(embeddedRipeData) < 18:
2017-09-21 17:24:51 +02:00
return 'ripetooshort', 0, 0, ''
2014-08-27 09:14:32 +02:00
elif len(embeddedRipeData) > 20:
2017-09-21 17:24:51 +02:00
return 'ripetoolong', 0, 0, ''
return 'otherproblem', 0, 0, ''
elif addressVersionNumber == 4:
2017-09-21 17:24:51 +02:00
embeddedRipeData = \
data[bytesUsedByVersionNumber + bytesUsedByStreamNumber:-4]
if embeddedRipeData[0:1] == b'\x00':
2017-09-21 17:24:51 +02:00
# In order to enforce address non-malleability, encoded
# RIPE data must have NULL bytes removed from the front
return 'encodingproblem', 0, 0, ''
2014-08-27 09:14:32 +02:00
elif len(embeddedRipeData) > 20:
2017-09-21 17:24:51 +02:00
return 'ripetoolong', 0, 0, ''
2014-08-27 09:14:32 +02:00
elif len(embeddedRipeData) < 4:
2017-09-21 17:24:51 +02:00
return 'ripetooshort', 0, 0, ''
x00string = b'\x00' * (20 - len(embeddedRipeData))
return status, addressVersionNumber, streamNumber, \
x00string + embeddedRipeData
2017-09-21 17:24:51 +02:00
2012-11-19 20:45:05 +01:00
def addBMIfNotPresent(address):
"""Prepend BM- to an address if it doesn't already have it"""
address = str(address).strip()
return address if address[:3] == 'BM-' else 'BM-' + address