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.
+++ /dev/null
-//! 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'
-})
--- /dev/null
+//! 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']
+}
--- /dev/null
+//! 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)
--- /dev/null
+//! 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 })
<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) {
}
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)
}
// 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'}`)
//! 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'
}
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
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
}
result = verification
}
- postMessage(result)
}
} catch (err: unknown) {
if (typeof err === 'object' && err != null) {
} 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 }
}
*/
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) {
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')
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' })
})
}
}
// 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')
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 {
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')
"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",
//! 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)
--- /dev/null
+//! 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'
+}