]> git.codecow.com Git - libnemo.git/commitdiff
Add support for Ledger psuedo-unlock in Vault so that Wallet isLocked still works...
authorChris Duncan <chris@zoso.dev>
Tue, 9 Sep 2025 21:01:19 +0000 (14:01 -0700)
committerChris Duncan <chris@zoso.dev>
Tue, 9 Sep 2025 21:01:19 +0000 (14:01 -0700)
src/lib/vault/vault-worker.ts
src/lib/wallet/unlock.ts
test/test.ledger.mjs

index c229a9198f9975a321e63caeb079afe1dc4229da..fb837d7776c6b3189869caa97abf6b5eac7e650e 100644 (file)
@@ -1,7 +1,7 @@
 //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
-import { NamedData } from '#types'
+import { NamedData, WalletType } from '#types'
 import { BIP44_COIN_NANO } from '../constants'
 import { Bip39, Bip44, Blake2b, NanoNaCl, WalletAesGcm } from '../crypto'
 import { Passkey } from './passkey'
@@ -111,7 +111,10 @@ export class VaultWorker {
        * Generates a new mnemonic and seed and then returns the initialization vector
        * vector, salt, and encrypted data representing the wallet in a locked state.
        */
-       create (type?: 'BIP-44' | 'BLAKE2b', key?: CryptoKey, keySalt?: ArrayBuffer, mnemonicSalt?: string): Promise<NamedData<ArrayBuffer>> {
+       create (type?: WalletType, key?: CryptoKey, keySalt?: ArrayBuffer, mnemonicSalt?: string): Promise<NamedData<ArrayBuffer>> {
+               if (type !== 'BIP-44' && type !== 'BLAKE2b') {
+                       throw new TypeError('Unsupported software wallet algorithm', { cause: type })
+               }
                try {
                        const entropy = crypto.getRandomValues(new Uint8Array(32))
                        return Bip39.fromEntropy(entropy)
@@ -170,7 +173,10 @@ export class VaultWorker {
        * Encrypts an existing seed or mnemonic+salt and returns the initialization
        * vector, salt, and encrypted data representing the wallet in a locked state.
        */
-       load (type?: 'BIP-44' | 'BLAKE2b', key?: CryptoKey, keySalt?: ArrayBuffer, secret?: string | ArrayBuffer, mnemonicSalt?: string): Promise<NamedData<ArrayBuffer>> {
+       load (type?: WalletType, key?: CryptoKey, keySalt?: ArrayBuffer, secret?: string | ArrayBuffer, mnemonicSalt?: string): Promise<NamedData<ArrayBuffer>> {
+               if (type !== 'BIP-44' && type !== 'BLAKE2b') {
+                       throw new TypeError('Unsupported software wallet algorithm', { cause: type })
+               }
                return this.#load(type, key, keySalt, secret, mnemonicSalt)
                        .then(record => {
                                if (this.#seed == null) {
@@ -236,6 +242,12 @@ export class VaultWorker {
                if (type == null) {
                        throw new TypeError('Wallet type is required')
                }
+               if (type === 'Ledger') {
+                       this.#locked = false
+                       BROWSER: postMessage('unlocked')
+                       NODE: this.#parentPort?.postMessage('unlocked')
+                       return Promise.resolve()
+               }
                if (key == null) {
                        throw new TypeError('Wallet password is required')
                }
@@ -554,7 +566,7 @@ export class VaultWorker {
        // Algorithm used for wallet functions
        #parseType (action: string, data: { [key: string]: unknown }) {
                if (['create', 'load', 'unlock'].includes(action)) {
-                       if (data.type !== 'BIP-44' && data.type !== 'BLAKE2b') {
+                       if (data.type !== 'BIP-44' && data.type !== 'BLAKE2b' && data.type !== 'Ledger') {
                                throw new TypeError(`Type is required to ${action} wallet`)
                        }
                } else if (data.type !== undefined) {
index c53d5fd5100f7bbb0178f7b78bca6fedf56f58a2..bf5ba375f49ae2232d202f4a3b3189a4113a60a9 100644 (file)
@@ -1,6 +1,7 @@
 //! 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 { Vault } from '../vault'
 import { Wallet } from '../wallet'
@@ -9,28 +10,33 @@ import { _get } from './get'
 export async function _unlock (wallet: Wallet, vault: Vault, password?: string): Promise<void>
 export async function _unlock (wallet: Wallet, vault: Vault, password: unknown): Promise<void> {
        try {
+               const data: NamedData = {
+                       action: 'unlock',
+                       type: wallet.type
+               }
                if (wallet.type === 'Ledger') {
                        const { Ledger } = await import('./ledger')
                        const status = await Ledger.connect()
                        if (await status !== 'CONNECTED') {
                                throw new Error('Failed to unlock wallet', { cause: status })
                        }
+                       data.password = new ArrayBuffer(0)
+                       data.iv = new ArrayBuffer(0)
+                       data.keySalt = new ArrayBuffer(0)
+                       data.encrypted = new ArrayBuffer(0)
                } else {
                        if (typeof password !== 'string') {
                                throw new TypeError('Password must be a string')
                        }
                        const { iv, salt, encrypted } = await _get(wallet.id)
-                       await vault.request({
-                               action: 'unlock',
-                               type: wallet.type,
-                               password: utf8.toBuffer(password),
-                               iv,
-                               keySalt: salt,
-                               encrypted
-                       })
-                       if (wallet.isLocked) {
-                               throw new Error('Unlock request to Vault failed')
-                       }
+                       data.password = utf8.toBuffer(password)
+                       data.iv = iv
+                       data.keySalt = salt
+                       data.encrypted = encrypted
+               }
+               await vault.request(data)
+               if (wallet.isLocked) {
+                       throw new Error('Unlock request failed')
                }
        } catch (err) {
                throw new Error('Failed to unlock wallet', { cause: err })
index 8736eea72f4c99497bcebc5395e9dfededfcf460..3b39498c75922acacd704482051f28ba978e5ae9 100644 (file)
@@ -49,6 +49,7 @@ await Promise.all([
 
                await test('request permissions', async () => {
                        await assert.rejects(wallet.unlock(), 'expect DISCONNECTED')
+                       assert.equal(wallet.isLocked, true)
 
                        await assert.rejects(async () => {
                                await click(
@@ -56,6 +57,7 @@ await Promise.all([
                                        async () => wallet.unlock()
                                )
                        }, 'expect BUSY')
+                       assert.equal(wallet.isLocked, true)
 
                        await assert.rejects(async () => {
                                await click(
@@ -63,6 +65,7 @@ await Promise.all([
                                        async () => wallet.unlock()
                                )
                        }, 'expect LOCKED')
+                       assert.equal(wallet.isLocked, true)
 
                        await assert.resolves(async () => {
                                await click(
@@ -70,6 +73,7 @@ await Promise.all([
                                        async () => wallet.unlock()
                                )
                        }, 'expect CONNECTED')
+                       assert.equal(wallet.isLocked, false)
                })
 
                await test('get first account', async () => {