--- /dev/null
+//! 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 })
+ }
+}
* @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',
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 })
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
* @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
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
}\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
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()
})
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()
})
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 = []
})
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 = []
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()
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()
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
})\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
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