From: Chris Duncan Date: Tue, 14 Oct 2025 18:41:16 +0000 (-0700) Subject: Start migrating account load functions. X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=da4d9c98045205c48a33c8b4f5537fd3ccdd4036;p=libnemo.git Start migrating account load functions. --- diff --git a/src/lib/account/index.ts b/src/lib/account/index.ts index 7c780a9..ac15c20 100644 --- a/src/lib/account/index.ts +++ b/src/lib/account/index.ts @@ -7,10 +7,11 @@ import { bytes, hex } from '../convert' import { NanoNaCl } from '../crypto' import { Rpc } from '../rpc' import { Address } from './address' +import { _load } from './load' import { _refresh } from './refresh' import { _validate } from './validate' -type KeyPair = { +export type KeyPair = { index?: number privateKey?: string | Uint8Array publicKey?: string | Uint8Array @@ -245,14 +246,7 @@ export class Account { * @returns {(Account | Account[] | Promise)} Promise for array of new Account objects */ static load (input: string | Uint8Array | KeyPair | (string | Uint8Array | KeyPair)[], type?: 'private'): Account | Account[] | Promise { - const isInputArray = Array.isArray(input) - const inputs = isInputArray ? input : [input] - if (this.#isKeyPairs(inputs) && type === 'private') { - return this.#fromPrivate(inputs) - .then(r => isInputArray ? r : r[0]) - } else { - return isInputArray ? this.#fromPublic(inputs) : this.#fromPublic(inputs)[0] - } + return _load(input, type) } /** @@ -277,121 +271,4 @@ export class Account { static validate (address: string): asserts address is string { return _validate(address) } - - /** - * Instantiates an Account object from its private key which is used to derive - * the corresponding public key and then discarded. - * - * @param {KeyPair} keypairs - Indexes and keys of the accounts - * @returns {Promise} Promise for new Account objects - */ - static async #fromPrivate (keypairs: KeyPair[]): Promise { - try { - const accounts: Account[] = [] - for (let keypair of keypairs) { - let { index, privateKey } = keypair - if (index == null) { - throw new RangeError('Index missing for Account') - } - if (typeof privateKey === 'string' && RegExp(`^[A-F0-9]{${ACCOUNT_KEY_HEX_LENGTH}}$`, 'i').test(privateKey)) { - privateKey = hex.toBytes(privateKey) - } - if (!(privateKey instanceof Uint8Array) || privateKey.every(v => v === 0)) { - throw new TypeError('Invalid private key') - } - if (privateKey.byteLength !== ACCOUNT_KEY_BYTE_LENGTH) { - throw new TypeError(`Private key must be ${ACCOUNT_KEY_BYTE_LENGTH} bytes`) - } - const publicKey = await NanoNaCl.convert(privateKey) - const address = new Address(publicKey) - this.#isInternal = true - const account = new this(address, publicKey, index) - this.#isInternal = false - accounts.push(account) - } - return accounts - } catch (err) { - throw new Error('Failed to import Accounts from private keys', { cause: err }) - } - } - - /** - * Instantiates Account objects from public data, each specifying either its - * public key or its Nano address. - * - * @param {(string | Uint8Array|KeyPair)[]} input - Public keys or addresses of the accounts - * @returns {Account[]} The instantiated Account objects - */ - static #fromPublic (input: (string | Uint8Array | KeyPair)[] | unknown): Account[] { - try { - const keypairs = this.#isKeyPairs(input) - ? input - : this.#isKeys(input) - ? input.map(i => { return { publicKey: i } as KeyPair }) - : [] - if (keypairs.length === 0) { - throw new TypeError('Invalid public input for Account') - } - - const accounts: Account[] = [] - for (let keypair of keypairs) { - if (keypair.publicKey == null) { - throw new TypeError('Account address or public key is required', { cause: keypair.publicKey }) - } - const { index } = keypair - const address = new Address(keypair.publicKey) - const publicKey = address.toPublicKey() - this.#isInternal = true - const account = new this(address, publicKey, index) - this.#isInternal = false - accounts.push(account) - } - return accounts - } catch (err) { - console.error(err) - throw new TypeError('Failed to import Account from public data', { cause: { err } }) - } - } - - static #isKey (input: unknown): input is string | Uint8Array { - return typeof input === 'string' || (input instanceof Uint8Array && 'buffer' in input) - } - - static #isKeys (input: unknown): input is (string | Uint8Array)[] { - if (Array.isArray(input)) { - for (const i of input) { - if (!this.#isKey(i)) { - return false - } - } - } - return true - } - - static #isKeyPair (input: unknown): input is KeyPair { - if (typeof input === 'object') { - const obj = input as { [key: string]: unknown } - if ('index' in obj && typeof obj.index === 'number') { - return true - } - if ('publicKey' in obj && this.#isKey(obj.publicKey)) { - return true - } - if ('privateKey' in obj && this.#isKey(obj.privateKey)) { - return true - } - } - return false - } - - static #isKeyPairs (input: unknown): input is KeyPair[] { - if (Array.isArray(input)) { - for (const i of input) { - if (!this.#isKeyPair(i)) { - return false - } - } - } - return true - } } diff --git a/src/lib/account/load.ts b/src/lib/account/load.ts new file mode 100644 index 0000000..b897bc9 --- /dev/null +++ b/src/lib/account/load.ts @@ -0,0 +1,139 @@ +//! SPDX-FileCopyrightText: 2025 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +import { Account, KeyPair } from '../account' +import { Address } from './address' +import { ACCOUNT_KEY_BYTE_LENGTH, ACCOUNT_KEY_HEX_LENGTH } from '../constants' +import { hex } from '../convert' +import { NanoNaCl } from '../crypto' + +export function _load (input: string | Uint8Array | KeyPair | (string | Uint8Array | KeyPair)[], type?: 'private'): Account | Account[] | Promise { + const isInputArray = Array.isArray(input) + const inputs = isInputArray ? input : [input] + if (isKeyPairs(inputs) && type === 'private') { + return fromPrivate(inputs) + .then(r => isInputArray ? r : r[0]) + } else { + return isInputArray ? fromPublic(inputs) : fromPublic(inputs)[0] + } +} + +/** +* Instantiates an Account object from its private key which is used to derive +* the corresponding public key and then discarded. +* +* @param {KeyPair} keypairs - Indexes and keys of the accounts +* @returns {Promise} Promise for new Account objects +*/ +async function fromPrivate (keypairs: KeyPair[]): Promise { + try { + const accounts: Account[] = [] + for (let keypair of keypairs) { + let { index, privateKey } = keypair + if (index == null) { + throw new RangeError('Index missing for Account') + } + if (typeof privateKey === 'string' && RegExp(`^[A-F0-9]{${ACCOUNT_KEY_HEX_LENGTH}}$`, 'i').test(privateKey)) { + privateKey = hex.toBytes(privateKey) + } + if (!(privateKey instanceof Uint8Array) || privateKey.every(v => v === 0)) { + throw new TypeError('Invalid private key') + } + if (privateKey.byteLength !== ACCOUNT_KEY_BYTE_LENGTH) { + throw new TypeError(`Private key must be ${ACCOUNT_KEY_BYTE_LENGTH} bytes`) + } + const publicKey = await NanoNaCl.convert(privateKey) + const address = new Address(publicKey) + this.#isInternal = true + const account = new this(address, publicKey, index) + this.#isInternal = false + accounts.push(account) + } + return accounts + } catch (err) { + throw new Error('Failed to import Accounts from private keys', { cause: err }) + } +} + +/** +* Instantiates Account objects from public data, each specifying either its +* public key or its Nano address. +* +* @param {(string | Uint8Array|KeyPair)[]} input - Public keys or addresses of the accounts +* @returns {Account[]} The instantiated Account objects +*/ +function fromPublic (input: (string | Uint8Array | KeyPair)[] | unknown): Account[] { + try { + const keypairs = isKeyPairs(input) + ? input + : isKeys(input) + ? input.map(i => { return { publicKey: i } as KeyPair }) + : [] + if (keypairs.length === 0) { + throw new TypeError('Invalid public input for Account') + } + + const accounts: Account[] = [] + for (let keypair of keypairs) { + if (keypair.publicKey == null) { + throw new TypeError('Account address or public key is required', { cause: keypair.publicKey }) + } + const { index } = keypair + const address = new Address(keypair.publicKey) + const publicKey = address.toPublicKey() + this.#isInternal = true + const account = new this(address, publicKey, index) + this.#isInternal = false + accounts.push(account) + } + return accounts + } catch (err) { + console.error(err) + throw new TypeError('Failed to import Account from public data', { cause: { err } }) + } +} + + +function isKeyPair (input: unknown): input is KeyPair { + if (typeof input === 'object') { + const obj = input as { [key: string]: unknown } + if ('index' in obj && typeof obj.index === 'number') { + return true + } + if ('publicKey' in obj && isKey(obj.publicKey)) { + return true + } + if ('privateKey' in obj && isKey(obj.privateKey)) { + return true + } + } + return false +} + + +function isKeyPairs (input: unknown): input is KeyPair[] { + if (Array.isArray(input)) { + for (const i of input) { + if (!isKeyPair(i)) { + return false + } + } + } + return true +} + + +function isKey (input: unknown): input is string | Uint8Array { + return typeof input === 'string' || (input instanceof Uint8Array && 'buffer' in input) +} + +function isKeys (input: unknown): input is (string | Uint8Array)[] { + if (Array.isArray(input)) { + for (const i of input) { + if (!isKey(i)) { + return false + } + } + } + return true +}