]> git.codecow.com Git - libnemo.git/commitdiff
Combine imports from public data into one method. Reorganize methods.
authorChris Duncan <chris@zoso.dev>
Thu, 17 Jul 2025 16:25:23 +0000 (09:25 -0700)
committerChris Duncan <chris@zoso.dev>
Thu, 17 Jul 2025 16:25:23 +0000 (09:25 -0700)
src/lib/account.ts

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