UNPKG

xahau-address-codec

Version:

encodes/decodes base58 encoded XAH Ledger identifiers

186 lines (173 loc) 4.92 kB
import { concat, equal, hexToBytes } from '@xrplf/isomorphic/utils' import { codec, encodeSeed, decodeSeed, encodeAccountID, decodeAccountID, encodeNodePublic, decodeNodePublic, encodeAccountPublic, decodeAccountPublic, isValidClassicAddress, } from './xah-codec' const PREFIX_BYTES = { // 5, 68 main: Uint8Array.from([0x05, 0x44]), // 4, 147 test: Uint8Array.from([0x04, 0x93]), } const MAX_32_BIT_UNSIGNED_INT = 4294967295 function classicAddressToXAddress( classicAddress: string, tag: number | false, test: boolean, ): string { const accountId = decodeAccountID(classicAddress) return encodeXAddress(accountId, tag, test) } function encodeXAddress( accountId: Uint8Array, tag: number | false, test: boolean, ): string { if (accountId.length !== 20) { // RIPEMD160 is 160 bits = 20 bytes throw new Error('Account ID must be 20 bytes') } if (tag !== false && tag > MAX_32_BIT_UNSIGNED_INT) { throw new Error('Invalid tag') } const theTag = tag || 0 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Passing null is a common js mistake const flag = tag === false || tag == null ? 0 : 1 /* eslint-disable no-bitwise --- * need to use bitwise operations here */ const bytes = concat([ test ? PREFIX_BYTES.test : PREFIX_BYTES.main, accountId, Uint8Array.from([ // 0x00 if no tag, 0x01 if 32-bit tag flag, // first byte theTag & 0xff, // second byte (theTag >> 8) & 0xff, // third byte (theTag >> 16) & 0xff, // fourth byte (theTag >> 24) & 0xff, 0, 0, 0, // four zero bytes (reserved for 64-bit tags) 0, ]), ]) /* eslint-enable no-bitwise */ return codec.encodeChecked(bytes) } function xAddressToClassicAddress(xAddress: string): { classicAddress: string tag: number | false test: boolean } { /* eslint-disable @typescript-eslint/naming-convention -- * TODO 'test' should be something like 'isTest', do this later */ const { accountId, tag, test } = decodeXAddress(xAddress) /* eslint-enable @typescript-eslint/naming-convention */ const classicAddress = encodeAccountID(accountId) return { classicAddress, tag, test, } } function decodeXAddress(xAddress: string): { accountId: Uint8Array tag: number | false test: boolean } { const decoded = codec.decodeChecked(xAddress) /* eslint-disable @typescript-eslint/naming-convention -- * TODO 'test' should be something like 'isTest', do this later */ const test = isUint8ArrayForTestAddress(decoded) /* eslint-enable @typescript-eslint/naming-convention */ const accountId = decoded.slice(2, 22) const tag = tagFromUint8Array(decoded) return { accountId, tag, test, } } function isUint8ArrayForTestAddress(buf: Uint8Array): boolean { const decodedPrefix = buf.slice(0, 2) if (equal(PREFIX_BYTES.main, decodedPrefix)) { return false } if (equal(PREFIX_BYTES.test, decodedPrefix)) { return true } throw new Error('Invalid X-address: bad prefix') } function tagFromUint8Array(buf: Uint8Array): number | false { const flag = buf[22] if (flag >= 2) { // No support for 64-bit tags at this time throw new Error('Unsupported X-address') } if (flag === 1) { // Little-endian to big-endian return buf[23] + buf[24] * 0x100 + buf[25] * 0x10000 + buf[26] * 0x1000000 } if (flag !== 0) { throw new Error('flag must be zero to indicate no tag') } if (!equal(hexToBytes('0000000000000000'), buf.slice(23, 23 + 8))) { throw new Error('remaining bytes must be zero') } return false } function isValidXAddress(xAddress: string): boolean { try { decodeXAddress(xAddress) } catch (_error) { return false } return true } export { // Codec with XAH alphabet codec, // Encode entropy as a "seed" encodeSeed, // Decode a seed into an object with its version, type, and bytes decodeSeed, // Encode bytes as a classic address (r...) encodeAccountID, // Decode a classic address to its raw bytes decodeAccountID, // Encode bytes to XAH Ledger node public key format encodeNodePublic, // Decode an XAH Ledger node public key into its raw bytes decodeNodePublic, // Encode a public key, as for payment channels encodeAccountPublic, // Decode a public key, as for payment channels decodeAccountPublic, // Check whether a classic address (r...) is valid isValidClassicAddress, // Derive X-address from classic address, tag, and network ID classicAddressToXAddress, // Encode account ID, tag, and network ID to X-address encodeXAddress, // Decode X-address to account ID, tag, and network ID xAddressToClassicAddress, // Convert X-address to classic address, tag, and network ID decodeXAddress, // Check whether an X-address (X...) is valid isValidXAddress, }