import {base58, blake2b} from './imports' import {DecodeSubstrateAddressResult, validate} from './index' import {normalizeEthereumAddress} from './ethereum' import {hexToU8a, u8aToHex} from './stringUtils' import * as string from "./stringUtils"; const blake2AsU8a = (u8a: Uint8Array, dkLen: 8 | 16 | 32 | 48 | 64 = 32): Uint8Array => { return blake2b(u8a, {dkLen}) } const u8aConcat = (u8as: readonly Uint8Array[]): Uint8Array => { let offset = 0 let length = 0 for (let i = 0; i < u8as.length; i++) { length += u8as[i].length } const result = new Uint8Array(length) for (let i = 0; i < u8as.length; i++) { result.set(u8as[i], offset) offset += u8as[i].length } return result } // strToU8a('SS58PRE') const SS58_PREFIX = new Uint8Array([83, 83, 53, 56, 80, 82, 69]) const sshash = (data: Uint8Array): Uint8Array => { return blake2AsU8a(u8aConcat([SS58_PREFIX, data]), 64); } const checkAddressChecksum = (decoded: Uint8Array): [boolean, number, number, number] => { const ss58Length = (decoded[0] & 0b0100_0000) ? 2 : 1; const ss58Decoded = ss58Length === 1 ? decoded[0] : ((decoded[0] & 0x3f) << 2) | (decoded[1] >> 6) | ((decoded[1] & 0x3f) << 8); // 32/33 bytes public + 2 bytes checksum + prefix const isPublicKey = [34 + ss58Length, 35 + ss58Length].includes(decoded.length); const length = decoded.length - (isPublicKey ? 2 : 1); // calculate the hash and do the checksum byte checks const hash = sshash(decoded.subarray(0, length)); const isValid = (decoded[0] & 0x80) === 0 && ![46, 47].includes(decoded[0]) && ( isPublicKey ? decoded[decoded.length - 2] === hash[0] && decoded[decoded.length - 1] === hash[1] : decoded[decoded.length - 1] === hash[0] ); return [isValid, length, ss58Length, ss58Decoded]; } export const normalizeSubstrateAddress = (address: string, prefix: number = 42): string => { return encodeSubstrateAddress(decodeSubstrateAddress(address).u8a, prefix) } export function encodeSubstrateAddress (key: Uint8Array | string | bigint, ss58Format: number = 42): string { const u8a :Uint8Array = typeof key === 'string' ? hexToU8a(key) : typeof key === 'bigint' ? hexToU8a(key.toString(16)) : key if (ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) { throw new Error(`ss58Format is not valid, received ${typeof ss58Format} "${ss58Format}"`) } const allowedDecodedLengths = [1, 2, 4, 8, 32, 33] if (!allowedDecodedLengths.includes(u8a.length)) { throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`) } const u8aPrefix = ss58Format < 64 ? new Uint8Array([ss58Format]) : new Uint8Array([ ((ss58Format & 0xfc) >> 2) | 0x40, (ss58Format >> 8) | ((ss58Format & 0x03) << 6) ]) const input = u8aConcat([u8aPrefix, u8a]) return base58.encode( u8aConcat([ input, sshash(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1) ]) ); } export function decodeSubstrateAddress (address: string, ignoreChecksum?: boolean, ss58Format: number = -1): DecodeSubstrateAddressResult { let realError: Error | null = null try { const decoded = base58.decode(address); const allowedEncodedLengths = [3, 4, 6, 10, 35, 36, 37, 38] if (!allowedEncodedLengths.includes(decoded.length)) { realError = new Error(`key length is not valid, decoded key length is ${decoded.length}, valid values are ${allowedEncodedLengths.join(', ')}`) throw realError } const [isValid, endPos, ss58Length, ss58Decoded] = checkAddressChecksum(decoded) if (!ignoreChecksum && !isValid) { realError = new Error(`Invalid decoded address checksum`) throw realError } if (![-1, ss58Decoded].includes(ss58Format)) { realError = new Error(`Expected ss58Format ${ss58Format}, received ${ss58Decoded}`) throw realError } const publicKey = decoded.slice(ss58Length, endPos) const hex = u8aToHex(publicKey) return { u8a: publicKey, hex, bigint: BigInt(hex) } } catch (error) { throw realError ? realError : new Error(`Decoding ${address}: ${(error as Error).message}`) } } type SubAddressObj = { Substrate: string } export const compareSubstrateAddresses = (address1: string | SubAddressObj | object, address2: string | SubAddressObj | object): boolean => { const addr1 = typeof address1 === 'string' ? address1 : ((address1 as SubAddressObj).Substrate || (address1 as any).substrate) as string | undefined const addr2 = typeof address2 === 'string' ? address2 : ((address2 as SubAddressObj).Substrate || (address2 as any).substrate) as string | undefined if (!addr1 || !addr2) { return false } try { const decoded1 = decodeSubstrateAddress(addr1) const decoded2 = decodeSubstrateAddress(addr2) return decoded1.bigint === decoded2.bigint } catch(e) { return false } } export const addressToEvm = (address: string, ignoreChecksum?: boolean): string => { const truncated = decodeSubstrateAddress(address, ignoreChecksum).u8a.subarray(0, 20) return normalizeEthereumAddress(u8aToHex(truncated)) } // strToU8a('evm:') const EVM_PREFIX_U8A = new Uint8Array([101, 118, 109, 58]) export const evmToAddress = (evmAddress: string, ss58Format: number = 42): string => { validate.ethereumAddress(evmAddress) const message = u8aConcat([EVM_PREFIX_U8A, hexToU8a(evmAddress)]) return encodeSubstrateAddress(blake2AsU8a(message), ss58Format) }