From ba9c5aa819f2a11e3f13641728477c3b9b100e2c Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Fri, 29 Aug 2025 21:44:32 -0700 Subject: [PATCH] Fix missing returns in promise chain. Add input validation. --- src/lib/crypto/bip44.ts | 60 +++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/src/lib/crypto/bip44.ts b/src/lib/crypto/bip44.ts index adcdfaa..8042068 100644 --- a/src/lib/crypto/bip44.ts +++ b/src/lib/crypto/bip44.ts @@ -19,26 +19,35 @@ export class Bip44 { * @param {number} account - Account number between 0 and 2^31-1 * @returns {Promise} Private child key for the account */ - static ckd (seed: ArrayBuffer, coin: number, account: number, chain?: number, address?: number): Promise { + static ckd (seed: ArrayBuffer, coin: number, account: number, change?: number, address?: number): Promise { if (seed.byteLength < 16 || seed.byteLength > 64) { throw new RangeError(`Invalid seed length`) } + if (!Number.isSafeInteger(coin) || coin < 0 || coin > 0x7fffffff) { + throw new RangeError(`Invalid coin 0x${coin.toString(16)}`) + } if (!Number.isSafeInteger(account) || account < 0 || account > 0x7fffffff) { - throw new RangeError(`Invalid child key index 0x${account.toString(16)}`) + throw new RangeError(`Invalid account index 0x${account.toString(16)}`) + } + if (change != null && (!Number.isSafeInteger(change) || change < 0 || change > 1)) { + throw new RangeError(`Invalid change index 0x${account.toString(16)}`) } - return this.slip10(SLIP10_ED25519, seed) + if (address != null && (!Number.isSafeInteger(address) || address < 0 || address > 0x7fffffff)) { + throw new RangeError(`Invalid address index 0x${account.toString(16)}`) + } + return this.#slip10(SLIP10_ED25519, seed) .then(masterKey => { - return this.CKDpriv(masterKey, BIP44_PURPOSE + HARDENED_OFFSET) + return this.#CKDpriv(masterKey, BIP44_PURPOSE + HARDENED_OFFSET) .then(purposeKey => { - return this.CKDpriv(purposeKey, coin + HARDENED_OFFSET) + return this.#CKDpriv(purposeKey, coin + HARDENED_OFFSET) .then(coinKey => { - return this.CKDpriv(coinKey, account + HARDENED_OFFSET) + return this.#CKDpriv(coinKey, account + HARDENED_OFFSET) .then(accountKey => { - if (chain == null) return accountKey.privateKey - this.CKDpriv(accountKey, chain + HARDENED_OFFSET) + if (change == null) return accountKey.privateKey + return this.#CKDpriv(accountKey, change + HARDENED_OFFSET) .then(chainKey => { if (address == null) return chainKey.privateKey - this.CKDpriv(chainKey, address + HARDENED_OFFSET) + return this.#CKDpriv(chainKey, address + HARDENED_OFFSET) .then(addressKey => { return addressKey.privateKey }) @@ -47,15 +56,12 @@ export class Bip44 { }) }) }) - .catch(err => { - throw new Error('Failed to derive BIP-44 account', { cause: err }) - }) } - static slip10 (curve: string, S: ArrayBuffer): Promise { + static #slip10 (curve: string, S: ArrayBuffer): Promise { const key = new Uint8Array(new TextEncoder().encode(curve)) const data = new Uint8Array(S) - return this.hmac(key, data) + return this.#hmac(key, data) .then(I => { const IL = I.slice(0, I.byteLength / 2) const IR = I.slice(I.byteLength / 2) @@ -63,13 +69,13 @@ export class Bip44 { }) } - static CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number): Promise { + static #CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number): Promise { const key = new Uint8Array(chainCode) const data = new Uint8Array(37) data.set([0]) - data.set(this.ser256(privateKey), 1) - data.set(this.ser32(index), 33) - return this.hmac(key, data) + data.set(this.#ser256(privateKey), 1) + data.set(this.#ser32(index), 33) + return this.#hmac(key, data) .then(I => { const IL = I.slice(0, I.byteLength / 2) const IR = I.slice(I.byteLength / 2) @@ -77,29 +83,17 @@ export class Bip44 { }) } - static ser32 (integer: number): Uint8Array { - if (typeof integer !== 'number') { - throw new TypeError(`Expected a number, received ${typeof integer}`) - } - if (integer > 0xffffffff) { - throw new RangeError(`Expected 32-bit integer, received ${integer.toString(2).length}-bit value: ${integer}`) - } + static #ser32 (integer: number): Uint8Array { const view = new DataView(new ArrayBuffer(4)) view.setUint32(0, integer, false) return new Uint8Array(view.buffer) } - static ser256 (integer: ArrayBuffer): Uint8Array { - if (!(integer instanceof ArrayBuffer)) { - throw new TypeError(`Expected ArrayBuffer, received ${typeof integer}`) - } - if (integer.byteLength > 32) { - throw new RangeError(`Expected 32-byte integer, received ${integer.byteLength}-byte value: ${integer}`) - } + static #ser256 (integer: ArrayBuffer): Uint8Array { return new Uint8Array(integer) } - static hmac (key: Uint8Array, data: Uint8Array): Promise { + static #hmac (key: Uint8Array, data: Uint8Array): Promise { return crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-512' }, false, ['sign']) .then(pk => { return crypto.subtle.sign('HMAC', pk, data) -- 2.47.3