]> zoso.dev Git - libnemo.git/commitdiff
Eliminate remaining imported convert functions. Add type guard to initial seed ingest...
authorChris Duncan <chris@zoso.dev>
Fri, 22 Nov 2024 11:58:26 +0000 (03:58 -0800)
committerChris Duncan <chris@zoso.dev>
Fri, 22 Nov 2024 11:58:26 +0000 (03:58 -0800)
src/lib/workers/ckdBip44.ts

index a05aef49a527f5f07713bf6e91b319ff96b59e7d..e48156e4c081546557392e059445fa483f09330d 100644 (file)
@@ -2,8 +2,8 @@
 // SPDX-License-Identifier: GPL-3.0-or-later
 
 type ExtendedKey = {
-       privateKey: string
-       chainCode: string
+       privateKey: DataView
+       chainCode: DataView
 }
 
 async function ckdBip44 () {
@@ -11,12 +11,9 @@ async function ckdBip44 () {
        const BIP44_PURPOSE = 44
        const HARDENED_OFFSET = 0x80000000
        const SLIP10_ED25519 = 'ed25519 seed'
-       let bytes: any
-       let hex: any
        let addEventListener = globalThis.addEventListener
        let postMessage = globalThis.postMessage
        if (addEventListener == null || postMessage == null) {
-               ({ bytes, hex } = await import(new URL('./dist/lib/convert.js', import.meta.url).toString()))
                const { isMainThread, parentPort } = await import('node:worker_threads')
                if (!isMainThread && parentPort) {
                        addEventListener = Object.getPrototypeOf(parentPort).addListener.bind(parentPort)
@@ -41,6 +38,9 @@ async function ckdBip44 () {
        * @returns Private child key for the account
        */
        async function nanoCKD (seed: string, index: number): Promise<string> {
+               if (seed.length < 32 || seed.length > 128) {
+                       throw new RangeError(`Invalid seed length`)
+               }
                if (!Number.isSafeInteger(index) || index < 0 || index > 0x7fffffff) {
                        throw new RangeError(`Invalid child key index 0x${index.toString(16)}`)
                }
@@ -48,24 +48,29 @@ async function ckdBip44 () {
                const purposeKey = await CKDpriv(masterKey, BIP44_PURPOSE + HARDENED_OFFSET)
                const coinKey = await CKDpriv(purposeKey, BIP44_COIN_NANO + HARDENED_OFFSET)
                const accountKey = await CKDpriv(coinKey, index + HARDENED_OFFSET)
-               return accountKey.privateKey
+               const privateKey = new Uint8Array(accountKey.privateKey.buffer)
+               return privateKey.reduce((key, byte) => key.concat(byte.toString(16).padStart(2, '0')), '')
        }
 
        async function slip10 (curve: string, S: string): Promise<ExtendedKey> {
                const key = new TextEncoder().encode(curve)
-               const data = hex.toBytes(S)
+               const data = new Uint8Array(64)
+               data.set(S.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16)))
                const I = await hmac(key, data)
-               const IL = I.slice(0, I.length / 2)
-               const IR = I.slice(I.length / 2)
+               const IL = new DataView(I.buffer.slice(0, I.length / 2))
+               const IR = new DataView(I.buffer.slice(I.length / 2))
                return ({ privateKey: IL, chainCode: IR })
        }
 
        async function CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number): Promise<ExtendedKey> {
-               const key = hex.toBytes(chainCode)
-               const data = hex.toBytes(`00${bytes.toHex(ser256(privateKey))}${bytes.toHex(ser32(index))}`)
+               const key = new Uint8Array(chainCode.buffer)
+               const data = new Uint8Array(37)
+               data.set([0])
+               data.set(ser256(privateKey), 1)
+               data.set(ser32(index), 33)
                const I = await hmac(key, data)
-               const IL = I.slice(0, I.length / 2)
-               const IR = I.slice(I.length / 2)
+               const IL = new DataView(I.buffer.slice(0, I.length / 2))
+               const IR = new DataView(I.buffer.slice(I.length / 2))
                return ({ privateKey: IL, chainCode: IR })
        }
 
@@ -81,26 +86,24 @@ async function ckdBip44 () {
                return new Uint8Array(view.buffer)
        }
 
-       function ser256 (integer: string): Uint8Array {
-               if (typeof integer !== 'string') {
-                       throw new TypeError(`Expected string, received ${typeof integer}`)
+       function ser256 (integer: DataView): Uint8Array {
+               if (integer.constructor !== DataView) {
+                       throw new TypeError(`Expected DataView, received ${typeof integer}`)
                }
-               const bits = hex.toBin(integer)
-               if (bits.length > 256) {
-                       throw new RangeError(`Expected 256-bit integer, received ${bits.length}-bit value: ${integer}`)
+               if (integer.byteLength > 32) {
+                       throw new RangeError(`Expected 32-byte integer, received ${integer.byteLength}-byte value: ${integer}`)
                }
-               return hex.toBytes(integer, 32)
+               return new Uint8Array(integer.buffer)
        }
 
-       async function hmac (key: Uint8Array, data: Uint8Array): Promise<string> {
+       async function hmac (key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {
                const { subtle } = globalThis.crypto
                const pk = await subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-512' }, false, ['sign'])
                const signature = await subtle.sign('HMAC', pk, data)
-               return bytes.toHex(new Uint8Array(signature))
+               return new Uint8Array(signature)
        }
 }
 
 const ckdBip44Worker = `(${ckdBip44.toString()})()`
-// const ckdBip44Worker = `data:text/javascript,(${ckdBip44.toString()})()`
 export { ckdBip44Worker }