--- /dev/null
+// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>\r
+// SPDX-License-Identifier: GPL-3.0-or-later\r
+\r
+import { KeyPair, Wallet } from '.'\r
+import { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js'\r
+import { SEED_LENGTH_BIP44 } from '#src/lib/constants.js'\r
+import { Entropy } from '#src/lib/entropy.js'\r
+import { Pool } from '#src/lib/pool.js'\r
+import { Bip44CkdWorker } from '#workers'\r
+\r
+/**\r
+* Hierarchical deterministic (HD) wallet created by using a source of entropy to\r
+* derive a mnemonic phrase. The mnemonic phrase, in combination with an optional\r
+* salt, is used to generate a seed. A value can be provided as a parameter for\r
+* entropy, mnemonic + salt, or seed; if no argument is passed, a new entropy\r
+* value will be generated using a cryptographically strong pseudorandom number\r
+* generator.\r
+*\r
+* Importantly, the salt is not stored in the instantiated Wallet object. If a\r
+* salt is used, then losing it means losing the ability to regenerate the seed\r
+* from the mnemonic.\r
+*\r
+* Accounts are derived from the seed. Private keys are derived using a BIP-44\r
+* derivation path. The public key is derived from the private key using the\r
+* Ed25519 key algorithm. Account addresses are derived as described in the nano\r
+* documentation (https://docs.nano.org)\r
+*\r
+* A password must be provided when creating or importing the wallet and is used\r
+* to lock and unlock the wallet. The wallet will be initialized as locked. When\r
+* the wallet is unlocked, a new password can be specified using the lock()\r
+* method.\r
+*/\r
+export class Bip44Wallet extends Wallet {\r
+ static #isInternal: boolean = false\r
+ #poolBip44Ckd: Pool\r
+\r
+ constructor (id: Entropy, seed: string, mnemonic?: Bip39Mnemonic) {\r
+ if (!Bip44Wallet.#isInternal) {\r
+ throw new Error(`Bip44Wallet cannot be instantiated directly. Use 'await Bip44Wallet.create()' instead.`)\r
+ }\r
+ Bip44Wallet.#isInternal = false\r
+ super(id, seed, mnemonic)\r
+ this.#poolBip44Ckd = new Pool(Bip44CkdWorker)\r
+ }\r
+\r
+ /**\r
+ * Removes encrypted secrets in storage and releases variable references to\r
+ * allow garbage collection.\r
+ */\r
+ destroy () {\r
+ super.destroy()\r
+ this.#poolBip44Ckd.terminate()\r
+ }\r
+\r
+ /**\r
+ * Creates a new HD wallet by using an entropy value generated using a\r
+ * cryptographically strong pseudorandom number generator.\r
+ *\r
+ * @param {string} password - Encrypts the wallet to lock and unlock it\r
+ * @param {string} [salt=''] - Used when generating the final seed\r
+ * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
+ */\r
+ static async create (password: string, salt?: string): Promise<Bip44Wallet>\r
+ /**\r
+ * Creates a new HD wallet by using an entropy value generated using a\r
+ * cryptographically strong pseudorandom number generator.\r
+ *\r
+ * @param {CryptoKey} key - Encrypts the wallet to lock and unlock it\r
+ * @param {string} [salt=''] - Used when generating the final seed\r
+ * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
+ */\r
+ static async create (key: CryptoKey, salt?: string): Promise<Bip44Wallet>\r
+ static async create (passkey: string | CryptoKey, salt: string = ''): Promise<Bip44Wallet> {\r
+ try {\r
+ const e = await Entropy.create()\r
+ return await Bip44Wallet.fromEntropy(passkey as string, e.hex, salt)\r
+ } catch (err) {\r
+ throw new Error(`Error creating new Bip44Wallet: ${err}`)\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Creates a new HD wallet by using a pregenerated entropy value. The user\r
+ * must ensure that it is cryptographically strongly random.\r
+ *\r
+ * @param {string} password - Used to lock and unlock the wallet\r
+ * @param {string} entropy - Used when generating the initial mnemonic phrase\r
+ * @param {string} [salt=''] - Used when generating the final seed\r
+ * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
+ */\r
+ static async fromEntropy (password: string, entropy: string, salt?: string): Promise<Bip44Wallet>\r
+ /**\r
+ * Creates a new HD wallet by using a pregenerated entropy value. The user\r
+ * must ensure that it is cryptographically strongly random.\r
+ *\r
+ * @param {CryptoKey} key - Used to lock and unlock the wallet\r
+ * @param {string} entropy - Used when generating the initial mnemonic phrase\r
+ * @param {string} [salt=''] - Used when generating the final seed\r
+ * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
+ */\r
+ static async fromEntropy (key: CryptoKey, entropy: string, salt?: string): Promise<Bip44Wallet>\r
+ static async fromEntropy (passkey: string | CryptoKey, entropy: string, salt: string = ''): Promise<Bip44Wallet> {\r
+ try {\r
+ const id = await Entropy.create(16)\r
+ const e = await Entropy.import(entropy)\r
+ const m = await Bip39Mnemonic.fromEntropy(e.hex)\r
+ const s = await m.toBip39Seed(salt)\r
+ Bip44Wallet.#isInternal = true\r
+ const wallet = new this(id, s, m)\r
+ await wallet.lock(passkey as string)\r
+ return wallet\r
+ } catch (err) {\r
+ throw new Error(`Error importing Bip44Wallet from entropy: ${err}`)\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Creates a new HD wallet by using a pregenerated mnemonic phrase.\r
+ *\r
+ * @param {string} password - Used to lock and unlock the wallet\r
+ * @param {string} mnemonic - Used when generating the final seed\r
+ * @param {string} [salt=''] - Used when generating the final seed\r
+ * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
+ */\r
+ static async fromMnemonic (password: string, mnemonic: string, salt?: string): Promise<Bip44Wallet>\r
+ /**\r
+ * Creates a new HD wallet by using a pregenerated mnemonic phrase.\r
+ *\r
+ * @param {CryptoKey} key - Used to lock and unlock the wallet\r
+ * @param {string} mnemonic - Used when generating the final seed\r
+ * @param {string} [salt=''] - Used when generating the final seed\r
+ * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
+ */\r
+ static async fromMnemonic (key: CryptoKey, mnemonic: string, salt?: string): Promise<Bip44Wallet>\r
+ static async fromMnemonic (passkey: string | CryptoKey, mnemonic: string, salt: string = ''): Promise<Bip44Wallet> {\r
+ try {\r
+ const id = await Entropy.create(16)\r
+ const m = await Bip39Mnemonic.fromPhrase(mnemonic)\r
+ const s = await m.toBip39Seed(salt)\r
+ Bip44Wallet.#isInternal = true\r
+ const wallet = new this(id, s, m)\r
+ await wallet.lock(passkey as string)\r
+ return wallet\r
+ } catch (err) {\r
+ throw new Error(`Error importing Bip44Wallet from mnemonic: ${err}`)\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Creates a new HD wallet by using a pregenerated seed value. This seed cannot\r
+ * be used to regenerate any higher level randomness which includes entropy,\r
+ * mnemonic phrase, and salt.\r
+ *\r
+ * @param {string} password - Used to lock and unlock the wallet\r
+ * @param {string} seed - Hexadecimal 128-character string used to derive private-public key pairs\r
+ * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
+ */\r
+ static async fromSeed (password: string, seed: string): Promise<Bip44Wallet>\r
+ /**\r
+ * Creates a new HD wallet by using a pregenerated seed value. This seed cannot\r
+ * be used to regenerate any higher level randomness which includes entropy,\r
+ * mnemonic phrase, and salt.\r
+ *\r
+ * @param {CryptoKey} key - Used to lock and unlock the wallet\r
+ * @param {string} seed - Hexadecimal 128-character string used to derive private-public key pairs\r
+ * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
+ */\r
+ static async fromSeed (key: CryptoKey, seed: string): Promise<Bip44Wallet>\r
+ static async fromSeed (passkey: string | CryptoKey, seed: string): Promise<Bip44Wallet> {\r
+ if (seed.length !== SEED_LENGTH_BIP44) {\r
+ throw new Error(`Expected a ${SEED_LENGTH_BIP44}-character seed, but received ${seed.length}-character string.`)\r
+ }\r
+ if (!/^[0-9a-fA-F]+$/i.test(seed)) {\r
+ throw new Error('Seed contains invalid hexadecimal characters.')\r
+ }\r
+ const id = await Entropy.create(16)\r
+ Bip44Wallet.#isInternal = true\r
+ const wallet = new this(id, seed)\r
+ await wallet.lock(passkey as string)\r
+ return wallet\r
+ }\r
+\r
+ /**\r
+ * Retrieves an existing HD wallet from session storage using its ID.\r
+ *\r
+ * @param {string} id - Generated when the wallet was initially created\r
+ * @returns {Bip44Wallet} Restored locked Bip44Wallet\r
+ */\r
+ static async restore (id: string): Promise<Bip44Wallet> {\r
+ if (typeof id !== 'string' || id === '') {\r
+ throw new TypeError('Wallet ID is required to restore')\r
+ }\r
+ Bip44Wallet.#isInternal = true\r
+ return new this(await Entropy.import(id), '')\r
+ }\r
+\r
+ /**\r
+ * Derives BIP-44 Nano account private keys.\r
+ *\r
+ * @param {number[]} indexes - Indexes of the accounts\r
+ * @returns {Promise<Account>}\r
+ */\r
+ async ckd (indexes: number[]): Promise<KeyPair[]> {\r
+ const data: any = []\r
+ indexes.forEach(i => data.push({ seed: this.seed, index: i }))\r
+ const privateKeys: KeyPair[] = await this.#poolBip44Ckd.assign(data)\r
+ return privateKeys\r
+ }\r
+}\r
--- /dev/null
+// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>\r
+// SPDX-License-Identifier: GPL-3.0-or-later\r
+\r
+import { KeyPair, Wallet } from '.'\r
+import { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js'\r
+import { Blake2b } from '#src/lib/blake2b.js'\r
+import { SEED_LENGTH_BLAKE2B } from '#src/lib/constants.js'\r
+import { hex } from '#src/lib/convert.js'\r
+import { Entropy } from '#src/lib/entropy.js'\r
+\r
+/**\r
+* BLAKE2b wallet created by deriving a mnemonic phrase from a seed or vice\r
+* versa. If no value is provided for either, a new BIP-39 seed and mnemonic will\r
+* be generated using a cryptographically strong pseudorandom number generator.\r
+*\r
+* Account private keys are derived on an ad hoc basis using the Blake2b hashing\r
+* function. Account public key are derived from the private key using the\r
+* Ed25519 key algorithm. Account addresses are derived from the public key as\r
+* described in the Nano documentation.\r
+* https://docs.nano.org/integration-guides/the-basics/\r
+*\r
+* A password must be provided when creating or importing the wallet and is used\r
+* to lock and unlock the wallet. The wallet will be initialized as locked. When\r
+* the wallet is unlocked, a new password can be specified using the lock()\r
+* method.\r
+*/\r
+export class Blake2bWallet extends Wallet {\r
+ static #isInternal: boolean = false\r
+\r
+ constructor (id: Entropy, seed: string, mnemonic?: Bip39Mnemonic) {\r
+ if (!Blake2bWallet.#isInternal) {\r
+ throw new Error(`Blake2bWallet cannot be instantiated directly. Use 'await Blake2bWallet.create()' instead.`)\r
+ }\r
+ Blake2bWallet.#isInternal = false\r
+ super(id, seed, mnemonic)\r
+ }\r
+\r
+ /**\r
+ * Creates a new BLAKE2b wallet by using a seed generated using a\r
+ * cryptographically strong pseudorandom number generator.\r
+ *\r
+ * @param {string} password - Encrypts the wallet to lock and unlock it\r
+ * @returns {Blake2bWallet} A newly instantiated Blake2bWallet\r
+ */\r
+ static async create (password: string): Promise<Blake2bWallet>\r
+ /**\r
+ * Creates a new BLAKE2b wallet by using a seed generated using a\r
+ * cryptographically strong pseudorandom number generator.\r
+ *\r
+ * @param {CryptoKey} key - Encrypts the wallet to lock and unlock it\r
+ * @returns {Blake2bWallet} A newly instantiated Blake2bWallet\r
+ */\r
+ static async create (key: CryptoKey): Promise<Blake2bWallet>\r
+ static async create (passkey: string | CryptoKey): Promise<Blake2bWallet> {\r
+ try {\r
+ const seed = await Entropy.create()\r
+ return await Blake2bWallet.fromSeed(passkey as string, seed.hex)\r
+ } catch (err) {\r
+ throw new Error(`Error creating new Blake2bWallet: ${err}`)\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Creates a new BLAKE2b wallet by using a pregenerated seed. The user must\r
+ * ensure that it is cryptographically strongly random.\r
+ *\r
+ * @param {string} password - Used to lock and unlock the wallet\r
+ * @param {string} seed - Hexadecimal 64-character string used to derive private-public key pairs\r
+ * @returns {Blake2bWallet} A newly instantiated Blake2bWallet\r
+ */\r
+ static async fromSeed (password: string, seed: string): Promise<Blake2bWallet>\r
+ /**\r
+ * Creates a new BLAKE2b wallet by using a pregenerated seed. The user must\r
+ * ensure that it is cryptographically strongly random.\r
+ *\r
+ * @param {CryptoKey} key - Used to lock and unlock the wallet\r
+ * @param {string} seed - Hexadecimal 64-character string used to derive private-public key pairs\r
+ * @returns {Blake2bWallet} A newly instantiated Blake2bWallet\r
+ */\r
+ static async fromSeed (key: CryptoKey, seed: string): Promise<Blake2bWallet>\r
+ static async fromSeed (passkey: string | CryptoKey, seed: string): Promise<Blake2bWallet> {\r
+ if (seed.length !== SEED_LENGTH_BLAKE2B) {\r
+ throw new Error(`Expected a ${SEED_LENGTH_BLAKE2B}-character seed, but received ${seed.length}-character string.`)\r
+ }\r
+ if (!/^[0-9a-fA-F]+$/i.test(seed)) {\r
+ throw new Error('Seed contains invalid hexadecimal characters.')\r
+ }\r
+ const id = await Entropy.create(16)\r
+ const s = seed\r
+ const m = await Bip39Mnemonic.fromEntropy(seed)\r
+ Blake2bWallet.#isInternal = true\r
+ const wallet = new this(id, s, m)\r
+ await wallet.lock(passkey as string)\r
+ return wallet\r
+ }\r
+\r
+ /**\r
+ * Creates a new BLAKE2b wallet by using a pregenerated mnemonic phrase.\r
+ *\r
+ * @param {string} password - Used to lock and unlock the wallet\r
+ * @param {string} mnemonic - Used when generating the final seed\r
+ * @returns {Blake2bWallet} A newly instantiated Blake2bWallet\r
+ */\r
+ static async fromMnemonic (password: string, mnemonic: string): Promise<Blake2bWallet>\r
+ /**\r
+ * Creates a new BLAKE2b wallet by using a pregenerated mnemonic phrase.\r
+ *\r
+ * @param {CryptoKey} key - Used to lock and unlock the wallet\r
+ * @param {string} mnemonic - Used when generating the final seed\r
+ * @returns {Blake2bWallet} A newly instantiated Blake2bWallet\r
+ */\r
+ static async fromMnemonic (key: CryptoKey, mnemonic: string): Promise<Blake2bWallet>\r
+ static async fromMnemonic (passkey: string | CryptoKey, mnemonic: string): Promise<Blake2bWallet> {\r
+ try {\r
+ const id = await Entropy.create(16)\r
+ const m = await Bip39Mnemonic.fromPhrase(mnemonic)\r
+ const s = await m.toBlake2bSeed()\r
+ Blake2bWallet.#isInternal = true\r
+ const wallet = new this(id, s, m)\r
+ await wallet.lock(passkey as string)\r
+ return wallet\r
+ } catch (err) {\r
+ throw new Error(`Error importing Blake2bWallet from mnemonic: ${err}`)\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Retrieves an existing BLAKE2b wallet from session storage using its ID.\r
+ *\r
+ * @param {string} id - Generated when the wallet was initially created\r
+ * @returns {Blake2bWallet} Restored locked Blake2bWallet\r
+ */\r
+ static async restore (id: string): Promise<Blake2bWallet> {\r
+ if (typeof id !== 'string' || id === '') {\r
+ throw new TypeError('Wallet ID is required to restore')\r
+ }\r
+ Blake2bWallet.#isInternal = true\r
+ return new this(await Entropy.import(id), '')\r
+ }\r
+\r
+ /**\r
+ * Derives BLAKE2b account private keys.\r
+ *\r
+ * @param {number[]} indexes - Indexes of the accounts\r
+ * @returns {Promise<Account>}\r
+ */\r
+ async ckd (indexes: number[]): Promise<KeyPair[]> {\r
+ const results = indexes.map(index => {\r
+ const indexHex = index.toString(16).padStart(8, '0').toUpperCase()\r
+ const inputHex = `${this.seed}${indexHex}`.padStart(72, '0')\r
+ const inputBytes = hex.toBytes(inputHex)\r
+ const privateKey: string = new Blake2b(32).update(inputBytes).digest('hex')\r
+ return { privateKey, index }\r
+ })\r
+ return results\r
+ }\r
+}\r
\r
import { Account, AccountList } from '#src/lib/account.js'\r
import { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js'\r
-import { Blake2b } from '#src/lib/blake2b.js'\r
-import { ADDRESS_GAP, SEED_LENGTH_BIP44, SEED_LENGTH_BLAKE2B } from '#src/lib/constants.js'\r
-import { hex } from '#src/lib/convert.js'\r
+import { ADDRESS_GAP } from '#src/lib/constants.js'\r
import { Entropy } from '#src/lib/entropy.js'\r
-import { Ledger } from '#src/lib/ledger.js'\r
import { Pool } from '#src/lib/pool.js'\r
import { Rpc } from '#src/lib/rpc.js'\r
import { Safe } from '#src/lib/safe.js'\r
-import { Bip44CkdWorker, NanoNaClWorker } from '#workers'\r
+import { NanoNaClWorker } from '#workers'\r
\r
-type KeyPair = {\r
+export { Bip44Wallet } from './bip44-wallet'\r
+export { Blake2bWallet } from './blake2b-wallet'\r
+export { LedgerWallet } from './ledger-wallet'\r
+export type KeyPair = {\r
publicKey?: string,\r
privateKey?: string,\r
index?: number\r
* types of wallets are supported, each as a derived class: Bip44Wallet,\r
* Blake2bWallet, LedgerWallet.\r
*/\r
-abstract class Wallet {\r
+export abstract class Wallet {\r
#accounts: AccountList\r
#id: Entropy\r
#locked: boolean = true\r
return true\r
}\r
}\r
-\r
-/**\r
-* Hierarchical deterministic (HD) wallet created by using a source of entropy to\r
-* derive a mnemonic phrase. The mnemonic phrase, in combination with an optional\r
-* salt, is used to generate a seed. A value can be provided as a parameter for\r
-* entropy, mnemonic + salt, or seed; if no argument is passed, a new entropy\r
-* value will be generated using a cryptographically strong pseudorandom number\r
-* generator.\r
-*\r
-* Importantly, the salt is not stored in the instantiated Wallet object. If a\r
-* salt is used, then losing it means losing the ability to regenerate the seed\r
-* from the mnemonic.\r
-*\r
-* Accounts are derived from the seed. Private keys are derived using a BIP-44\r
-* derivation path. The public key is derived from the private key using the\r
-* Ed25519 key algorithm. Account addresses are derived as described in the nano\r
-* documentation (https://docs.nano.org)\r
-*\r
-* A password must be provided when creating or importing the wallet and is used\r
-* to lock and unlock the wallet. The wallet will be initialized as locked. When\r
-* the wallet is unlocked, a new password can be specified using the lock()\r
-* method.\r
-*/\r
-export class Bip44Wallet extends Wallet {\r
- static #isInternal: boolean = false\r
- #poolBip44Ckd: Pool\r
-\r
- constructor (id: Entropy, seed: string, mnemonic?: Bip39Mnemonic) {\r
- if (!Bip44Wallet.#isInternal) {\r
- throw new Error(`Bip44Wallet cannot be instantiated directly. Use 'await Bip44Wallet.create()' instead.`)\r
- }\r
- Bip44Wallet.#isInternal = false\r
- super(id, seed, mnemonic)\r
- this.#poolBip44Ckd = new Pool(Bip44CkdWorker)\r
- }\r
-\r
- /**\r
- * Removes encrypted secrets in storage and releases variable references to\r
- * allow garbage collection.\r
- */\r
- destroy () {\r
- super.destroy()\r
- this.#poolBip44Ckd.terminate()\r
- }\r
-\r
- /**\r
- * Creates a new HD wallet by using an entropy value generated using a\r
- * cryptographically strong pseudorandom number generator.\r
- *\r
- * @param {string} password - Encrypts the wallet to lock and unlock it\r
- * @param {string} [salt=''] - Used when generating the final seed\r
- * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
- */\r
- static async create (password: string, salt?: string): Promise<Bip44Wallet>\r
- /**\r
- * Creates a new HD wallet by using an entropy value generated using a\r
- * cryptographically strong pseudorandom number generator.\r
- *\r
- * @param {CryptoKey} key - Encrypts the wallet to lock and unlock it\r
- * @param {string} [salt=''] - Used when generating the final seed\r
- * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
- */\r
- static async create (key: CryptoKey, salt?: string): Promise<Bip44Wallet>\r
- static async create (passkey: string | CryptoKey, salt: string = ''): Promise<Bip44Wallet> {\r
- try {\r
- const e = await Entropy.create()\r
- return await Bip44Wallet.fromEntropy(passkey as string, e.hex, salt)\r
- } catch (err) {\r
- throw new Error(`Error creating new Bip44Wallet: ${err}`)\r
- }\r
- }\r
-\r
- /**\r
- * Creates a new HD wallet by using a pregenerated entropy value. The user\r
- * must ensure that it is cryptographically strongly random.\r
- *\r
- * @param {string} password - Used to lock and unlock the wallet\r
- * @param {string} entropy - Used when generating the initial mnemonic phrase\r
- * @param {string} [salt=''] - Used when generating the final seed\r
- * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
- */\r
- static async fromEntropy (password: string, entropy: string, salt?: string): Promise<Bip44Wallet>\r
- /**\r
- * Creates a new HD wallet by using a pregenerated entropy value. The user\r
- * must ensure that it is cryptographically strongly random.\r
- *\r
- * @param {CryptoKey} key - Used to lock and unlock the wallet\r
- * @param {string} entropy - Used when generating the initial mnemonic phrase\r
- * @param {string} [salt=''] - Used when generating the final seed\r
- * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
- */\r
- static async fromEntropy (key: CryptoKey, entropy: string, salt?: string): Promise<Bip44Wallet>\r
- static async fromEntropy (passkey: string | CryptoKey, entropy: string, salt: string = ''): Promise<Bip44Wallet> {\r
- try {\r
- const id = await Entropy.create(16)\r
- const e = await Entropy.import(entropy)\r
- const m = await Bip39Mnemonic.fromEntropy(e.hex)\r
- const s = await m.toBip39Seed(salt)\r
- Bip44Wallet.#isInternal = true\r
- const wallet = new this(id, s, m)\r
- await wallet.lock(passkey as string)\r
- return wallet\r
- } catch (err) {\r
- throw new Error(`Error importing Bip44Wallet from entropy: ${err}`)\r
- }\r
- }\r
-\r
- /**\r
- * Creates a new HD wallet by using a pregenerated mnemonic phrase.\r
- *\r
- * @param {string} password - Used to lock and unlock the wallet\r
- * @param {string} mnemonic - Used when generating the final seed\r
- * @param {string} [salt=''] - Used when generating the final seed\r
- * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
- */\r
- static async fromMnemonic (password: string, mnemonic: string, salt?: string): Promise<Bip44Wallet>\r
- /**\r
- * Creates a new HD wallet by using a pregenerated mnemonic phrase.\r
- *\r
- * @param {CryptoKey} key - Used to lock and unlock the wallet\r
- * @param {string} mnemonic - Used when generating the final seed\r
- * @param {string} [salt=''] - Used when generating the final seed\r
- * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
- */\r
- static async fromMnemonic (key: CryptoKey, mnemonic: string, salt?: string): Promise<Bip44Wallet>\r
- static async fromMnemonic (passkey: string | CryptoKey, mnemonic: string, salt: string = ''): Promise<Bip44Wallet> {\r
- try {\r
- const id = await Entropy.create(16)\r
- const m = await Bip39Mnemonic.fromPhrase(mnemonic)\r
- const s = await m.toBip39Seed(salt)\r
- Bip44Wallet.#isInternal = true\r
- const wallet = new this(id, s, m)\r
- await wallet.lock(passkey as string)\r
- return wallet\r
- } catch (err) {\r
- throw new Error(`Error importing Bip44Wallet from mnemonic: ${err}`)\r
- }\r
- }\r
-\r
- /**\r
- * Creates a new HD wallet by using a pregenerated seed value. This seed cannot\r
- * be used to regenerate any higher level randomness which includes entropy,\r
- * mnemonic phrase, and salt.\r
- *\r
- * @param {string} password - Used to lock and unlock the wallet\r
- * @param {string} seed - Hexadecimal 128-character string used to derive private-public key pairs\r
- * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
- */\r
- static async fromSeed (password: string, seed: string): Promise<Bip44Wallet>\r
- /**\r
- * Creates a new HD wallet by using a pregenerated seed value. This seed cannot\r
- * be used to regenerate any higher level randomness which includes entropy,\r
- * mnemonic phrase, and salt.\r
- *\r
- * @param {CryptoKey} key - Used to lock and unlock the wallet\r
- * @param {string} seed - Hexadecimal 128-character string used to derive private-public key pairs\r
- * @returns {Bip44Wallet} A newly instantiated Bip44Wallet\r
- */\r
- static async fromSeed (key: CryptoKey, seed: string): Promise<Bip44Wallet>\r
- static async fromSeed (passkey: string | CryptoKey, seed: string): Promise<Bip44Wallet> {\r
- if (seed.length !== SEED_LENGTH_BIP44) {\r
- throw new Error(`Expected a ${SEED_LENGTH_BIP44}-character seed, but received ${seed.length}-character string.`)\r
- }\r
- if (!/^[0-9a-fA-F]+$/i.test(seed)) {\r
- throw new Error('Seed contains invalid hexadecimal characters.')\r
- }\r
- const id = await Entropy.create(16)\r
- Bip44Wallet.#isInternal = true\r
- const wallet = new this(id, seed)\r
- await wallet.lock(passkey as string)\r
- return wallet\r
- }\r
-\r
- /**\r
- * Retrieves an existing HD wallet from session storage using its ID.\r
- *\r
- * @param {string} id - Generated when the wallet was initially created\r
- * @returns {Bip44Wallet} Restored locked Bip44Wallet\r
- */\r
- static async restore (id: string): Promise<Bip44Wallet> {\r
- if (typeof id !== 'string' || id === '') {\r
- throw new TypeError('Wallet ID is required to restore')\r
- }\r
- Bip44Wallet.#isInternal = true\r
- return new this(await Entropy.import(id), '')\r
- }\r
-\r
- /**\r
- * Derives BIP-44 Nano account private keys.\r
- *\r
- * @param {number[]} indexes - Indexes of the accounts\r
- * @returns {Promise<Account>}\r
- */\r
- async ckd (indexes: number[]): Promise<KeyPair[]> {\r
- const data: any = []\r
- indexes.forEach(i => data.push({ seed: this.seed, index: i }))\r
- const privateKeys: KeyPair[] = await this.#poolBip44Ckd.assign(data)\r
- return privateKeys\r
- }\r
-}\r
-\r
-/**\r
-* BLAKE2b wallet created by deriving a mnemonic phrase from a seed or vice\r
-* versa. If no value is provided for either, a new BIP-39 seed and mnemonic will\r
-* be generated using a cryptographically strong pseudorandom number generator.\r
-*\r
-* Account private keys are derived on an ad hoc basis using the Blake2b hashing\r
-* function. Account public key are derived from the private key using the\r
-* Ed25519 key algorithm. Account addresses are derived from the public key as\r
-* described in the Nano documentation.\r
-* https://docs.nano.org/integration-guides/the-basics/\r
-*\r
-* A password must be provided when creating or importing the wallet and is used\r
-* to lock and unlock the wallet. The wallet will be initialized as locked. When\r
-* the wallet is unlocked, a new password can be specified using the lock()\r
-* method.\r
-*/\r
-export class Blake2bWallet extends Wallet {\r
- static #isInternal: boolean = false\r
-\r
- constructor (id: Entropy, seed: string, mnemonic?: Bip39Mnemonic) {\r
- if (!Blake2bWallet.#isInternal) {\r
- throw new Error(`Blake2bWallet cannot be instantiated directly. Use 'await Blake2bWallet.create()' instead.`)\r
- }\r
- Blake2bWallet.#isInternal = false\r
- super(id, seed, mnemonic)\r
- }\r
-\r
- /**\r
- * Creates a new BLAKE2b wallet by using a seed generated using a\r
- * cryptographically strong pseudorandom number generator.\r
- *\r
- * @param {string} password - Encrypts the wallet to lock and unlock it\r
- * @returns {Blake2bWallet} A newly instantiated Blake2bWallet\r
- */\r
- static async create (password: string): Promise<Blake2bWallet>\r
- /**\r
- * Creates a new BLAKE2b wallet by using a seed generated using a\r
- * cryptographically strong pseudorandom number generator.\r
- *\r
- * @param {CryptoKey} key - Encrypts the wallet to lock and unlock it\r
- * @returns {Blake2bWallet} A newly instantiated Blake2bWallet\r
- */\r
- static async create (key: CryptoKey): Promise<Blake2bWallet>\r
- static async create (passkey: string | CryptoKey): Promise<Blake2bWallet> {\r
- try {\r
- const seed = await Entropy.create()\r
- return await Blake2bWallet.fromSeed(passkey as string, seed.hex)\r
- } catch (err) {\r
- throw new Error(`Error creating new Blake2bWallet: ${err}`)\r
- }\r
- }\r
-\r
- /**\r
- * Creates a new BLAKE2b wallet by using a pregenerated seed. The user must\r
- * ensure that it is cryptographically strongly random.\r
- *\r
- * @param {string} password - Used to lock and unlock the wallet\r
- * @param {string} seed - Hexadecimal 64-character string used to derive private-public key pairs\r
- * @returns {Blake2bWallet} A newly instantiated Blake2bWallet\r
- */\r
- static async fromSeed (password: string, seed: string): Promise<Blake2bWallet>\r
- /**\r
- * Creates a new BLAKE2b wallet by using a pregenerated seed. The user must\r
- * ensure that it is cryptographically strongly random.\r
- *\r
- * @param {CryptoKey} key - Used to lock and unlock the wallet\r
- * @param {string} seed - Hexadecimal 64-character string used to derive private-public key pairs\r
- * @returns {Blake2bWallet} A newly instantiated Blake2bWallet\r
- */\r
- static async fromSeed (key: CryptoKey, seed: string): Promise<Blake2bWallet>\r
- static async fromSeed (passkey: string | CryptoKey, seed: string): Promise<Blake2bWallet> {\r
- if (seed.length !== SEED_LENGTH_BLAKE2B) {\r
- throw new Error(`Expected a ${SEED_LENGTH_BLAKE2B}-character seed, but received ${seed.length}-character string.`)\r
- }\r
- if (!/^[0-9a-fA-F]+$/i.test(seed)) {\r
- throw new Error('Seed contains invalid hexadecimal characters.')\r
- }\r
- const id = await Entropy.create(16)\r
- const s = seed\r
- const m = await Bip39Mnemonic.fromEntropy(seed)\r
- Blake2bWallet.#isInternal = true\r
- const wallet = new this(id, s, m)\r
- await wallet.lock(passkey as string)\r
- return wallet\r
- }\r
-\r
- /**\r
- * Creates a new BLAKE2b wallet by using a pregenerated mnemonic phrase.\r
- *\r
- * @param {string} password - Used to lock and unlock the wallet\r
- * @param {string} mnemonic - Used when generating the final seed\r
- * @returns {Blake2bWallet} A newly instantiated Blake2bWallet\r
- */\r
- static async fromMnemonic (password: string, mnemonic: string): Promise<Blake2bWallet>\r
- /**\r
- * Creates a new BLAKE2b wallet by using a pregenerated mnemonic phrase.\r
- *\r
- * @param {CryptoKey} key - Used to lock and unlock the wallet\r
- * @param {string} mnemonic - Used when generating the final seed\r
- * @returns {Blake2bWallet} A newly instantiated Blake2bWallet\r
- */\r
- static async fromMnemonic (key: CryptoKey, mnemonic: string): Promise<Blake2bWallet>\r
- static async fromMnemonic (passkey: string | CryptoKey, mnemonic: string): Promise<Blake2bWallet> {\r
- try {\r
- const id = await Entropy.create(16)\r
- const m = await Bip39Mnemonic.fromPhrase(mnemonic)\r
- const s = await m.toBlake2bSeed()\r
- Blake2bWallet.#isInternal = true\r
- const wallet = new this(id, s, m)\r
- await wallet.lock(passkey as string)\r
- return wallet\r
- } catch (err) {\r
- throw new Error(`Error importing Blake2bWallet from mnemonic: ${err}`)\r
- }\r
- }\r
-\r
- /**\r
- * Retrieves an existing BLAKE2b wallet from session storage using its ID.\r
- *\r
- * @param {string} id - Generated when the wallet was initially created\r
- * @returns {Blake2bWallet} Restored locked Blake2bWallet\r
- */\r
- static async restore (id: string): Promise<Blake2bWallet> {\r
- if (typeof id !== 'string' || id === '') {\r
- throw new TypeError('Wallet ID is required to restore')\r
- }\r
- Blake2bWallet.#isInternal = true\r
- return new this(await Entropy.import(id), '')\r
- }\r
-\r
- /**\r
- * Derives BLAKE2b account private keys.\r
- *\r
- * @param {number[]} indexes - Indexes of the accounts\r
- * @returns {Promise<Account>}\r
- */\r
- async ckd (indexes: number[]): Promise<KeyPair[]> {\r
- const results = indexes.map(index => {\r
- const indexHex = index.toString(16).padStart(8, '0').toUpperCase()\r
- const inputHex = `${this.seed}${indexHex}`.padStart(72, '0')\r
- const inputBytes = hex.toBytes(inputHex)\r
- const privateKey: string = new Blake2b(32).update(inputBytes).digest('hex')\r
- return { privateKey, index }\r
- })\r
- return results\r
- }\r
-}\r
-\r
-/**\r
-* Ledger hardware wallet created by communicating with a Ledger device via ADPU\r
-* calls. This wallet does not feature any seed nor mnemonic phrase as all\r
-* private keys are held in the secure chip of the device. As such, the user\r
-* is responsible for using Ledger technology to back up these pieces of data.\r
-*\r
-* Usage of this wallet is generally controlled by calling functions of the\r
-* `ledger` object. For example, the wallet interface should have a button to\r
-* initiate a device connection by calling `wallet.ledger.connect()`. For more\r
-* information, refer to the ledger.js service file.\r
-*/\r
-export class LedgerWallet extends Wallet {\r
- static #isInternal: boolean = false\r
- #ledger: Ledger\r
-\r
- get ledger () { return this.#ledger }\r
-\r
- constructor (id: Entropy, ledger: Ledger) {\r
- if (!LedgerWallet.#isInternal) {\r
- throw new Error(`LedgerWallet cannot be instantiated directly. Use 'await LedgerWallet.create()' instead.`)\r
- }\r
- LedgerWallet.#isInternal = false\r
- super(id)\r
- this.#ledger = ledger\r
- }\r
-\r
- /**\r
- * Creates a new Ledger hardware wallet communication layer by dynamically\r
- * importing the ledger.js service.\r
- *\r
- * @returns {LedgerWallet} A wallet containing accounts and a Ledger device communication object\r
- */\r
- static async create (): Promise<LedgerWallet> {\r
- const { Ledger } = await import('../ledger')\r
- const l = await Ledger.init()\r
- const id = await Entropy.create(16)\r
- LedgerWallet.#isInternal = true\r
- return new this(id, l)\r
- }\r
-\r
- /**\r
- * Retrieves an existing Ledger wallet from session storage using its ID.\r
- *\r
- * @param {string} id - Generated when the wallet was initially created\r
- * @returns {LedgerWallet} Restored LedgerWallet\r
- */\r
- static async restore (id: string): Promise<LedgerWallet> {\r
- if (typeof id !== 'string' || id === '') {\r
- throw new TypeError('Wallet ID is required to restore')\r
- }\r
- const { Ledger } = await import('../ledger')\r
- const l = await Ledger.init()\r
- LedgerWallet.#isInternal = true\r
- return new this(await Entropy.import(id), l)\r
- }\r
-\r
- /**\r
- * Gets the public key for an account from the Ledger device.\r
- *\r
- * @param {number[]} indexes - Indexes of the accounts\r
- * @returns {Promise<Account>}\r
- */\r
- async ckd (indexes: number[]): Promise<KeyPair[]> {\r
- const results: KeyPair[] = []\r
- for (const index of indexes) {\r
- const { status, publicKey } = await this.ledger.account(index)\r
- if (status === 'OK' && publicKey != null) {\r
- results.push({ publicKey, index })\r
- } else {\r
- throw new Error(`Error getting Ledger account: ${status}`)\r
- }\r
- }\r
- return results\r
- }\r
-\r
- /**\r
- * Attempts to close the current process on the Ledger device.\r
- *\r
- * Overrides the default wallet `lock()` method since as a hardware wallet it\r
- * does not need to be encrypted by software.\r
- *\r
- * @returns True if successfully locked\r
- */\r
- async lock (): Promise<boolean> {\r
- if (this.ledger == null) {\r
- return false\r
- }\r
- const result = await this.ledger.close()\r
- return result.status === 'OK'\r
- }\r
-\r
- /**\r
- * Attempts to connect to the Ledger device.\r
- *\r
- * Overrides the default wallet `unlock()` method since as a hardware wallet it\r
- * does not need to be encrypted by software.\r
- *\r
- * @returns True if successfully unlocked\r
- */\r
- async unlock (): Promise<boolean> {\r
- if (this.ledger == null) {\r
- return false\r
- }\r
- const result = await this.ledger.connect()\r
- return result === 'OK'\r
- }\r
-}\r
--- /dev/null
+// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>\r
+// SPDX-License-Identifier: GPL-3.0-or-later\r
+\r
+import { KeyPair, Wallet } from '.'\r
+import { Entropy } from '#src/lib/entropy.js'\r
+import { Ledger } from '#src/lib/ledger.js'\r
+\r
+/**\r
+* Ledger hardware wallet created by communicating with a Ledger device via ADPU\r
+* calls. This wallet does not feature any seed nor mnemonic phrase as all\r
+* private keys are held in the secure chip of the device. As such, the user\r
+* is responsible for using Ledger technology to back up these pieces of data.\r
+*\r
+* Usage of this wallet is generally controlled by calling functions of the\r
+* `ledger` object. For example, the wallet interface should have a button to\r
+* initiate a device connection by calling `wallet.ledger.connect()`. For more\r
+* information, refer to the ledger.js service file.\r
+*/\r
+export class LedgerWallet extends Wallet {\r
+ static #isInternal: boolean = false\r
+ #ledger: Ledger\r
+\r
+ get ledger () { return this.#ledger }\r
+\r
+ constructor (id: Entropy, ledger: Ledger) {\r
+ if (!LedgerWallet.#isInternal) {\r
+ throw new Error(`LedgerWallet cannot be instantiated directly. Use 'await LedgerWallet.create()' instead.`)\r
+ }\r
+ LedgerWallet.#isInternal = false\r
+ super(id)\r
+ this.#ledger = ledger\r
+ }\r
+\r
+ /**\r
+ * Creates a new Ledger hardware wallet communication layer by dynamically\r
+ * importing the ledger.js service.\r
+ *\r
+ * @returns {LedgerWallet} A wallet containing accounts and a Ledger device communication object\r
+ */\r
+ static async create (): Promise<LedgerWallet> {\r
+ const { Ledger } = await import('../ledger')\r
+ const l = await Ledger.init()\r
+ const id = await Entropy.create(16)\r
+ LedgerWallet.#isInternal = true\r
+ return new this(id, l)\r
+ }\r
+\r
+ /**\r
+ * Retrieves an existing Ledger wallet from session storage using its ID.\r
+ *\r
+ * @param {string} id - Generated when the wallet was initially created\r
+ * @returns {LedgerWallet} Restored LedgerWallet\r
+ */\r
+ static async restore (id: string): Promise<LedgerWallet> {\r
+ if (typeof id !== 'string' || id === '') {\r
+ throw new TypeError('Wallet ID is required to restore')\r
+ }\r
+ const { Ledger } = await import('../ledger')\r
+ const l = await Ledger.init()\r
+ LedgerWallet.#isInternal = true\r
+ return new this(await Entropy.import(id), l)\r
+ }\r
+\r
+ /**\r
+ * Gets the public key for an account from the Ledger device.\r
+ *\r
+ * @param {number[]} indexes - Indexes of the accounts\r
+ * @returns {Promise<Account>}\r
+ */\r
+ async ckd (indexes: number[]): Promise<KeyPair[]> {\r
+ const results: KeyPair[] = []\r
+ for (const index of indexes) {\r
+ const { status, publicKey } = await this.ledger.account(index)\r
+ if (status === 'OK' && publicKey != null) {\r
+ results.push({ publicKey, index })\r
+ } else {\r
+ throw new Error(`Error getting Ledger account: ${status}`)\r
+ }\r
+ }\r
+ return results\r
+ }\r
+\r
+ /**\r
+ * Attempts to close the current process on the Ledger device.\r
+ *\r
+ * Overrides the default wallet `lock()` method since as a hardware wallet it\r
+ * does not need to be encrypted by software.\r
+ *\r
+ * @returns True if successfully locked\r
+ */\r
+ async lock (): Promise<boolean> {\r
+ if (this.ledger == null) {\r
+ return false\r
+ }\r
+ const result = await this.ledger.close()\r
+ return result.status === 'OK'\r
+ }\r
+\r
+ /**\r
+ * Attempts to connect to the Ledger device.\r
+ *\r
+ * Overrides the default wallet `unlock()` method since as a hardware wallet it\r
+ * does not need to be encrypted by software.\r
+ *\r
+ * @returns True if successfully unlocked\r
+ */\r
+ async unlock (): Promise<boolean> {\r
+ if (this.ledger == null) {\r
+ return false\r
+ }\r
+ const result = await this.ledger.connect()\r
+ return result === 'OK'\r
+ }\r
+}\r