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

346 lines
11 KiB
Python
Raw Normal View History

2012-11-19 20:45:05 +01:00
import hashlib
2017-09-21 17:24:51 +02:00
from struct import pack, unpack
2013-03-01 23:44:52 +01:00
from pyelliptic import arithmetic
2016-03-23 23:26:57 +01:00
from binascii import hexlify, unhexlify
2012-11-19 20:45:05 +01:00
2017-09-21 17:24:51 +02:00
from debug import logger
2013-05-02 17:53:54 +02:00
2017-09-21 17:24:51 +02:00
# There is another copy of this function in Bitmessagemain.py
2012-11-19 20:45:05 +01:00
def convertIntToString(n):
a = __builtins__.hex(n)
if a[-1:] == 'L':
a = a[:-1]
if (len(a) % 2) == 0:
2016-03-23 23:26:57 +01:00
return unhexlify(a[2:])
2012-11-19 20:45:05 +01:00
else:
2016-03-23 23:26:57 +01:00
return unhexlify('0'+a[2:])
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
`num`: The number to encode
`alphabet`: The alphabet to use for encoding
"""
if (num == 0):
return alphabet[0]
arr = []
base = len(alphabet)
while num:
rem = num % base
2017-09-21 17:24:51 +02:00
# print 'num is:', num
2012-11-19 20:45:05 +01:00
num = num // base
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
Arguments:
- `string`: The encoded string
- `alphabet`: The alphabet to use for encoding
"""
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: # 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
2012-11-19 20:45:05 +01:00
def encodeVarint(integer):
if integer < 0:
logger.error('varint cannot be < 0')
2012-11-19 20:45:05 +01:00
raise SystemExit
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:
logger.error('varint cannot be >= 18446744073709551616')
2012-11-19 20:45:05 +01:00
raise SystemExit
2017-09-21 17:24:51 +02:00
2014-08-27 09:14:32 +02:00
class varintDecodeError(Exception):
pass
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
2012-11-19 20:45:05 +01:00
if len(data) == 0:
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):
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):
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.'
)
2013-01-16 17:52:52 +01:00
if ripe[:2] == '\x00\x00':
ripe = ripe[2:]
elif ripe[:1] == '\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.')
2013-08-13 03:59:38 +02:00
ripe = ripe.lstrip('\x00')
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]
2017-09-21 17:24:51 +02:00
asInt = int(hexlify(storedBinaryData) + hexlify(checksum), 16)
return 'BM-' + encodeBase58(asInt)
2012-11-19 20:45:05 +01:00
def decodeAddress(address):
2017-09-21 17:24:51 +02:00
# returns (status, address version number, stream number,
# data (almost certainly a ripe hash))
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
2012-11-19 20:45:05 +01:00
hexdata = hex(integer)[2:-1]
if len(hexdata) % 2 != 0:
hexdata = '0' + hexdata
2017-09-21 17:24:51 +02:00
# print 'hexdata', hexdata
2012-11-19 20:45:05 +01:00
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()
2017-09-21 17:24:51 +02:00
# print 'sha after first hashing: ', sha.hexdigest()
2012-11-19 20:45:05 +01:00
sha = hashlib.new('sha512')
sha.update(currentHash)
2017-09-21 17:24:51 +02:00
# print 'sha after second hashing: ', sha.hexdigest()
2012-11-19 20:45:05 +01:00
if checksum != sha.digest()[0:4]:
status = 'checksumfailed'
2017-09-21 17:24:51 +02:00
return status, 0, 0, ''
# else:
2012-11-19 20:45:05 +01:00
# print 'checksum PASSED'
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, ''
# print 'addressVersionNumber', addressVersionNumber
# print 'bytesUsedByVersionNumber', bytesUsedByVersionNumber
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, ''
# print streamNumber
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, \
'\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, \
'\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, ''
else:
2017-09-21 17:24:51 +02:00
return 'otherproblem', 0, 0, ''
elif addressVersionNumber == 4:
2017-09-21 17:24:51 +02:00
embeddedRipeData = \
data[bytesUsedByVersionNumber + bytesUsedByStreamNumber:-4]
2014-08-27 09:14:32 +02:00
if embeddedRipeData[0:1] == '\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, ''
else:
2014-08-27 09:14:32 +02:00
x00string = '\x00' * (20 - len(embeddedRipeData))
2017-09-21 17:24:51 +02:00
return status, addressVersionNumber, streamNumber, \
x00string + embeddedRipeData
2012-11-19 20:45:05 +01:00
def addBMIfNotPresent(address):
address = str(address).strip()
return address if address[:3] == 'BM-' else 'BM-' + address
2012-11-19 20:45:05 +01:00
2017-09-21 17:24:51 +02:00
2012-11-19 20:45:05 +01:00
if __name__ == "__main__":
2017-09-21 17:24:51 +02:00
print(
'\nLet us make an address from scratch. Suppose we generate two'
' random 32 byte values and call the first one the signing key'
' and the second one the encryption key:'
)
privateSigningKey = \
'93d0b61371a54b53df143b954035d612f8efa8a3ed1cf842c2186bfd8f876665'
privateEncryptionKey = \
'4b0b73a54e19b059dc274ab69df095fe699f43b17397bca26fdf40f4d7400a3a'
print(
'\nprivateSigningKey = %s\nprivateEncryptionKey = %s' %
(privateSigningKey, privateEncryptionKey)
)
print(
'\nNow let us convert them to public keys by doing'
' an elliptic curve point multiplication.'
)
2013-03-01 23:44:52 +01:00
publicSigningKey = arithmetic.privtopub(privateSigningKey)
publicEncryptionKey = arithmetic.privtopub(privateEncryptionKey)
2017-09-21 17:24:51 +02:00
print(
'\npublicSigningKey = %s\npublicEncryptionKey = %s' %
(publicSigningKey, publicEncryptionKey)
)
print(
'\nNotice that they both begin with the \\x04 which specifies'
' the encoding type. This prefix is not send over the wire.'
' You must strip if off before you send your public key across'
' the wire, and you must add it back when you receive a public key.'
)
publicSigningKeyBinary = \
arithmetic.changebase(publicSigningKey, 16, 256, minlen=64)
publicEncryptionKeyBinary = \
arithmetic.changebase(publicEncryptionKey, 16, 256, minlen=64)
2012-11-19 20:45:05 +01:00
ripe = hashlib.new('ripemd160')
sha = hashlib.new('sha512')
2017-09-21 17:24:51 +02:00
sha.update(publicSigningKeyBinary + publicEncryptionKeyBinary)
2012-11-19 20:45:05 +01:00
ripe.update(sha.digest())
2013-03-01 23:44:52 +01:00
addressVersionNumber = 2
streamNumber = 1
2017-09-21 17:24:51 +02:00
print(
'\nRipe digest that we will encode in the address: %s' %
hexlify(ripe.digest())
)
returnedAddress = \
encodeAddress(addressVersionNumber, streamNumber, ripe.digest())
print('Encoded address: %s' % returnedAddress)
status, addressVersionNumber, streamNumber, data = \
decodeAddress(returnedAddress)
print(
'\nAfter decoding address:\n\tStatus: %s'
'\n\taddressVersionNumber %s'
'\n\tstreamNumber %s'
'\n\tlength of data (the ripe hash): %s'
'\n\tripe data: %s' %
(status, addressVersionNumber, streamNumber, len(data), hexlify(data))
)