From 8e10f13c2e97c5905739c55f65b770a307648e25 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Sun, 22 Mar 2026 23:42:52 -0700 Subject: [PATCH] Replace internal derive-sign-verify NanoNaCl with library nano25519. --- package.json | 3 +- src/lib/account/index.ts | 4 +- src/lib/block.ts | 9 +- src/lib/crypto/index.ts | 3 +- src/lib/crypto/nano-nacl.ts | 609 ---------------------------------- src/lib/tools.ts | 7 +- src/lib/vault/index.ts | 8 +- src/lib/vault/vault-worker.ts | 7 +- 8 files changed, 23 insertions(+), 627 deletions(-) delete mode 100644 src/lib/crypto/nano-nacl.ts diff --git a/package.json b/package.json index b182e47..2448643 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,8 @@ "@ledgerhq/hw-transport-web-ble": "^6.33.0", "@ledgerhq/hw-transport-webhid": "^6.33.0", "@ledgerhq/hw-transport-webusb": "^6.32.0", - "nano-pow": "^5.1.10" + "nano-pow": "^5.1.10", + "nano25519": "^1.0.1" }, "devDependencies": { "@types/node": "^25.5.0", diff --git a/src/lib/account/index.ts b/src/lib/account/index.ts index 7c780a9..aba05b8 100644 --- a/src/lib/account/index.ts +++ b/src/lib/account/index.ts @@ -1,10 +1,10 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later +import * as nano25519 from 'nano25519' import { Block } from '../block' import { ACCOUNT_KEY_BYTE_LENGTH, ACCOUNT_KEY_HEX_LENGTH } from '../constants' import { bytes, hex } from '../convert' -import { NanoNaCl } from '../crypto' import { Rpc } from '../rpc' import { Address } from './address' import { _refresh } from './refresh' @@ -302,7 +302,7 @@ export class Account { if (privateKey.byteLength !== ACCOUNT_KEY_BYTE_LENGTH) { throw new TypeError(`Private key must be ${ACCOUNT_KEY_BYTE_LENGTH} bytes`) } - const publicKey = await NanoNaCl.convert(privateKey) + const publicKey = await nano25519.derive(privateKey) const address = new Address(publicKey) this.#isInternal = true const account = new this(address, publicKey, index) diff --git a/src/lib/block.ts b/src/lib/block.ts index e5cd617..790d2ea 100644 --- a/src/lib/block.ts +++ b/src/lib/block.ts @@ -2,10 +2,11 @@ //! SPDX-License-Identifier: GPL-3.0-or-later import { NanoPow } from 'nano-pow' +import * as nano25519 from 'nano25519' import { Account } from './account' -import { BURN_PUBLIC_KEY, PREAMBLE, DIFFICULTY_RECEIVE, DIFFICULTY_SEND, UNITS } from './constants' +import { BURN_PUBLIC_KEY, DIFFICULTY_RECEIVE, DIFFICULTY_SEND, PREAMBLE, UNITS } from './constants' import { bytes, dec, hex } from './convert' -import { Blake2b, NanoNaCl } from './crypto' +import { Blake2b } from './crypto' import { Rpc } from './rpc' import { Tools } from './tools' import { Wallet } from './wallet' @@ -411,7 +412,7 @@ export class Block { return new Promise(async (resolve, reject) => { try { if (typeof input === 'string' && /^[A-F0-9]{64}$/i.test(input)) { - const signature = await NanoNaCl.detached(hex.toBytes(this.hash), hex.toBytes(input)) + const signature = await nano25519.sign(hex.toBytes(this.hash), hex.toBytes(input)) this.signature = bytes.toHex(signature) } else if (input instanceof Wallet && typeof index === 'number' && (frontier === undefined || frontier instanceof (this.constructor as typeof Block)) @@ -443,7 +444,7 @@ export class Block { if (typeof key !== 'string') { throw new Error('Invalid key') } - return await NanoNaCl.verify(hex.toBytes(this.hash), hex.toBytes(this.signature ?? ''), hex.toBytes(key)) + return await nano25519.verify(hex.toBytes(this.hash), hex.toBytes(this.signature ?? ''), hex.toBytes(key)) } catch (err) { throw new Error('Failed to verify block signature', { cause: err }) } diff --git a/src/lib/crypto/index.ts b/src/lib/crypto/index.ts index c0f3d8c..dee8ec6 100644 --- a/src/lib/crypto/index.ts +++ b/src/lib/crypto/index.ts @@ -4,8 +4,7 @@ import { Bip39 } from './bip39' import { Bip44 } from './bip44' import { Blake2b } from './blake2b' -import { NanoNaCl } from './nano-nacl' import { Secp256k1 } from './secp256k1' import { WalletAesGcm } from './wallet-aes-gcm' -export { Bip39, Bip44, Blake2b, NanoNaCl, Secp256k1, WalletAesGcm } +export { Bip39, Bip44, Blake2b, Secp256k1, WalletAesGcm } diff --git a/src/lib/crypto/nano-nacl.ts b/src/lib/crypto/nano-nacl.ts deleted file mode 100644 index 8ce4bd3..0000000 --- a/src/lib/crypto/nano-nacl.ts +++ /dev/null @@ -1,609 +0,0 @@ -//! SPDX-FileCopyrightText: 2025 Chris Duncan -//! SPDX-License-Identifier: GPL-3.0-or-later - -import { Blake2b } from '.' - -/** -* Ported in 2014 by Dmitry Chestnykh and Devi Mandiri. -* Public domain. -* -* Implementation derived from TweetNaCl version 20140427: http://tweetnacl.cr.yp.to/ -* -* Modified to hash secret key to public key using BLAKE2b instead of SHA-512 per -* Nano cryptocurrency specifications: 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 D: Float64Array = new Float64Array([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]) - static D2: Float64Array = new Float64Array([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]) - static X: Float64Array = new Float64Array([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]) - static Y: Float64Array = new Float64Array([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]) - static I: Float64Array = new Float64Array([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]) - static XY: Float64Array = new Float64Array([0xdd90, 0xa5b7, 0x8ab3, 0x6dde, 0x52f5, 0x7751, 0x9f80, 0x20f0, 0xe37d, 0x64ab, 0x4e8e, 0x66ea, 0x7665, 0xd78b, 0x5f0f, 0xe787]) - - /** - * Checks first 32 bytes of two byte arrays to see if they are equivalent. - * - * @returns 0 if first 32 bytes are equal, else 1 if there is at least one bit difference - */ - static vn (x: Uint8Array, y: Uint8Array): number { - let d: number = 0 - for (let i = 0; i < 32; i++) { - d |= x[i] ^ y[i] - } - return ((1 & ((d - 1) >>> 8)) - 1) & 1 - } - - static pow2523 (out: Float64Array, i: Float64Array): void { - const c: Float64Array = new Float64Array(16) - c.set(i.subarray(0, 16), 0) - for (let a = 0; a < 249; a++) { - this.Square(c, c) - this.Multiply(c, c, i) - } - this.Square(c, c) - this.Square(c, c) - this.Multiply(c, c, i) - out.set(c, 0) - } - - static car25519 (out: Float64Array): void { - let v, c - const s = 1 << 16 - c = 1 - for (let i = 0; i < 16; i++) { - v = out[i] + c + s - 1 - out[i] = v % s - c = (v / s) | 0 - } - out[0] += 38 * (c - 1) - } - - static inv25519 (out: Float64Array, i: Float64Array): void { - const c: Float64Array = new Float64Array(16) - c.set(i.subarray(0, 16), 0) - for (let a = 0; a < 249; a++) { - this.Square(c, c) - this.Multiply(c, c, i) - } - this.Square(c, c) - this.Square(c, c) - this.Multiply(c, c, i) - this.Square(c, c) - this.Square(c, c) - this.Multiply(c, c, i) - this.Square(c, c) - this.Multiply(c, c, i) - out.set(c, 0) - } - - static neq25519 (a: Float64Array, b: Float64Array): number { - const c: Uint8Array = new Uint8Array(32) - const d: Uint8Array = new Uint8Array(32) - this.pack25519(c, a) - this.pack25519(d, b) - return this.vn(c, d) - } - - static pack25519 (out: Uint8Array, n: Float64Array): void { - let b: number - const m: Float64Array = new Float64Array(16) - const t: Float64Array = new Float64Array(16) - t.set(n, 0) - 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++) { - out[2 * i] = t[i] & 0xff - out[2 * i + 1] = t[i] >> 8 - } - } - - static par25519 (a: Float64Array): 0 | 1 { - const d: Uint8Array = new Uint8Array(32) - this.pack25519(d, a) - return (d[0] & 1) as 0 | 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 unpack25519 (out: Float64Array, n: Uint8Array): void { - for (let i = 0; i < 16; i++) { - out[i] = n[2 * i] + (n[2 * i + 1] << 8) - } - out[15] &= (1 << 15) - 1 - } - - static Add (out: Float64Array, a: Float64Array, b: Float64Array): void { - for (let i = 0; i < 16; i++) { - out[i] = a[i] + b[i] - } - } - - static Subtract (out: Float64Array, a: Float64Array, b: Float64Array): void { - for (let i = 0; i < 16; i++) { - out[i] = a[i] - b[i] - } - } - - static Multiply (out: Float64Array, a: Float64Array, b: Float64Array): void { - let v, c - const s = 1 << 16 - const t = new Float64Array(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 = 0 - for (let i = 0; i < 16; i++) { - v = t[i] + c - t[i] = v % s - c = (v / s) | 0 - } - t[0] += 38 * c - - // second carry - c = 0 - for (let i = 0; i < 16; i++) { - v = t[i] + c - t[i] = v % s - c = (v / s) | 0 - } - t[0] += 38 * c - - // assign result to output - out.set(t.slice(0, 16), 0) - } - - static Square (out: Float64Array, a: Float64Array): void { - let v, c - const s = 1 << 16 - const t = new Float64Array(31) - t.fill(0) - - // init t values, same as Multiply except we can skip some iterations of - // the inner loop since a[x]*a[y] + a[y]*a[x] = 2*a[x]*a[y] - for (let i = 0; i < 16; i++) { - for (let j = i; j < 16; j++) { - //@ts-expect-error - t[i + j] += a[i] * a[j] * ((i < j) + 1) - } - } - - for (let i = 0; i < 15; i++) { - t[i] += 38 * t[i + 16] - } - // t15 left as is - - // first carry - c = 0 - for (let i = 0; i < 16; i++) { - v = t[i] + c - t[i] = v % s - c = (v / s) | 0 - } - t[0] += 38 * c - - // second carry - c = 0 - for (let i = 0; i < 16; i++) { - v = t[i] + c - t[i] = v % s - c = (v / s) | 0 - } - t[0] += 38 * c - - // assign result to output - out.set(t.slice(0, 16), 0) - } - - static add (p: Float64Array[], q: Float64Array[]): void { - const a: Float64Array = new Float64Array(16) - const b: Float64Array = new Float64Array(16) - const c: Float64Array = new Float64Array(16) - const d: Float64Array = new Float64Array(16) - const e: Float64Array = new Float64Array(16) - const f: Float64Array = new Float64Array(16) - const g: Float64Array = new Float64Array(16) - const h: Float64Array = new Float64Array(16) - const t: Float64Array = new Float64Array(16) - - this.Subtract(a, p[1], p[0]) - this.Subtract(t, q[1], q[0]) - this.Multiply(a, a, t) - this.Add(b, p[0], p[1]) - this.Add(t, q[0], q[1]) - this.Multiply(b, b, t) - this.Multiply(c, p[3], q[3]) - this.Multiply(c, c, this.D2) - this.Multiply(d, p[2], q[2]) - this.Add(d, d, d) - this.Subtract(e, b, a) - this.Subtract(f, d, c) - this.Add(g, d, c) - this.Add(h, b, a) - - this.Multiply(p[0], e, f) - this.Multiply(p[1], h, g) - this.Multiply(p[2], g, f) - this.Multiply(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 = new Float64Array(16) - const ty: Float64Array = new Float64Array(16) - const zi: Float64Array = new Float64Array(16) - this.inv25519(zi, p[2]) - this.Multiply(tx, p[0], zi) - this.Multiply(ty, p[1], zi) - this.pack25519(r, ty) - r[31] ^= this.par25519(tx) << 7 - } - - static scalarmult (p: Float64Array[], q: Float64Array[], s: Uint8Array): void { - p[0].fill(0) - p[1].fill(0).set([1], 0) - p[2].fill(0).set([1], 0) - p[3].fill(0) - 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[] = [new Float64Array(16), new Float64Array(16), new Float64Array(16), new Float64Array(16)] - q[0].set(this.X, 0) - q[1].set(this.Y, 0) - q[2].set([1], 0) - q[3].set(this.XY, 0) - 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 c, t, v - for (let i = 63; i >= 32; --i) { - c = 0 - for (let j = i - 32, k = i - 12; j < k; j++) { - t = 16 * x[i] * this.L[j - (i - 32)] - v = x[j] + c - t - c = ((v + 128) / 256) | 0 - x[j] = v - (c * 256) - } - x[i - 12] += c - x[i] = 0 - } - c = 0 - for (let j = 0; j < 32; j++) { - x[j] += c - (x[31] >> 4) * this.L[j] - c = x[j] >> 8 - x[j] &= 255 - } - for (let j = 0; j < 32; j++) { - x[j] -= c * this.L[j] - } - for (let 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) - x.set(r, 0) - r.fill(0) - this.modL(r, x) - } - - static unpackneg (r: Float64Array[], p: Uint8Array): number { - const t: Float64Array = new Float64Array(16) - const chk: Float64Array = new Float64Array(16) - const num: Float64Array = new Float64Array(16) - const den: Float64Array = new Float64Array(16) - const den2: Float64Array = new Float64Array(16) - const den4: Float64Array = new Float64Array(16) - const den6: Float64Array = new Float64Array(16) - - r[2].fill(0).set([1], 0) - this.unpack25519(r[1], p) - this.Square(num, r[1]) - this.Multiply(den, num, this.D) - this.Subtract(num, num, r[2]) - this.Add(den, r[2], den) - - this.Square(den2, den) - this.Square(den4, den2) - this.Multiply(den6, den4, den2) - this.Multiply(t, den6, num) - this.Multiply(t, t, den) - - this.pow2523(t, t) - this.Multiply(t, t, num) - this.Multiply(t, t, den) - this.Multiply(t, t, den) - this.Multiply(r[0], t, den) - - this.Square(chk, r[0]) - this.Multiply(chk, chk, den) - - // if neq is true, multiply r[0] by I, else multiply by 1 for a no-op - let neq = this.neq25519(chk, num) - const I = new Float64Array(this.I) - for (let i = 0; i < 16; i++) { - I[i] *= neq - } - I[0] += neq ^ 1 - this.Multiply(r[0], r[0], I) - - this.Square(chk, r[0]) - this.Multiply(chk, chk, den) - - neq = 0 - this.neq25519(chk, num) - - // if par25519, subtract r[0] from 0 to swap sign, else keep current values - //@ts-expect-error - const par = (this.par25519(r[0]) === p[31] >> 7) ^ 1 - for (let i = 0; i < 16; i++) { - r[0][i] = (2 * par * r[0][i]) - r[0][i] - } - this.Multiply(r[3], r[0], r[1]) - return neq - } - - static crypto_sign (sm: Uint8Array, m: Uint8Array, n: number, sk: Uint8Array, pk: Uint8Array): void { - const p: Float64Array[] = [new Float64Array(16), new Float64Array(16), new Float64Array(16), new Float64Array(16)] - - const d = new Blake2b(64).update(sk).digest() - d[0] &= 248 - d[31] &= 127 - d[31] |= 64 - - sm.set(m.subarray(0, n), 64) - sm.set(d.subarray(32, 64), 32) - - const r = new Blake2b(64).update(sm.subarray(32)).digest() - this.reduce(r) - this.scalarbase(p, r) - this.pack(sm, p) - - sm.set(pk, 32) - const h = new Blake2b(64).update(sm).digest() - this.reduce(h) - - const x = new Float64Array(64) - x.set(r.subarray(0, 32)) - 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) - } - - static crypto_sign_open (m: Uint8Array, sm: Uint8Array, n: number, pk: Uint8Array): number { - const t = new Uint8Array(32) - const p: Float64Array[] = [new Float64Array(16), new Float64Array(16), new Float64Array(16), new Float64Array(16)] - const q: Float64Array[] = [new Float64Array(16), new Float64Array(16), new Float64Array(16), new Float64Array(16)] - - // eventually used in returned result but allow processing to continue - const neg = this.unpackneg(q, pk) & 1 - - m.set(sm.subarray(0, n), 0) - m.set(pk.subarray(0, 32), 32) - const h = new Blake2b(64).update(m).digest() - 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 any bits unequal, zero out and return -1 - const vn = this.vn(sm, t) ^ 1 - for (let i = 0; i < n; i++) { - m[i] = sm[i + 64] * vn - } - n = (n * vn) - (vn ^ 1) - return (n * (neg ^ 1)) - neg - } - - /** - * 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 { - if (!(seed instanceof Uint8Array)) { - throw new TypeError('Seed must be Uint8Array') - } - if (seed.byteLength !== this.crypto_sign_SEEDBYTES) { - throw new Error(`Seed must be ${this.crypto_sign_SEEDBYTES} bytes`) - } - const s = new Uint8Array(seed) - seed = undefined - const pk = new Uint8Array(this.crypto_sign_PUBLICKEYBYTES) - const p: Float64Array[] = [new Float64Array(16), new Float64Array(16), new Float64Array(16), new Float64Array(16)] - - const hash = new Blake2b(64).update(s).digest() - 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 sm = this.sign(message as Uint8Array, privateKey as Uint8Array) - return new Uint8Array(sm.buffer.slice(0, this.crypto_sign_BYTES)) - } 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 { - if (!(signedMessage instanceof Uint8Array) || signedMessage.byteLength < this.crypto_sign_BYTES) { - throw new TypeError('Signed message must be Uint8Array') - } - if (!(publicKey instanceof Uint8Array) || publicKey.byteLength !== this.crypto_sign_PUBLICKEYBYTES) { - throw new Error(`Public key must be ${this.crypto_sign_PUBLICKEYBYTES}-byte Uint8Array`) - } - const sm = new Uint8Array(signedMessage) - const smLen = sm.byteLength - const pub = new Uint8Array(publicKey) - const tmp = new Uint8Array(smLen) - const mLen = this.crypto_sign_open(tmp, sm, smLen, pub) - if (mLen < 0) { - throw new Error('Signature verification failed') - } - const m = new Uint8Array(mLen) - m.set(tmp.subarray(0, mLen), 0) - 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 { - if (!(message instanceof Uint8Array)) { - throw new TypeError('Message must be Uint8Array') - } - if (!(privateKey instanceof Uint8Array) || privateKey.byteLength !== this.crypto_sign_SEEDBYTES) { - throw new Error(`Private key must be ${this.crypto_sign_PRIVATEKEYBYTES}-byte Uint8Array`) - } - const prv = new Uint8Array(privateKey) - privateKey = undefined - const mLen = message.byteLength - const msg = new Uint8Array(message) - const signed = new Uint8Array(this.crypto_sign_BYTES + mLen) - const pub = this.convert(prv) - this.crypto_sign(signed, msg, mLen, 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} signedMessage - 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 (signedMessage: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean - static verify (signedMessage: unknown, signature: unknown, publicKey: unknown): boolean { - try { - if (!(signedMessage instanceof Uint8Array)) { - throw new TypeError('Signed message must be Uint8Array') - } - if (!(signature instanceof Uint8Array) || signature.byteLength !== this.crypto_sign_BYTES) { - throw new Error(`Signature must be ${this.crypto_sign_BYTES}-byte Uint8Array`) - } - if (!(publicKey instanceof Uint8Array) || publicKey.byteLength !== this.crypto_sign_PUBLICKEYBYTES) { - throw new Error(`Public key must be ${this.crypto_sign_PUBLICKEYBYTES}-byte Uint8Array`) - } - const msg = new Uint8Array(signedMessage) - const sig = new Uint8Array(signature) - const pub = new Uint8Array(publicKey) - const smLen = this.crypto_sign_BYTES + msg.byteLength - const sm = new Uint8Array(smLen) - const m = new Uint8Array(smLen) - sm.set(sig, 0) - sm.set(msg, this.crypto_sign_BYTES) - return (this.crypto_sign_open(m, sm, smLen, pub) >= 0) - } catch (err) { - throw new Error('Failed to verify signature on message with the given public key', { cause: err }) - } - } -} diff --git a/src/lib/tools.ts b/src/lib/tools.ts index 59ca5d4..2e1a89d 100644 --- a/src/lib/tools.ts +++ b/src/lib/tools.ts @@ -1,11 +1,12 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later +import * as nano25519 from 'nano25519' import { Account } from './account' import { Block } from './block' import { MAX_SUPPLY, UNITS } from './constants' import { bytes, hex } from './convert' -import { Blake2b, NanoNaCl } from './crypto' +import { Blake2b } from './crypto' import { Rpc } from './rpc' import { Wallet } from './wallet' @@ -140,7 +141,7 @@ export class Tools { static async sign (key: string | Uint8Array, ...input: string[]): Promise { if (typeof key === 'string') key = hex.toBytes(key) try { - const signature = await NanoNaCl.detached(this.hash(input), key) + const signature = await nano25519.sign(this.hash(input), key) return bytes.toHex(signature) } catch (err) { throw new Error(`Failed to sign message with private key`, { cause: err }) @@ -218,7 +219,7 @@ export class Tools { static async verify (key: string | Uint8Array, signature: string, ...input: string[]): Promise { if (typeof key === 'string') key = hex.toBytes(key) try { - return await NanoNaCl.verify(this.hash(input), hex.toBytes(signature), key) + return await nano25519.verify(this.hash(input), hex.toBytes(signature), key) } catch (err) { throw new Error('Failed to verify signature', { cause: err }) } finally { diff --git a/src/lib/vault/index.ts b/src/lib/vault/index.ts index 710deb0..93ae64b 100644 --- a/src/lib/vault/index.ts +++ b/src/lib/vault/index.ts @@ -1,9 +1,10 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later +import * as nano25519 from 'nano25519' import { Worker as NodeWorker } from 'node:worker_threads' import { default as CONSTANTS } from '../constants' -import { Bip39, Bip44, Blake2b, NanoNaCl, Secp256k1, WalletAesGcm } from '../crypto' +import { Bip39, Bip44, Blake2b, Secp256k1, WalletAesGcm } from '../crypto' import { Data } from '../database' import { Passkey } from './passkey' import { VaultTimer } from './vault-timer' @@ -122,13 +123,15 @@ export class Vault { } } +const nano25519Url = URL.createObjectURL(new Blob([`${nano25519}`], { type: 'text/javascript' })) + const blob = ` + const nano25519 = await import(${nano25519Url}) ${CONSTANTS} const ${Secp256k1.name} = ${Secp256k1} const ${Bip39.name} = ${Bip39} const ${Bip44.name} = ${Bip44} const ${Blake2b.name} = ${Blake2b} - const ${NanoNaCl.name} = ${NanoNaCl} const ${WalletAesGcm.name} = ${WalletAesGcm} const ${Passkey.name} = ${Passkey} const ${VaultTimer.name} = ${VaultTimer} @@ -137,7 +140,6 @@ const blob = ` ${Bip39.name === 'Bip39' ? '' : `const Bip39 = ${Bip39.name}`} ${Bip44.name === 'Bip44' ? '' : `const Bip44 = ${Bip44.name}`} ${Blake2b.name === 'Blake2b' ? '' : `const Blake2b = ${Blake2b.name}`} - ${NanoNaCl.name === 'NanoNaCl' ? '' : `const NanoNaCl = ${NanoNaCl.name}`} ${WalletAesGcm.name === 'WalletAesGcm' ? '' : `const WalletAesGcm = ${WalletAesGcm.name}`} ${Passkey.name === 'Passkey' ? '' : `const Passkey = ${Passkey.name}`} ${VaultTimer.name === 'VaultTimer' ? '' : `const VaultTimer = ${VaultTimer.name}`} diff --git a/src/lib/vault/vault-worker.ts b/src/lib/vault/vault-worker.ts index 66436e0..20e0815 100644 --- a/src/lib/vault/vault-worker.ts +++ b/src/lib/vault/vault-worker.ts @@ -1,8 +1,9 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later +import * as nano25519 from 'nano25519' import { BIP44_COIN_NANO } from '../constants' -import { Bip39, Bip44, Blake2b, NanoNaCl, WalletAesGcm } from '../crypto' +import { Bip39, Bip44, Blake2b, WalletAesGcm } from '../crypto' import { WalletType } from '../wallet' import { Passkey } from './passkey' import { VaultTimer } from './vault-timer' @@ -193,7 +194,7 @@ export class VaultWorker { ? Bip44.ckd('Bitcoin seed', this.#seed, 0x100, index, 0, 0) : Blake2b.ckd(this.#seed, index) return derive.then(prv => { - const pub = NanoNaCl.convert(new Uint8Array(prv)) + const pub = nano25519.derive(new Uint8Array(prv)) this.#timer = new VaultTimer(() => this.lock(), this.#timeout) return { index, publicKey: pub.buffer } }) @@ -259,7 +260,7 @@ export class VaultWorker { ? Blake2b.ckd(this.#seed, index) : Bip44.ckd(this.#type === 'Exodus' ? 'Bitcoin seed' : 'ed25519 seed', this.#seed, BIP44_COIN_NANO, index) return derive.then(prv => { - const sig = NanoNaCl.detached(new Uint8Array(data), new Uint8Array(prv)) + const sig = nano25519.sign(new Uint8Array(data), new Uint8Array(prv)) this.#timer = new VaultTimer(() => this.lock(), this.#timeout) return { signature: sig.buffer } }) -- 2.47.3