]> git.codecow.com Git - libnemo.git/commitdiff
Deprecate NanoNaCl worker and just use it in eventual Safe worker.
authorChris Duncan <chris@zoso.dev>
Mon, 28 Jul 2025 21:49:17 +0000 (14:49 -0700)
committerChris Duncan <chris@zoso.dev>
Mon, 28 Jul 2025 21:49:17 +0000 (14:49 -0700)
src/lib/account.ts
src/lib/block.ts
src/lib/nano-nacl.ts [new file with mode: 0644]
src/lib/safe/index.ts
src/lib/safe/worker-queue.ts
src/lib/tools.ts
src/lib/wallets/bip44-wallet.ts
src/types.d.ts

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