From f6cc4370b0551f3d407d76527a4abde2cfae103d Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Mon, 28 Jul 2025 14:49:17 -0700 Subject: [PATCH] Deprecate NanoNaCl worker and just use it in eventual Safe worker. --- src/lib/account.ts | 17 +- src/lib/block.ts | 10 +- src/lib/nano-nacl.ts | 638 ++++++++++++++++++++++++++++++++ src/lib/safe/index.ts | 2 +- src/lib/safe/worker-queue.ts | 2 - src/lib/tools.ts | 18 +- src/lib/wallets/bip44-wallet.ts | 3 +- src/types.d.ts | 1 - 8 files changed, 651 insertions(+), 40 deletions(-) create mode 100644 src/lib/nano-nacl.ts diff --git a/src/lib/account.ts b/src/lib/account.ts index 176e065..5fea41c 100644 --- a/src/lib/account.ts +++ b/src/lib/account.ts @@ -5,9 +5,10 @@ import { Blake2b } from './blake2b' import { ChangeBlock, ReceiveBlock, SendBlock } from './block' import { ACCOUNT_KEY_BYTE_LENGTH, ACCOUNT_KEY_HEX_LENGTH, ALPHABET, PREFIX, PREFIX_LEGACY } from './constants' import { base32, bytes, hex, utf8 } from './convert' +import { NanoNaCl } from './nano-nacl' import { Rpc } from './rpc' import { Key, KeyPair, NamedData } from '#types' -import { NanoNaClWorker, SafeWorker } from '#workers' +import { SafeWorker } from '#workers' /** * Represents a single Nano address and the associated public key. To include the @@ -209,12 +210,8 @@ export class Account { */ async sign (block: ChangeBlock | ReceiveBlock | SendBlock, password: string): Promise { try { - const { signature } = await NanoNaClWorker.request({ - method: 'detached', - privateKey: await this.#getPrivateKey(password), - msg: hex.toBuffer(block.hash) - }) - block.signature = bytes.toHex(new Uint8Array(signature)) + const signature = await NanoNaCl.detached(hex.toBytes(block.hash), new Uint8Array(await this.#getPrivateKey(password))) + block.signature = bytes.toHex(signature) return block.signature } catch (err) { throw new Error(`Failed to sign block`, { cause: err }) @@ -318,11 +315,7 @@ export class Account { this.#validateKey(privateKey) if (typeof privateKey === 'string') privateKey = hex.toBytes(privateKey) try { - const result = await NanoNaClWorker.request({ - method: 'convert', - privateKey: privateKey.buffer.slice() - }) - const publicKey = new Uint8Array(result.publicKey) + const publicKey = await NanoNaCl.convert(privateKey) privateAccounts[bytes.toHex(publicKey)] = privateKey.buffer const address = this.#keyToAddress(publicKey) this.#isInternal = true diff --git a/src/lib/block.ts b/src/lib/block.ts index d969a65..352e3e5 100644 --- a/src/lib/block.ts +++ b/src/lib/block.ts @@ -6,8 +6,8 @@ import { Account } from './account' import { Blake2b } from './blake2b' import { BURN_ADDRESS, PREAMBLE, DIFFICULTY_RECEIVE, DIFFICULTY_SEND } from './constants' import { dec, hex } from './convert' +import { NanoNaCl } from './nano-nacl' import { Rpc } from './rpc' -import { NanoNaClWorker } from '#workers' /** * Represents a block as defined by the Nano cryptocurrency protocol. The Block @@ -188,13 +188,7 @@ abstract class Block { throw new Error('Provide a key for block signature verification.') } try { - const { isVerified } = await NanoNaClWorker.request({ - method: 'verify', - msg: hex.toBuffer(this.hash), - signature: hex.toBuffer(this.signature ?? ''), - publicKey: hex.toBuffer(key) - }) - return isVerified + return await NanoNaCl.verify(hex.toBytes(this.hash), hex.toBytes(this.signature ?? ''), hex.toBytes(key)) } catch (err) { throw new Error(`Failed to derive public key from private key`, { cause: err }) } diff --git a/src/lib/nano-nacl.ts b/src/lib/nano-nacl.ts new file mode 100644 index 0000000..7bd20e7 --- /dev/null +++ b/src/lib/nano-nacl.ts @@ -0,0 +1,638 @@ +//! SPDX-FileCopyrightText: 2025 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +'use strict' + +import { Blake2b } from './blake2b' + +/** +* Ported in 2014 by Dmitry Chestnykh and Devi Mandiri. +* Public domain. +* +* Implementation derived from TweetNaCl version 20140427. +* See for details: http://tweetnacl.cr.yp.to/ +* +* Modified in 2024 by Chris Duncan to hash secret key to public key using +* BLAKE2b instead of SHA-512 as specified in the documentation for Nano +* cryptocurrency. +* See for details: https://docs.nano.org/integration-guides/the-basics/ +* Original source commit: https://github.com/dchest/tweetnacl-js/blob/71df1d6a1d78236ca3e9f6c788786e21f5a651a6/nacl-fast.js +*/ +export class NanoNaCl { + static crypto_sign_BYTES: 64 = 64 + static crypto_sign_PUBLICKEYBYTES: 32 = 32 + static crypto_sign_PRIVATEKEYBYTES: 32 = 32 + static crypto_sign_SEEDBYTES: 32 = 32 + + static gf = function (init?: number[]): Float64Array { + const r = new Float64Array(16) + if (init) for (let i = 0; i < init.length; i++) { + r[i] = init[i] + } + return r + } + + static gf0: Float64Array = this.gf() + static gf1: Float64Array = this.gf([1]) + static D: Float64Array = this.gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]) + static D2: Float64Array = this.gf([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]) + static X: Float64Array = this.gf([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]) + static Y: Float64Array = this.gf([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]) + static I: Float64Array = this.gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]) + + static vn (x: Uint8Array, xi: number, y: Uint8Array, yi: number, n: number): number { + let d = 0 + for (let i = 0; i < n; i++) { + d |= x[xi + i] ^ y[yi + i] + } + return (1 & ((d - 1) >>> 8)) - 1 + } + + static crypto_verify_32 (x: Uint8Array, xi: number, y: Uint8Array, yi: number): number { + return this.vn(x, xi, y, yi, 32) + } + + static set25519 (r: Float64Array, a: Float64Array): void { + for (let i = 0; i < 16; i++) { + r[i] = a[i] | 0 + } + } + + static car25519 (o: Float64Array): void { + let v, c = 1 + for (let i = 0; i < 16; i++) { + v = o[i] + c + 65535 + c = Math.floor(v / 65536) + o[i] = v - c * 65536 + } + o[0] += 38 * (c - 1) + } + + static sel25519 (p: Float64Array, q: Float64Array, b: number): void { + let t + const c = ~(b - 1) + for (let i = 0; i < 16; i++) { + t = c & (p[i] ^ q[i]) + p[i] ^= t + q[i] ^= t + } + } + + static pack25519 (o: Uint8Array, n: Float64Array): void { + let b: number + const m: Float64Array = this.gf() + const t: Float64Array = this.gf() + for (let i = 0; i < 16; i++) { + t[i] = n[i] + } + this.car25519(t) + this.car25519(t) + this.car25519(t) + for (let j = 0; j < 2; j++) { + m[0] = t[0] - 0xffed + for (let i = 1; i < 15; i++) { + m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1) + m[i - 1] &= 0xffff + } + m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1) + b = (m[15] >> 16) & 1 + m[14] &= 0xffff + this.sel25519(t, m, 1 - b) + } + for (let i = 0; i < 16; i++) { + o[2 * i] = t[i] & 0xff + o[2 * i + 1] = t[i] >> 8 + } + } + + static neq25519 (a: Float64Array, b: Float64Array): number { + const c = new Uint8Array(32) + const d = new Uint8Array(32) + this.pack25519(c, a) + this.pack25519(d, b) + return this.crypto_verify_32(c, 0, d, 0) + } + + static par25519 (a: Float64Array): number { + const d = new Uint8Array(32) + this.pack25519(d, a) + return d[0] & 1 + } + + static unpack25519 (o: Float64Array, n: Uint8Array): void { + for (let i = 0; i < 16; i++) { + o[i] = n[2 * i] + (n[2 * i + 1] << 8) + } + o[15] &= (1 << 15) - 1 + } + + static A (o: Float64Array, a: Float64Array, b: Float64Array): void { + for (let i = 0; i < 16; i++) { + o[i] = a[i] + b[i] + } + } + + static Z (o: Float64Array, a: Float64Array, b: Float64Array): void { + for (let i = 0; i < 16; i++) { + o[i] = a[i] - b[i] + } + } + + static M (o: Float64Array, a: Float64Array, b: Float64Array): void { + let v, c, s = 1 << 16, t = new Array(31) + t.fill(0) + + // init t values + for (let i = 0; i < 16; i++) { + for (let j = 0; j < 16; j++) { + t[i + j] += a[i] * b[j] + } + } + + for (let i = 0; i < 15; i++) { + t[i] += 38 * t[i + 16] + } + // t15 left as is + + // first carry + c = 1 + for (let i = 0; i < 16; i++) { + v = t[i] + c + s - 1 + c = Math.floor(v / s) + t[i] = v - c * s + } + t[0] += 38 * (c - 1) + + // second carry + c = 1 + for (let i = 0; i < 16; i++) { + v = t[i] + c + s - 1 + c = Math.floor(v / s) + t[i] = v - c * s + } + t[0] += 38 * (c - 1) + + // assign result to output + for (let i = 0; i < 16; i++) { + o[i] = t[i] + } + } + + static S (o: Float64Array, a: Float64Array): void { + this.M(o, a, a) + } + + static inv25519 (o: Float64Array, i: Float64Array): void { + const c: Float64Array = new Float64Array(16) + for (let a = 0; a < 16; a++) { + c[a] = i[a] + } + for (let a = 253; a >= 0; a--) { + this.S(c, c) + if (a !== 2 && a !== 4) this.M(c, c, i) + } + for (let a = 0; a < 16; a++) { + o[a] = c[a] + } + } + + static pow2523 (o: Float64Array, i: Float64Array): void { + const c: Float64Array = this.gf() + for (let a = 0; a < 16; a++) { + c[a] = i[a] + } + for (let a = 250; a >= 0; a--) { + this.S(c, c) + if (a !== 1) this.M(c, c, i) + } + for (let a = 0; a < 16; a++) { + o[a] = c[a] + } + } + + // Note: difference from TweetNaCl - BLAKE2b used to hash instead of SHA-512. + static crypto_hash (out: Uint8Array, m: Uint8Array, n: number): number { + const input = new Uint8Array(n) + for (let i = 0; i < n; ++i) { + input[i] = m[i] + } + const hash = new Blake2b(64).update(m).digest() + for (let i = 0; i < 64; ++i) { + out[i] = hash[i] + } + return 0 + } + + static add (p: Float64Array[], q: Float64Array[]): void { + const a: Float64Array = this.gf() + const b: Float64Array = this.gf() + const c: Float64Array = this.gf() + const d: Float64Array = this.gf() + const e: Float64Array = this.gf() + const f: Float64Array = this.gf() + const g: Float64Array = this.gf() + const h: Float64Array = this.gf() + const t: Float64Array = this.gf() + + this.Z(a, p[1], p[0]) + this.Z(t, q[1], q[0]) + this.M(a, a, t) + this.A(b, p[0], p[1]) + this.A(t, q[0], q[1]) + this.M(b, b, t) + this.M(c, p[3], q[3]) + this.M(c, c, this.D2) + this.M(d, p[2], q[2]) + this.A(d, d, d) + this.Z(e, b, a) + this.Z(f, d, c) + this.A(g, d, c) + this.A(h, b, a) + + this.M(p[0], e, f) + this.M(p[1], h, g) + this.M(p[2], g, f) + this.M(p[3], e, h) + } + + static cswap (p: Float64Array[], q: Float64Array[], b: number): void { + for (let i = 0; i < 4; i++) { + this.sel25519(p[i], q[i], b) + } + } + + static pack (r: Uint8Array, p: Float64Array[]): void { + const tx: Float64Array = this.gf() + const ty: Float64Array = this.gf() + const zi: Float64Array = this.gf() + this.inv25519(zi, p[2]) + this.M(tx, p[0], zi) + this.M(ty, p[1], zi) + this.pack25519(r, ty) + r[31] ^= this.par25519(tx) << 7 + } + + static scalarmult (p: Float64Array[], q: Float64Array[], s: Uint8Array): void { + this.set25519(p[0], this.gf0) + this.set25519(p[1], this.gf1) + this.set25519(p[2], this.gf1) + this.set25519(p[3], this.gf0) + for (let i = 255; i >= 0; --i) { + const b = (s[(i / 8) | 0] >> (i & 7)) & 1 + this.cswap(p, q, b) + this.add(q, p) + this.add(p, p) + this.cswap(p, q, b) + } + } + + static scalarbase (p: Float64Array[], s: Uint8Array): void { + const q: Float64Array[] = [this.gf(), this.gf(), this.gf(), this.gf()] + this.set25519(q[0], this.X) + this.set25519(q[1], this.Y) + this.set25519(q[2], this.gf1) + this.M(q[3], this.X, this.Y) + this.scalarmult(p, q, s) + } + + static L = new Float64Array([ + 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, + 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 + ]) + + static modL (r: Uint8Array, x: Float64Array): void { + let carry, i, j, k + for (i = 63; i >= 32; --i) { + carry = 0 + for (j = i - 32, k = i - 12; j < k; ++j) { + x[j] += carry - 16 * x[i] * this.L[j - (i - 32)] + carry = Math.floor((x[j] + 128) / 256) + x[j] -= carry * 256 + } + x[j] += carry + x[i] = 0 + } + carry = 0 + for (j = 0; j < 32; j++) { + x[j] += carry - (x[31] >> 4) * this.L[j] + carry = x[j] >> 8 + x[j] &= 255 + } + for (j = 0; j < 32; j++) { + x[j] -= carry * this.L[j] + } + for (i = 0; i < 32; i++) { + x[i + 1] += x[i] >> 8 + r[i] = x[i] & 255 + } + } + + static reduce (r: Uint8Array): void { + let x = new Float64Array(64) + for (let i = 0; i < 64; i++) { + x[i] = r[i] + } + for (let i = 0; i < 64; i++) { + r[i] = 0 + } + this.modL(r, x) + } + + // Note: difference from C - smlen returned, not passed as argument. + static crypto_sign (sm: Uint8Array, m: Uint8Array, n: number, sk: Uint8Array, pk: Uint8Array): number { + const d = new Uint8Array(64) + const h = new Uint8Array(64) + const r = new Uint8Array(64) + const x = new Float64Array(64) + const p: Float64Array[] = [this.gf(), this.gf(), this.gf(), this.gf()] + + this.crypto_hash(d, sk, 32) + d[0] &= 248 + d[31] &= 127 + d[31] |= 64 + + const smlen = n + 64 + for (let i = 0; i < n; i++) { + sm[64 + i] = m[i] + } + for (let i = 0; i < 32; i++) { + sm[32 + i] = d[32 + i] + } + + this.crypto_hash(r, sm.subarray(32), n + 32) + this.reduce(r) + this.scalarbase(p, r) + this.pack(sm, p) + + for (let i = 0; i < 32; i++) { + sm[i + 32] = pk[i] + } + this.crypto_hash(h, sm, n + 64) + this.reduce(h) + + for (let i = 0; i < 64; i++) { + x[i] = 0 + } + for (let i = 0; i < 32; i++) { + x[i] = r[i] + } + for (let i = 0; i < 32; i++) { + for (let j = 0; j < 32; j++) { + x[i + j] += h[i] * d[j] + } + } + + this.modL(sm.subarray(32), x) + return smlen + } + + static unpackneg (r: Float64Array[], p: Uint8Array): -1 | 0 { + const t: Float64Array = this.gf() + const chk: Float64Array = this.gf() + const num: Float64Array = this.gf() + const den: Float64Array = this.gf() + const den2: Float64Array = this.gf() + const den4: Float64Array = this.gf() + const den6: Float64Array = this.gf() + + this.set25519(r[2], this.gf1) + this.unpack25519(r[1], p) + this.S(num, r[1]) + this.M(den, num, this.D) + this.Z(num, num, r[2]) + this.A(den, r[2], den) + + this.S(den2, den) + this.S(den4, den2) + this.M(den6, den4, den2) + this.M(t, den6, num) + this.M(t, t, den) + + this.pow2523(t, t) + this.M(t, t, num) + this.M(t, t, den) + this.M(t, t, den) + this.M(r[0], t, den) + + this.S(chk, r[0]) + this.M(chk, chk, den) + if (this.neq25519(chk, num)) this.M(r[0], r[0], this.I) + + this.S(chk, r[0]) + this.M(chk, chk, den) + + if (this.neq25519(chk, num)) return -1 + + if (this.par25519(r[0]) === (p[31] >> 7)) this.Z(r[0], this.gf0, r[0]) + this.M(r[3], r[0], r[1]) + return 0 + } + + static crypto_sign_open (m: Uint8Array, sm: Uint8Array, n: number, pk: Uint8Array): number { + const t = new Uint8Array(32) + const h = new Uint8Array(64) + const p: Float64Array[] = [this.gf(), this.gf(), this.gf(), this.gf()] + const q: Float64Array[] = [this.gf(), this.gf(), this.gf(), this.gf()] + + if (n < 64) return -1 + + if (this.unpackneg(q, pk)) return -1 + + for (let i = 0; i < n; i++) { + m[i] = sm[i] + } + for (let i = 0; i < 32; i++) { + m[i + 32] = pk[i] + } + this.crypto_hash(h, m, n) + this.reduce(h) + this.scalarmult(p, q, h) + + this.scalarbase(q, sm.subarray(32)) + this.add(p, q) + this.pack(t, p) + + n -= 64 + if (this.crypto_verify_32(sm, 0, t, 0)) { + for (let i = 0; i < n; i++) { + m[i] = 0 + } + return -1 + } + + for (let i = 0; i < n; i++) { + m[i] = sm[i + 64] + } + return n + } + + /** + * Type-checks arbitrary number of arguments to verify whether they are all + * Uint8Array objects. + * + * @param {object} args - Key-value pairs of arguments to be checked + * @throws {TypeError} If any argument is not a Uint8Array + */ + static #checkArrayTypes (args: { [i: string]: unknown }): asserts args is { [i: string]: Uint8Array } { + for (const arg of Object.keys(args)) { + if (typeof arg !== 'object') { + throw new TypeError(`Invalid input, expected Uint8Array, actual ${typeof arg}`) + } + const obj = arg as { [key: string]: unknown } + if (!(obj instanceof Uint8Array)) { + throw new TypeError(`Invalid input, expected Uint8Array, actual ${obj.constructor?.name ?? typeof arg}`) + } + } + } + + /** + * Derives a public key from a private key "seed". + * + * @param {Uint8Array} seed - 32-byte private key + * @returns {Uint8Array} 32-byte public key + */ + static convert (seed: Uint8Array): Uint8Array + static convert (seed: unknown): Uint8Array { + try { + const args: { [i: string]: unknown } = { s: seed } + this.#checkArrayTypes(args) + const { s } = args + if (s.length !== this.crypto_sign_SEEDBYTES) { + throw new Error('Invalid seed size to convert to public key') + } + const pk = new Uint8Array(this.crypto_sign_PUBLICKEYBYTES) + const p: Float64Array[] = [this.gf(), this.gf(), this.gf(), this.gf()] + + const hash = new Uint8Array(64) + this.crypto_hash(hash, s, 64) + hash[0] &= 248 + hash[31] &= 127 + hash[31] |= 64 + + this.scalarbase(p, hash) + this.pack(pk, p) + + return pk + } catch (err) { + throw new Error('Failed to convert seed to public key', { cause: err }) + } + } + + /** + * Signs the message using the secret key and returns a signature. + * + * @param {Uint8Array} message - Message to sign + * @param {Uint8Array} privateKey - 32-byte key to use for signing + * @returns {Uint8Array} 64-byte signature + */ + static detached (message: Uint8Array, privateKey: Uint8Array): Uint8Array + static detached (message: unknown, privateKey: unknown): Uint8Array { + try { + const args: { [i: string]: unknown } = { msg: message, prv: privateKey } + this.#checkArrayTypes(args) + const { msg, prv } = args + const signed = this.sign(msg, prv) + const sig = new Uint8Array(this.crypto_sign_BYTES) + for (let i = 0; i < sig.length; i++) { + sig[i] = signed[i] + } + return sig + } catch (err) { + throw new Error('Failed to sign and return signature', { cause: err }) + } + } + + /** + * Verifies the signed message and returns the message without signature. + * + * @param {Uint8Array} signedMessage - Signed message + * @param {Uint8Array} publicKey - 32-byte key used to sign message + * @returns {Uint8Array} Message without signature + */ + static open (signedMessage: Uint8Array, publicKey: Uint8Array): Uint8Array + static open (signedMessage: unknown, publicKey: unknown): Uint8Array { + try { + const args: { [i: string]: unknown } = { signed: signedMessage, pub: publicKey } + this.#checkArrayTypes(args) + const { signed, pub } = args + if (pub.byteLength !== this.crypto_sign_PUBLICKEYBYTES) { + throw new Error(`Invalid public key size to open message, expected ${this.crypto_sign_PUBLICKEYBYTES}, actual ${pub.byteLength}`) + } + const tmp = new Uint8Array(signed.length) + const mlen = this.crypto_sign_open(tmp, signed, signed.length, pub) + + if (mlen < 0) { + throw new Error('Signature verification failed') + } + + const m = new Uint8Array(mlen) + for (let i = 0; i < m.length; i++) { + m[i] = tmp[i] + } + return m + } catch (err) { + throw new Error('Failed to open message', { cause: err }) + } + } + + /** + * Signs the message using the secret key and returns a signed message. + * + * @param {Uint8Array} message - Message to be signed + * @param {Uint8Array} privateKey - 32-byte key to use for signing + * @returns {Uint8Array} Signed message + */ + static sign (message: Uint8Array, privateKey: Uint8Array): Uint8Array + static sign (message: unknown, privateKey: unknown): Uint8Array { + try { + const args: { [i: string]: unknown } = { msg: message, prv: privateKey } + this.#checkArrayTypes(args) + const { msg, prv } = args + if (prv.byteLength !== this.crypto_sign_PRIVATEKEYBYTES) { + throw new Error(`Invalid key byte length to sign message, expected ${this.crypto_sign_PRIVATEKEYBYTES}, actual ${prv.byteLength}`) + } + const signed = new Uint8Array(this.crypto_sign_BYTES + msg.length) + const pub = this.convert(prv) + this.crypto_sign(signed, msg, msg.length, prv, pub) + return signed + } catch (err) { + throw new Error('Failed to sign and return message', { cause: err }) + } + } + + /** + * Verifies a detached signature for a message. + * + * @param {Uint8Array} message - Signed message + * @param {Uint8Array} signature - 64-byte signature + * @param {Uint8Array} publicKey - 32-byte key used for signing + * @returns {boolean} - True if `publicKey` was used to sign `msg` and generate `sig`, else false + */ + static verify (message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean + static verify (message: unknown, signature: unknown, publicKey: unknown): boolean { + try { + const args: { [i: string]: unknown } = { msg: message, sig: signature, pub: publicKey } + this.#checkArrayTypes(args) + const { msg, sig, pub } = args + if (sig.length !== this.crypto_sign_BYTES) { + throw new Error('Invalid signature size to verify signature') + } + if (pub.length !== this.crypto_sign_PUBLICKEYBYTES) { + throw new Error('Invalid public key size to verify signature') + } + const sm = new Uint8Array(this.crypto_sign_BYTES + msg.length) + const m = new Uint8Array(this.crypto_sign_BYTES + msg.length) + for (let i = 0; i < this.crypto_sign_BYTES; i++) { + sm[i] = sig[i] + } + for (let i = 0; i < msg.length; i++) { + sm[i + this.crypto_sign_BYTES] = msg[i] + } + return (this.crypto_sign_open(m, sm, sm.length, pub) >= 0) + } catch (err) { + throw new Error('Failed to sign and return signature', { cause: err }) + } + } +} diff --git a/src/lib/safe/index.ts b/src/lib/safe/index.ts index 545a401..de3b2eb 100644 --- a/src/lib/safe/index.ts +++ b/src/lib/safe/index.ts @@ -1,4 +1,4 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -export { NanoNaClWorker, PasskeyWorker, SafeWorker } from './worker-queue' +export { PasskeyWorker, SafeWorker } from './worker-queue' diff --git a/src/lib/safe/worker-queue.ts b/src/lib/safe/worker-queue.ts index 19d427f..899fe40 100644 --- a/src/lib/safe/worker-queue.ts +++ b/src/lib/safe/worker-queue.ts @@ -2,7 +2,6 @@ //! SPDX-License-Identifier: GPL-3.0-or-later import { Worker as NodeWorker } from 'node:worker_threads' -import { default as nacl } from './nano-nacl' import { default as passkey } from './passkey' import { default as safe } from './safe' import { Data, NamedData } from '#types' @@ -113,6 +112,5 @@ export class WorkerQueue { } } -export const NanoNaClWorker = new WorkerQueue(nacl) export const PasskeyWorker = new WorkerQueue(passkey) export const SafeWorker = new WorkerQueue(safe) diff --git a/src/lib/tools.ts b/src/lib/tools.ts index f8596e5..8143107 100644 --- a/src/lib/tools.ts +++ b/src/lib/tools.ts @@ -6,10 +6,10 @@ import { Blake2b } from './blake2b' import { SendBlock } from './block' import { UNITS } from './constants' import { bytes, hex } from './convert' +import { NanoNaCl } from './nano-nacl' import { Rpc } from './rpc' import { Bip44Wallet, Blake2bWallet, LedgerWallet } from './wallets' import { Key } from '#types' -import { NanoNaClWorker } from '#workers' type SweepResult = { status: "success" | "error" @@ -92,12 +92,8 @@ function hash (data: string | string[], encoding?: 'hex', format?: 'hex'): strin export async function sign (key: Key, ...input: string[]): Promise { if (typeof key === 'string') key = hex.toBytes(key) try { - const { signature } = await NanoNaClWorker.request({ - method: 'detached', - privateKey: key.buffer, - msg: hash(input).buffer - }) - return bytes.toHex(new Uint8Array(signature)) + const signature = await NanoNaCl.detached(hash(input), key) + return bytes.toHex(signature) } catch (err) { throw new Error(`Failed to sign message with private key`, { cause: err }) } finally { @@ -177,13 +173,7 @@ export async function sweep ( export async function verify (key: Key, signature: string, ...input: string[]): Promise { if (typeof key === 'string') key = hex.toBytes(key) try { - const { isVerified } = await NanoNaClWorker.request({ - method: 'verify', - msg: hash(input).buffer, - signature: hex.toBuffer(signature), - publicKey: key.buffer.slice() - }) - return isVerified + return await NanoNaCl.verify(hash(input), hex.toBytes(signature), key) } catch (err) { throw new Error('Failed to verify signature', { cause: err }) } finally { diff --git a/src/lib/wallets/bip44-wallet.ts b/src/lib/wallets/bip44-wallet.ts index 87ad894..3849232 100644 --- a/src/lib/wallets/bip44-wallet.ts +++ b/src/lib/wallets/bip44-wallet.ts @@ -4,10 +4,9 @@ import { Wallet } from '.' import { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js' import { SEED_LENGTH_BIP44 } from '#src/lib/constants.js' -import { bytes, hex, utf8 } from '#src/lib/convert.js' +import { hex } from '#src/lib/convert.js' import { Entropy } from '#src/lib/entropy.js' import { Key, KeyPair } from '#types' -import { Bip44CkdWorker } from '#workers' /** * Hierarchical deterministic (HD) wallet created by using a source of entropy to diff --git a/src/types.d.ts b/src/types.d.ts index e02eb80..5246dbe 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1054,7 +1054,6 @@ export declare class WorkerQueue { terminate (): void } export declare const Bip44CkdWorker: WorkerQueue -export declare const NanoNaClWorker: WorkerQueue export declare const SafeWorker: WorkerQueue export type UnknownNumber = number | unknown -- 2.47.3