From 61449285d510002d34c39209e7db3ead0ea5a405 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Sat, 15 Aug 2026 02:47:47 -0700 Subject: [PATCH] Deprecate complicated worker state management and just determine if it's listening. Restore test webpage checks. --- index.html | 88 +++++++++++++++++------------------ src/lib/worker.ts | 115 ++++++++++++++++++++++------------------------ 2 files changed, 98 insertions(+), 105 deletions(-) diff --git a/index.html b/index.html index a4c7061..aef5223 100644 --- a/index.html +++ b/index.html @@ -289,50 +289,50 @@ SPDX-License-Identifier: GPL-3.0-or-later passes += +test failures += +!test - // // test both strings and bytes as input - // for (const { privateKey, publicKey, message, signature } of PYTHON_ED25519_BLAKE2B_VECTORS) { - // result = nano25519.derive(privateKey) - // test = result.toLowerCase() === publicKey - // check(`derive from ${privateKey}`, test) - // passes += +test - // failures += +!test - - // result = nano25519.sign(message, privateKey + publicKey) - // test = result.toLowerCase() === signature.slice(0, 128) - // check(`sign message ${message}`, test) - // passes += +test - // failures += +!test - - // result = nano25519.verify(signature.slice(0, 128), message, publicKey) - // test = result === true - // check(`verify signature ${signature.slice(0, 128)}`, test) - // passes += +test - // failures += +!test - - // const privateKeyBytes = new Uint8Array(privateKey.match(/.{2}/g)?.map(b => parseInt(b, 16)) ?? []) - // const publicKeyBytes = new Uint8Array(publicKey.match(/.{2}/g)?.map(b => parseInt(b, 16)) ?? []) - // const secretKeyBytes = new Uint8Array([...privateKeyBytes, ...publicKeyBytes]) - // const messageBytes = new Uint8Array(message.match(/.{2}/g)?.map(b => parseInt(b, 16)) ?? []) - // const signatureBytes = new Uint8Array(signature.match(/.{2}/g)?.slice(0, 64).map(b => parseInt(b, 16)) ?? []) - - // result = nano25519.derive(privateKeyBytes) - // test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === publicKey - // check(`derive from ${privateKey}`, test) - // passes += +test - // failures += +!test - - // result = nano25519.sign(messageBytes, secretKeyBytes) - // test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === signature.slice(0, 128) - // check(`sign message ${message}`, test) - // passes += +test - // failures += +!test - - // result = nano25519.verify(signatureBytes, messageBytes, publicKeyBytes) - // test = result === true - // check(`verify signature ${signature.slice(0, 128)}`, test) - // passes += +test - // failures += +!test - // } + // test both strings and bytes as input + for (const { privateKey, publicKey, message, signature } of PYTHON_ED25519_BLAKE2B_VECTORS) { + result = nano25519.derive(privateKey) + test = result.toLowerCase() === publicKey + check(`derive from ${privateKey}`, test) + passes += +test + failures += +!test + + result = nano25519.sign(message, privateKey + publicKey) + test = result.toLowerCase() === signature.slice(0, 128) + check(`sign message ${message}`, test) + passes += +test + failures += +!test + + result = nano25519.verify(signature.slice(0, 128), message, publicKey) + test = result === true + check(`verify signature ${signature.slice(0, 128)}`, test) + passes += +test + failures += +!test + + const privateKeyBytes = new Uint8Array(privateKey.match(/.{2}/g)?.map(b => parseInt(b, 16)) ?? []) + const publicKeyBytes = new Uint8Array(publicKey.match(/.{2}/g)?.map(b => parseInt(b, 16)) ?? []) + const secretKeyBytes = new Uint8Array([...privateKeyBytes, ...publicKeyBytes]) + const messageBytes = new Uint8Array(message.match(/.{2}/g)?.map(b => parseInt(b, 16)) ?? []) + const signatureBytes = new Uint8Array(signature.match(/.{2}/g)?.slice(0, 64).map(b => parseInt(b, 16)) ?? []) + + result = nano25519.derive(privateKeyBytes) + test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === publicKey + check(`derive from ${privateKey}`, test) + passes += +test + failures += +!test + + result = nano25519.sign(messageBytes, secretKeyBytes) + test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === signature.slice(0, 128) + check(`sign message ${message}`, test) + passes += +test + failures += +!test + + result = nano25519.verify(signatureBytes, messageBytes, publicKeyBytes) + test = result === true + check(`verify signature ${signature.slice(0, 128)}`, test) + passes += +test + failures += +!test + } // XFAIL try { diff --git a/src/lib/worker.ts b/src/lib/worker.ts index f62fb4e..175f9ca 100644 --- a/src/lib/worker.ts +++ b/src/lib/worker.ts @@ -7,10 +7,10 @@ import { MessagePort as NodeMessagePort, Worker as NodeWorker } from 'node:worke import nano25519_wasm from '../../build/nano25519.wasm' import { nano25519_init } from './nano25519' -type Action = 'derive' | 'sign' | 'start' | 'stop' | 'verify' +type Action = 'derive' | 'sign' | 'start' | 'verify' type Data = { - id: string + id: UUID action: string message?: string | ArrayBuffer privateKey?: string @@ -20,7 +20,6 @@ type Data = { } const nano25519_worker_init = ({ derive, sign, verify }: ReturnType) => { - let isListening = false let host: NodeMessagePort | null = null /** @@ -41,40 +40,45 @@ const nano25519_worker_init = ({ derive, sign, verify }: ReturnType - let id: undefined | string + let id: undefined | UUID try { - const data: Data = message.data as object & { id: string, action: string } + const data: Data = message.data as object & { id: UUID, action: Action } id = data.id - if (data.action === 'start') { - isListening = true - result = 'started' - } else if (data.action === 'stop') { - isListening = false - result = 'stopped' - } else if (isListening) { - const { action } = data - if (action === 'derive') { + switch (data.action) { + case 'start': { + result = 'listening' + break + } + case 'derive': { const { privateKey } = data const publicKey = derive(privateKey) if (publicKey == null) { throw new TypeError('Invalid public key from WASM derive()') } result = publicKey - } else if (action === 'sign') { + break + } + case 'sign': { const { message, secretKey } = data const signature = sign(message, secretKey) if (signature == null) { throw new TypeError('Invalid signature from WASM sign()') } result = signature - } else if (action === 'verify') { + break + } + case 'verify': { const { message, publicKey, signature } = data const verification = verify(signature, message, publicKey) if (verification == null) { throw new TypeError('Invalid verification from WASM verify()') } result = verification + break + } + default: { + throw new TypeError(`Invalid action '${data.action}'`) } } } catch (err: unknown) { @@ -123,6 +127,7 @@ function isBytes (a: unknown): a is Uint8Array { function init (): void { try { BROWSER: { + if (url) URL.revokeObjectURL(url) url = URL.createObjectURL(new Blob([nano25519_worker], { type: 'text/javascript' })) worker = new Worker(url, { type: 'module' }) worker.onmessage = report @@ -165,6 +170,7 @@ function report (msg: { data: Record }): void { const { result } = data console.log('received result from worker') if (typeof result !== 'boolean' && typeof result !== 'string' && !isBytes(result)) { + console.error(result) return err('Invalid return type') } return ok(result) @@ -173,7 +179,6 @@ function report (msg: { data: Record }): void { // Reconstruct worker when errors occur function reset (): void { console.warn(`nano25519 encountered an error. Reinitializing...`) - isWorkerReady = false worker.terminate() init() } @@ -181,6 +186,7 @@ function reset (): void { // Check that the worker is running and listening before sending messages async function start (): Promise { return starting ??= new Promise((resolve, reject): void => { + console.log('starting worker') if (!isWorkerReady) init() const id = crypto.randomUUID() tasks.set(id, [resolve, reject]) @@ -206,16 +212,9 @@ async function dispatch (data: Record<'action', Action> & Record { - console.log('stopping worker') - const result = await dispatch({ action: 'stop' }) - if (result === 'stopped') { - console.log('worker stopped successfully') - } else if (typeof result === 'string') { - throw new Error(result) - } else { - throw new Error('unknown error while stopping worker') +class Nano25519ResultError extends Error { + constructor (action: string, cause?: unknown) { + super(`${action} result invalid`, { cause }) } } @@ -224,44 +223,38 @@ export async function run (data: Record<'action', 'sign'> & Record & Record>): Promise export async function run (data: Record<'action', 'derive' | 'sign' | 'verify'> & Record>): Promise> { try { - await start() - if (url) URL.revokeObjectURL(url) - } catch (err: any) { - throw new Error('Error initializing worker') + const result = await start() + if (result === 'listening') { + console.log('worker listening') + } else if (typeof result === 'string') { + throw new Error(result) + } else { + throw new Error('unknown error') + } + } catch (e: any) { + throw new Error('failed to start worker', { cause: e }) } - try { - const result = await dispatch(data) - switch (data.action) { - case 'derive': { - if ((isBytes(result) && result.byteLength === 32) || (typeof result === 'string' && /^[0-9a-f]{64}$/i.test(result))) { - return result - } else { - throw new Error('derive result invalid') - } - } - case 'sign': { - if ((isBytes(result) && result.byteLength === 64) || (typeof result === 'string' && /^[0-9a-f]{128}$/i.test(result))) { - return result - } else { - throw new Error('sign result invalid') - } + + const result = await dispatch(data) + switch (data.action) { + case 'derive': { + if ((isBytes(result) && result.byteLength === 32) || (typeof result === 'string' && /^[0-9a-f]{64}$/i.test(result))) { + return result } - case 'verify': { - if (typeof result === 'boolean') { - return result - } else { - throw new Error('verify result invalid') - } + break + } + case 'sign': { + if ((isBytes(result) && result.byteLength === 64) || (typeof result === 'string' && /^[0-9a-f]{128}$/i.test(result))) { + return result } + break } - } catch (err: any) { - try { - await stop() - } catch (e: any) { - console.error('failed to stop worker') - reset() - } finally { - throw new Error('Error dispatching async request') + case 'verify': { + if (typeof result === 'boolean') { + return result + } + break } } + throw new Nano25519ResultError(data.action, result) } -- 2.52.0