@unique-nft/api
Version:
Definitely typed JS API for the Unique Network blockchain
171 lines (134 loc) • 5.75 kB
text/typescript
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)
}