]> git.codecow.com Git - libnemo.git/commitdiff
Extract wallet create to separate module. Allow mnemonic salt to be excluded (undefin...
authorChris Duncan <chris@zoso.dev>
Sun, 10 Aug 2025 05:42:53 +0000 (22:42 -0700)
committerChris Duncan <chris@zoso.dev>
Sun, 10 Aug 2025 05:42:53 +0000 (22:42 -0700)
src/lib/wallet/create.ts [new file with mode: 0644]
src/lib/wallet/load.ts
src/lib/wallet/wallet.ts
test/perf.account.mjs
test/perf.wallet.mjs
test/test.create-wallet.mjs

diff --git a/src/lib/wallet/create.ts b/src/lib/wallet/create.ts
new file mode 100644 (file)
index 0000000..59fa4b2
--- /dev/null
@@ -0,0 +1,37 @@
+//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+import { Database } from '../database'
+import { _load } from './load'
+import { Wallet } from './wallet'
+
+/**
+* Creates a new HD wallet by using an entropy value generated using a
+* cryptographically strong pseudorandom number generator.
+*
+* @param {string} password - Encrypts the wallet to lock and unlock it
+* @param {string} [salt=''] - Used when generating the final seed
+* @returns {Wallet} A newly instantiated Wallet
+*/
+export async function _create (wallet: Wallet, password: ArrayBuffer, mnemonicSalt?: string) {
+       try {
+               const { iv, salt, encrypted, seed, mnemonic } = await wallet.safe.request<ArrayBuffer>({
+                       action: 'create',
+                       type: wallet.type,
+                       password,
+                       mnemonicSalt: mnemonicSalt ?? ''
+               })
+               const record = {
+                       id: wallet.id,
+                       type: wallet.type,
+                       iv,
+                       salt,
+                       encrypted
+               }
+               await Database.add({ [wallet.id]: record }, Wallet.DB_NAME)
+               return { mnemonic, seed }
+       } catch (err) {
+               await wallet.destroy()
+               throw new Error('Error creating new Wallet', { cause: err })
+       }
+}
index 245f28abd0ec5f2a2b80446bac119b19c720fee9..7a01d8c972b285a1b503ab0ccae9443318d64001 100644 (file)
@@ -15,7 +15,7 @@ import { Wallet } from './wallet'
 * @param {string} [salt=''] - Used when generating the final seed
 * @returns {Wallet} A newly instantiated Wallet
 */
-export async function _load (wallet: Wallet, password: ArrayBuffer, secret: string, mnemonicSalt?: string): Promise<Wallet> {
+export async function _load (wallet: Wallet, password: ArrayBuffer, secret: string, mnemonicSalt?: string): Promise<void> {
        try {
                const data: NamedData = {
                        action: 'load',
@@ -40,7 +40,6 @@ export async function _load (wallet: Wallet, password: ArrayBuffer, secret: stri
                        encrypted
                }
                await Database.add({ [wallet.id]: record }, Wallet.DB_NAME)
-               return wallet
        } catch (err) {
                await wallet.destroy()
                throw new Error('Error creating new Wallet', { cause: err })
index e3446c35815f7610fd3ef1c35e139510d6a35b9d..61f0828eb6b23bce25326d9a7e77dbc6636b5db1 100644 (file)
@@ -11,6 +11,7 @@ import { _load } from './load'
 import { Rpc } from '../rpc'\r
 import { default as SafeWorker } from './safe'\r
 import { WorkerQueue } from './worker-queue'\r
+import { _create } from './create'\r
 \r
 /**\r
 * Represents a wallet containing numerous Nano accounts derived from a single\r
@@ -41,31 +42,25 @@ export class Wallet {
        * @param {string} [salt=''] - Used when generating the final seed\r
        * @returns {Wallet} A newly instantiated Wallet\r
        */\r
-       static async create (type: 'BIP-44' | 'BLAKE2b', password: string, mnemonicSalt?: string): Promise<Wallet> {\r
+       static async create (password: string, type: 'BIP-44' | 'BLAKE2b', mnemonicSalt?: string): Promise<Wallet>\r
+       static async create (password: unknown, type: unknown, mnemonicSalt?: unknown): Promise<Wallet> {\r
+               if (typeof password !== 'string') {\r
+                       throw new TypeError('Password must be a string')\r
+               }\r
+               const passwordBuffer = utf8.toBuffer(password)\r
+               password = undefined\r
+               if (type !== 'BIP-44' && type !== 'BLAKE2b') {\r
+                       throw new TypeError('Invalid wallet type')\r
+               }\r
+               if (mnemonicSalt !== undefined && typeof mnemonicSalt !== 'string') {\r
+                       throw new TypeError('Mnemonic salt must be a string')\r
+               }\r
                Wallet.#isInternal = true\r
                const self = new this(type)\r
-               try {\r
-                       const { iv, salt, encrypted, seed, mnemonic } = await self.#safe.request<ArrayBuffer>({\r
-                               action: 'create',\r
-                               type,\r
-                               password: utf8.toBuffer(password),\r
-                               mnemonicSalt: mnemonicSalt ?? ''\r
-                       })\r
-                       self.#mnemonic = mnemonic\r
-                       self.#seed = seed\r
-                       const record = {\r
-                               id: self.id,\r
-                               type,\r
-                               iv,\r
-                               salt,\r
-                               encrypted\r
-                       }\r
-                       await Database.add({ [self.id]: record }, Wallet.DB_NAME)\r
-                       return self\r
-               } catch (err) {\r
-                       await self.destroy()\r
-                       throw new Error('Error creating new Wallet', { cause: err })\r
-               }\r
+               const { mnemonic, seed } = await _create(self, passwordBuffer, mnemonicSalt)\r
+               self.#mnemonic = mnemonic\r
+               self.#seed = seed\r
+               return self\r
        }\r
 \r
        /**\r
@@ -110,7 +105,7 @@ export class Wallet {
                        throw new TypeError('Password must be a string')\r
                }\r
                const passwordBuffer = utf8.toBuffer(password)\r
-               password = ''\r
+               password = undefined\r
                if (type !== 'BIP-44' && type !== 'BLAKE2b') {\r
                        throw new TypeError('Invalid wallet type', { cause: type })\r
                }\r
@@ -122,7 +117,8 @@ export class Wallet {
                }\r
                Wallet.#isInternal = true\r
                const self = new this(type)\r
-               return _load(self, passwordBuffer, secret, mnemonicSalt)\r
+               await _load(self, passwordBuffer, secret, mnemonicSalt)\r
+               return self\r
        }\r
 \r
        /**\r
index 90d8384e8aa564114105710a49dd6335724e0837..0e5919a71f1756bb602c83e6352514f66742ca41 100644 (file)
@@ -21,7 +21,7 @@ await Promise.all([
                const COUNT = 0x200
 
                await test(`Time to create ${COUNT} BIP-44 accounts`, async () => {
-                       const wallet = await Wallet.create('BIP-44', NANO_TEST_VECTORS.PASSWORD)
+                       const wallet = await Wallet.create(NANO_TEST_VECTORS.PASSWORD, 'BIP-44')
                        await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)
 
                        const start = performance.now()
@@ -35,7 +35,7 @@ await Promise.all([
                })
 
                await test(`Time to create ${COUNT} BLAKE2b accounts`, async () => {
-                       const wallet = await Wallet.create('BLAKE2b', NANO_TEST_VECTORS.PASSWORD)
+                       const wallet = await Wallet.create(NANO_TEST_VECTORS.PASSWORD, 'BLAKE2b')
                        await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)
 
                        const start = performance.now()
@@ -49,7 +49,7 @@ await Promise.all([
                })
 
                await test(`Time to create 1 BIP-44 account ${COUNT} times`, async () => {
-                       const wallet = await Wallet.create('BIP-44', NANO_TEST_VECTORS.PASSWORD)
+                       const wallet = await Wallet.create(NANO_TEST_VECTORS.PASSWORD, 'BIP-44')
                        await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)
 
                        const times = []
@@ -65,7 +65,7 @@ await Promise.all([
                })
 
                await test(`Time to create 1 BLAKE2b account ${COUNT} times`, async () => {
-                       const wallet = await Wallet.create('BLAKE2b', NANO_TEST_VECTORS.PASSWORD)
+                       const wallet = await Wallet.create(NANO_TEST_VECTORS.PASSWORD, 'BLAKE2b')
                        await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)
 
                        const times = []
index 6764e9ee58a3ae083ee0c4a7a95399abdb613b03..30e1a0a3162b4a67d65f0c2b2313554684bfe96d 100644 (file)
@@ -24,7 +24,7 @@ await Promise.all([
                        const times = []
                        for (let i = 0; i < COUNT; i++) {
                                const start = performance.now()
-                               const wallet = await Wallet.create('BIP-44', NANO_TEST_VECTORS.PASSWORD)
+                               const wallet = await Wallet.create(NANO_TEST_VECTORS.PASSWORD, 'BIP-44')
                                const end = performance.now()
                                times.push(end - start)
                                await wallet.destroy()
@@ -36,7 +36,7 @@ await Promise.all([
                        const times = []
                        for (let i = 0; i < COUNT; i++) {
                                const start = performance.now()
-                               const wallet = await Wallet.create('BLAKE2b', NANO_TEST_VECTORS.PASSWORD)
+                               const wallet = await Wallet.create(NANO_TEST_VECTORS.PASSWORD, 'BLAKE2b')
                                const end = performance.now()
                                times.push(end - start)
                                await wallet.destroy()
index cb3c6febc002c8d814a7589c9372e73e1483c086..a8eedf29f095d9902392652e81e70e7cb3ad7cb3 100644 (file)
@@ -20,14 +20,14 @@ await Promise.all([
        suite('Create wallets', async () => {\r
 \r
                await test('destroy BIP-44 wallet before unlocking', async () => {\r
-                       const wallet = await Wallet.create('BIP-44', NANO_TEST_VECTORS.PASSWORD)\r
+                       const wallet = await Wallet.create(NANO_TEST_VECTORS.PASSWORD, 'BIP-44')\r
 \r
                        await assert.resolves(wallet.destroy())\r
                        await assert.rejects(wallet.unlock(NANO_TEST_VECTORS.PASSWORD))\r
                })\r
 \r
                await test('BIP-44 wallet with random entropy', async () => {\r
-                       const wallet = await Wallet.create('BIP-44', NANO_TEST_VECTORS.PASSWORD)\r
+                       const wallet = await Wallet.create(NANO_TEST_VECTORS.PASSWORD, 'BIP-44')\r
                        await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
 \r
                        assert.ok('id' in wallet)\r
@@ -48,7 +48,7 @@ await Promise.all([
                })\r
 \r
                await test('BLAKE2b wallet with random entropy', async () => {\r
-                       const wallet = await Wallet.create('BLAKE2b', NANO_TEST_VECTORS.PASSWORD)\r
+                       const wallet = await Wallet.create(NANO_TEST_VECTORS.PASSWORD, 'BLAKE2b')\r
                        await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
 \r
                        assert.ok('id' in wallet)\r
@@ -68,13 +68,11 @@ await Promise.all([
                        await assert.resolves(wallet.destroy())\r
                })\r
 \r
-               await test('BIP-44 replace invalid salt with empty string', async () => {\r
+               await test('BIP-44 throw on invalid salt', async () => {\r
                        const invalidArgs = [null, true, false, 0, 1, 2, { foo: 'bar' }]\r
                        for (const arg of invalidArgs) {\r
                                //@ts-expect-error\r
-                               const wallet = Wallet.create('BIP-44', NANO_TEST_VECTORS.PASSWORD, arg)\r
-                               await assert.resolves(wallet)\r
-                               await assert.resolves((await wallet).destroy())\r
+                               await assert.rejects(Wallet.create(NANO_TEST_VECTORS.PASSWORD, 'BIP-44', arg))\r
                        }\r
                })\r
 \r