]> git.codecow.com Git - libnemo.git/commitdiff
Implement password update method for wallet using vault entry point.
authorChris Duncan <chris@zoso.dev>
Tue, 2 Sep 2025 20:42:25 +0000 (13:42 -0700)
committerChris Duncan <chris@zoso.dev>
Tue, 2 Sep 2025 20:42:25 +0000 (13:42 -0700)
src/lib/wallet/index.ts
src/lib/wallet/update.ts [new file with mode: 0644]
src/types.d.ts
test/test.lock-unlock.mjs

index 684b61647dd7769404aa5a7e301b56f1ab71fcfb..4497d460ec9fb323bd068e412c64f965448e4c1b 100644 (file)
@@ -20,6 +20,7 @@ import { _restore } from './restore'
 import { _sign } from './sign'\r
 import { _unlock } from './unlock'\r
 import { _unopened } from './unopened'\r
+import { _update } from './update'\r
 import { _verify } from './verify'\r
 \r
 /**\r
@@ -320,6 +321,16 @@ export class Wallet {
                return await _unopened(this, rpc, batchSize, from)\r
        }\r
 \r
+       /**\r
+       * Updates the password used to encrypt the wallet. The wallet must be unlocked\r
+       * prior to update.\r
+       *\r
+       * @param {string} password Used to re-encrypt the wallet\r
+       */\r
+       async update (password: string): Promise<void> {\r
+               await _update(this, this.#vault, password)\r
+       }\r
+\r
        /**\r
        * Checks whether a given seed matches the wallet seed. The wallet must be\r
        * unlocked prior to verification.\r
diff --git a/src/lib/wallet/update.ts b/src/lib/wallet/update.ts
new file mode 100644 (file)
index 0000000..4e30de5
--- /dev/null
@@ -0,0 +1,40 @@
+//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
+//! 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<void>
+export async function _update (wallet: Wallet, vault: Vault, password: unknown): Promise<void> {
+       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<ArrayBuffer>({
+                               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 })
+       }
+}
index 4d8eb03b0ed9d5a82a50f763820da878a3f80f0a..be64a935cfca3e7f7dc12cb7576fdbd83218f631 100644 (file)
@@ -745,6 +745,13 @@ export declare class Wallet {
        */
        unopened (rpc: Rpc, batchSize?: number, from?: number): Promise<Account>
        /**
+       * 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<void>
+       /**
        * Checks whether a given seed matches the wallet seed. The wallet must be
        * unlocked prior to verification.
        *
index bca0ae1eefd93e2daa520ee4272211d9c8c7632f..14386df9e90191a33e0f4aaa986eefd3d44dfeed 100644 (file)
@@ -39,6 +39,31 @@ await Promise.all([
                        await assert.resolves(wallet.destroy())\r
                })\r
 \r
+               await test('change the password on a BIP-44 wallet', async () => {\r
+                       const wallet = await Wallet.load('BIP-44', '', NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD)\r
+                       await wallet.unlock('')\r
+\r
+                       assert.ok('mnemonic' in wallet)\r
+                       assert.ok('seed' in wallet)\r
+                       assert.ok(wallet.mnemonic === undefined)\r
+                       assert.ok(wallet.seed === undefined)\r
+                       assert.ok(await wallet.verify(NANO_TEST_VECTORS.MNEMONIC))\r
+                       assert.ok(await wallet.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
+\r
+                       await wallet.update(NANO_TEST_VECTORS.PASSWORD)\r
+                       wallet.lock()\r
+                       await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+\r
+                       assert.ok('mnemonic' in wallet)\r
+                       assert.ok('seed' in wallet)\r
+                       assert.ok(wallet.mnemonic === undefined)\r
+                       assert.ok(wallet.seed === undefined)\r
+                       assert.ok(await wallet.verify(NANO_TEST_VECTORS.MNEMONIC))\r
+                       assert.ok(await wallet.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
+\r
+                       await assert.resolves(wallet.destroy())\r
+               })\r
+\r
                await test('fail to unlock a Bip44Wallet with different passwords', async () => {\r
                        const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD)\r
                        await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
@@ -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)\r
                        await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
 \r
-                       //@ts-expect-error\r
                        await assert.rejects(wallet.unlock(), { message: 'Failed to unlock wallet' })\r
                        //@ts-expect-error\r
                        await assert.rejects(wallet.unlock(1), { message: 'Failed to unlock wallet' })\r
@@ -107,7 +131,6 @@ await Promise.all([
                await test('fail to unlock a Blake2bWallet with no input', async () => {\r
                        const wallet = await Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_1)\r
 \r
-                       //@ts-expect-error\r
                        await assert.rejects(wallet.unlock(), { message: 'Failed to unlock wallet' })\r
                        //@ts-expect-error\r
                        await assert.rejects(wallet.unlock(1), { message: 'Failed to unlock wallet' })\r