]> git.codecow.com Git - libnemo.git/commitdiff
Fix missing returns in promise chain. Add input validation.
authorChris Duncan <chris@zoso.dev>
Sat, 30 Aug 2025 04:44:32 +0000 (21:44 -0700)
committerChris Duncan <chris@zoso.dev>
Sat, 30 Aug 2025 04:44:32 +0000 (21:44 -0700)
src/lib/crypto/bip44.ts

index adcdfaa26533c45098278e9580ceec8bb736a546..80420685c50beda14c54840fdd7a342ce3aee777 100644 (file)
@@ -19,26 +19,35 @@ export class Bip44 {
        * @param {number} account - Account number between 0 and 2^31-1
        * @returns {Promise<ArrayBuffer>} Private child key for the account
        */
-       static ckd (seed: ArrayBuffer, coin: number, account: number, chain?: number, address?: number): Promise<ArrayBuffer | undefined> {
+       static ckd (seed: ArrayBuffer, coin: number, account: number, change?: number, address?: number): Promise<ArrayBuffer> {
                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<ExtendedKey> {
+       static #slip10 (curve: string, S: ArrayBuffer): Promise<ExtendedKey> {
                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<ExtendedKey> {
+       static #CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number): Promise<ExtendedKey> {
                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<ArrayBuffer> {
-               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<ArrayBuffer> {
                const view = new DataView(new ArrayBuffer(4))
                view.setUint32(0, integer, false)
                return new Uint8Array(view.buffer)
        }
 
-       static ser256 (integer: ArrayBuffer): Uint8Array<ArrayBuffer> {
-               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<ArrayBuffer> {
                return new Uint8Array(integer)
        }
 
-       static hmac (key: Uint8Array<ArrayBuffer>, data: Uint8Array<ArrayBuffer>): Promise<ArrayBuffer> {
+       static #hmac (key: Uint8Array<ArrayBuffer>, data: Uint8Array<ArrayBuffer>): Promise<ArrayBuffer> {
                return crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-512' }, false, ['sign'])
                        .then(pk => {
                                return crypto.subtle.sign('HMAC', pk, data)