addresses.py
1 """ 2 Operations with addresses 3 """ 4 # pylint: disable=inconsistent-return-statements 5 6 import logging 7 from binascii import hexlify, unhexlify 8 from struct import pack, unpack 9 10 try: 11 from highlevelcrypto import double_sha512 12 except ImportError: 13 from highlevelcrypto import double_sha512 14 15 16 logger = logging.getLogger('default') 17 18 ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 19 20 21 def encodeBase58(num): 22 """Encode a number in Base X 23 24 Args: 25 num: The number to encode 26 alphabet: The alphabet to use for encoding 27 """ 28 if num < 0: 29 return None 30 if num == 0: 31 return ALPHABET[0] 32 arr = [] 33 base = len(ALPHABET) 34 while num: 35 num, rem = divmod(num, base) 36 arr.append(ALPHABET[rem]) 37 arr.reverse() 38 return ''.join(arr) 39 40 41 def decodeBase58(string): 42 """Decode a Base X encoded string into the number 43 44 Args: 45 string: The encoded string 46 alphabet: The alphabet to use for encoding 47 """ 48 base = len(ALPHABET) 49 num = 0 50 51 try: 52 for char in string: 53 num *= base 54 num += ALPHABET.index(char) 55 except ValueError: 56 # character not found (like a space character or a 0) 57 return 0 58 return num 59 60 61 class varintEncodeError(Exception): 62 """Exception class for encoding varint""" 63 pass 64 65 66 class varintDecodeError(Exception): 67 """Exception class for decoding varint data""" 68 pass 69 70 71 def encodeVarint(integer): 72 """Convert integer into varint bytes""" 73 if integer < 0: 74 raise varintEncodeError('varint cannot be < 0') 75 if integer < 253: 76 return pack('>B', integer) 77 if integer >= 253 and integer < 65536: 78 return pack('>B', 253) + pack('>H', integer) 79 if integer >= 65536 and integer < 4294967296: 80 return pack('>B', 254) + pack('>I', integer) 81 if integer >= 4294967296 and integer < 18446744073709551616: 82 return pack('>B', 255) + pack('>Q', integer) 83 if integer >= 18446744073709551616: 84 raise varintEncodeError('varint cannot be >= 18446744073709551616') 85 86 87 def decodeVarint(data): 88 """ 89 Decodes an encoded varint to an integer and returns it. 90 Per protocol v3, the encoded value must be encoded with 91 the minimum amount of data possible or else it is malformed. 92 Returns a tuple: (theEncodedValue, theSizeOfTheVarintInBytes) 93 """ 94 95 if not data: 96 return (0, 0) 97 firstByte, = unpack('>B', data[0:1]) 98 if firstByte < 253: 99 # encodes 0 to 252 100 return (firstByte, 1) # the 1 is the length of the varint 101 if firstByte == 253: 102 # encodes 253 to 65535 103 if len(data) < 3: 104 raise varintDecodeError( 105 'The first byte of this varint as an integer is %s' 106 ' but the total length is only %s. It needs to be' 107 ' at least 3.' % (firstByte, len(data))) 108 encodedValue, = unpack('>H', data[1:3]) 109 if encodedValue < 253: 110 raise varintDecodeError( 111 'This varint does not encode the value with the lowest' 112 ' possible number of bytes.') 113 return (encodedValue, 3) 114 if firstByte == 254: 115 # encodes 65536 to 4294967295 116 if len(data) < 5: 117 raise varintDecodeError( 118 'The first byte of this varint as an integer is %s' 119 ' but the total length is only %s. It needs to be' 120 ' at least 5.' % (firstByte, len(data))) 121 encodedValue, = unpack('>I', data[1:5]) 122 if encodedValue < 65536: 123 raise varintDecodeError( 124 'This varint does not encode the value with the lowest' 125 ' possible number of bytes.') 126 return (encodedValue, 5) 127 if firstByte == 255: 128 # encodes 4294967296 to 18446744073709551615 129 if len(data) < 9: 130 raise varintDecodeError( 131 'The first byte of this varint as an integer is %s' 132 ' but the total length is only %s. It needs to be' 133 ' at least 9.' % (firstByte, len(data))) 134 encodedValue, = unpack('>Q', data[1:9]) 135 if encodedValue < 4294967296: 136 raise varintDecodeError( 137 'This varint does not encode the value with the lowest' 138 ' possible number of bytes.') 139 return (encodedValue, 9) 140 141 142 def encodeAddress(version, stream, ripe): 143 """Convert ripe to address""" 144 if version >= 2 and version < 4: 145 if len(ripe) != 20: 146 raise Exception( 147 'Programming error in encodeAddress: The length of' 148 ' a given ripe hash was not 20.' 149 ) 150 151 if ripe[:2] == b'\x00\x00': 152 ripe = ripe[2:] 153 elif ripe[:1] == b'\x00': 154 ripe = ripe[1:] 155 elif version == 4: 156 if len(ripe) != 20: 157 raise Exception( 158 'Programming error in encodeAddress: The length of' 159 ' a given ripe hash was not 20.') 160 ripe = ripe.lstrip(b'\x00') 161 162 storedBinaryData = encodeVarint(version) + encodeVarint(stream) + ripe 163 164 # Generate the checksum 165 checksum = double_sha512(storedBinaryData)[0:4] 166 167 # FIXME: encodeBase58 should take binary data, to reduce conversions 168 # encodeBase58(storedBinaryData + checksum) 169 asInt = int(hexlify(storedBinaryData) + hexlify(checksum), 16) 170 # should it be str? If yes, it should be everywhere in the code 171 return 'BM-' + encodeBase58(asInt) 172 173 174 def decodeAddress(address): 175 """ 176 returns (status, address version number, stream number, 177 data (almost certainly a ripe hash)) 178 """ 179 # pylint: disable=too-many-return-statements,too-many-statements 180 # pylint: disable=too-many-branches 181 182 address = str(address).strip() 183 184 if address[:3] == 'BM-': 185 integer = decodeBase58(address[3:]) 186 else: 187 integer = decodeBase58(address) 188 if integer == 0: 189 status = 'invalidcharacters' 190 return status, 0, 0, '' 191 # after converting to hex, the string will be prepended 192 # with a 0x and appended with a L in python2 193 hexdata = hex(integer)[2:].rstrip('L') 194 195 if len(hexdata) % 2 != 0: 196 hexdata = '0' + hexdata 197 198 data = unhexlify(hexdata) 199 checksum = data[-4:] 200 201 if checksum != double_sha512(data[:-4])[0:4]: 202 status = 'checksumfailed' 203 return status, 0, 0, '' 204 205 try: 206 addressVersionNumber, bytesUsedByVersionNumber = decodeVarint(data[:9]) 207 except varintDecodeError as e: 208 logger.error(str(e)) 209 status = 'varintmalformed' 210 return status, 0, 0, '' 211 212 if addressVersionNumber > 4: 213 logger.error('cannot decode address version numbers this high') 214 status = 'versiontoohigh' 215 return status, 0, 0, '' 216 elif addressVersionNumber == 0: 217 logger.error('cannot decode address version numbers of zero.') 218 status = 'versiontoohigh' 219 return status, 0, 0, '' 220 221 try: 222 streamNumber, bytesUsedByStreamNumber = \ 223 decodeVarint(data[bytesUsedByVersionNumber:]) 224 except varintDecodeError as e: 225 logger.error(str(e)) 226 status = 'varintmalformed' 227 return status, 0, 0, '' 228 229 status = 'success' 230 if addressVersionNumber == 1: 231 return status, addressVersionNumber, streamNumber, data[-24:-4] 232 elif addressVersionNumber == 2 or addressVersionNumber == 3: 233 embeddedRipeData = \ 234 data[bytesUsedByVersionNumber + bytesUsedByStreamNumber:-4] 235 if len(embeddedRipeData) == 19: 236 return status, addressVersionNumber, streamNumber, \ 237 b'\x00' + embeddedRipeData 238 elif len(embeddedRipeData) == 20: 239 return status, addressVersionNumber, streamNumber, \ 240 embeddedRipeData 241 elif len(embeddedRipeData) == 18: 242 return status, addressVersionNumber, streamNumber, \ 243 b'\x00\x00' + embeddedRipeData 244 elif len(embeddedRipeData) < 18: 245 return 'ripetooshort', 0, 0, '' 246 elif len(embeddedRipeData) > 20: 247 return 'ripetoolong', 0, 0, '' 248 return 'otherproblem', 0, 0, '' 249 elif addressVersionNumber == 4: 250 embeddedRipeData = \ 251 data[bytesUsedByVersionNumber + bytesUsedByStreamNumber:-4] 252 if embeddedRipeData[0:1] == b'\x00': 253 # In order to enforce address non-malleability, encoded 254 # RIPE data must have NULL bytes removed from the front 255 return 'encodingproblem', 0, 0, '' 256 elif len(embeddedRipeData) > 20: 257 return 'ripetoolong', 0, 0, '' 258 elif len(embeddedRipeData) < 4: 259 return 'ripetooshort', 0, 0, '' 260 x00string = b'\x00' * (20 - len(embeddedRipeData)) 261 return status, addressVersionNumber, streamNumber, \ 262 x00string + embeddedRipeData 263 264 265 def addBMIfNotPresent(address): 266 """Prepend BM- to an address if it doesn't already have it""" 267 address = str(address).strip() 268 return address if address[:3] == 'BM-' else 'BM-' + address