]> zoso.dev Git - libnemo.git/commitdiff
t
authorChris Duncan <chris@zoso.dev>
Sat, 9 Nov 2024 02:26:36 +0000 (18:26 -0800)
committerChris Duncan <chris@zoso.dev>
Sat, 9 Nov 2024 02:26:36 +0000 (18:26 -0800)
package.json
src/lib/ckd.ts [new file with mode: 0644]
src/lib/pool.ts [new file with mode: 0644]
src/lib/tools.ts
src/lib/wallet.ts

index 7dee5a72231f659ce0155f8d152534618996d63c..61389ae17c4b6b9b4b9ca871a4a49550e4410995 100644 (file)
@@ -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 (file)
index 0000000..f7e2d05
--- /dev/null
@@ -0,0 +1,71 @@
+// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>
+// 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<Account>}
+*/
+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<Account>}
+*/
+async function ckdBip44 (seed: string, index: number): Promise<Account> {
+       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<Account>}
+*/
+async function ckdBlake2b (seed: string, index: number): Promise<Account> {
+       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<Account>}
+*/
+export async function ckdLedger (ledger: Ledger, index: number): Promise<Account | null> {
+       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 (file)
index 0000000..a07087c
--- /dev/null
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>
+// 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<any> {
+               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)
+                       }
+               })
+       }
+}
index c9ba7883cf3ec30e2d10ec6bf2b8dcaecc783bae..d5c24728c52137571f83143f864e049d118c6732 100644 (file)
@@ -88,18 +88,6 @@ export async function hash (data: string | string[]): Promise<string> {
        return bytes.toHex(hash)
 }
 
-
-/**
-* Checks the endianness of the current machine.
-*
-* @returns {Promise<boolean>} 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 }
index 3608804b155f2b8df326918201d15dc9b01ccee8..163a3aa26b30b9ee09df5ccdb29739dffd7ae600 100644 (file)
@@ -7,11 +7,14 @@ import { nanoCKD } from './bip32-key-derivation.js'
 import { ADDRESS_GAP, SEED_LENGTH_BIP44, SEED_LENGTH_BLAKE2B } from './constants.js'\r
 import { bytes, dec } from './convert.js'\r
 import { Entropy } from './entropy.js'\r
+import { Pool } from './pool.js'\r
 import { Rpc } from './rpc.js'\r
 import { Safe } from './safe.js'\r
 import Tools from './tools.js'\r
 import type { Ledger } from './ledger.js'\r
 \r
+const ckdPool = new Pool('ckd.js')\r
+\r
 /**\r
 * Represents a wallet containing numerous Nano accounts derived from a single\r
 * source, the form of which can vary based on the type of wallet. The Wallet\r
@@ -397,8 +400,7 @@ export class Bip44Wallet extends Wallet {
        * @returns {Promise<Account>}\r
        */\r
        async ckd (index: number): Promise<Account> {\r
-               const key = await nanoCKD(this.seed, index)\r
-               return await Account.fromPrivateKey(key, index)\r
+               return await ckdPool.work({ type: 'bip44', seed: this.seed, index })\r
        }\r
 }\r
 \r
@@ -539,13 +541,7 @@ export class Blake2bWallet extends Wallet {
        * @returns {Promise<Account>}\r
        */\r
        async ckd (index: number): Promise<Account> {\r
-               const indexBytes = dec.toBytes(index, 4)\r
-               if (await Tools.littleEndian()) {\r
-                       indexBytes.reverse()\r
-               }\r
-               const hash = await Tools.blake2b([this.seed, bytes.toHex(indexBytes)])\r
-               const key = bytes.toHex(hash)\r
-               return await Account.fromPrivateKey(key, index)\r
+               return await ckdPool.work({ type: 'blake2b', seed: this.seed, index })\r
        }\r
 }\r
 \r
@@ -627,10 +623,10 @@ export class LedgerWallet extends Wallet {
        * @returns True if successfully locked\r
        */\r
        async lock (): Promise<boolean> {\r
-               if (this.#ledger == null) {\r
+               if (this.ledger == null) {\r
                        return false\r
                }\r
-               const result = await this.#ledger.close()\r
+               const result = await this.ledger.close()\r
                return result === 'OK'\r
        }\r
 \r
@@ -643,10 +639,10 @@ export class LedgerWallet extends Wallet {
        * @returns True if successfully unlocked\r
        */\r
        async unlock (): Promise<boolean> {\r
-               if (this.#ledger == null) {\r
+               if (this.ledger == null) {\r
                        return false\r
                }\r
-               const result = await this.#ledger.connect()\r
+               const result = await this.ledger.connect()\r
                return result === 'OK'\r
        }\r
 }\r