Files
xahau.js/packages/ripple-address-codec/src/index.ts

137 lines
4.1 KiB
TypeScript

import {
codec,
encodeSeed,
decodeSeed,
encodeAccountID,
decodeAccountID,
encodeNodePublic,
decodeNodePublic,
encodeAccountPublic,
decodeAccountPublic,
isValidClassicAddress
} from './xrp-codec'
import * as assert from 'assert'
const PREFIX_BYTES = {
MAIN: Buffer.from([0x05, 0x44]), // 5, 68
TEST: Buffer.from([0x04, 0x93]) // 4, 147
}
function classicAddressToXAddress(classicAddress: string, tag: number | false, test: boolean): string {
const accountId = decodeAccountID(classicAddress)
return encodeXAddress(accountId, tag, test)
}
function encodeXAddress(accountId: Buffer, 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')
}
const MAX_32_BIT_UNSIGNED_INT = 4294967295
const flag = tag === false ? 0 : tag <= MAX_32_BIT_UNSIGNED_INT ? 1 : 2
if (flag === 2) {
throw new Error('Invalid tag')
}
if (tag === false) {
tag = 0
}
const bytes = Buffer.concat(
[
test ? PREFIX_BYTES.TEST : PREFIX_BYTES.MAIN,
accountId,
Buffer.from(
[
flag, // 0x00 if no tag, 0x01 if 32-bit tag
tag & 0xff, // first byte
(tag >> 8) & 0xff, // second byte
(tag >> 16) & 0xff, // third byte
(tag >> 24) & 0xff, // fourth byte
0, 0, 0, 0 // four zero bytes (reserved for 64-bit tags)
]
)
]
)
const xAddress = codec.encodeChecked(bytes)
return xAddress
}
function xAddressToClassicAddress(xAddress: string): {classicAddress: string, tag: number | false, test: boolean} {
const {
accountId,
tag,
test
} = decodeXAddress(xAddress)
const classicAddress = encodeAccountID(accountId)
return {
classicAddress,
tag,
test
}
}
function decodeXAddress(xAddress: string): {accountId: Buffer, tag: number | false, test: boolean} {
const decoded = codec.decodeChecked(xAddress)
const test = isBufferForTestAddress(decoded)
const accountId = decoded.slice(2, 22)
const tag = tagFromBuffer(decoded)
return {
accountId,
tag,
test
}
}
function isBufferForTestAddress(buf: Buffer): boolean {
const decodedPrefix = buf.slice(0, 2)
if (PREFIX_BYTES.MAIN.equals(decodedPrefix)) {
return false
} else if (PREFIX_BYTES.TEST.equals(decodedPrefix)) {
return true
} else {
throw new Error('Invalid X-address: bad prefix')
}
}
function tagFromBuffer(buf: Buffer): 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
}
assert.strictEqual(flag, 0, 'flag must be zero to indicate no tag')
assert.ok(Buffer.from('0000000000000000', 'hex').equals(buf.slice(23, 23 + 8)),
'remaining bytes must be zero')
return false
}
function isValidXAddress(xAddress: string): boolean {
try {
decodeXAddress(xAddress)
} catch (e) {
return false
}
return true
}
export {
codec, // Codec with XRP alphabet
encodeSeed, // Encode entropy as a "seed"
decodeSeed, // Decode a seed into an object with its version, type, and bytes
encodeAccountID, // Encode bytes as a classic address (r...)
decodeAccountID, // Decode a classic address to its raw bytes
encodeNodePublic, // Encode bytes to XRP Ledger node public key format
decodeNodePublic, // Decode an XRP Ledger node public key into its raw bytes
encodeAccountPublic, // Encode a public key, as for payment channels
decodeAccountPublic, // Decode a public key, as for payment channels
isValidClassicAddress, // Check whether a classic address (r...) is valid
classicAddressToXAddress, // Derive X-address from classic address, tag, and network ID
encodeXAddress, // Encode account ID, tag, and network ID to X-address
xAddressToClassicAddress, // Decode X-address to account ID, tag, and network ID
decodeXAddress, // Convert X-address to classic address, tag, and network ID
isValidXAddress // Check whether an X-address (X...) is valid
}