]> git.codecow.com Git - libnemo.git/commitdiff
Change bip44 class of all static methods to flat module.
authorChris Duncan <chris@codecow.com>
Thu, 14 May 2026 19:02:40 +0000 (12:02 -0700)
committerChris Duncan <chris@codecow.com>
Thu, 14 May 2026 19:02:40 +0000 (12:02 -0700)
src/lib/crypto/bip44.ts

index 38d748b1c493f61fb2b7cd0f667350f25774d3f7..aea3ad81237e6f636194c84c285463cf930a71d3 100644 (file)
@@ -11,127 +11,125 @@ type ExtendedKey = {
 
 type Curve = 'Bitcoin seed' | 'ed25519 seed'
 
-export class Bip44 {
-       static get BIP44_PURPOSE (): 44 { return 44 }
-       static get HARDENED_OFFSET (): 0x80000000 { return 0x80000000 }
+const BIP44_PURPOSE: 44 = 44
+const HARDENED_OFFSET: 0x80000000 = 0x80000000
 
-       /**
-       * Derives a private child key for a coin by following the specified BIP-32 and
-       * BIP-44 derivation path. Purpose is always 44'. For ed25519, only hardened
-       * child keys are supported.
-       *
-       * @param {string} curve - 'Bitcoin seed' or 'ed25519 seed'
-       * @param {ArrayBuffer} seed - Hexadecimal seed derived from mnemonic phrase
-       * @param {number} coin - Number registered to a specific coin in SLIP-0044
-       * @param {number} account - Account number between 0 and 2^31-1
-       * @param {number} [change] - Used for change transactions, 0 for external and 1 for internal
-       * @param {number} [address] - Sequentially increasing index of addresses to use for each account
-       * @returns {Promise<ArrayBuffer>} Private child key for the account
-       */
-       static ckd (curve: Curve, seed: ArrayBuffer, coin: number, account: number, change?: number, address?: number): Promise<ArrayBuffer> {
-               if (curve !== 'Bitcoin seed' && curve !== 'ed25519 seed') {
-                       throw new TypeError(`Unsupported curve ${curve}`)
-               }
-               if (seed.byteLength < 16 || seed.byteLength > 64) {
-                       throw new RangeError(`Invalid seed byte length ${seed.byteLength}`)
-               }
-               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 account index 0x${account.toString(16)}`)
-               }
-               if (change !== undefined && (!Number.isSafeInteger(change) || change < 0 || change > 1)) {
-                       throw new RangeError(`Invalid change index 0x${account.toString(16)}`)
-               }
-               if (address !== undefined && (!Number.isSafeInteger(address) || address < 0 || address > 0x7fffffff)) {
-                       throw new RangeError(`Invalid address index 0x${account.toString(16)}`)
-               }
-               return this.slip10(curve, seed)
-                       .then(masterKey => this.CKDpriv(curve, masterKey, this.BIP44_PURPOSE + this.HARDENED_OFFSET))
-                       .then(purposeKey => this.CKDpriv(curve, purposeKey, coin + this.HARDENED_OFFSET))
-                       .then(coinKey => this.CKDpriv(curve, coinKey, account + this.HARDENED_OFFSET))
-                       .then(accountKey => this.CKDpriv(curve, accountKey, change))
-                       .then(chainKey => this.CKDpriv(curve, chainKey, address))
-                       .then(addressKey => addressKey.privateKey)
+/**
+ * Derives a private child key for a coin by following the specified BIP-32 and
+ * BIP-44 derivation path. Purpose is always 44'. For ed25519, only hardened
+ * child keys are supported.
+ *
+ * @param {string} curve - 'Bitcoin seed' or 'ed25519 seed'
+ * @param {ArrayBuffer} seed - Hexadecimal seed derived from mnemonic phrase
+ * @param {number} coin - Number registered to a specific coin in SLIP-0044
+ * @param {number} account - Account number between 0 and 2^31-1
+ * @param {number} [change] - Used for change transactions, 0 for external and 1 for internal
+ * @param {number} [address] - Sequentially increasing index of addresses to use for each account
+ * @returns {Promise<ArrayBuffer>} Private child key for the account
+ */
+export function ckd (curve: Curve, seed: ArrayBuffer, coin: number, account: number, change?: number, address?: number): Promise<ArrayBuffer> {
+       if (curve !== 'Bitcoin seed' && curve !== 'ed25519 seed') {
+               throw new TypeError(`Unsupported curve ${curve}`)
        }
-
-       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)
-                       .then(I => {
-                               const IL = I.slice(0, I.byteLength / 2)
-                               const IR = I.slice(I.byteLength / 2)
-                               return ({ privateKey: IL, chainCode: IR })
-                       })
+       if (seed.byteLength < 16 || seed.byteLength > 64) {
+               throw new RangeError(`Invalid seed byte length ${seed.byteLength}`)
        }
-
-       static CKDpriv (curve: Curve, { privateKey, chainCode }: ExtendedKey, index?: number): Promise<ExtendedKey> {
-               if (index === undefined) {
-                       return Promise.resolve({ privateKey, chainCode })
-               }
-               const pk = new Uint8Array(privateKey)
-               const key = new Uint8Array(chainCode)
-               const data = new Uint8Array(37)
-               if (index >= this.HARDENED_OFFSET) {
-                       data.set([0])
-                       data.set(pk, 1)
-                       data.set(this.ser32(index), 33)
-               } else if (curve === 'ed25519 seed') {
-                       throw new RangeError('Only hardened child keys are supported for ed25519')
-               } else {
-                       data.set(secp256k1_getPublicKey(pk))
-                       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)
-                               if (curve === 'ed25519 seed') {
-                                       return ({ privateKey: IL, chainCode: IR })
-                               } else {
-                                       const ILparsed = this.parse256(new Uint8Array(IL))
-                                       if (ILparsed >= Point.CURVE().n) {
-                                               throw new Error('Invalid child key is greater than the order of the curve')
-                                       }
-                                       const pkParsed = this.parse256(pk)
-                                       const childKey = (ILparsed + pkParsed) % Point.CURVE().n
-                                       if (childKey === 0n) {
-                                               throw new Error('Invalid child key is zero')
-                                       }
-                                       return ({ privateKey: this.ser256(childKey).buffer, chainCode: IR })
-                               }
-                       })
+       if (!Number.isSafeInteger(coin) || coin < 0 || coin > 0x7fffffff) {
+               throw new RangeError(`Invalid coin 0x${coin.toString(16)}`)
        }
-
-       static ser32 (integer: number): Uint8Array<ArrayBuffer> {
-               const view = new DataView(new ArrayBuffer(4))
-               view.setUint32(0, integer, false)
-               return new Uint8Array(view.buffer)
+       if (!Number.isSafeInteger(account) || account < 0 || account > 0x7fffffff) {
+               throw new RangeError(`Invalid account index 0x${account.toString(16)}`)
        }
+       if (change !== undefined && (!Number.isSafeInteger(change) || change < 0 || change > 1)) {
+               throw new RangeError(`Invalid change index 0x${account.toString(16)}`)
+       }
+       if (address !== undefined && (!Number.isSafeInteger(address) || address < 0 || address > 0x7fffffff)) {
+               throw new RangeError(`Invalid address index 0x${account.toString(16)}`)
+       }
+       return slip10(curve, seed)
+               .then(masterKey => CKDpriv(curve, masterKey, BIP44_PURPOSE + HARDENED_OFFSET))
+               .then(purposeKey => CKDpriv(curve, purposeKey, coin + HARDENED_OFFSET))
+               .then(coinKey => CKDpriv(curve, coinKey, account + HARDENED_OFFSET))
+               .then(accountKey => CKDpriv(curve, accountKey, change))
+               .then(chainKey => CKDpriv(curve, chainKey, address))
+               .then(addressKey => addressKey.privateKey)
+}
+
+function slip10 (curve: string, S: ArrayBuffer): Promise<ExtendedKey> {
+       const key = new Uint8Array(new TextEncoder().encode(curve))
+       const data = new Uint8Array(S)
+       return hmac(key, data)
+               .then(I => {
+                       const IL = I.slice(0, I.byteLength / 2)
+                       const IR = I.slice(I.byteLength / 2)
+                       return ({ privateKey: IL, chainCode: IR })
+               })
+}
 
-       static ser256 (integer: bigint): Uint8Array<ArrayBuffer> {
-               let bytes = new Uint8Array(32)
-               for (let i = bytes.byteLength - 1; i >= 0; i--) {
-                       bytes[i] = Number(integer & 0xffn)
-                       integer >>= 8n
-               }
-               return bytes
+function CKDpriv (curve: Curve, { privateKey, chainCode }: ExtendedKey, index?: number): Promise<ExtendedKey> {
+       if (index === undefined) {
+               return Promise.resolve({ privateKey, chainCode })
        }
+       const pk = new Uint8Array(privateKey)
+       const key = new Uint8Array(chainCode)
+       const data = new Uint8Array(37)
+       if (index >= HARDENED_OFFSET) {
+               data.set([0])
+               data.set(pk, 1)
+               data.set(ser32(index), 33)
+       } else if (curve === 'ed25519 seed') {
+               throw new RangeError('Only hardened child keys are supported for ed25519')
+       } else {
+               data.set(secp256k1_getPublicKey(pk))
+               data.set(ser32(index), 33)
+       }
+       return hmac(key, data)
+               .then(I => {
+                       const IL = I.slice(0, I.byteLength / 2)
+                       const IR = I.slice(I.byteLength / 2)
+                       if (curve === 'ed25519 seed') {
+                               return ({ privateKey: IL, chainCode: IR })
+                       } else {
+                               const ILparsed = parse256(new Uint8Array(IL))
+                               if (ILparsed >= Point.CURVE().n) {
+                                       throw new Error('Invalid child key is greater than the order of the curve')
+                               }
+                               const pkParsed = parse256(pk)
+                               const childKey = (ILparsed + pkParsed) % Point.CURVE().n
+                               if (childKey === 0n) {
+                                       throw new Error('Invalid child key is zero')
+                               }
+                               return ({ privateKey: ser256(childKey).buffer, chainCode: IR })
+                       }
+               })
+}
 
-       static parse256 (integer: Uint8Array<ArrayBuffer>): bigint {
-               let result = 0n
-               for (let i = 0; i < integer.byteLength; i++) {
-                       result = (result << 8n) | BigInt(integer[i])
-               }
-               return result
+function ser32 (integer: number): Uint8Array<ArrayBuffer> {
+       const view = new DataView(new ArrayBuffer(4))
+       view.setUint32(0, integer, false)
+       return new Uint8Array(view.buffer)
+}
+
+function ser256 (integer: bigint): Uint8Array<ArrayBuffer> {
+       let bytes = new Uint8Array(32)
+       for (let i = bytes.byteLength - 1; i >= 0; i--) {
+               bytes[i] = Number(integer & 0xffn)
+               integer >>= 8n
        }
+       return bytes
+}
 
-       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)
-                       })
+function parse256 (integer: Uint8Array<ArrayBuffer>): bigint {
+       let result = 0n
+       for (let i = 0; i < integer.byteLength; i++) {
+               result = (result << 8n) | BigInt(integer[i])
        }
+       return result
+}
+
+function 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)
+               })
 }