From 317e4d73ddd76d80cd50472f89d9f006ac52f736 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Fri, 22 Aug 2025 02:31:22 -0700 Subject: [PATCH] Implement class to represent Nano addresses. --- src/lib/account/address.ts | 74 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/lib/account/address.ts diff --git a/src/lib/account/address.ts b/src/lib/account/address.ts new file mode 100644 index 0000000..2633ee7 --- /dev/null +++ b/src/lib/account/address.ts @@ -0,0 +1,74 @@ +//! SPDX-FileCopyrightText: 2025 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +import { ACCOUNT_KEY_HEX_LENGTH, ALPHABET, PREFIX, PREFIX_LEGACY } from "../constants" +import { base32, bytes, hex } from "../convert" +import { Blake2b } from "../crypto" + +class Address { + #address: string + + /** + * Converts a public key to a Nano address. + * + * @param {Uint8Array} publicKey - Public key bytes as Uint8Array + * @returns Nano address string using `nano_` prefix + */ + static fromPublicKey (publicKey: ArrayBuffer | Uint8Array): string { + const checksum = new Blake2b(5).update(publicKey).digest().reverse() + const encodedPublicKey = bytes.toBase32(publicKey) + const encodedChecksum = bytes.toBase32(checksum) + return `${PREFIX}${encodedPublicKey}${encodedChecksum}` + } + + constructor (address: string) + constructor (publicKey: string | ArrayBuffer | Uint8Array) + constructor (value: unknown) { + if (typeof value !== 'string' && !(value instanceof ArrayBuffer) && !(value instanceof Uint8Array)) { + throw new TypeError('Invalid address input', { cause: value }) + } + if (typeof value === 'string') { + if (RegExp(`^[A-F0-9]{${ACCOUNT_KEY_HEX_LENGTH}}$`, 'i').test(value)) { + this.#address = Address.fromPublicKey(hex.toBuffer(value)) + } else if (RegExp(`^(${PREFIX}|${PREFIX_LEGACY})[13][${ALPHABET}]{59}$`).test(value)) { + this.#address = value + } else { + throw new TypeError('Invalid address input', { cause: value }) + } + } else if (value instanceof ArrayBuffer || value instanceof Uint8Array) { + const v = new Uint8Array(value) + this.#address = Address.fromPublicKey(v) + } else { + throw new TypeError('Invalid address input', { cause: value }) + } + this.#address = this.#address.replace(PREFIX_LEGACY, PREFIX) + } + + [Symbol.toPrimitive] (hint: string) { + if (hint === 'string' || hint === 'default') { + return this.#address + } + throw new SyntaxError('invalid Address syntax') + } + + get length (): number { return `${PREFIX}${this.#address}`.length } + + toString (): string { return `${PREFIX}${this.#address}` } + + valueOf (): string { return `${PREFIX}${this.#address}` } + + /** + * Converts a Nano address to a public key. + * + * @returns Public key bytes as ArrayBuffer + */ + toPublicKey (): ArrayBuffer { + const publicKey = base32.toBuffer(this.#address.slice(-60, -8)) + const checksum = base32.toBuffer(this.#address.slice(-8)) + const rechecksum = new Blake2b(5).update(publicKey).digest().reverse() + if (bytes.toHex(checksum) !== bytes.toHex(rechecksum)) { + throw new Error('Checksum mismatch in address') + } + return publicKey + } +} -- 2.47.3