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
}\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
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
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