]> git.codecow.com Git - libnemo.git/commitdiff
Implement class to represent Nano addresses.
authorChris Duncan <chris@zoso.dev>
Fri, 22 Aug 2025 09:31:22 +0000 (02:31 -0700)
committerChris Duncan <chris@zoso.dev>
Fri, 22 Aug 2025 09:31:22 +0000 (02:31 -0700)
src/lib/account/address.ts [new file with mode: 0644]

diff --git a/src/lib/account/address.ts b/src/lib/account/address.ts
new file mode 100644 (file)
index 0000000..2633ee7
--- /dev/null
@@ -0,0 +1,74 @@
+//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
+//! 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<ArrayBuffer>): 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<ArrayBuffer>)
+       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
+       }
+}