From: Chris Duncan Date: Sun, 27 Jul 2025 09:14:59 +0000 (-0700) Subject: Include wallet type in export to assist in restoration after page reload. Accept... X-Git-Tag: v0.10.5~50^2~1 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=0879f3793feb064bad1fa66871ad19a514f7088e;p=libnemo.git Include wallet type in export to assist in restoration after page reload. Accept only string passwords to lock and unlock wallet and remove deprecated tests. --- diff --git a/src/lib/wallets/wallet.ts b/src/lib/wallets/wallet.ts index d4981c3..63184a0 100644 --- a/src/lib/wallets/wallet.ts +++ b/src/lib/wallets/wallet.ts @@ -8,7 +8,7 @@ import { ADDRESS_GAP } from '#src/lib/constants.js' import { bytes, hex, utf8 } from '#src/lib/convert.js' import { Entropy } from '#src/lib/entropy.js' import { Rpc } from '#src/lib/rpc.js' -import { Key, KeyPair, WalletType } from '#types' +import { KeyPair, NamedData, WalletType } from '#types' import { SafeWorker } from '#workers' /** @@ -26,16 +26,26 @@ export abstract class Wallet { * * @returns Array of hexadecimal-formatted wallet IDs */ - static async export (): Promise { + static async export (): Promise | null> { try { const response = await SafeWorker.request({ method: 'export', store: 'Wallet' }) - return Object.keys(response) + const ids = Object.keys(response) + const data: NamedData = {} + ids.map(i => { + const [type, id] = i.split('_') + if (data[type] == null) { + data[type] = [id] + } else { + data[type].push(id) + } + }) + return data } catch (err) { console.error(err) - return [] + return null } } @@ -190,28 +200,24 @@ export abstract class Wallet { * Locks the wallet and all currently derived accounts with a password that * will be needed to unlock it later. * - * @param {Key} password Used to lock the wallet + * @param {string} password Used to lock the wallet * @returns True if successfully locked */ - async lock (password: Key): Promise { - if (typeof password === 'string') { - password = utf8.toBytes(password) - } + async lock (password: string): Promise { try { - if (password == null || !(password instanceof Uint8Array)) { - throw new Error('password must be string or bytes') + if (typeof password !== 'string') { + throw new TypeError('Invalid password') } const serialized = JSON.stringify({ id: this.id, mnemonic: this.#m?.phrase, seed: this.#s == null ? this.#s : bytes.toHex(this.#s) }) - const encoded = utf8.toBytes(serialized) const success = await SafeWorker.request({ method: 'store', store: 'Wallet', - [this.id]: encoded.buffer, - password: password.buffer + [this.id]: utf8.toBuffer(serialized), + password: utf8.toBuffer(password) }) if (!success) { throw null @@ -224,8 +230,6 @@ export abstract class Wallet { return this.#locked } catch (err) { throw new Error('failed to lock wallet', { cause: err }) - } finally { - bytes.erase(password) } } @@ -279,29 +283,24 @@ export abstract class Wallet { /** * Unlocks the wallet using the same password as used prior to lock it. * - * @param {Key} password Used previously to lock the wallet + * @param {string} password Used previously to lock the wallet * @returns True if successfully unlocked */ - async unlock (password: Key): Promise { - if (typeof password === 'string') { - password = utf8.toBytes(password) - } - let decoded, deserialized, id, mnemonic, seed + async unlock (password: string): Promise { + let response: NamedData try { - if (password == null || !(password instanceof Uint8Array)) { - throw new Error('password must be string or bytes') + if (typeof password !== 'string') { + throw new TypeError('Invalid password') } const response = await SafeWorker.request({ method: 'fetch', names: this.id, store: 'Wallet', - password: password.buffer + password: utf8.toBuffer(password) }) - decoded = bytes.toUtf8(new Uint8Array(response[this.id])) - deserialized = JSON.parse(decoded) - id = deserialized.id - mnemonic = deserialized.mnemonic - seed = deserialized.seed + const decoded = bytes.toUtf8(new Uint8Array(response[this.id])) + const deserialized = JSON.parse(decoded) + let { id, mnemonic, seed } = deserialized if (id == null) { throw new Error('ID is null') } @@ -321,9 +320,6 @@ export abstract class Wallet { return true } catch (err) { throw new Error('failed to unlock wallet', { cause: err }) - } finally { - bytes.erase(password) - decoded = deserialized = id = mnemonic = seed = undefined } } diff --git a/test/test.lock-unlock.mjs b/test/test.lock-unlock.mjs index 31f258d..a9ed5c5 100644 --- a/test/test.lock-unlock.mjs +++ b/test/test.lock-unlock.mjs @@ -46,29 +46,6 @@ await Promise.all([ await assert.resolves(wallet.destroy()) }) - await test('locking and unlocking a Bip44Wallet with random bytes', async () => { - const wallet = await Bip44Wallet.fromMnemonic(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD) - await wallet.unlock(NANO_TEST_VECTORS.PASSWORD) - const key = globalThis.crypto.getRandomValues(new Uint8Array(64)) - - const lockResult = await wallet.lock(new Uint8Array(key)) - assert.ok(lockResult) - assert.ok('mnemonic' in wallet) - assert.ok('seed' in wallet) - assert.throws(() => wallet.mnemonic) - assert.throws(() => wallet.seed) - - const unlockResult = await wallet.unlock(new Uint8Array(key)) - - assert.equal(unlockResult, true) - assert.ok('mnemonic' in wallet) - assert.ok('seed' in wallet) - assert.equal(wallet.mnemonic, NANO_TEST_VECTORS.MNEMONIC) - assert.equal(wallet.seed, NANO_TEST_VECTORS.BIP39_SEED) - - await assert.resolves(wallet.destroy()) - }) - await test('locking and unlocking a Bip44Wallet Account with a password', async () => { const wallet = await Bip44Wallet.fromMnemonic(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD) await wallet.unlock(NANO_TEST_VECTORS.PASSWORD) @@ -101,23 +78,6 @@ await Promise.all([ await assert.resolves(wallet.destroy()) }) - await test('fail to unlock a Bip44Wallet with different random bytes', async () => { - const wallet = await Bip44Wallet.fromMnemonic(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD) - await wallet.unlock(NANO_TEST_VECTORS.PASSWORD) - const rightKey = globalThis.crypto.getRandomValues(new Uint8Array(64)) - const wrongKey = globalThis.crypto.getRandomValues(new Uint8Array(64)) - const lockResult = await wallet.lock(new Uint8Array(rightKey)) - - await assert.rejects(wallet.unlock(new Uint8Array(wrongKey)), { message: 'Failed to unlock wallet' }) - assert.equal(lockResult, true) - assert.ok('mnemonic' in wallet) - assert.ok('seed' in wallet) - assert.throws(() => wallet.mnemonic) - assert.throws(() => wallet.seed) - - await assert.resolves(wallet.destroy()) - }) - await test('fail to unlock a Bip44Wallet with different valid inputs', async () => { const wallet = await Bip44Wallet.fromMnemonic(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD) const key = globalThis.crypto.getRandomValues(new Uint8Array(64)) @@ -196,30 +156,6 @@ await Promise.all([ await assert.resolves(wallet.destroy()) }) - await test('locking and unlocking a Blake2bWallet with random bytes', async () => { - const wallet = await Blake2bWallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_1) - await wallet.unlock(NANO_TEST_VECTORS.PASSWORD) - const key = globalThis.crypto.getRandomValues(new Uint8Array(64)) - const lockResult = await wallet.lock(new Uint8Array(key)) - - assert.equal(lockResult, true) - assert.ok('mnemonic' in wallet) - assert.ok('seed' in wallet) - assert.throws(() => wallet.mnemonic) - assert.throws(() => wallet.seed) - - const unlockResult = await wallet.unlock(new Uint8Array(key)) - - assert.equal(lockResult, true) - assert.equal(unlockResult, true) - assert.ok('mnemonic' in wallet) - assert.ok('seed' in wallet) - assert.equal(wallet.mnemonic, TREZOR_TEST_VECTORS.MNEMONIC_1) - assert.equal(wallet.seed, TREZOR_TEST_VECTORS.ENTROPY_1) - - await assert.resolves(wallet.destroy()) - }) - await test('locking and unlocking a Blake2bWallet Account with a password', async () => { const wallet = await Blake2bWallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_0) await wallet.unlock(NANO_TEST_VECTORS.PASSWORD) @@ -249,23 +185,6 @@ await Promise.all([ await assert.resolves(wallet.destroy()) }) - await test('fail to unlock a Blake2bWallet with different keys', async () => { - const wallet = await Blake2bWallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_1) - await wallet.unlock(NANO_TEST_VECTORS.PASSWORD) - const rightKey = globalThis.crypto.getRandomValues(new Uint8Array(64)) - const wrongKey = globalThis.crypto.getRandomValues(new Uint8Array(64)) - const lockResult = await wallet.lock(new Uint8Array(rightKey)) - - await assert.rejects(wallet.unlock(new Uint8Array(wrongKey)), { message: 'Failed to unlock wallet' }) - assert.equal(lockResult, true) - assert.ok('mnemonic' in wallet) - assert.ok('seed' in wallet) - assert.throws(() => wallet.mnemonic) - assert.throws(() => wallet.seed) - - await assert.resolves(wallet.destroy()) - }) - await test('fail to unlock a Blake2bWallet with different valid inputs', async () => { const wallet = await Blake2bWallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_1) const key = globalThis.crypto.getRandomValues(new Uint8Array(64))