From a7e5f26eacd93edbb52fdda00b226476d06dbeb0 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Thu, 17 Jul 2025 09:25:23 -0700 Subject: [PATCH] Combine imports from public data into one method. Reorganize methods. --- src/lib/account.ts | 213 ++++++++++++++++++++++++--------------------- 1 file changed, 113 insertions(+), 100 deletions(-) diff --git a/src/lib/account.ts b/src/lib/account.ts index 3f7c4a8..bfcfe45 100644 --- a/src/lib/account.ts +++ b/src/lib/account.ts @@ -167,116 +167,21 @@ export class Account { if (Array.isArray(input)) { if (password != null) { return new Promise((resolve, reject): void => { - this.#fromPrivateKeys(input, password) + this.#fromPrivate(input, password) .then(r => resolve(r)) .catch(e => reject(e)) }) } - if (input[0] instanceof Uint8Array || /^[A-Fa-f0-9]{64}$/.test(input[0])) { - return this.#fromPublicKeys(input) - } - return this.#fromAddresses(input as string[]) + return this.#fromPublic(input) } else { if (password != null) { return new Promise((resolve, reject): void => { - this.#fromPrivateKeys([input] as string[], password) + this.#fromPrivate([input] as string[], password) .then(r => resolve(r[0])) .catch(e => reject(e)) }) } - if (input instanceof Uint8Array || /^[A-Fa-f0-9]{64}$/.test(input)) { - return this.#fromPublicKeys([input] as string[])[0] - } - return this.#fromAddresses([input] as string[])[0] - } - } - - /** - * Instantiates an Account object from its Nano address. - * - * @param {string} address - Address of the account - * @returns {Account} The instantiated Account object - */ - static #fromAddresses (addresses: string[]): Account[] { - const accounts: Account[] = [] - for (let address of addresses) { - this.#isInternal = true - this.validate(address) - const publicKey = this.#addressToKey(address) - accounts.push(new this(address, publicKey)) - } - return accounts - } - - /** - * Instantiates an Account object from its public key. It is unable to sign - * blocks or messages since it has no private key. - * - * @param {(string|Uint8Array)} publicKey - Public key of the account - * @returns {Account} The instantiated Account object - */ - static #fromPublicKeys (publicKeys: string[] | Uint8Array[]): Account[] { - const accounts: Account[] = [] - for (let publicKey of publicKeys) { - this.#validateKey(publicKey) - if (typeof publicKey === 'string') publicKey = hex.toBytes(publicKey) - const address = this.#keyToAddress(publicKey) - this.#isInternal = true - accounts.push(new this(address, publicKey)) - } - return accounts - } - - /** - * Instantiates an Account object from its private key which is then encrypted - * and stored in IndexedDB. The corresponding public key will automatically be - * derived and saved. - * - * @param {(string|Uint8Array)} privateKeys - Private key of the account - * @param {number} [index] - Account number used when deriving the key - * @returns {Account} A new Account object - */ - static async #fromPrivateKeys (privateKeys: string[] | Uint8Array[], password: string | Uint8Array): Promise { - if (typeof password === 'string') password = utf8.toBytes(password) - if (password == null || !(password instanceof Uint8Array)) { - throw new Error('Invalid password when importing Account') - } - - const keypairs: Data = {} - for (let privateKey of privateKeys) { - this.#validateKey(privateKey) - if (typeof privateKey === 'string') privateKey = hex.toBytes(privateKey) - let publicKey: string - try { - const headers = { - method: 'convert' - } - const data = { - privateKey: new Uint8Array(privateKey).buffer - } - publicKey = await NanoNaClWorker.add(headers, data) - keypairs[publicKey] = privateKey.buffer - } catch (err) { - throw new Error(`Failed to derive public key from private key`, { cause: err }) - } - } - - const accounts = await this.#fromPublicKeys(Object.keys(keypairs)) - try { - const headers = { - method: 'set', - store: 'Account', - password: password.buffer - } - const isLocked = await SafeWorker.add(headers, keypairs) - if (!isLocked) { - throw null - } - return accounts - } catch (err) { - throw new Error(`Failed to lock Accounts`, { cause: err }) - } finally { - bytes.erase(password) + return this.#fromPublic(input)[0] } } @@ -411,6 +316,12 @@ export class Account { } } + /** + * Converts a Nano address to a public key. + * + * @param {string} address - Prefixed with `nano_` + * @returns Public key bytes as Uint8Array + */ static #addressToKey (address: string): Uint8Array { const publicKey = base32.toBytes(address.slice(-60, -8)) const checksum = base32.toBytes(address.slice(-8)) @@ -421,6 +332,102 @@ export class Account { return publicKey } + /** + * Instantiates an Account object from its private key which is then encrypted + * and stored in IndexedDB. The corresponding public key will automatically be + * derived and saved. + * + * @param {(string|Uint8Array)} privateKeys - Private key of the account + * @param {number} [index] - Account number used when deriving the key + * @returns {Account} A new Account object + */ + static async #fromPrivate (privateKeys: string[] | Uint8Array[], password: string | Uint8Array): Promise { + if (typeof password === 'string') password = utf8.toBytes(password) + if (password == null || !(password instanceof Uint8Array)) { + throw new Error('Invalid password when importing Account') + } + + const keypairs: Data = {} + for (let privateKey of privateKeys) { + this.#validateKey(privateKey) + if (typeof privateKey === 'string') privateKey = hex.toBytes(privateKey) + let publicKey: string + try { + const headers = { + method: 'convert' + } + const data = { + privateKey: new Uint8Array(privateKey).buffer + } + publicKey = await NanoNaClWorker.add(headers, data) + keypairs[publicKey] = privateKey.buffer + } catch (err) { + throw new Error(`Failed to derive public key from private key`, { cause: err }) + } + } + + const accounts = await this.import(Object.keys(keypairs)) + try { + const headers = { + method: 'set', + store: 'Account', + password: password.buffer + } + const isLocked = await SafeWorker.add(headers, keypairs) + if (!isLocked) { + throw null + } + return accounts + } catch (err) { + throw new Error(`Failed to lock Accounts`, { cause: err }) + } finally { + bytes.erase(password) + } + } + + /** + * Instantiates Account objects from public data, each specifying either its + * public key or its Nano address. + * + * @param {(string[]|Uint8Array[])} input - Public keys or addresses of the accounts + * @returns {Account[]} The instantiated Account objects + */ + static #fromPublic (input: string[] | Uint8Array[] | unknown): Account[] { + const inputArray: unknown[] = Array.isArray(input) ? input : [input] + const accounts: Account[] = [] + let address: string + let publicKey: Uint8Array + for (let i of inputArray) { + let keyError, addressError + try { + this.#validateKey(i) + publicKey = (typeof i === 'string') + ? hex.toBytes(i) + : i + address = this.#keyToAddress(publicKey) + } catch (err) { + keyError = err + try { + this.validate(i) + address = i + publicKey = this.#addressToKey(address) + } catch (err) { + addressError = err + throw new TypeError('Failed to import Account from public data', { cause: { keyError, addressError } }) + } + } + this.#isInternal = true + accounts.push(new this(address, publicKey)) + } + return accounts + } + + /** + * Converts a public key to a Nano address. + * + * @param {Uint8Array} publicKey - Public key bytes as Uint8Array + * @returns Nano address string using `nano_` prefix + */ static #keyToAddress (publicKey: Uint8Array): string { const checksum = new Blake2b(5).update(publicKey).digest().reverse() const encodedPublicKey = bytes.toBase32(publicKey) @@ -428,7 +435,13 @@ export class Account { return `${PREFIX}${encodedPublicKey}${encodedChecksum}` } - static #validateKey (key: unknown): asserts key is (string | Uint8Array) { + /** + * Validates a public or private key is 32-byte array or a 64-char hex string. + * + * @param {unknown} key - Key bytes as Uint8Array or hexadecimal string + * @throws If key is invalid + */ + static #validateKey (key: unknown): asserts key is (string | Uint8Array) { if (key === undefined) { throw new TypeError(`Key is undefined`) } -- 2.47.3