From: Chris Duncan Date: Tue, 26 Nov 2024 19:14:46 +0000 (-0800) Subject: Exporting import.meta.url does not work since once it is bundled, all files share... X-Git-Url: https://zoso.dev/?a=commitdiff_plain;h=f26264a863b5e85fb5c1b46f6872a140447b3347;p=libnemo.git Exporting import.meta.url does not work since once it is bundled, all files share the same URL and the Pool does not know which worker to spin up. It seems like a completely bundled solution will require creating workers from function strings, and that will require embedding all logic inside the worker with no external imports as far as I have been able to determine. Along with exporting workers as function strings, update BLAKE2b ckd to accept blake2b as a function string for its hashing. --- diff --git a/src/lib/pool.ts b/src/lib/pool.ts index 0c3f9fd..6b86516 100644 --- a/src/lib/pool.ts +++ b/src/lib/pool.ts @@ -32,7 +32,8 @@ export class Pool { return true } - constructor (url: string) { + constructor (fn: string) { + const url = URL.createObjectURL(new Blob([fn], { type: 'text/javascript' })) for (let i = navigator.hardwareConcurrency - 1; i > 0; i--) { const thread = { isBusy: false, diff --git a/src/lib/wallet.ts b/src/lib/wallet.ts index e0e553b..afa02be 100644 --- a/src/lib/wallet.ts +++ b/src/lib/wallet.ts @@ -572,7 +572,7 @@ export class Blake2bWallet extends Wallet { async ckd (index: number | number[]): Promise { if (!Array.isArray(index)) index = [index] const data: any = [] - index.forEach(i => data.push({ seed: this.seed, index: i })) + index.forEach(i => data.push({ blake2b: blake2b.toString(), index: i, seed: this.seed })) const results: [{ index: number, key: string }] = await this.#pool.work(data) const accounts = [] for (const result of results) { diff --git a/src/lib/workers/ckdBip44.ts b/src/lib/workers/ckdBip44.ts index 95b3b9b..7eaf9c1 100644 --- a/src/lib/workers/ckdBip44.ts +++ b/src/lib/workers/ckdBip44.ts @@ -6,91 +6,94 @@ type ExtendedKey = { chainCode: DataView } -const BIP44_COIN_NANO = 165 -const BIP44_PURPOSE = 44 -const HARDENED_OFFSET = 0x80000000 -const SLIP10_ED25519 = 'ed25519 seed' +async function fn () { + const BIP44_COIN_NANO = 165 + const BIP44_PURPOSE = 44 + const HARDENED_OFFSET = 0x80000000 + const SLIP10_ED25519 = 'ed25519 seed' -/** -* Listens for messages from a calling function. -*/ -addEventListener('message', (message) => { - const { seed, index } = message.data ?? message - nanoCKD(seed, index).then(key => postMessage({ index, key })) -}) + /** + * Listens for messages from a calling function. + */ + addEventListener('message', (message) => { + const { seed, index } = message.data ?? message + nanoCKD(seed, index).then(key => postMessage({ index, key })) + }) -/** -* Derives a private child key following the BIP-32 and BIP-44 derivation path -* registered to the Nano block lattice. Only hardened child keys are defined. -* -* @param {string} seed - Hexadecimal seed derived from mnemonic phrase -* @param {number} index - Account number between 0 and 2^31-1 -* @returns Private child key for the account -*/ -async function nanoCKD (seed: string, index: number): Promise { - if (seed.length < 32 || seed.length > 128) { - throw new RangeError(`Invalid seed length`) + /** + * Derives a private child key following the BIP-32 and BIP-44 derivation path + * registered to the Nano block lattice. Only hardened child keys are defined. + * + * @param {string} seed - Hexadecimal seed derived from mnemonic phrase + * @param {number} index - Account number between 0 and 2^31-1 + * @returns Private child key for the account + */ + async function nanoCKD (seed: string, index: number): Promise { + console.log(`seed: ${seed}; index: ${index}`) + 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)}`) + } + const masterKey = await slip10(SLIP10_ED25519, seed) + 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) + const privateKey = new Uint8Array(accountKey.privateKey.buffer) + return privateKey.reduce((key, byte) => key.concat(byte.toString(16).padStart(2, '0')), '') } - if (!Number.isSafeInteger(index) || index < 0 || index > 0x7fffffff) { - throw new RangeError(`Invalid child key index 0x${index.toString(16)}`) - } - const masterKey = await slip10(SLIP10_ED25519, seed) - 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) - 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 { - const key = new TextEncoder().encode(curve) - 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 = 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 { - 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 = new DataView(I.buffer.slice(0, I.length / 2)) - const IR = new DataView(I.buffer.slice(I.length / 2)) - return ({ privateKey: IL, chainCode: IR }) -} -function ser32 (integer: number): Uint8Array { - if (typeof integer !== 'number') { - throw new TypeError(`Expected a number, received ${typeof integer}`) + async function slip10 (curve: string, S: string): Promise { + const key = new TextEncoder().encode(curve) + 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 = new DataView(I.buffer.slice(0, I.length / 2)) + const IR = new DataView(I.buffer.slice(I.length / 2)) + return ({ privateKey: IL, chainCode: IR }) } - if (integer > 0xffffffff) { - throw new RangeError(`Expected 32-bit integer, received ${integer.toString(2).length}-bit value: ${integer}`) + + async function CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number): Promise { + 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 = new DataView(I.buffer.slice(0, I.length / 2)) + const IR = new DataView(I.buffer.slice(I.length / 2)) + return ({ privateKey: IL, chainCode: IR }) } - const view = new DataView(new ArrayBuffer(4)) - view.setUint32(0, integer, false) - return new Uint8Array(view.buffer) -} -function ser256 (integer: DataView): Uint8Array { - if (integer.constructor !== DataView) { - throw new TypeError(`Expected DataView, received ${typeof integer}`) + function 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}`) + } + const view = new DataView(new ArrayBuffer(4)) + view.setUint32(0, integer, false) + return new Uint8Array(view.buffer) } - if (integer.byteLength > 32) { - throw new RangeError(`Expected 32-byte integer, received ${integer.byteLength}-byte value: ${integer}`) + + function ser256 (integer: DataView): Uint8Array { + if (integer.constructor !== DataView) { + throw new TypeError(`Expected DataView, received ${typeof integer}`) + } + if (integer.byteLength > 32) { + throw new RangeError(`Expected 32-byte integer, received ${integer.byteLength}-byte value: ${integer}`) + } + return new Uint8Array(integer.buffer) } - return new Uint8Array(integer.buffer) -} -async function hmac (key: Uint8Array, data: Uint8Array): Promise { - 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 new Uint8Array(signature) + async function hmac (key: Uint8Array, data: Uint8Array): Promise { + 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 new Uint8Array(signature) + } } -export default import.meta.url +export default `(${fn.toString()})()` diff --git a/src/lib/workers/ckdBlake2b.ts b/src/lib/workers/ckdBlake2b.ts index 6f27858..edb5b83 100644 --- a/src/lib/workers/ckdBlake2b.ts +++ b/src/lib/workers/ckdBlake2b.ts @@ -1,29 +1,31 @@ // SPDX-FileCopyrightText: 2024 Chris Duncan // SPDX-License-Identifier: GPL-3.0-or-later -import blake2b from 'blake2b' +async function fn () { + /** + * Listens for messages from a calling function. + */ + addEventListener('message', (message) => { + const { seed, index, blake2b } = message.data ?? message + ckdBlake2b(seed, index, blake2b).then(key => postMessage({ index, key })) + }) -/** -* Listens for messages from a calling function. -*/ -addEventListener('message', (message) => { - const { seed, index } = message.data ?? message - ckdBlake2b(seed, index).then(key => postMessage({ index, key })) -}) - -/** -* Derives BLAKE2b account private keys. -* -* @param {number} index - Index of the account -* @returns {Promise} -*/ -async function ckdBlake2b (seed: string, index: number): Promise { - const indexHex = index.toString(16).padStart(8, '0').toUpperCase() - const inputHex = `${seed}${indexHex}`.padStart(72, '0') - const inputArray = (inputHex.match(/.{1,2}/g) ?? []).map(h => parseInt(h, 16)) - const inputBytes = Uint8Array.from(inputArray) - const hash = blake2b(32).update(inputBytes).digest('hex') - return hash + /** + * Derives BLAKE2b account private keys. + * + * @param {number} index - Index of the account + * @returns {Promise} + */ + async function ckdBlake2b (seed: string, index: number, blake2b: string): Promise { + const blake = Function(`return ${blake2b}`) + console.log(blake) + const indexHex = index.toString(16).padStart(8, '0').toUpperCase() + const inputHex = `${seed}${indexHex}`.padStart(72, '0') + const inputArray = (inputHex.match(/.{1,2}/g) ?? []).map(h => parseInt(h, 16)) + const inputBytes = Uint8Array.from(inputArray) + const hash = blake(32).update(inputBytes).digest('hex') + return hash + } } -export default import.meta.url +export default `(${fn.toString()})()`