From: Chris Duncan Date: Mon, 18 Aug 2025 18:09:26 +0000 (-0700) Subject: Move auto lock into vault from wallet. X-Git-Tag: v0.10.5~41^2~65 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=c82f71f25b7557d1be373f8352e7e5386d2c809d;p=libnemo.git Move auto lock into vault from wallet. --- diff --git a/src/lib/vault/vault.ts b/src/lib/vault/vault.ts index 73248c8..d6b45b5 100644 --- a/src/lib/vault/vault.ts +++ b/src/lib/vault/vault.ts @@ -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 { + 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> { 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> { + static verify (seed?: ArrayBuffer, mnemonicPhrase?: string): NamedData { try { if (this.#locked) { throw new Error('Wallet is locked') diff --git a/src/lib/wallet/index.ts b/src/lib/wallet/index.ts index f139056..1a3006b 100644 --- a/src/lib/wallet/index.ts +++ b/src/lib/wallet/index.ts @@ -265,7 +265,6 @@ export class Wallet { throw new Error('Failed to delete wallet from database') } this.#vault.terminate() - clearTimeout(this.#lockTimer) } catch (err) { console.error(err) throw new Error('Failed to destroy wallet', { cause: err }) @@ -277,7 +276,6 @@ export class Wallet { */ lock (): void { _lock(this.#vault) - clearTimeout(this.#lockTimer) } /** @@ -317,8 +315,6 @@ export class Wallet { */ async sign (index: number, block: Block): Promise { await _sign(this.#vault, index, block) - clearTimeout(this.#lockTimer) - this.#lockTimer = setTimeout(() => this.lock(), 300000) } /** @@ -328,8 +324,6 @@ export class Wallet { */ async unlock (password: string): Promise { await _unlock(this, this.#vault, password) - clearTimeout(this.#lockTimer) - this.#lockTimer = setTimeout(() => this.lock(), 300000) } /** @@ -382,7 +376,6 @@ export class Wallet { async verify (mnemonic: string): Promise async verify (secret: string): Promise { try { - clearTimeout(this.#lockTimer) const data: NamedData = { action: 'verify' } @@ -398,14 +391,11 @@ export class Wallet { return isVerified } catch (err) { throw new Error('Failed to verify wallet', { cause: err }) - } finally { - this.#lockTimer = setTimeout(() => this.lock(), 300000) } } static #isInternal: boolean = false #accounts: AccountList - #lockTimer?: any #id: string #mnemonic?: ArrayBuffer #vault: WorkerQueue diff --git a/test/test.lock-unlock.mjs b/test/test.lock-unlock.mjs index f806ea6..3370ecc 100644 --- a/test/test.lock-unlock.mjs +++ b/test/test.lock-unlock.mjs @@ -114,5 +114,23 @@ await Promise.all([ await assert.resolves(wallet.destroy()) }) + + await test('fail to access a wallet after automatic lock', { skip: true }, 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) + + assert.ok(await wallet.verify(NANO_TEST_VECTORS.MNEMONIC)) + assert.ok(await wallet.verify(NANO_TEST_VECTORS.BIP39_SEED)) + + await new Promise(async (resolve) => { + console.log('Waiting 5 minutes...') + setTimeout(async () => { + await assert.rejects(wallet.verify(NANO_TEST_VECTORS.MNEMONIC)) + await assert.rejects(wallet.verify(NANO_TEST_VECTORS.BIP39_SEED)) + await assert.resolves(wallet.destroy()) + resolve(null) + }, 301000) + }) + }) }) ])