From b388bf03ee2451d921c1a01cdeb89d2c2e568bbb Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Mon, 18 Nov 2024 22:55:55 -0800 Subject: [PATCH] Integrate blake2b package, replacing blakejs and blake2b-wasm. Restore arrow notation to test methods. Remove ckd and thread class files for now, perhaps to be implemented later. --- package-lock.json | 26 ++++++--------- package.json | 6 ++-- src/lib/account.ts | 8 ++--- src/lib/ckd.ts | 60 ----------------------------------- src/lib/curve25519.ts | 4 +-- src/lib/ed25519.ts | 15 ++++----- src/lib/safe.ts | 2 -- src/lib/thread.ts | 59 ---------------------------------- src/lib/tools.ts | 9 +++--- src/lib/wallet.ts | 4 +-- test/derive-accounts.test.mjs | 10 +++--- 11 files changed, 35 insertions(+), 168 deletions(-) delete mode 100644 src/lib/ckd.ts delete mode 100644 src/lib/thread.ts diff --git a/package-lock.json b/package-lock.json index 2b136a8..592337e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,10 @@ "version": "0.0.19", "license": "(GPL-3.0-or-later AND MIT)", "dependencies": { - "blake2b": "^2.1.4", - "blake2b-wasm": "^2.4.0", - "blakejs": "^1.2.1" + "blake2b": "^2.1.4" }, "devDependencies": { - "@types/blake2b-wasm": "^2.4.3", + "@types/blake2b": "^2.1.3", "@types/node": "^22.8.6", "@types/w3c-web-hid": "^1.0.6", "@types/w3c-web-usb": "^1.0.10", @@ -520,17 +518,17 @@ "license": "Apache-2.0", "optional": true }, - "node_modules/@types/blake2b-wasm": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@types/blake2b-wasm/-/blake2b-wasm-2.4.3.tgz", - "integrity": "sha512-emsOJOuF5shxg5zhN3CHOy4BO/a26O++yk0ncFW9fePquKSGs1g6PIps8u8zFmApJjIkMQr7neVUqvoic4BRFw==", + "node_modules/@types/blake2b": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@types/blake2b/-/blake2b-2.1.3.tgz", + "integrity": "sha512-MFCdX0MNxFBP/xEILO5Td0kv6nI7+Q2iRWZbTL/yzH2/eDVZS5Wd1LHdsmXClvsCyzqaZfHFzZaN6BUeUCfSDA==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { - "version": "22.8.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.6.tgz", - "integrity": "sha512-tosuJYKrIqjQIlVCM4PEGxOmyg3FCPa/fViuJChnGeEIhjA46oy8FMVoF9su1/v8PNs2a8Q0iFNyOx0uOF91nw==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "dev": true, "license": "MIT", "dependencies": { @@ -584,12 +582,6 @@ "nanoassert": "^2.0.0" } }, - "node_modules/blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", - "license": "MIT" - }, "node_modules/esbuild": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", diff --git a/package.json b/package.json index ce5973c..4990a99 100644 --- a/package.json +++ b/package.json @@ -45,9 +45,7 @@ "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" }, "dependencies": { - "blake2b": "^2.1.4", - "blake2b-wasm": "^2.4.0", - "blakejs": "^1.2.1" + "blake2b": "^2.1.4" }, "optionalDependencies": { "@ledgerhq/hw-transport-web-ble": "^6.29.4", @@ -55,7 +53,7 @@ "@ledgerhq/hw-transport-webusb": "^6.29.4" }, "devDependencies": { - "@types/blake2b-wasm": "^2.4.3", + "@types/blake2b": "^2.1.3", "@types/node": "^22.8.6", "@types/w3c-web-hid": "^1.0.6", "@types/w3c-web-usb": "^1.0.10", diff --git a/src/lib/account.ts b/src/lib/account.ts index 7a111d3..d01a988 100644 --- a/src/lib/account.ts +++ b/src/lib/account.ts @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2024 Chris Duncan // SPDX-License-Identifier: GPL-3.0-or-later -import { blake2b } from 'blakejs' +import blake2b from 'blake2b' import { ACCOUNT_KEY_LENGTH, ALPHABET, PREFIX, PREFIX_LEGACY } from './constants.js' import { base32, bytes, hex } from './convert.js' import Ed25519 from './ed25519.js' @@ -143,7 +143,7 @@ export class Account { const expectedChecksum = address.slice(-8) const keyBase32 = address.slice(address.indexOf('_') + 1, -8) const keyBuf = base32.toBytes(keyBase32) - const actualChecksumBuf = blake2b(keyBuf, undefined, 5).reverse() + const actualChecksumBuf = blake2b(5, undefined, undefined, undefined, true).update(keyBuf).digest().reverse() const actualChecksum = bytes.toBase32(actualChecksumBuf) if (expectedChecksum !== actualChecksum) { @@ -186,7 +186,7 @@ export class Account { static #addressToKey (v: string): string { const keyBytes = base32.toBytes(v.substring(0, 52)) const checksumBytes = base32.toBytes(v.substring(52, 60)) - const blakeHash = blake2b(keyBytes, undefined, 5).reverse() + const blakeHash = blake2b(5, undefined, undefined, undefined, true).update(keyBytes).digest().reverse() if (bytes.toHex(checksumBytes) !== bytes.toHex(blakeHash)) { throw new Error('Checksum mismatch in address') } @@ -195,7 +195,7 @@ export class Account { static async #keyToAddress (key: string): Promise { const publicKeyBytes = hex.toBytes(key) - const checksum = blake2b(publicKeyBytes, undefined, 5).reverse() + const checksum = blake2b(5, undefined, undefined, undefined, true).update(publicKeyBytes).digest().reverse() const encoded = bytes.toBase32(publicKeyBytes) const encodedChecksum = bytes.toBase32(checksum) return `${PREFIX}${encoded}${encodedChecksum}` diff --git a/src/lib/ckd.ts b/src/lib/ckd.ts deleted file mode 100644 index 78cb8cc..0000000 --- a/src/lib/ckd.ts +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Chris Duncan -// SPDX-License-Identifier: GPL-3.0-or-later - -import blake2b from 'blake2b-wasm' -import { nanoCKD } from './bip32-key-derivation.js' - -let addEventListener = globalThis.addEventListener -let postMessage = globalThis.postMessage -if (addEventListener == null || postMessage == null) { - const { isMainThread, parentPort } = await import('node:worker_threads') - if (!isMainThread && parentPort) { - addEventListener = Object.getPrototypeOf(parentPort).addListener.bind(parentPort) - postMessage = Object.getPrototypeOf(parentPort).postMessage.bind(parentPort) - } -} - -/** -* Derives BIP-44 Nano account private keys. -* -* @param {number} index - Index of the account -* @returns {Promise} -*/ -addEventListener('message', (message) => { - const { type, seed, index } = message.data ?? message - switch (type) { - case 'bip44': { - ckdBip44(seed, index).then(postMessage) - break - } - case 'blake2b': { - ckdBlake2b(seed, index).then(postMessage) - break - } - } -}) - -/** -* Derives BIP-44 Nano account private keys. -* -* @param {number} index - Index of the account -* @returns {Promise} -*/ -async function ckdBip44 (seed: string, index: number): Promise { - return nanoCKD(seed, index) -} - -/** -* 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().update(inputBytes).digest('hex') - return hash -} diff --git a/src/lib/curve25519.ts b/src/lib/curve25519.ts index 14ba60f..1386d88 100644 --- a/src/lib/curve25519.ts +++ b/src/lib/curve25519.ts @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2024 Chris Duncan // SPDX-License-Identifier: GPL-3.0-or-later -import { blake2b } from 'blakejs' +import blake2b from 'blake2b' /** * Derived from: @@ -686,7 +686,7 @@ export default class Curve25519 { for (let i = 0; i < n; ++i) { input[i] = m[i] } - const hash = blake2b(input) + const hash = blake2b(64).update(input).digest() for (let i = 0; i < 64; ++i) { out[i] = hash[i] } diff --git a/src/lib/ed25519.ts b/src/lib/ed25519.ts index 395a409..60631cd 100644 --- a/src/lib/ed25519.ts +++ b/src/lib/ed25519.ts @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: 2024 Chris Duncan // SPDX-License-Identifier: GPL-3.0-or-later -import blakejs from 'blakejs' -const { blake2b, blake2bInit, blake2bUpdate, blake2bFinal } = blakejs +import blake2b from 'blake2b' import { bytes, hex } from './convert.js' import Curve25519 from './curve25519.js' @@ -26,7 +25,7 @@ const L: Uint8Array = new Uint8Array([0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, function getPublicKey (privateKey: string): string { const pk = new Uint8Array(32) const p = [curve.gf(), curve.gf(), curve.gf(), curve.gf()] - const h = blake2b(hex.toBytes(privateKey), undefined, 64).slice(0, 32) + const h = blake2b(64).update(hex.toBytes(privateKey)).digest().slice(0, 32) h[0] &= 0xf8 h[31] &= 0x7f @@ -94,11 +93,11 @@ function verify (msg: Uint8Array, publicKey: Uint8Array, signature: Uint8Array): return false } - const ctx = blake2bInit(64, undefined) - blake2bUpdate(ctx, signature.subarray(0, 32)) - blake2bUpdate(ctx, publicKey) - blake2bUpdate(ctx, msg) - let k = blake2bFinal(ctx) + const k = blake2b(64) + .update(signature.subarray(0, 32)) + .update(publicKey) + .update(msg) + .digest() reduce(k) scalarmult(p, q, k) diff --git a/src/lib/safe.ts b/src/lib/safe.ts index 3a417c0..8654756 100644 --- a/src/lib/safe.ts +++ b/src/lib/safe.ts @@ -3,13 +3,11 @@ import { buffer, hex, utf8 } from './convert.js' import { Entropy } from './entropy.js' -// import { Thread } from './thread.js' const { subtle } = globalThis.crypto const ERR_MSG = 'Failed to store item in Safe' export class Safe { #storage = globalThis.sessionStorage - // #thread = new Thread(new URL('./ckd.js', import.meta.url)) /** * Encrypts data with a password and stores it in the Safe. diff --git a/src/lib/thread.ts b/src/lib/thread.ts deleted file mode 100644 index 79addc4..0000000 --- a/src/lib/thread.ts +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Chris Duncan -// SPDX-License-Identifier: GPL-3.0-or-later - -if (globalThis.Worker == null) { - const { Worker } = await import('node:worker_threads') - //@ts-expect-error - Worker.prototype.addEventListener = Worker.prototype.addListener - //@ts-expect-error - globalThis.Worker = Worker -} - -type Task = { - data: object, - resolve: Function -} - -/** -* Processes tasks from a queue using a Web Worker. -*/ -export class Thread { - #isAvailable: boolean = true - #queue: Task[] = [] - #task?: Task - #worker: Worker - - #post (next: Task) { - this.#isAvailable = false - this.#task = next - this.#worker.postMessage(next.data) - } - - constructor (url: string | URL) { - this.#worker = new Worker(new URL(url, import.meta.url), { type: 'module' }) - this.#worker.addEventListener('message', (event) => { - const result = event.data ?? event - if (this.#task == null) { - throw new ReferenceError(`Error resolving Worker result: ${result}`) - } - const resolve = this.#task.resolve - const next = this.#queue.shift() - if (next == null) { - this.#isAvailable = true - } else { - this.#post(next) - } - resolve(result) - }) - } - - async work (data: object): Promise { - return new Promise(resolve => { - if (this.#isAvailable) { - this.#post({ data, resolve }) - } else { - this.#queue.push({ data, resolve }) - } - }) - } -} diff --git a/src/lib/tools.ts b/src/lib/tools.ts index d5c2472..796df72 100644 --- a/src/lib/tools.ts +++ b/src/lib/tools.ts @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: 2024 Chris Duncan // SPDX-License-Identifier: GPL-3.0-or-later -import blakejs from 'blakejs' -const { blake2bInit, blake2bUpdate, blake2bFinal } = blakejs +import blake from 'blake2b' import { Account } from './account.js' import { UNITS } from './constants.js' import { bytes, hex } from './convert.js' @@ -19,9 +18,9 @@ import { SendBlock } from './block.js' */ export async function blake2b (data: string | string[]): Promise { if (!Array.isArray(data)) data = [data] - const ctx = blake2bInit(32) - data.forEach(str => blake2bUpdate(ctx, hex.toBytes(str))) - return blake2bFinal(ctx) + const hash = blake(32) + data.forEach(str => hash.update(hex.toBytes(str))) + return hash.digest() } /** diff --git a/src/lib/wallet.ts b/src/lib/wallet.ts index b180edb..ae3bea2 100644 --- a/src/lib/wallet.ts +++ b/src/lib/wallet.ts @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2024 Chris Duncan // SPDX-License-Identifier: GPL-3.0-or-later -import blake2b from 'blake2b-wasm' +import blake2b from 'blake2b' import { Account } from './account.js' import { nanoCKD } from './bip32-key-derivation.js' import { Bip39Mnemonic } from './bip39-mnemonic.js' @@ -536,7 +536,7 @@ export class Blake2bWallet extends Wallet { */ async ckd (index: number): Promise { const input = `${this.seed}${dec.toHex(index, 8)}` - const key = blake2b().update(hex.toBytes(input)).digest('hex') + const key = blake2b(32).update(hex.toBytes(input)).digest('hex') if (typeof key !== 'string') { throw new TypeError('BLAKE2b child key derivation returned invalid data') } diff --git a/test/derive-accounts.test.mjs b/test/derive-accounts.test.mjs index e8e7948..f4833c1 100644 --- a/test/derive-accounts.test.mjs +++ b/test/derive-accounts.test.mjs @@ -15,7 +15,7 @@ describe('derive child accounts from the same seed', async function () { const wallet = await Bip44Wallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED) await wallet.unlock(NANO_TEST_VECTORS.PASSWORD) - it('should derive the first account from the given BIP-44 seed', async function () { + it('should derive the first account from the given BIP-44 seed', async () => { const accounts = await wallet.accounts() assert.equal(accounts.length, 1) @@ -24,7 +24,7 @@ describe('derive child accounts from the same seed', async function () { assert.equal(accounts[0].address, NANO_TEST_VECTORS.ADDRESS_0) }) - it('should derive low indexed accounts from the given BIP-44 seed', async function () { + it('should derive low indexed accounts from the given BIP-44 seed', async () => { const accounts = await wallet.accounts(1, 2) assert.equal(accounts.length, 2) @@ -36,7 +36,7 @@ describe('derive child accounts from the same seed', async function () { assert.equal(accounts[1].address, NANO_TEST_VECTORS.ADDRESS_2) }) - it('should derive high indexed accounts from the given seed', async function () { + it('should derive high indexed accounts from the given seed', async () => { const accounts = await wallet.accounts(0x70000000, 0x700000ff) assert.equal(accounts.length, 0x100) @@ -49,7 +49,7 @@ describe('derive child accounts from the same seed', async function () { } }) - it('should derive accounts for a BLAKE2b wallet', async function () { + it('should derive accounts for a BLAKE2b wallet', async () => { const bwallet = await Blake2bWallet.create(NANO_TEST_VECTORS.PASSWORD) await bwallet.unlock(NANO_TEST_VECTORS.PASSWORD) const lowAccounts = await bwallet.accounts(0, 2) @@ -79,7 +79,7 @@ describe('derive child accounts from the same seed', async function () { describe('Ledger device accounts', { skip: true }, async () => { const wallet = await LedgerWallet.create() - it('should fetch the first account from a Ledger device', async function () { + it('should fetch the first account from a Ledger device', async () => { const accounts = await wallet.accounts() assert.equal(accounts.length, 1) -- 2.34.1