]> git.codecow.com Git - nano25519.git/commitdiff
Add NodeJS support and update tests.
authorChris Duncan <chris@codecow.com>
Wed, 18 Mar 2026 21:39:16 +0000 (14:39 -0700)
committerChris Duncan <chris@codecow.com>
Wed, 18 Mar 2026 21:39:16 +0000 (14:39 -0700)
Fix node worker construction, message handling, and parent port import.
Expand esbuild config functionality and build options.
Move test vectors to separate file, rename .env vector import, and add tests.

esbuild.mjs [deleted file]
esbuild/config.mjs [new file with mode: 0644]
esbuild/dev.mjs [new file with mode: 0644]
esbuild/prod.mjs [new file with mode: 0644]
index.html
index.ts
package.json
test.mjs
vectors.mjs [new file with mode: 0644]

diff --git a/esbuild.mjs b/esbuild.mjs
deleted file mode 100644 (file)
index 8e8240e..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
-//! SPDX-License-Identifier: GPL-3.0-or-later
-
-import { build } from 'esbuild'
-
-/**
- * @type import("esbuild").BuildOptions
- */
-const common = {
-       bundle: true,
-       loader: {
-               '.wasm': 'binary'
-       },
-       format: 'esm',
-       legalComments: 'inline',
-       outdir: 'dist',
-       target: 'esnext',
-       dropLabels: process.env.NODE_ENV === 'development' ? [] : [ 'LOG' ]
-}
-
-await build({
-       ...common,
-       platform: 'browser',
-       entryPoints: [
-               { in: './index.ts', out: 'browser' }
-       ]
-})
-
-await build({
-       ...common,
-       platform: 'node',
-       entryPoints: [
-               { in: './index.ts', out: 'node' }
-       ],
-       packages: 'external'
-})
diff --git a/esbuild/config.mjs b/esbuild/config.mjs
new file mode 100644 (file)
index 0000000..dabf9d1
--- /dev/null
@@ -0,0 +1,51 @@
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+/**
+ * @type import("esbuild").BuildOptions
+ */
+const commonOptions = {
+       bundle: true,
+       loader: {
+               '.wasm': 'binary'
+       },
+       format: 'esm',
+       legalComments: 'inline',
+       outdir: 'dist',
+}
+
+/**
+ * @type {import('esbuild').BuildOptions}
+ */
+export const prodOptions = {
+       drop: ['console', 'debugger'],
+       minify: true,
+       sourcemap: false
+}
+
+/**
+ * @type import("esbuild").BuildOptions
+ */
+export const browserOptions = {
+       ...commonOptions,
+       platform: 'browser',
+       target: 'esnext',
+       entryPoints: [
+               { in: './index.ts', out: 'browser' }
+       ],
+       dropLabels: ['NODE']
+}
+
+/**
+ * @type import("esbuild").BuildOptions
+ */
+export const nodeOptions = {
+       ...commonOptions,
+       platform: 'node',
+       target: 'node22',
+       entryPoints: [
+               { in: './index.ts', out: 'node' }
+       ],
+       packages: 'external',
+       dropLabels: ['BROWSER']
+}
diff --git a/esbuild/dev.mjs b/esbuild/dev.mjs
new file mode 100644 (file)
index 0000000..e632a0b
--- /dev/null
@@ -0,0 +1,8 @@
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+import { build } from 'esbuild'
+import { browserOptions, nodeOptions } from './config.mjs'
+
+await build(browserOptions)
+await build(nodeOptions)
diff --git a/esbuild/prod.mjs b/esbuild/prod.mjs
new file mode 100644 (file)
index 0000000..26ded3e
--- /dev/null
@@ -0,0 +1,8 @@
+//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+import { build } from 'esbuild'
+import { browserOptions, nodeOptions, prodOptions } from './config.mjs'
+
+await build({ ...browserOptions, ...prodOptions })
+await build({ ...nodeOptions, ...prodOptions })
index 8be9240ffc2c535d042872822e00ff3b7f3623d9..c299e535b946632d844d14afc83d94edf5a2576e 100644 (file)
@@ -15,7 +15,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
        <script type="module">
                await libsodium.ready
                await sodium.ready
-               let NanoNaCl, TEST_VECTORS
+               let NanoNaCl, NANO_ORG_VECTOR, PYTHON_ED25519_BLAKE2B_VECTOR, PRIVATE_VECTOR
                try {
                        NanoNaCl = await import('./dist/browser.js')
                } catch (err) {
@@ -33,7 +33,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
                }
                window.NanoNaCl = NanoNaCl
                try {
-                       ({ TEST_VECTORS } = await import('./env.mjs'))
+                       ({ NANO_ORG_VECTOR, PYTHON_ED25519_BLAKE2B_VECTOR } = await import('./vectors.mjs'))
+               } catch (err) {
+                       console.error(err)
+               }
+               try {
+                       ({ PRIVATE_VECTOR } = await import('./env.mjs'))
                } catch (err) {
                        console.error(err)
                }
@@ -204,93 +209,123 @@ SPDX-License-Identifier: GPL-3.0-or-later
                        // self-check
                        document.getElementById('status').innerHTML = `RUNNING SELF-CHECK`
                        console.log(`%cNanoNaCl`, 'color:green', 'Checking validation against known values')
-                       // https://docs.nano.org/integration-guides/key-management/#success-response_2
-                       TEST_VECTORS ??= {
-                               blockHash: 'BB569136FA05F8CBF65CEF2EDE368475B289C4477342976556BA4C0DDF216E45',
-                               blockHashBytes: new Uint8Array([0xBB, 0x56, 0x91, 0x36, 0xFA, 0x05, 0xF8, 0xCB, 0xF6, 0x5C, 0xEF, 0x2E, 0xDE, 0x36, 0x84, 0x75, 0xB2, 0x89, 0xC4, 0x47, 0x73, 0x42, 0x97, 0x65, 0x56, 0xBA, 0x4C, 0x0D, 0xDF, 0x21, 0x6E, 0x45]),
-                               privateKey: '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3',
-                               privateKeyBytes: new Uint8Array([0x78, 0x11, 0x86, 0xFB, 0x9E, 0xF1, 0x7D, 0xB6, 0xE3, 0xD1, 0x05, 0x65, 0x50, 0xD9, 0xFA, 0xE5, 0xD5, 0xBB, 0xAD, 0xA6, 0xA6, 0xBC, 0x37, 0x0E, 0x4C, 0xBB, 0x93, 0x8B, 0x1D, 0xC7, 0x1D, 0xA3]),
-                               publicKey: '3068BB1CA04525BB0E416C485FE6A67FD52540227D267CC8B6E8DA958A7FA039',
-                               publicKeyBytes: new Uint8Array([0x30, 0x68, 0xBB, 0x1C, 0xA0, 0x45, 0x25, 0xBB, 0x0E, 0x41, 0x6C, 0x48, 0x5F, 0xE6, 0xA6, 0x7F, 0xD5, 0x25, 0x40, 0x22, 0x7D, 0x26, 0x7C, 0xC8, 0xB6, 0xE8, 0xDA, 0x95, 0x8A, 0x7F, 0xA0, 0x39]),
-                               secretKey: '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA33068BB1CA04525BB0E416C485FE6A67FD52540227D267CC8B6E8DA958A7FA039',
-                               secretKeyBytes: new Uint8Array([0x78, 0x11, 0x86, 0xFB, 0x9E, 0xF1, 0x7D, 0xB6, 0xE3, 0xD1, 0x05, 0x65, 0x50, 0xD9, 0xFA, 0xE5, 0xD5, 0xBB, 0xAD, 0xA6, 0xA6, 0xBC, 0x37, 0x0E, 0x4C, 0xBB, 0x93, 0x8B, 0x1D, 0xC7, 0x1D, 0xA3, 0x30, 0x68, 0xBB, 0x1C, 0xA0, 0x45, 0x25, 0xBB, 0x0E, 0x41, 0x6C, 0x48, 0x5F, 0xE6, 0xA6, 0x7F, 0xD5, 0x25, 0x40, 0x22, 0x7D, 0x26, 0x7C, 0xC8, 0xB6, 0xE8, 0xDA, 0x95, 0x8A, 0x7F, 0xA0, 0x39]),
-                               signature: '74BCC59DBA39A1E34A5F75F96D6DE9154E3477AAD7DE30EA563DFCFE501A804228008F98DDF4A15FD35705102785C50EF76732C3A74B0FEC5B0DD67B574A5900',
-                               signatureBytes: new Uint8Array([0x74, 0xBC, 0xC5, 0x9D, 0xBA, 0x39, 0xA1, 0xE3, 0x4A, 0x5F, 0x75, 0xF9, 0x6D, 0x6D, 0xE9, 0x15, 0x4E, 0x34, 0x77, 0xAA, 0xD7, 0xDE, 0x30, 0xEA, 0x56, 0x3D, 0xFC, 0xFE, 0x50, 0x1A, 0x80, 0x42, 0x28, 0x00, 0x8F, 0x98, 0xDD, 0xF4, 0xA1, 0x5F, 0xD3, 0x57, 0x05, 0x10, 0x27, 0x85, 0xC5, 0x0E, 0xF7, 0x67, 0x32, 0xC3, 0xA7, 0x4B, 0x0F, 0xEC, 0x5B, 0x0D, 0xD6, 0x7B, 0x57, 0x4A, 0x59, 0x00]),
-                               badSignature: '74BCC59DBA39A1E34A5F75F96D6DE9154E3477AAD7DE30EA563DFCFE501A804215d484f5f757b4b7a9f4fcb2057fa423f76732c3a74b0fec5b0dd67b574a5910'
-                       }
                        const zeroes = '0000000000000000000000000000000000000000000000000000000000000000'
                        const expect = []
                        let result
 
                        // PASS
-                       result = NanoNaCl.derive(TEST_VECTORS.privateKeyBytes)
-                       console.log('expected: ', TEST_VECTORS.publicKeyBytes)
+                       result = NanoNaCl.derive(NANO_ORG_VECTOR.privateKeyBytes)
+                       console.log('expected: ', NANO_ORG_VECTOR.publicKeyBytes)
                        console.log('actual:', result)
-                       result = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase() === TEST_VECTORS.publicKey.toUpperCase()
+                       result = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase() === NANO_ORG_VECTOR.publicKey.toUpperCase()
                        console.log(`derive() output for good private key is ${result === true ? 'correct' : 'incorrect'}`)
                        expect.push(result)
 
-                       result = NanoNaCl.sign(TEST_VECTORS.blockHashBytes, TEST_VECTORS.secretKeyBytes)
-                       console.log('expected: ', TEST_VECTORS.signatureBytes)
+                       result = NanoNaCl.sign(NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.secretKeyBytes)
+                       console.log('expected: ', NANO_ORG_VECTOR.signatureBytes)
                        console.log('actual:', result)
-                       result = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase() === TEST_VECTORS.signature.toUpperCase()
+                       result = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase() === NANO_ORG_VECTOR.signature.toUpperCase()
                        console.log(`sign() output for good block hash and secret key is ${result === true ? 'correct' : 'incorrect'}`)
                        expect.push(result)
 
-                       result = NanoNaCl.verify(TEST_VECTORS.signatureBytes, TEST_VECTORS.blockHashBytes, TEST_VECTORS.publicKeyBytes)
+                       result = NanoNaCl.verify(NANO_ORG_VECTOR.signatureBytes, NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.publicKeyBytes)
                        console.log('expected:', true)
                        console.log('actual:', result)
                        result = result === true
                        console.log(`verify() output for good block hash, signature, and private key is ${result === true ? 'correct' : 'incorrect'}`)
                        expect.push(result)
 
-                       result = await NanoNaCl.deriveAsync(TEST_VECTORS.privateKey)
-                       console.log('expected: ', TEST_VECTORS.publicKey.toUpperCase())
+                       result = await NanoNaCl.deriveAsync(NANO_ORG_VECTOR.privateKey)
+                       console.log('expected: ', NANO_ORG_VECTOR.publicKey.toUpperCase())
                        console.log('actual:', result?.toUpperCase?.())
-                       result = result.toUpperCase() === TEST_VECTORS.publicKey.toUpperCase()
+                       result = result.toUpperCase() === NANO_ORG_VECTOR.publicKey.toUpperCase()
                        console.log(`deriveAsync() output for good private key is ${result === true ? 'correct' : 'incorrect'}`)
                        expect.push(result)
 
-                       result = await NanoNaCl.signAsync(TEST_VECTORS.blockHash, TEST_VECTORS.secretKey)
-                       console.log('expected: ', TEST_VECTORS.signature.toUpperCase())
+                       result = await NanoNaCl.signAsync(NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.secretKey)
+                       console.log('expected: ', NANO_ORG_VECTOR.signature.toUpperCase())
                        console.log('actual:', result?.toUpperCase?.())
-                       result = result.toUpperCase() === TEST_VECTORS.signature.toUpperCase()
+                       result = result.toUpperCase() === NANO_ORG_VECTOR.signature.toUpperCase()
                        console.log(`signAsync() output for good block hash and secret key is ${result === true ? 'correct' : 'incorrect'}`)
                        expect.push(result)
 
-                       result = await NanoNaCl.verifyAsync(TEST_VECTORS.signature, TEST_VECTORS.blockHash, TEST_VECTORS.publicKey)
+                       result = await NanoNaCl.verifyAsync(NANO_ORG_VECTOR.signature, NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.publicKey)
                        console.log('expected:', true)
                        console.log('actual:', result)
                        result = result === true
                        console.log(`verifyAsync() output for good block hash, signature, and private key is ${result === true ? 'correct' : 'incorrect'}`)
                        expect.push(result)
 
+                       result = await NanoNaCl.deriveAsync(PYTHON_ED25519_BLAKE2B_VECTOR.privateKey)
+                       console.log('expected: ', PYTHON_ED25519_BLAKE2B_VECTOR.publicKey.toUpperCase())
+                       console.log('actual:', result?.toUpperCase?.())
+                       result = result.toUpperCase() === PYTHON_ED25519_BLAKE2B_VECTOR.publicKey.toUpperCase()
+                       console.log(`deriveAsync() output for python vector private key is ${result === true ? 'correct' : 'incorrect'}`)
+                       expect.push(result)
+
+                       result = await NanoNaCl.signAsync(PYTHON_ED25519_BLAKE2B_VECTOR.blockHash, PYTHON_ED25519_BLAKE2B_VECTOR.secretKey)
+                       console.log('expected: ', PYTHON_ED25519_BLAKE2B_VECTOR.signature.toUpperCase())
+                       console.log('actual:', result?.toUpperCase?.())
+                       result = result.toUpperCase() === PYTHON_ED25519_BLAKE2B_VECTOR.signature.toUpperCase()
+                       console.log(`signAsync() output for python vector block hash and secret key is ${result === true ? 'correct' : 'incorrect'}`)
+                       expect.push(result)
+
+                       result = await NanoNaCl.verifyAsync(PYTHON_ED25519_BLAKE2B_VECTOR.signature, PYTHON_ED25519_BLAKE2B_VECTOR.blockHash, PYTHON_ED25519_BLAKE2B_VECTOR.publicKey)
+                       console.log('expected:', true)
+                       console.log('actual:', result)
+                       result = result === true
+                       console.log(`verifyAsync() output for python block hash, signature, and private key is ${result === true ? 'correct' : 'incorrect'}`)
+                       expect.push(result)
+
+                       if (PRIVATE_VECTOR) {
+                               result = await NanoNaCl.deriveAsync(PRIVATE_VECTOR.privateKey)
+                               console.log('expected: ', PRIVATE_VECTOR.publicKey.toUpperCase())
+                               console.log('actual:', result?.toUpperCase?.())
+                               result = result.toUpperCase() === PRIVATE_VECTOR.publicKey.toUpperCase()
+                               console.log(`deriveAsync() output for private vector private key is ${result === true ? 'correct' : 'incorrect'}`)
+                               expect.push(result)
+
+                               result = await NanoNaCl.signAsync(PRIVATE_VECTOR.blockHash, PRIVATE_VECTOR.secretKey)
+                               console.log('expected: ', PRIVATE_VECTOR.signature.toUpperCase())
+                               console.log('actual:', result?.toUpperCase?.())
+                               result = result.toUpperCase() === PRIVATE_VECTOR.signature.toUpperCase()
+                               console.log(`signAsync() output for private vector block hash and secret key is ${result === true ? 'correct' : 'incorrect'}`)
+                               expect.push(result)
+
+                               result = await NanoNaCl.verifyAsync(PRIVATE_VECTOR.signature, PRIVATE_VECTOR.blockHash, PRIVATE_VECTOR.publicKey)
+                               console.log('expected:', true)
+                               console.log('actual:', result)
+                               result = result === true
+                               console.log(`verifyAsync() output for private block hash, signature, and private key is ${result === true ? 'correct' : 'incorrect'}`)
+                               expect.push(result)
+                       }
+
                        // XFAIL
-                       result = await NanoNaCl.verifyAsync(TEST_VECTORS.badSignature, TEST_VECTORS.blockHash, TEST_VECTORS.publicKey)
+                       result = await NanoNaCl.verifyAsync(NANO_ORG_VECTOR.badSignature, NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.publicKey)
                        console.log(result)
                        result = result === false
                        console.log(`verify() output for non-canonical signature is ${result === true ? 'correct' : 'incorrect'}`)
                        expect.push(result)
 
-                       result = await NanoNaCl.verifyAsync(TEST_VECTORS.signature, random(), TEST_VECTORS.publicKey)
+                       result = await NanoNaCl.verifyAsync(NANO_ORG_VECTOR.signature, random(), NANO_ORG_VECTOR.publicKey)
                        console.log(result)
                        result = result === false
                        console.log(`verify() output for random block hash is ${result === true ? 'correct' : 'incorrect'}`)
                        expect.push(result)
 
-                       result = await NanoNaCl.verifyAsync(TEST_VECTORS.signature, zeroes, TEST_VECTORS.publicKey)
+                       result = await NanoNaCl.verifyAsync(NANO_ORG_VECTOR.signature, zeroes, NANO_ORG_VECTOR.publicKey)
                        console.log(result)
                        result = result === false
                        console.log(`verify() output for bad block hash is ${result === true ? 'correct' : 'incorrect'}`)
                        expect.push(result)
 
-                       result = await NanoNaCl.verifyAsync(`${zeroes}${zeroes}`, TEST_VECTORS.blockHash, TEST_VECTORS.publicKey)
+                       result = await NanoNaCl.verifyAsync(`${zeroes}${zeroes}`, NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.publicKey)
                        console.log(result)
                        result = result === false
                        console.log(`verify() output for bad signature is ${result === true ? 'correct' : 'incorrect'}`)
                        expect.push(result)
 
-                       result = await NanoNaCl.verifyAsync(TEST_VECTORS.signature, TEST_VECTORS.blockHash, zeroes)
+                       result = await NanoNaCl.verifyAsync(NANO_ORG_VECTOR.signature, NANO_ORG_VECTOR.blockHash, zeroes)
                        console.log(result)
                        result = result === false
                        console.log(`verify() output for bad public key is ${result === true ? 'correct' : 'incorrect'}`)
index 9a1935b3a117a5c4c4fc866fd70abd357a00a5bf..92c0415f380bfdf83bed5e2c70f4063283be5cd3 100644 (file)
--- a/index.ts
+++ b/index.ts
@@ -1,6 +1,7 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
+import { Worker as NodeWorker, isMainThread, parentPort } from 'node:worker_threads'
 //@ts-expect-error
 import nacl from './build/nano-nacl.wasm'
 
@@ -161,12 +162,14 @@ const startNanoNaCl = (bytes: number[]): { derive: typeof derive, sign: typeof s
        }
 
        let isListening = false
+       let host: any = null
 
        /**
         * Parses inbound data when NanoNaCl is started as a Web Worker.
         * @param {object} message.data - String of worker commands and related data
         */
-       async function handleMessage (message: unknown): Promise<void> {
+       function handleMessage (message: unknown): void {
+               NODE: if (host == null) setTimeout(() => handleMessage(message), 0)
                let result: any = null
                try {
                        if (message == null
@@ -179,10 +182,10 @@ const startNanoNaCl = (bytes: number[]): { derive: typeof derive, sign: typeof s
                        const msg = message as { [key: string]: string }
                        if (msg.data === 'start') {
                                isListening = true
-                               postMessage('started')
+                               result = 'started'
                        } else if (msg.data === 'stop') {
                                isListening = false
-                               postMessage('stopped')
+                               result = 'stopped'
                        } else if (isListening) {
                                const data: Data = JSON.parse(msg.data)
                                const { action } = data
@@ -214,7 +217,6 @@ const startNanoNaCl = (bytes: number[]): { derive: typeof derive, sign: typeof s
                                        }
                                        result = verification
                                }
-                               postMessage(result)
                        }
                } catch (err: unknown) {
                        if (typeof err === 'object' && err != null) {
@@ -225,10 +227,21 @@ const startNanoNaCl = (bytes: number[]): { derive: typeof derive, sign: typeof s
                        } else {
                                result = JSON.stringify(err)
                        }
-                       postMessage(result)
+               } finally {
+                       BROWSER: postMessage(result)
+                       NODE: host?.postMessage({ data: result })
+               }
+       }
+       BROWSER: addEventListener('message', handleMessage)
+       NODE: {
+               if (host == null) {
+                       import('node:worker_threads')
+                               .then(({ parentPort }): void => {
+                                       host = parentPort
+                                       host?.on('message', handleMessage)
+                               })
                }
        }
-       addEventListener('message', handleMessage)
 
        return { derive, sign, verify }
 }
@@ -238,14 +251,19 @@ const startNanoNaCl = (bytes: number[]): { derive: typeof derive, sign: typeof s
  */
 let isWorkerReady: boolean = false
 let isWorkerListening: boolean = false
-let worker: Worker
+let worker: Worker | NodeWorker
 let url: string
 
 // Create worker module
 function init (): void {
        try {
                url = URL.createObjectURL(new Blob([NanoNaClWorker], { type: 'text/javascript' }))
-               worker = new Worker(url, { type: 'module' })
+               BROWSER: worker = new Worker(url, { type: 'module' })
+               NODE: worker = new NodeWorker(NanoNaClWorker, {
+                       eval: true,
+                       stderr: false,
+                       stdout: false
+               })
                console.log(`NanoNaCl initialized.`)
                isWorkerReady = true
        } catch (err) {
@@ -267,8 +285,7 @@ async function start (): Promise<void> {
        if (!isWorkerReady) init()
        if (!isWorkerListening) {
                return new Promise(async (resolve, reject): Promise<void> => {
-                       worker.onerror = err => reject(err.message)
-                       worker.onmessage = (msg): void => {
+                       const onstarted = (msg: any): void => {
                                const result = msg.data
                                if (result === 'started') {
                                        console.log('worker started successfully')
@@ -278,7 +295,17 @@ async function start (): Promise<void> {
                                        reject(result)
                                }
                        }
-                       worker.postMessage('start')
+                       //@ts-expect-error
+                       BROWSER: worker.onerror = err => reject(err.message)
+                       //@ts-expect-error
+                       BROWSER: worker.onmessage = onstarted
+                       //@ts-expect-error
+                       NODE: worker.on('error', err => reject(err.message))
+                       //@ts-expect-error
+                       NODE: worker.on('message', onstarted)
+                       console.log(`starting worker`)
+                       BROWSER: worker.postMessage('start')
+                       NODE: worker.postMessage({ data: 'start' })
                })
        }
 }
@@ -286,22 +313,29 @@ async function start (): Promise<void> {
 // Send command and relevant data to NanoNaCl worker
 async function dispatch (data: { [key: string]: string }): Promise<Uint8Array<ArrayBuffer>> {
        return new Promise((resolve, reject) => {
-               worker.onerror = reject
-               worker.onmessage = (msg): void => {
+               const onresult = (msg: any): void => {
                        const result = msg.data
-                       LOG: console.log(`received result from worker`)
+                       console.log(`received result from worker`)
                        resolve(result)
                }
-               LOG: console.log(`sending data to worker`)
-               worker.postMessage(JSON.stringify(data))
+               //@ts-expect-error
+               BROWSER: worker.onerror = reject
+               //@ts-expect-error
+               BROWSER: worker.onmessage = onresult
+               //@ts-expect-error
+               NODE: worker.on('error', reject)
+               //@ts-expect-error
+               NODE: worker.on('message', onresult)
+               console.log(`sending data to worker`)
+               BROWSER: worker.postMessage(JSON.stringify(data))
+               NODE: worker.postMessage({ data: JSON.stringify(data) })
        })
 }
 
 // Request that the worker stop listening without terminating
 async function stop (): Promise<void> {
        return new Promise((resolve, reject): void => {
-               worker.onerror = reject
-               worker.onmessage = (msg): void => {
+               const onstop = (msg: any): void => {
                        const result = msg.data
                        if (result === 'stopped') {
                                console.log('worker stopped successfully')
@@ -311,15 +345,25 @@ async function stop (): Promise<void> {
                                reject(result)
                        }
                }
-               worker.postMessage('stop')
+               //@ts-expect-error
+               BROWSER: worker.onerror = reject
+               //@ts-expect-error
+               BROWSER: worker.onmessage = onstop
+               //@ts-expect-error
+               NODE: worker.on('error', reject)
+               //@ts-expect-error
+               NODE: worker.on('message', onstop)
+               console.log(`stopping worker`)
+               BROWSER: worker.postMessage('stop')
+               NODE: worker.postMessage({ data: 'stop' })
        })
 }
 
 async function run (data: Record<string, string>): Promise<string> {
        try {
                await start()
-       } catch (err) {
-               console.error(err)
+       } catch (err: any) {
+               console.error(err?.stack ?? err)
                throw new Error('Error initializing worker')
        }
        try {
@@ -330,7 +374,7 @@ async function run (data: Record<string, string>): Promise<string> {
                return [...result].map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase()
        } catch (err: any) {
                try {
-                       console.error(err)
+                       console.error(err?.stack ?? err)
                        await stop()
                } catch (e: any) {
                        console.error('failed to stop worker', err?.message ?? err ?? 'unknown reason')
index c198f0e9077926ba6663a17a9f3bda4fbedb3520..f8043adfaa6f9590c3886a69a3d36c54e121001b 100644 (file)
                "url": "git+https://git.codecow.com/nano-nacl.git"
        },
        "scripts": {
-               "build": "npm run clean && npm run compile && node ./esbuild.mjs",
+               "build": "npm run clean && npm run compile && node ./esbuild/dev.mjs",
+               "build:prod": "npm run clean && npm run compile && node ./esbuild/prod.mjs",
                "clean": "rm -rf {build,dist}",
                "compile": "asc ./assembly/index.ts && tsc",
-               "prepublishOnly": "npm run build",
-               "test": "npm run build && node ./test.mjs"
+               "prepublishOnly": "npm run test:prod",
+               "test": "npm run build && node ./test.mjs",
+               "test:prod": "npm run build:prod && node ./test.mjs"
        },
        "devDependencies": {
                "@types/node": "^25.5.0",
index 3f93af033d03650574c87eb9c52f50fbf6d2f750..5016653c6a96d4abc5c1c9cd839a3959612fc189 100644 (file)
--- a/test.mjs
+++ b/test.mjs
@@ -1,29 +1,53 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
-import * as NanoNaCl from './dist/node.js'
 
-// const { derive, sign, verify } = NanoNaCl
-const { sign } = NanoNaCl
+import { derive, deriveAsync, sign, signAsync, verify, verifyAsync } from './dist/node.js'
+import { NANO_ORG_VECTOR, PYTHON_ED25519_BLAKE2B_VECTOR } from './vectors.mjs'
 
 /**
+ * @param {string} name
  * @param {boolean} test
  */
-function check (test) {
+function check (name, test) {
        if (typeof test !== 'boolean') {
                throw 'invalid test'
        }
-       console.log(test ? 'pass' : 'fail')
+       console.log(`${name}: ${test ? 'pass' : 'fail'}`)
 }
 
-const sk = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'
-const hash = 'BB569136FA05F8CBF65CEF2EDE368475B289C4477342976556BA4C0DDF216E45'
-const sig = '74BCC59DBA39A1E34A5F75F96D6DE9154E3477AAD7DE30EA563DFCFE501A804228008F98DDF4A15FD35705102785C50EF76732C3A74B0FEC5B0DD67B574A5900'
+/**
+ * @type {any} result
+ */
+let result = await deriveAsync(PYTHON_ED25519_BLAKE2B_VECTOR.privateKey)
+result = result.toUpperCase() === PYTHON_ED25519_BLAKE2B_VECTOR.publicKey
+check('deriveAsync', result)
+let passing = result
+
+result = await signAsync(PYTHON_ED25519_BLAKE2B_VECTOR.blockHash, PYTHON_ED25519_BLAKE2B_VECTOR.secretKey)
+result = result.toUpperCase() === PYTHON_ED25519_BLAKE2B_VECTOR.signature
+check('signAsync', result)
+passing &&= result
+
+result = await verifyAsync(PYTHON_ED25519_BLAKE2B_VECTOR.signature, PYTHON_ED25519_BLAKE2B_VECTOR.blockHash, PYTHON_ED25519_BLAKE2B_VECTOR.publicKey)
+result = result === true
+check('verifyAsync', result)
+passing &&= result
+
+result = derive(NANO_ORG_VECTOR.privateKeyBytes)
+result = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase() === NANO_ORG_VECTOR.publicKey
+check('deriveAsync', result)
+passing &&= result
+
+result = sign(NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.secretKeyBytes)
+result = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase() === NANO_ORG_VECTOR.signature
+check('signAsync', result)
+passing &&= result
 
-// const pk = await derive(sk)
-// check(pk === '3068BB1CA04525BB0E416C485FE6A67FD52540227D267CC8B6E8DA958A7FA039')
+result = verify(NANO_ORG_VECTOR.signatureBytes, NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.publicKeyBytes)
+result = result === true
+check('verifyAsync', result)
+passing &&= result
 
-const signature = await sign(hash, sk)
-check(signature === sig)
+console.log(`${passing ? '\x1b[32mPASSED' : '\x1b[31mFAILED'}\x1b[0m`)
 
-// const v = await verify(hash, sig, pk)
-// check(v === true)
+process.exit(passing ? 0 : 1)
diff --git a/vectors.mjs b/vectors.mjs
new file mode 100644 (file)
index 0000000..51f1042
--- /dev/null
@@ -0,0 +1,26 @@
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+// https://docs.nano.org/integration-guides/key-management/#success-response_2
+export const NANO_ORG_VECTOR = {
+       blockHash: 'BB569136FA05F8CBF65CEF2EDE368475B289C4477342976556BA4C0DDF216E45',
+       blockHashBytes: new Uint8Array([0xBB, 0x56, 0x91, 0x36, 0xFA, 0x05, 0xF8, 0xCB, 0xF6, 0x5C, 0xEF, 0x2E, 0xDE, 0x36, 0x84, 0x75, 0xB2, 0x89, 0xC4, 0x47, 0x73, 0x42, 0x97, 0x65, 0x56, 0xBA, 0x4C, 0x0D, 0xDF, 0x21, 0x6E, 0x45]),
+       privateKey: '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3',
+       privateKeyBytes: new Uint8Array([0x78, 0x11, 0x86, 0xFB, 0x9E, 0xF1, 0x7D, 0xB6, 0xE3, 0xD1, 0x05, 0x65, 0x50, 0xD9, 0xFA, 0xE5, 0xD5, 0xBB, 0xAD, 0xA6, 0xA6, 0xBC, 0x37, 0x0E, 0x4C, 0xBB, 0x93, 0x8B, 0x1D, 0xC7, 0x1D, 0xA3]),
+       publicKey: '3068BB1CA04525BB0E416C485FE6A67FD52540227D267CC8B6E8DA958A7FA039',
+       publicKeyBytes: new Uint8Array([0x30, 0x68, 0xBB, 0x1C, 0xA0, 0x45, 0x25, 0xBB, 0x0E, 0x41, 0x6C, 0x48, 0x5F, 0xE6, 0xA6, 0x7F, 0xD5, 0x25, 0x40, 0x22, 0x7D, 0x26, 0x7C, 0xC8, 0xB6, 0xE8, 0xDA, 0x95, 0x8A, 0x7F, 0xA0, 0x39]),
+       secretKey: '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA33068BB1CA04525BB0E416C485FE6A67FD52540227D267CC8B6E8DA958A7FA039',
+       secretKeyBytes: new Uint8Array([0x78, 0x11, 0x86, 0xFB, 0x9E, 0xF1, 0x7D, 0xB6, 0xE3, 0xD1, 0x05, 0x65, 0x50, 0xD9, 0xFA, 0xE5, 0xD5, 0xBB, 0xAD, 0xA6, 0xA6, 0xBC, 0x37, 0x0E, 0x4C, 0xBB, 0x93, 0x8B, 0x1D, 0xC7, 0x1D, 0xA3, 0x30, 0x68, 0xBB, 0x1C, 0xA0, 0x45, 0x25, 0xBB, 0x0E, 0x41, 0x6C, 0x48, 0x5F, 0xE6, 0xA6, 0x7F, 0xD5, 0x25, 0x40, 0x22, 0x7D, 0x26, 0x7C, 0xC8, 0xB6, 0xE8, 0xDA, 0x95, 0x8A, 0x7F, 0xA0, 0x39]),
+       signature: '74BCC59DBA39A1E34A5F75F96D6DE9154E3477AAD7DE30EA563DFCFE501A804228008F98DDF4A15FD35705102785C50EF76732C3A74B0FEC5B0DD67B574A5900',
+       signatureBytes: new Uint8Array([0x74, 0xBC, 0xC5, 0x9D, 0xBA, 0x39, 0xA1, 0xE3, 0x4A, 0x5F, 0x75, 0xF9, 0x6D, 0x6D, 0xE9, 0x15, 0x4E, 0x34, 0x77, 0xAA, 0xD7, 0xDE, 0x30, 0xEA, 0x56, 0x3D, 0xFC, 0xFE, 0x50, 0x1A, 0x80, 0x42, 0x28, 0x00, 0x8F, 0x98, 0xDD, 0xF4, 0xA1, 0x5F, 0xD3, 0x57, 0x05, 0x10, 0x27, 0x85, 0xC5, 0x0E, 0xF7, 0x67, 0x32, 0xC3, 0xA7, 0x4B, 0x0F, 0xEC, 0x5B, 0x0D, 0xD6, 0x7B, 0x57, 0x4A, 0x59, 0x00]),
+       badSignature: '74BCC59DBA39A1E34A5F75F96D6DE9154E3477AAD7DE30EA563DFCFE501A804215d484f5f757b4b7a9f4fcb2057fa423f76732c3a74b0fec5b0dd67b574a5910'
+}
+
+// https://github.com/Matoking/python-ed25519-blake2b/blob/master/kat-ed25519-blake2b.txt
+export const PYTHON_ED25519_BLAKE2B_VECTOR = {
+       privateKey: '8ED7A797B9CEA8A8370D419136BCDF683B759D2E3C6947F17E13E2485AA9D420',
+       publicKey: '5377FE897EB39FA321B342D490426BCB00E1B75045074D24F110BA1933C61347',
+       secretKey: '8ED7A797B9CEA8A8370D419136BCDF683B759D2E3C6947F17E13E2485AA9D4205377FE897EB39FA321B342D490426BCB00E1B75045074D24F110BA1933C61347',
+       blockHash: 'A750C232933DC14B1184D86D8B4CE72E16D69744BA69818B6AC33B1D823BB2C3',
+       signature: 'C9EB3EBEF0B74D13A71E625CA2F3ABFA17514D5F31DD03A2E2C8B6ED3BF1EA99E3E9289AAA48E66079F2FF484321E884A91BA03062A657CB1588974CEBC0C505'
+}