From 908bde8f2f7cfc79c5d6183f7ab7e38f1313937a Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Thu, 14 May 2026 13:32:57 -0700 Subject: [PATCH] Reuse text encoder to avoid initializing on every call. Consolidate redundant assignments in CKDpriv. Alias byte arrayts for legibility. Pass buffers to HMAC. --- src/lib/crypto/bip44.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/lib/crypto/bip44.ts b/src/lib/crypto/bip44.ts index 135bae7..922b7d1 100644 --- a/src/lib/crypto/bip44.ts +++ b/src/lib/crypto/bip44.ts @@ -3,6 +3,7 @@ import { Point, getPublicKey as secp256k1_getPublicKey } from '@noble/secp256k1' +type Bytes = Uint8Array type Curve = 'Bitcoin seed' | 'ed25519 seed' type ExtendedKey = { privateKey: ArrayBuffer @@ -11,6 +12,7 @@ type ExtendedKey = { } const BIP44_PURPOSE: 44 = 44 const HARDENED_OFFSET: 0x80000000 = 0x80000000 +const ENCODER: TextEncoder = new TextEncoder() /** * Derives a private child key for a coin by following the specified BIP-32 and @@ -74,8 +76,8 @@ function ckd (curve: Curve, seed: ArrayBuffer, coin: number, account: number, ch } function slip10 (curve: string, S: ArrayBuffer): Promise { - const key = new Uint8Array(new TextEncoder().encode(curve)) - const data = new Uint8Array(S) + const key = ENCODER.encode(curve).buffer + const data = S return hmac(key, data) .then(I => { const IL = I.slice(0, I.byteLength / 2) @@ -89,18 +91,17 @@ function CKDpriv (curve: Curve, { privateKey, chainCode }: ExtendedKey, index?: return Promise.resolve({ privateKey, chainCode }) } const pk = new Uint8Array(privateKey) - const key = new Uint8Array(chainCode) - const data = new Uint8Array(37) + const t = new Uint8Array(37) if (index >= HARDENED_OFFSET) { - data.set([0]) - data.set(pk, 1) - data.set(ser32(index), 33) + t.set(pk, 1) } 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) + t.set(secp256k1_getPublicKey(pk)) } + t.set(ser32(index), 33) + const key = chainCode + const data = t.buffer return hmac(key, data) .then(I => { const IL = I.slice(0, I.byteLength / 2) @@ -122,13 +123,13 @@ function CKDpriv (curve: Curve, { privateKey, chainCode }: ExtendedKey, index?: }) } -function ser32 (integer: number): Uint8Array { +function ser32 (integer: number): Bytes { const view = new DataView(new ArrayBuffer(4)) view.setUint32(0, integer, false) return new Uint8Array(view.buffer) } -function ser256 (integer: bigint): Uint8Array { +function ser256 (integer: bigint): Bytes { let bytes = new Uint8Array(32) for (let i = bytes.byteLength - 1; i >= 0; i--) { bytes[i] = Number(integer & 0xffn) @@ -137,7 +138,7 @@ function ser256 (integer: bigint): Uint8Array { return bytes } -function parse256 (integer: Uint8Array): bigint { +function parse256 (integer: Bytes): bigint { let result = 0n for (let i = 0; i < integer.byteLength; i++) { result = (result << 8n) | BigInt(integer[i]) @@ -145,7 +146,7 @@ function parse256 (integer: Uint8Array): bigint { return result } -function hmac (key: Uint8Array, data: Uint8Array): Promise { +function hmac (key: ArrayBuffer, data: ArrayBuffer): Promise { return crypto.subtle .importKey('raw', key, { name: 'HMAC', hash: 'SHA-512' }, false, ['sign']) .then(pk => crypto.subtle.sign('HMAC', pk, data)) -- 2.47.3