From: Chris Duncan Date: Tue, 2 Sep 2025 20:42:25 +0000 (-0700) Subject: Implement password update method for wallet using vault entry point. X-Git-Tag: v0.10.5~35^2~6 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=e22c6b0be6beded4f0a773ca4dfa9305ade10ea8;p=libnemo.git Implement password update method for wallet using vault entry point. --- diff --git a/src/lib/wallet/index.ts b/src/lib/wallet/index.ts index 684b616..4497d46 100644 --- a/src/lib/wallet/index.ts +++ b/src/lib/wallet/index.ts @@ -20,6 +20,7 @@ import { _restore } from './restore' import { _sign } from './sign' import { _unlock } from './unlock' import { _unopened } from './unopened' +import { _update } from './update' import { _verify } from './verify' /** @@ -320,6 +321,16 @@ export class Wallet { return await _unopened(this, rpc, batchSize, from) } + /** + * Updates the password used to encrypt the wallet. The wallet must be unlocked + * prior to update. + * + * @param {string} password Used to re-encrypt the wallet + */ + async update (password: string): Promise { + await _update(this, this.#vault, password) + } + /** * Checks whether a given seed matches the wallet seed. The wallet must be * unlocked prior to verification. diff --git a/src/lib/wallet/update.ts b/src/lib/wallet/update.ts new file mode 100644 index 0000000..4e30de5 --- /dev/null +++ b/src/lib/wallet/update.ts @@ -0,0 +1,40 @@ +//! SPDX-FileCopyrightText: 2025 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +import { NamedData } from '#types' +import { utf8 } from '../convert' +import { Database } from '../database' +import { Vault } from '../vault' +import { Wallet } from '../wallet' +import { _get } from './get' + +export async function _update (wallet: Wallet, vault: Vault, password?: string): Promise +export async function _update (wallet: Wallet, vault: Vault, password: unknown): Promise { + try { + const record: NamedData = { + id: wallet.id, + type: wallet.type + } + if (wallet.type === 'Ledger') { + return + } else { + if (typeof password !== 'string') { + throw new TypeError('Password must be a string') + } + const { encrypted } = await _get(wallet.id) + const response = await vault.request({ + action: 'update', + type: wallet.type, + password: utf8.toBuffer(password), + encrypted + }) + password = undefined + record.iv = response.iv + record.salt = response.salt + record.encrypted = response.encrypted + } + await Database.put({ [wallet.id]: record }, Wallet.DB_NAME) + } catch (err) { + throw new Error('Failed to unlock wallet', { cause: err }) + } +} diff --git a/src/types.d.ts b/src/types.d.ts index 4d8eb03..be64a93 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -745,6 +745,13 @@ export declare class Wallet { */ unopened (rpc: Rpc, batchSize?: number, from?: number): Promise /** + * Updates the password used to encrypt the wallet. The wallet must be unlocked + * prior to update. + * + * @param {string} password Used to re-encrypt the wallet + */ + update (password: string): Promise + /** * Checks whether a given seed matches the wallet seed. The wallet must be * unlocked prior to verification. * diff --git a/test/test.lock-unlock.mjs b/test/test.lock-unlock.mjs index bca0ae1..14386df 100644 --- a/test/test.lock-unlock.mjs +++ b/test/test.lock-unlock.mjs @@ -39,6 +39,31 @@ await Promise.all([ await assert.resolves(wallet.destroy()) }) + await test('change the password on a BIP-44 wallet', async () => { + const wallet = await Wallet.load('BIP-44', '', NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD) + await wallet.unlock('') + + assert.ok('mnemonic' in wallet) + assert.ok('seed' in wallet) + assert.ok(wallet.mnemonic === undefined) + assert.ok(wallet.seed === undefined) + assert.ok(await wallet.verify(NANO_TEST_VECTORS.MNEMONIC)) + assert.ok(await wallet.verify(NANO_TEST_VECTORS.BIP39_SEED)) + + await wallet.update(NANO_TEST_VECTORS.PASSWORD) + wallet.lock() + await wallet.unlock(NANO_TEST_VECTORS.PASSWORD) + + assert.ok('mnemonic' in wallet) + assert.ok('seed' in wallet) + assert.ok(wallet.mnemonic === undefined) + assert.ok(wallet.seed === undefined) + assert.ok(await wallet.verify(NANO_TEST_VECTORS.MNEMONIC)) + assert.ok(await wallet.verify(NANO_TEST_VECTORS.BIP39_SEED)) + + await assert.resolves(wallet.destroy()) + }) + await test('fail to unlock a Bip44Wallet with different passwords', async () => { const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD) await wallet.unlock(NANO_TEST_VECTORS.PASSWORD) @@ -66,7 +91,6 @@ await Promise.all([ const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD) await wallet.unlock(NANO_TEST_VECTORS.PASSWORD) - //@ts-expect-error await assert.rejects(wallet.unlock(), { message: 'Failed to unlock wallet' }) //@ts-expect-error await assert.rejects(wallet.unlock(1), { message: 'Failed to unlock wallet' }) @@ -107,7 +131,6 @@ await Promise.all([ await test('fail to unlock a Blake2bWallet with no input', async () => { const wallet = await Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_1) - //@ts-expect-error await assert.rejects(wallet.unlock(), { message: 'Failed to unlock wallet' }) //@ts-expect-error await assert.rejects(wallet.unlock(1), { message: 'Failed to unlock wallet' })