From f65b9e050feb51c273ee7e04aec6ca4aa22edb55 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Fri, 8 Nov 2024 18:26:36 -0800 Subject: [PATCH] t --- package.json | 2 +- src/lib/ckd.ts | 71 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib/pool.ts | 46 ++++++++++++++++++++++++++++++ src/lib/tools.ts | 14 +--------- src/lib/wallet.ts | 22 ++++++--------- 5 files changed, 128 insertions(+), 27 deletions(-) create mode 100644 src/lib/ckd.ts create mode 100644 src/lib/pool.ts diff --git a/package.json b/package.json index 7dee5a7..61389ae 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "url": "git+https://zoso.dev/libnemo.git" }, "scripts": { - "build": "rm -rf dist && tsc && esbuild main.min=dist/main.js global.min=dist/global.js --outdir=dist --target=es2022 --format=esm --platform=browser --bundle --minify --sourcemap", + "build": "rm -rf dist && tsc && esbuild main.min=dist/main.js global.min=dist/global.js --outdir=dist --target=es2022 --format=esm --platform=node --bundle --minify --sourcemap", "test": "npm run build && node --test --env-file .env", "test:coverage": "npm run test -- --experimental-test-coverage", "test:coverage:report": "npm run test:coverage -- --test-reporter=lcov --test-reporter-destination=coverage.info && genhtml coverage.info --output-directory test/coverage && rm coverage.info && xdg-open test/coverage/index.html" diff --git a/src/lib/ckd.ts b/src/lib/ckd.ts new file mode 100644 index 0000000..f7e2d05 --- /dev/null +++ b/src/lib/ckd.ts @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2024 Chris Duncan +// SPDX-License-Identifier: GPL-3.0-or-later + +import { Account } from './account.js' +import { nanoCKD } from './bip32-key-derivation.js' +import { bytes, dec } from './convert.js' +import Tools from './tools.js' +import type { Ledger } from './ledger.js' + +/** +* Derives BIP-44 Nano account private keys. +* +* @param {number} index - Index of the account +* @returns {Promise} +*/ +onmessage = async (event) => { + let result = null + const { type, seed, index } = event.data + switch (type) { + case 'bip44': { + result = await ckdBip44(seed, index) + break + } + case 'blake2b': { + result = await ckdBlake2b(seed, index) + break + } + case 'ledger': { + result = await ckdLedger(seed, index) + break + } + } + postMessage(result) +} + +/** +* Derives BIP-44 Nano account private keys. +* +* @param {number} index - Index of the account +* @returns {Promise} +*/ +async function ckdBip44 (seed: string, index: number): Promise { + const key = await nanoCKD(seed, index) + return await Account.fromPrivateKey(key, index) +} + +/** +* Derives BLAKE2b account private keys. +* +* @param {number} index - Index of the account +* @returns {Promise} +*/ +async function ckdBlake2b (seed: string, index: number): Promise { + const hash = await Tools.blake2b([seed, dec.toHex(index, 4)]) + const key = bytes.toHex(hash) + return await Account.fromPrivateKey(key, index) +} + +/** +* Gets the public key for an account from the Ledger device. +* +* @param {number} index - Index of the account +* @returns {Promise} +*/ +export async function ckdLedger (ledger: Ledger, index: number): Promise { + const { status, publicKey } = await ledger.account(index) + if (status === 'OK' && publicKey != null) { + return await Account.fromPublicKey(publicKey, index) + } + return null +} diff --git a/src/lib/pool.ts b/src/lib/pool.ts new file mode 100644 index 0000000..a07087c --- /dev/null +++ b/src/lib/pool.ts @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2024 Chris Duncan +// SPDX-License-Identifier: GPL-3.0-or-later + +const Worker = globalThis.Worker ?? (await import('node:worker_threads')).Worker +/** +* Assigns a Web Worker to process data. Creates a new one if none are available. +* +* @param {object} data - Arbitrary data encapsulated in a JSON object +* @param {string} signature - Hexadcimal-formatted signature +* @param {...string} input - Data to be verified +* @returns {boolean} True if the data was signed by the public key's matching private key +*/ +export class Pool { + #threads + #url + + constructor (url: string | URL) { + this.#url = new URL(url, import.meta.url) + this.#threads = [...Array(navigator.hardwareConcurrency)] + this.#threads.forEach(slot => { + slot = { + worker: new Worker(this.#url), + tasks: [], + get isAvailable () { return this.tasks.length === 0 } + } + }) + } + + async work (data: object): Promise { + return new Promise((resolve) => { + const thread = this.#threads.reduce((curr, next) => { + next.tasks.length < curr.tasks.length + ? next + : curr + }) + thread.tasks.push(data) + thread.worker.postMessage(thread.tasks.shift()) + thread.worker.onmessage = (event: any) => { + if (thread.tasks.length > 0) { + thread.worker.postMessage(thread.tasks.shift()) + } + resolve(event.data) + } + }) + } +} diff --git a/src/lib/tools.ts b/src/lib/tools.ts index c9ba788..d5c2472 100644 --- a/src/lib/tools.ts +++ b/src/lib/tools.ts @@ -88,18 +88,6 @@ export async function hash (data: string | string[]): Promise { return bytes.toHex(hash) } - -/** -* Checks the endianness of the current machine. -* -* @returns {Promise} True if little-endian, else false -*/ -export async function littleEndian () { - const buffer = new ArrayBuffer(2) - new DataView(buffer).setUint16(0, 256, true) - return new Uint16Array(buffer)[0] === 256 -} - /** * Signs arbitrary strings with a private key using the Ed25519 signature scheme. * @@ -188,4 +176,4 @@ export async function verify (key: string, signature: string, ...input: string[] hex.toBytes(signature)) } -export default { blake2b, convert, hash, littleEndian, sign, sweep, verify } +export default { blake2b, convert, hash, sign, sweep, verify } diff --git a/src/lib/wallet.ts b/src/lib/wallet.ts index 3608804..163a3aa 100644 --- a/src/lib/wallet.ts +++ b/src/lib/wallet.ts @@ -7,11 +7,14 @@ import { nanoCKD } from './bip32-key-derivation.js' import { ADDRESS_GAP, SEED_LENGTH_BIP44, SEED_LENGTH_BLAKE2B } from './constants.js' import { bytes, dec } from './convert.js' import { Entropy } from './entropy.js' +import { Pool } from './pool.js' import { Rpc } from './rpc.js' import { Safe } from './safe.js' import Tools from './tools.js' import type { Ledger } from './ledger.js' +const ckdPool = new Pool('ckd.js') + /** * Represents a wallet containing numerous Nano accounts derived from a single * source, the form of which can vary based on the type of wallet. The Wallet @@ -397,8 +400,7 @@ export class Bip44Wallet extends Wallet { * @returns {Promise} */ async ckd (index: number): Promise { - const key = await nanoCKD(this.seed, index) - return await Account.fromPrivateKey(key, index) + return await ckdPool.work({ type: 'bip44', seed: this.seed, index }) } } @@ -539,13 +541,7 @@ export class Blake2bWallet extends Wallet { * @returns {Promise} */ async ckd (index: number): Promise { - const indexBytes = dec.toBytes(index, 4) - if (await Tools.littleEndian()) { - indexBytes.reverse() - } - const hash = await Tools.blake2b([this.seed, bytes.toHex(indexBytes)]) - const key = bytes.toHex(hash) - return await Account.fromPrivateKey(key, index) + return await ckdPool.work({ type: 'blake2b', seed: this.seed, index }) } } @@ -627,10 +623,10 @@ export class LedgerWallet extends Wallet { * @returns True if successfully locked */ async lock (): Promise { - if (this.#ledger == null) { + if (this.ledger == null) { return false } - const result = await this.#ledger.close() + const result = await this.ledger.close() return result === 'OK' } @@ -643,10 +639,10 @@ export class LedgerWallet extends Wallet { * @returns True if successfully unlocked */ async unlock (): Promise { - if (this.#ledger == null) { + if (this.ledger == null) { return false } - const result = await this.#ledger.connect() + const result = await this.ledger.connect() return result === 'OK' } } -- 2.34.1