]> git.codecow.com Git - libnemo.git/commitdiff
Typecheck ckd index arg and use VaultError consistently.
authorChris Duncan <chris@codecow.com>
Sat, 8 Aug 2026 16:22:01 +0000 (09:22 -0700)
committerChris Duncan <chris@codecow.com>
Sat, 8 Aug 2026 16:22:01 +0000 (09:22 -0700)
src/lib/vault/vault-worker.ts

index 53162f16dd711b7856bcb5b5d8442a8d20dbb8b5..2f7214862256ba77e411424d3bc688b7417d8385 100644 (file)
@@ -8,6 +8,7 @@ import { TaskData } from '.'
 import { BIP44_COIN_NANO } from '../constants'
 import { dec, utf8 } from '../convert'
 import { Bip39, Bip44, Blake2b, WalletAesGcm } from '../crypto'
+import { VaultError } from '../errors'
 import { WalletType } from '../wallet'
 import { parseAction, parseData, parseId, parseIv, parseKeySalt, parseType } from './parsers'
 import { passkey } from './passkey'
@@ -23,10 +24,10 @@ let _mnemonic: ArrayBuffer | undefined = undefined
 
 const listener = (event: MessageEvent<any>): void => {
        if (event.data == null) {
-               throw new TypeError('Worker received no data')
+               throw new VaultError('Worker received no data')
        }
        if (typeof event.data !== 'object') {
-               throw new Error('Invalid data')
+               throw new VaultError('Invalid data')
        }
        const data = event.data as Record<string, unknown>
        const { taskUrl, taskId } = data
@@ -77,7 +78,7 @@ const listener = (event: MessageEvent<any>): void => {
                                        return verify(seed, mnemonicPhrase)
                                }
                                default: {
-                                       throw new Error(`Unknown wallet action '${action}'`)
+                                       throw new VaultError(`Unknown wallet action '${action}'`)
                                }
                        }
                })
@@ -118,14 +119,14 @@ function config (timeout?: number): Promise<void> {
        try {
                _timer?.pause()
                if (_locked) {
-                       throw new Error('Wallet is locked')
+                       throw new VaultError('Wallet is locked')
                }
                if (typeof timeout === 'number') {
                        if (timeout < 10) {
-                               throw new RangeError('Timeout must be at least 10 seconds')
+                               throw new VaultError('Timeout must be at least 10 seconds')
                        }
                        if (timeout > 600) {
-                               throw new RangeError('Timeout must be at most 10 minutes')
+                               throw new VaultError('Timeout must be at most 10 minutes')
                        }
                        _timeout = timeout * 1000
                        _timer = new VaultTimer(_autolock, _timeout)
@@ -134,7 +135,7 @@ function config (timeout?: number): Promise<void> {
        } catch (err) {
                console.error(err)
                _timer?.resume()
-               throw new Error('Failed to configure Vault', { cause: err })
+               throw new VaultError('Failed to configure Vault', { cause: err })
        }
 }
 
@@ -144,7 +145,7 @@ function config (timeout?: number): Promise<void> {
  */
 function create (type?: WalletType, id?: UUID, key?: CryptoKey, keySalt?: ArrayBuffer, mnemonicSalt?: string): Promise<Record<string, ArrayBuffer>> {
        if (type !== 'BIP-44' && type !== 'BLAKE2b') {
-               throw new TypeError('Unsupported software wallet algorithm', { cause: type })
+               throw new VaultError('Unsupported software wallet algorithm', { cause: type })
        }
        const entropy = crypto.getRandomValues(new Uint8Array(32))
        return Bip39.fromEntropy(entropy)
@@ -152,7 +153,7 @@ function create (type?: WalletType, id?: UUID, key?: CryptoKey, keySalt?: ArrayB
                .then(({ iv, salt, encrypted }) => {
                        entropy.fill(0)
                        if (_seed == null || _mnemonic == null) {
-                               throw new Error('Failed to generate seed and mnemonic')
+                               throw new VaultError('Failed to generate seed and mnemonic')
                        }
                        const seed = _seed.slice()
                        const mnemonic = _mnemonic.slice()
@@ -160,7 +161,7 @@ function create (type?: WalletType, id?: UUID, key?: CryptoKey, keySalt?: ArrayB
                })
                .catch(err => {
                        console.error(err)
-                       throw new Error('Failed to create wallet', { cause: err })
+                       throw new VaultError('Failed to create wallet', { cause: err })
                })
                .finally(() => lock())
 }
@@ -174,16 +175,16 @@ function derive (index?: number | Uint32Array): Promise<Record<string, number |
        try {
                _timer.pause()
                if (_locked) {
-                       throw new Error('Wallet is locked')
+                       throw new VaultError('Wallet is locked')
                }
                if (_seed == null) {
-                       throw new Error('Wallet seed not found')
+                       throw new VaultError('Wallet seed not found')
                }
                if (_type !== 'BIP-44' && _type !== 'BLAKE2b' && _type !== 'Exodus') {
-                       throw new Error('Invalid wallet type')
+                       throw new VaultError('Invalid wallet type')
                }
                if (index == null) {
-                       throw new Error('Invalid wallet account index(es)')
+                       throw new VaultError('Invalid wallet account index(es)')
                }
                const promises = []
                const values = typeof index === 'number' ? [index] : index.values()
@@ -207,12 +208,12 @@ function derive (index?: number | Uint32Array): Promise<Record<string, number |
                        .catch(err => {
                                console.error(err)
                                _timer.resume()
-                               throw new Error('Failed to derive account', { cause: err })
+                               throw new VaultError('Failed to derive account', { cause: err })
                        })
        } catch (err) {
                console.error(err)
                _timer.resume()
-               throw new Error('Failed to derive account', { cause: err })
+               throw new VaultError('Failed to derive account', { cause: err })
        }
 }
 
@@ -222,18 +223,18 @@ function derive (index?: number | Uint32Array): Promise<Record<string, number |
  */
 function load (type?: WalletType, id?: UUID, key?: CryptoKey, keySalt?: ArrayBuffer, secret?: string | ArrayBuffer, mnemonicSalt?: string): Promise<Record<string, ArrayBuffer>> {
        if (type !== 'BIP-44' && type !== 'BLAKE2b' && type !== 'Exodus') {
-               throw new TypeError('Unsupported software wallet algorithm', { cause: type })
+               throw new VaultError('Unsupported software wallet algorithm', { cause: type })
        }
        return _load(type, id, key, keySalt, secret, mnemonicSalt)
                .then(record => {
                        if (_seed == null) {
-                               throw new Error('Wallet seed not found')
+                               throw new VaultError('Wallet seed not found')
                        }
                        return record
                })
                .catch(err => {
                        console.error(err)
-                       throw new Error('Failed to load wallet', { cause: err })
+                       throw new VaultError('Failed to load wallet', { cause: err })
                })
                .finally(() => lock())
 }
@@ -256,16 +257,16 @@ function sign (index?: Uint32Array, data?: ArrayBuffer): Promise<Record<string,
        try {
                _timer.pause()
                if (_locked) {
-                       throw new Error('Wallet is locked')
+                       throw new VaultError('Wallet is locked')
                }
                if (_seed == null) {
-                       throw new Error('Wallet seed not found')
+                       throw new VaultError('Wallet seed not found')
                }
                if (index == null) {
-                       throw new Error('Wallet account index is required to sign')
+                       throw new VaultError('Wallet account index is required to sign')
                }
                if (data == null) {
-                       throw new Error('Data to sign not found')
+                       throw new VaultError('Data to sign not found')
                }
                return _ckd(index[0])
                        .then(result => {
@@ -282,12 +283,12 @@ function sign (index?: Uint32Array, data?: ArrayBuffer): Promise<Record<string,
                        .catch(err => {
                                console.error(err)
                                _timer.resume()
-                               throw new Error('Failed to sign message', { cause: err })
+                               throw new VaultError('Failed to sign message', { cause: err })
                        })
        } catch (err) {
                console.error(err)
                _timer.resume()
-               throw new Error('Failed to sign message', { cause: err })
+               throw new VaultError('Failed to sign message', { cause: err })
        }
 }
 
@@ -296,31 +297,31 @@ function sign (index?: Uint32Array, data?: ArrayBuffer): Promise<Record<string,
  */
 function unlock (type?: WalletType, id?: UUID, key?: CryptoKey, iv?: ArrayBuffer, encrypted?: ArrayBuffer): Promise<Record<string, boolean>> {
        if (type == null) {
-               throw new TypeError('Wallet type is required')
+               throw new VaultError('Wallet type is required')
        }
        if (type !== 'BIP-44' && type !== 'BLAKE2b' && type !== 'Exodus') {
-               throw new TypeError('Invalid wallet type', { cause: type })
+               throw new VaultError('Invalid wallet type', { cause: type })
        }
        if (id == null) {
-               throw new TypeError('Wallet ID is required')
+               throw new VaultError('Wallet ID is required')
        }
        if (key == null) {
-               throw new TypeError('Wallet password is required')
+               throw new VaultError('Wallet password is required')
        }
        if (iv == null) {
-               throw new TypeError('Wallet IV is required')
+               throw new VaultError('Wallet IV is required')
        }
        if (encrypted == null) {
-               throw new TypeError('Wallet encrypted data is required')
+               throw new VaultError('Wallet encrypted data is required')
        }
        _timer?.pause()
        return WalletAesGcm.decrypt(type, id, key, iv, encrypted)
                .then(({ mnemonic, seed }) => {
                        if (!(seed instanceof ArrayBuffer)) {
-                               throw new TypeError('Invalid seed')
+                               throw new VaultError('Invalid seed')
                        }
                        if (mnemonic != null && !(mnemonic instanceof ArrayBuffer)) {
-                               throw new TypeError('Invalid mnemonic')
+                               throw new VaultError('Invalid mnemonic')
                        }
                        _type = type
                        _id = id
@@ -333,7 +334,7 @@ function unlock (type?: WalletType, id?: UUID, key?: CryptoKey, iv?: ArrayBuffer
                .catch(err => {
                        console.error(err)
                        _timer?.resume()
-                       throw new Error('Failed to unlock wallet', { cause: err })
+                       throw new VaultError('Failed to unlock wallet', { cause: err })
                })
 }
 
@@ -344,19 +345,19 @@ function update (key?: CryptoKey, salt?: ArrayBuffer): Promise<Record<string, Ar
        try {
                _timer.pause()
                if (_locked) {
-                       throw new Error('Wallet is locked')
+                       throw new VaultError('Wallet is locked')
                }
                if (_seed == null) {
-                       throw new Error('Wallet seed not found')
+                       throw new VaultError('Wallet seed not found')
                }
                if (_type == null) {
-                       throw new Error('Wallet type not found')
+                       throw new VaultError('Wallet type not found')
                }
                if (_id == null) {
-                       throw new Error('Wallet ID not found')
+                       throw new VaultError('Wallet ID not found')
                }
                if (key == null || salt == null) {
-                       throw new TypeError('Wallet password is required')
+                       throw new VaultError('Wallet password is required')
                }
                return WalletAesGcm.encrypt(_type, _id, key, _seed, _mnemonic)
                        .then(({ iv, encrypted }) => {
@@ -366,12 +367,12 @@ function update (key?: CryptoKey, salt?: ArrayBuffer): Promise<Record<string, Ar
                        .catch(err => {
                                console.error(err)
                                _timer.resume()
-                               throw new Error('Failed to update wallet password', { cause: err })
+                               throw new VaultError('Failed to update wallet password', { cause: err })
                        })
        } catch (err) {
                console.error(err)
                _timer.resume()
-               throw new Error('Failed to update wallet password', { cause: err })
+               throw new VaultError('Failed to update wallet password', { cause: err })
        }
 }
 
@@ -382,16 +383,16 @@ function update (key?: CryptoKey, salt?: ArrayBuffer): Promise<Record<string, Ar
 function verify (seed?: ArrayBuffer, mnemonicPhrase?: string): Promise<Record<string, boolean>> {
        try {
                if (_locked) {
-                       throw new Error('Wallet is locked')
+                       throw new VaultError('Wallet is locked')
                }
                if (_seed == null) {
-                       throw new Error('Wallet seed not found')
+                       throw new VaultError('Wallet seed not found')
                }
                if (seed == null && mnemonicPhrase == null) {
-                       throw new Error('Seed or mnemonic phrase is required')
+                       throw new VaultError('Seed or mnemonic phrase is required')
                }
                if (seed != null && mnemonicPhrase != null) {
-                       throw new Error('Seed or mnemonic phrase must be verified separately')
+                       throw new VaultError('Seed or mnemonic phrase must be verified separately')
                }
                const expected = seed != null ? new Uint8Array(seed) : utf8.toBytes(mnemonicPhrase ?? '')
                const actual = seed != null ? new Uint8Array(_seed) : new Uint8Array(_mnemonic ?? [])
@@ -404,7 +405,7 @@ function verify (seed?: ArrayBuffer, mnemonicPhrase?: string): Promise<Record<st
                return Promise.resolve({ isVerified })
        } catch (err) {
                console.error(err)
-               throw new Error('Failed to verify wallet', { cause: err })
+               throw new VaultError('Failed to verify wallet', { cause: err })
        }
 }
 
@@ -435,9 +436,12 @@ const _index = new DataView(new ArrayBuffer(4))
  * @param {number} index - 4-byte index of account to derive
  * @returns {Promise<ArrayBuffer>} Private key for the account
  */
-function _ckd (index: number): Promise<ArrayBuffer> {
+function _ckd (index?: number): Promise<ArrayBuffer> {
        if (_seed == null) {
-               throw new Error('Wallet seed not found')
+               throw new VaultError('Wallet seed not found')
+       }
+       if (typeof index !== 'number' || index < 0) {
+               throw new VaultError('Invalid CKD index')
        }
        switch (_type) {
                case ('BIP-44'): {
@@ -462,45 +466,45 @@ function _ckd (index: number): Promise<ArrayBuffer> {
 function _load (type?: 'BIP-44' | 'BLAKE2b' | 'Exodus', id?: UUID, key?: CryptoKey, keySalt?: ArrayBuffer, secret?: string | ArrayBuffer, mnemonicSalt?: string): Promise<Record<string, ArrayBuffer>> {
        try {
                if (!_locked) {
-                       throw new Error('Wallet is in use')
+                       throw new VaultError('Wallet is in use')
                }
                if (key == null || keySalt == null) {
-                       throw new Error('Wallet password is required')
+                       throw new VaultError('Wallet password is required')
                }
                if (type == null) {
-                       throw new TypeError('Wallet type is required')
+                       throw new VaultError('Wallet type is required')
                }
                if (id == null) {
-                       throw new TypeError('Wallet ID is required')
+                       throw new VaultError('Wallet ID is required')
                }
                if (!utf8.isUuid(id)) {
-                       throw new TypeError('Invalid wallet ID')
+                       throw new VaultError('Invalid wallet ID')
                }
                if (type !== 'BIP-44' && type !== 'BLAKE2b' && type !== 'Exodus') {
-                       throw new TypeError('Invalid wallet type')
+                       throw new VaultError('Invalid wallet type')
                }
                if (secret == null) {
-                       throw new TypeError('Seed or mnemonic is required')
+                       throw new VaultError('Seed or mnemonic is required')
                }
                if (typeof secret !== 'string' && mnemonicSalt !== undefined) {
-                       throw new TypeError('Mnemonic must be a string')
+                       throw new VaultError('Mnemonic must be a string')
                }
                if (type === 'BIP-44') {
                        if (secret instanceof ArrayBuffer && (secret.byteLength < 16 || secret.byteLength > 64)) {
-                               throw new RangeError('Seed for BIP-44 wallet must be 16-64 bytes')
+                               throw new VaultError('Seed for BIP-44 wallet must be 16-64 bytes')
                        }
                }
                if (type === 'BLAKE2b') {
                        if (secret instanceof ArrayBuffer && secret.byteLength !== 32) {
-                               throw new RangeError('Seed for BLAKE2b wallet must be 32 bytes')
+                               throw new VaultError('Seed for BLAKE2b wallet must be 32 bytes')
                        }
                }
                if (type === 'Exodus') {
                        if (typeof secret === 'string' && secret.split(' ').length !== 12) {
-                               throw new RangeError('Mnemonic for Exodus wallet must be 12 words')
+                               throw new VaultError('Mnemonic for Exodus wallet must be 12 words')
                        }
                        if (secret instanceof ArrayBuffer && secret.byteLength !== 64) {
-                               throw new RangeError('Seed for Exodus wallet must be 64 bytes')
+                               throw new VaultError('Seed for Exodus wallet must be 64 bytes')
                        }
                }
                _type = type
@@ -533,6 +537,6 @@ function _load (type?: 'BIP-44' | 'BLAKE2b' | 'Exodus', id?: UUID, key?: CryptoK
                                .then(({ iv, encrypted }) => ({ iv, salt: keySalt, encrypted }))
                })
        } catch (err) {
-               throw new Error('Failed to load wallet', { cause: err })
+               throw new VaultError('Failed to load wallet', { cause: err })
        }
 }