]> git.codecow.com Git - libnemo.git/commitdiff
Move auto lock into vault from wallet.
authorChris Duncan <chris@zoso.dev>
Mon, 18 Aug 2025 18:09:26 +0000 (11:09 -0700)
committerChris Duncan <chris@zoso.dev>
Mon, 18 Aug 2025 18:09:26 +0000 (11:09 -0700)
src/lib/vault/vault.ts
src/lib/wallet/index.ts
test/test.lock-unlock.mjs

index 73248c86ad870719fe0c00b2e82b7cc1e8462aea..d6b45b59126848c91fce1f55374a8204169d8b1a 100644 (file)
@@ -12,6 +12,7 @@ import { default as Convert, utf8 } from '../convert'
 */
 export class Vault {
        static #locked: boolean = true
+       static #timeout: number | NodeJS.Timeout
        static #type?: 'BIP-44' | 'BLAKE2b'
        static #seed?: ArrayBuffer
        static #mnemonic?: ArrayBuffer
@@ -72,7 +73,7 @@ export class Vault {
                                                break
                                        }
                                        case 'verify': {
-                                               result = await this.verify(seed, mnemonicPhrase)
+                                               result = this.verify(seed, mnemonicPhrase)
                                                break
                                        }
                                        default: {
@@ -144,10 +145,12 @@ export class Vault {
                        if (typeof index !== 'number') {
                                throw new Error('Invalid wallet account index')
                        }
+                       clearTimeout(this.#timeout)
                        const prv = this.#type === 'BIP-44'
                                ? await Bip44.ckd(this.#seed, BIP44_COIN_NANO, index)
                                : await this.#deriveBlake2bPrivateKey(this.#seed, index)
                        const pub = await NanoNaCl.convert(new Uint8Array(prv))
+                       this.#timeout = setTimeout(() => this.lock(), 300000)
                        return { index, publicKey: pub.buffer }
                } catch (err) {
                        throw new Error('Failed to derive account', { cause: err })
@@ -173,6 +176,7 @@ export class Vault {
        }
 
        static lock (): NamedData<boolean> {
+               clearTimeout(this.#timeout)
                this.#mnemonic = undefined
                this.#seed = undefined
                this.#locked = true
@@ -197,10 +201,12 @@ export class Vault {
                        if (data == null) {
                                throw new Error('Data to sign not found')
                        }
+                       clearTimeout(this.#timeout)
                        const prv = this.#type === 'BIP-44'
                                ? await Bip44.ckd(this.#seed, BIP44_COIN_NANO, index)
                                : await this.#deriveBlake2bPrivateKey(this.#seed, index)
                        const sig = await NanoNaCl.detached(new Uint8Array(data), new Uint8Array(prv))
+                       this.#timeout = setTimeout(() => this.lock(), 300000)
                        return { signature: sig.buffer }
                } catch (err) {
                        throw new Error('Failed to sign message', { cause: err })
@@ -224,6 +230,7 @@ export class Vault {
                        if (encrypted == null) {
                                throw new TypeError('Wallet encrypted data is required')
                        }
+                       clearTimeout(this.#timeout)
                        await this.#decryptWallet(type, key, iv, encrypted)
                        if (!(this.#seed instanceof ArrayBuffer)) {
                                throw new TypeError('Invalid seed')
@@ -232,6 +239,7 @@ export class Vault {
                                throw new TypeError('Invalid mnemonic')
                        }
                        this.#locked = false
+                       this.#timeout = setTimeout(() => this.lock(), 300000)
                        return { isUnlocked: !this.#locked }
                } catch (err) {
                        console.error(err)
@@ -240,7 +248,7 @@ export class Vault {
        }
 
        /**
-       * Decrypts the input and sets the seed and, if it is included, the mnemonic.
+       * Re-encrypts the wallet with a new password.
        */
        static async update (key?: CryptoKey, keySalt?: ArrayBuffer): Promise<NamedData<ArrayBuffer>> {
                try {
@@ -253,7 +261,9 @@ export class Vault {
                        if (key == null || keySalt == null) {
                                throw new TypeError('Wallet password is required')
                        }
+                       clearTimeout(this.#timeout)
                        const { iv, encrypted } = await this.#encryptWallet(key)
+                       this.#timeout = setTimeout(() => this.lock(), 300000)
                        return { iv, salt: keySalt, encrypted }
                } catch (err) {
                        console.error(err)
@@ -265,7 +275,7 @@ export class Vault {
        * Checks the seed and, if it exists, the mnemonic against input. The wallet
        * must be unlocked prior to verification.
        */
-       static async verify (seed?: ArrayBuffer, mnemonicPhrase?: string): Promise<NamedData<boolean>> {
+       static verify (seed?: ArrayBuffer, mnemonicPhrase?: string): NamedData<boolean> {
                try {
                        if (this.#locked) {
                                throw new Error('Wallet is locked')
index f1390568770664cf9127057f2e7738b9bb621e56..1a3006b373c887a7dcb3204c1f781a61604d8c40 100644 (file)
@@ -265,7 +265,6 @@ export class Wallet {
                                throw new Error('Failed to delete wallet from database')\r
                        }\r
                        this.#vault.terminate()\r
-                       clearTimeout(this.#lockTimer)\r
                } catch (err) {\r
                        console.error(err)\r
                        throw new Error('Failed to destroy wallet', { cause: err })\r
@@ -277,7 +276,6 @@ export class Wallet {
        */\r
        lock (): void {\r
                _lock(this.#vault)\r
-               clearTimeout(this.#lockTimer)\r
        }\r
 \r
        /**\r
@@ -317,8 +315,6 @@ export class Wallet {
        */\r
        async sign (index: number, block: Block): Promise<void> {\r
                await _sign(this.#vault, index, block)\r
-               clearTimeout(this.#lockTimer)\r
-               this.#lockTimer = setTimeout(() => this.lock(), 300000)\r
        }\r
 \r
        /**\r
@@ -328,8 +324,6 @@ export class Wallet {
        */\r
        async unlock (password: string): Promise<void> {\r
                await _unlock(this, this.#vault, password)\r
-               clearTimeout(this.#lockTimer)\r
-               this.#lockTimer = setTimeout(() => this.lock(), 300000)\r
        }\r
 \r
        /**\r
@@ -382,7 +376,6 @@ export class Wallet {
        async verify (mnemonic: string): Promise<boolean>\r
        async verify (secret: string): Promise<boolean> {\r
                try {\r
-                       clearTimeout(this.#lockTimer)\r
                        const data: NamedData = {\r
                                action: 'verify'\r
                        }\r
@@ -398,14 +391,11 @@ export class Wallet {
                        return isVerified\r
                } catch (err) {\r
                        throw new Error('Failed to verify wallet', { cause: err })\r
-               } finally {\r
-                       this.#lockTimer = setTimeout(() => this.lock(), 300000)\r
                }\r
        }\r
 \r
        static #isInternal: boolean = false\r
        #accounts: AccountList\r
-       #lockTimer?: any\r
        #id: string\r
        #mnemonic?: ArrayBuffer\r
        #vault: WorkerQueue\r
index f806ea6f510ca6cab94e564035052158c0635740..3370ecce44580e8bad799f1ccc566ff0dcb3c413 100644 (file)
@@ -114,5 +114,23 @@ await Promise.all([
 \r
                        await assert.resolves(wallet.destroy())\r
                })\r
+\r
+               await test('fail to access a wallet after automatic lock', { skip: true }, 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
+\r
+                       assert.ok(await wallet.verify(NANO_TEST_VECTORS.MNEMONIC))\r
+                       assert.ok(await wallet.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
+\r
+                       await new Promise(async (resolve) => {\r
+                               console.log('Waiting 5 minutes...')\r
+                               setTimeout(async () => {\r
+                                       await assert.rejects(wallet.verify(NANO_TEST_VECTORS.MNEMONIC))\r
+                                       await assert.rejects(wallet.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
+                                       await assert.resolves(wallet.destroy())\r
+                                       resolve(null)\r
+                               }, 301000)\r
+                       })\r
+               })\r
        })\r
 ])\r