//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
-import { browserWorker, nodeWorker } from './inject/worker.mjs'
/**
* @type import('esbuild').BuildOptions
*/
{ in: 'src/index.ts', out: 'browser' }
],
dropLabels: ['NODE'],
- define: {
- 'NANO25519_WORKER': browserWorker
- },
}
/**
entryNames: '[name]',
external: ['node:worker_threads'],
dropLabels: ['BROWSER'],
- define: {
- 'NANO25519_WORKER': nodeWorker
- },
}
+++ /dev/null
-//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
-//! SPDX-License-Identifier: GPL-3.0-or-later
-
-import { build } from 'esbuild'
-
-/**
- * @type {import('esbuild').BuildOptions}
- */
-const workerOptions = {
- bundle: true,
- loader: {
- '.wasm': 'binary',
- },
- entryPoints: ['./src/lib/worker.ts'],
- format: 'esm',
- legalComments: 'inline',
- write: false,
- drop: ['console', 'debugger'],
- minify: true,
-}
-
-export const browserWorker = JSON.stringify((await build({
- ...workerOptions,
- platform: 'browser',
- target: 'es2022',
- dropLabels: ['NODE'],
-})).outputFiles?.[0].text)
-
-export const nodeWorker = JSON.stringify((await build({
- ...workerOptions,
- platform: 'node',
- target: 'node22',
- dropLabels: ['BROWSER'],
-})).outputFiles?.[0].text)
* Verify signature `s` was made by signing message `M` using public key `pub`.
* @returns -1 if signature fails to verify, else return 0 if signature is good
*/
-export function crypto_verify (s: StaticArray<u8>, M: StaticArray<u8>, mlen: i32, pub: StaticArray<u8>): i32 {
+export function crypto_verify_strict (s: StaticArray<u8>, M: StaticArray<u8>, mlen: i32, pub: StaticArray<u8>): i32 {
+
+ // fail if public key `k` is non-canonical (`p = 2²⁵⁵-19 ≤ k`)
+ if (!ge_is_canonical(pub)) return -1
+
+ // fail if private scalar `S` is non-canonical (`L ≤ S`)
+ memory.copy(changetype<usize>(S), changetype<usize>(s) + 32, 32)
+ if (!sc_is_canonical(S)) return -1
+
+ if (ge_frombytes_negate_vartime(A, pub) != 0) return -1
+ if (ge_has_small_order(A) != 0) return -1
+
+ if (ge_frombytes(expected_r, s) != 0) return -1
+ if (ge_has_small_order(expected_r) != 0) return -1
+
+ // signature is nonce point R and scalar S (R || S)
+ // data to hash is nonce point R, public key A, and message M
+ // from parameter arguments: R = s[0,32], A = pk, M = m
+ // R, S, A, and M are all 32-byte values in this implementation
+ blake2b.init().update(s, 32).update(pub, 32).update(M, mlen).digest(h)
+ sc_reduce(h)
+
+ ge_double_scalarmult_vartime_to_p3(sb_ah, h, A, S)
+ ge_sub_p3(check, expected_r, sb_ah)
+
+ return ge_has_small_order(check) - 1
+}
+
+/**
+ * Verify signature `s` was made by signing message `M` using public key `pub`.
+ * @returns -1 if signature fails to verify, else return 0 if signature is good
+ */
+export function crypto_verify_relaxed (s: StaticArray<u8>, M: StaticArray<u8>, mlen: i32, pub: StaticArray<u8>): i32 {
// fail if public key `k` is non-canonical (`p = 2²⁵⁵-19 ≤ k`)
if (!ge_is_canonical(pub)) return -1
import { crypto_derive } from './crypto_derive'
import { crypto_sign } from './crypto_sign'
-import { crypto_verify } from './crypto_verify'
+import { crypto_verify_relaxed, crypto_verify_strict } from './crypto_verify'
-const PRIVATEKEY_BYTES: i32 = 32
-const PUBLICKEY_BYTES: i32 = 32
-const SIGNATURE_BYTES: i32 = 64
+export const BLOCKHASH_BYTELENGTH: i32 = 32
+export const KEY_BYTELENGTH: i32 = 32
+export const SIGNATURE_BYTELENGTH: i32 = 64
+
+export const MESSAGE_BUFFER_BYTELENGTH: i32 = BLOCKHASH_BYTELENGTH << 10
+export const PRV_BUFFER_BYTELENGTH: i32 = KEY_BYTELENGTH
+export const PUB_BUFFER_BYTELENGTH: i32 = KEY_BYTELENGTH
+export const OUTPUT_BUFFER_BYTELENGTH: i32 = 1 << 10
+export const SIGNATURE_BUFFER_BYTELENGTH: i32 = SIGNATURE_BYTELENGTH << 10
// Static I/O buffers
-export const INPUT_BUFFER_BYTES: i32 = SIGNATURE_BYTES + PUBLICKEY_BYTES
-export const MESSAGE_BUFFER_BYTES: i32 = 32768
-export const OUTPUT_BUFFER_BYTES: i32 = SIGNATURE_BYTES
-const INPUT_BUFFER = new StaticArray<u8>(INPUT_BUFFER_BYTES)
-const MESSAGE_BUFFER = new StaticArray<u8>(MESSAGE_BUFFER_BYTES)
-const OUTPUT_BUFFER = new StaticArray<u8>(OUTPUT_BUFFER_BYTES)
-
-/** Returns the pointer to the static input buffer (96 bytes). */
-export function getInputPointer (): usize {
- return changetype<usize>(INPUT_BUFFER)
-}
+const MESSAGE_BUFFER = new StaticArray<u8>(MESSAGE_BUFFER_BYTELENGTH)
+const OUTPUT_BUFFER = new StaticArray<u8>(OUTPUT_BUFFER_BYTELENGTH)
+const PRV_BUFFER = new StaticArray<u8>(PRV_BUFFER_BYTELENGTH)
+const PUB_BUFFER = new StaticArray<u8>(PUB_BUFFER_BYTELENGTH)
+const SIGNATURE_BUFFER = new StaticArray<u8>(SIGNATURE_BUFFER_BYTELENGTH)
-/** Returns the pointer to the static message buffer (32 KiB). */
+/** Returns the pointer to the static message input buffer (32 KiB). */
export function getMessagePointer (): usize {
return changetype<usize>(MESSAGE_BUFFER)
}
-/** Returns the pointer to the static output buffer (64 bytes). */
+/** Returns the pointer to the static output buffer (1024 bytes). */
export function getOutputPointer (): usize {
return changetype<usize>(OUTPUT_BUFFER)
}
-const derive_prv = new StaticArray<u8>(PRIVATEKEY_BYTES)
-const derive_pub = new StaticArray<u8>(PUBLICKEY_BYTES)
+/** Returns the pointer to the static private key input buffer (32 bytes). */
+export function getPrivateKeyPointer (): usize {
+ return changetype<usize>(PRV_BUFFER)
+}
+
+/** Returns the pointer to the static public key input buffer (32 bytes). */
+export function getPublicKeyPointer (): usize {
+ return changetype<usize>(PUB_BUFFER)
+}
+
+/** Returns the pointer to the static signature input buffer (64 bytes). */
+export function getSignaturePointer (): usize {
+ return changetype<usize>(SIGNATURE_BUFFER)
+}
+
+const derive_prv = new StaticArray<u8>(KEY_BYTELENGTH)
+const derive_pub = new StaticArray<u8>(KEY_BYTELENGTH)
/**
* Derive a 32-byte Nano public key from a 32-byte private key. Parameters are
* read as bytes from the input buffer in the following order:
derive_pub.fill(0)
// Copy input buffer to local parameterss, then clear input buffer
- memory.copy(changetype<usize>(derive_prv), changetype<usize>(INPUT_BUFFER), PRIVATEKEY_BYTES)
- INPUT_BUFFER.fill(0)
+ memory.copy(changetype<usize>(derive_prv), changetype<usize>(PRV_BUFFER), KEY_BYTELENGTH)
+ PRV_BUFFER.fill(0)
// Derive, then clear local input
crypto_derive(derive_pub, derive_prv)
// Clear output buffer of prior data, then copy local result to output buffer
OUTPUT_BUFFER.fill(0)
- memory.copy(changetype<usize>(OUTPUT_BUFFER), changetype<usize>(derive_pub), PUBLICKEY_BYTES)
+ memory.copy(changetype<usize>(OUTPUT_BUFFER), changetype<usize>(derive_pub), KEY_BYTELENGTH)
// Clear local result
derive_pub.fill(0)
}
-const sign_prv = new StaticArray<u8>(PRIVATEKEY_BYTES)
-const sign_pub = new StaticArray<u8>(PUBLICKEY_BYTES)
-const sign_sig = new StaticArray<u8>(SIGNATURE_BYTES)
+const sign_prv = new StaticArray<u8>(KEY_BYTELENGTH)
+const sign_pub = new StaticArray<u8>(KEY_BYTELENGTH)
+const sign_sig = new StaticArray<u8>(SIGNATURE_BYTELENGTH)
/**
* Sign up to 32 KiB of data using a 64-byte secret key. Parameters are read as
* bytes from the input buffer in the following order:
* @param {i32} mlen Byte length of message to be signed, up to 32768
*/
export function sign (mlen: i32): void {
- if (mlen < 0 || mlen > MESSAGE_BUFFER_BYTES) {
- INPUT_BUFFER.fill(0)
+ if (mlen < 0 || mlen > MESSAGE_BUFFER_BYTELENGTH) {
+ PRV_BUFFER.fill(0)
+ PUB_BUFFER.fill(0)
MESSAGE_BUFFER.fill(0)
throw new Error('invalid message length')
}
sign_pub.fill(0)
sign_sig.fill(0)
- // Copy input buffer to local parameters, then clear input buffer
- memory.copy(changetype<usize>(sign_prv), changetype<usize>(INPUT_BUFFER), PRIVATEKEY_BYTES)
- memory.copy(changetype<usize>(sign_pub), changetype<usize>(INPUT_BUFFER) + PRIVATEKEY_BYTES, PUBLICKEY_BYTES)
- INPUT_BUFFER.fill(0)
+ // Copy input buffers to local parameters, then clear input buffer
+ memory.copy(changetype<usize>(sign_prv), changetype<usize>(PRV_BUFFER), KEY_BYTELENGTH)
+ PRV_BUFFER.fill(0)
+ memory.copy(changetype<usize>(sign_pub), changetype<usize>(PUB_BUFFER), KEY_BYTELENGTH)
+ PUB_BUFFER.fill(0)
// Sign message from buffer, then clear local input
crypto_sign(sign_sig, MESSAGE_BUFFER, mlen, sign_prv, sign_pub)
// Clear output buffer of prior data, then copy local result to output buffer
OUTPUT_BUFFER.fill(0)
- memory.copy(changetype<usize>(OUTPUT_BUFFER), changetype<usize>(sign_sig), SIGNATURE_BYTES)
+ memory.copy(changetype<usize>(OUTPUT_BUFFER), changetype<usize>(sign_sig), SIGNATURE_BYTELENGTH)
// Clear local result
sign_sig.fill(0)
}
-const verify_pub = new StaticArray<u8>(PUBLICKEY_BYTES)
-const verify_sig = new StaticArray<u8>(SIGNATURE_BYTES)
+const verify_pub = new StaticArray<u8>(KEY_BYTELENGTH)
+const verify_sig = new StaticArray<u8>(SIGNATURE_BYTELENGTH)
/**
* Verify a 64-byte detached signature for a variable-length message against a
* 32-byte public key. Parameters are read as bytes from the input buffer in the
* @returns {boolean} True if message was signed by public key's private key
*/
export function verify (mlen: i32): i32 {
- if (mlen < 0 || mlen > MESSAGE_BUFFER_BYTES) {
- INPUT_BUFFER.fill(0)
+ if (mlen < 0 || mlen > MESSAGE_BUFFER_BYTELENGTH) {
+ PRV_BUFFER.fill(0)
+ PUB_BUFFER.fill(0)
+ MESSAGE_BUFFER.fill(0)
+ throw new Error('invalid message length')
+ }
+
+ // Clear local buffers
+ verify_pub.fill(0)
+ verify_sig.fill(0)
+
+ // Copy input buffer to local parameters, then clear input buffer
+ memory.copy(changetype<usize>(verify_pub), changetype<usize>(PUB_BUFFER), KEY_BYTELENGTH)
+ PUB_BUFFER.fill(0)
+ memory.copy(changetype<usize>(verify_sig), changetype<usize>(SIGNATURE_BUFFER), SIGNATURE_BYTELENGTH)
+ SIGNATURE_BUFFER.fill(0)
+
+ // Verify message from buffer, then clear local input
+ const verified = crypto_verify_strict(verify_sig, MESSAGE_BUFFER, mlen, verify_pub)
+ MESSAGE_BUFFER.fill(0)
+ verify_pub.fill(0)
+ verify_sig.fill(0)
+
+ // Output buffer not used, so just return
+ return verified
+}
+
+
+const verify_blocks_pub = new StaticArray<u8>(KEY_BYTELENGTH)
+const verify_blocks_sig = new StaticArray<u8>(SIGNATURE_BYTELENGTH)
+/**
+ * Verify a 64-byte detached signature for a 32-byte Nano block hash against a
+ * 32-byte public key. Parameters are read as bytes from the input buffer in the
+ * following order:
+ *
+ * - `[0,63]: signature`
+ * - `[64,95]: public key`
+ *
+ * The message buffer can hold up 1024 block hashes to verify many signatures
+ * for a single public key.
+ * @param {u64} mlen Byte length of message that was signed, up to 32768
+ * @returns {boolean} True if message was signed by public key's private key
+ */
+export function verify_blocks (mlen: i32): i32 {
+ if (mlen < 0 || mlen > MESSAGE_BUFFER_BYTELENGTH) {
+ PRV_BUFFER.fill(0)
+ PUB_BUFFER.fill(0)
MESSAGE_BUFFER.fill(0)
throw new Error('invalid message length')
}
verify_sig.fill(0)
// Copy input buffer to local parameters, then clear input buffer
- memory.copy(changetype<usize>(verify_sig), changetype<usize>(INPUT_BUFFER), SIGNATURE_BYTES)
- memory.copy(changetype<usize>(verify_pub), changetype<usize>(INPUT_BUFFER) + SIGNATURE_BYTES, PUBLICKEY_BYTES)
- INPUT_BUFFER.fill(0)
+ memory.copy(changetype<usize>(verify_sig), changetype<usize>(PUB_BUFFER), SIGNATURE_BYTELENGTH)
+ PUB_BUFFER.fill(0)
+ memory.copy(changetype<usize>(verify_pub), changetype<usize>(SIGNATURE_BUFFER), KEY_BYTELENGTH)
+ SIGNATURE_BUFFER.fill(0)
// Verify message from buffer, then clear local input
- const verified = crypto_verify(verify_sig, MESSAGE_BUFFER, mlen, verify_pub)
+ const verified = crypto_verify_relaxed(verify_sig, MESSAGE_BUFFER, mlen, verify_pub)
MESSAGE_BUFFER.fill(0)
verify_pub.fill(0)
verify_sig.fill(0)
+++ /dev/null
-//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
-//! SPDX-License-Identifier: GPL-3.0-or-later
-
-import { run } from './lib/host'
-
-/**
- * Asynchronous Nano public key derivation using WebAssembly.
- * @param {Uint8Array<ArrayBuffer>} k - 64-character private key hex string
- * @returns Promise for 64-character public key hex string
- */
-export async function deriveAsync (privateKey: Uint8Array<ArrayBuffer>): Promise<Uint8Array<ArrayBuffer>>
-/**
- * Asynchronous Nano public key derivation using WebAssembly.
- * @param {string} k - 64-character private key hex string
- * @returns Promise for 64-character public key hex string
- */
-export async function deriveAsync (privateKey: string): Promise<string>
-export async function deriveAsync (privateKey: string | Uint8Array<ArrayBuffer>): Promise<string | Uint8Array<ArrayBuffer>> {
- return run({ action: 'derive', privateKey })
-}
-
-/**
- * Asynchronous signing using WebAssembly. To sign Nano blocks, the message
- * should be a 64-character block hash.
- * @param {Uint8Array<ArrayBuffer>} m - Variable-length message hex string up to 65536 characters
- * @param {Uint8Array<ArrayBuffer>} k - 128-character secret key (prv + pub) hex string
- * @returns Promise for 128-character detached signature hex string
- */
-export async function signAsync (message: Uint8Array<ArrayBuffer>, secretKey: Uint8Array<ArrayBuffer>): Promise<Uint8Array<ArrayBuffer>>
-/**
- * Asynchronous signing using WebAssembly. To sign Nano blocks, the message
- * should be a 64-character block hash.
- * @param {Uint8Array<ArrayBuffer>} m - Variable-length message hex string up to 65536 characters
- * @param {Uint8Array<ArrayBuffer>} k - 128-character secret key (prv + pub) hex string
- * @returns Promise for 128-character detached signature hex string
- */
-export async function signAsync (message: string, secretKey: string): Promise<string>
-export async function signAsync (message: string | Uint8Array<ArrayBuffer>, secretKey: string | Uint8Array<ArrayBuffer>): Promise<string | Uint8Array<ArrayBuffer>> {
- return run({ action: 'sign', message, secretKey })
-}
-
-/**
- * Asynchronous signature verification using WebAssembly. To verify Nano block
- * signatures, the message should be a 64-character block hash.
- * @param {Uint8Array<ArrayBuffer>} s - 128-character detached signature hex string
- * @param {Uint8Array<ArrayBuffer>} m - Variable-length message hex string to up 65536 characters
- * @param {Uint8Array<ArrayBuffer>} k - 64-character public key hex string
- * @returns Promise resolving to true if signature matches block hash and public key, else false
- */
-export async function verifyAsync (signature: Uint8Array<ArrayBuffer>, message: Uint8Array<ArrayBuffer>, publicKey: Uint8Array<ArrayBuffer>): Promise<boolean>
-/**
- * Asynchronous signature verification using WebAssembly. To verify Nano block
- * signatures, the message should be a 64-character block hash.
- * @param {Uint8Array<ArrayBuffer>} s - 128-character detached signature hex string
- * @param {Uint8Array<ArrayBuffer>} m - Variable-length message hex string to up 65536 characters
- * @param {Uint8Array<ArrayBuffer>} k - 64-character public key hex string
- * @returns Promise resolving to true if signature matches block hash and public key, else false
- */
-export async function verifyAsync (signature: string, message: string, publicKey: string): Promise<boolean>
-export async function verifyAsync (signature: string | Uint8Array<ArrayBuffer>, message: string | Uint8Array<ArrayBuffer>, publicKey: string | Uint8Array<ArrayBuffer>): Promise<boolean> {
- return run({ action: 'verify', signature, message, publicKey })
-}
//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
-export { deriveAsync, signAsync, verifyAsync } from './async'
-export { derive, sign, verify } from './sync'
+import * as nano25519 from './lib'
+
+/**
+ * Nano public key derivation using WebAssembly.
+ * @param {Uint8Array<ArrayBuffer>} prv - 32-byte private key
+ * @returns 32-byte public key
+ */
+export function derive (prv: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>
+/**
+ * Nano public key derivation using WebAssembly.
+ * @param {string} prv - 64-character hexadecimal private key
+ * @returns 64-character hexadecimal public key
+ */
+export function derive (prv: string): string
+/**
+ * Nano public key derivation using WebAssembly. Instead of allocating an output
+ * buffer internally for the return value, public key bytes are written to the
+ * the user-supplied output buffer.
+ * @param {Uint8Array<ArrayBuffer>} prv - 32-byte private key
+ * @param {Uint8Array<ArrayBuffer>} out - buffer to receive 32-byte public key
+ */
+export function derive (prv: Uint8Array<ArrayBuffer>, out: Uint8Array<ArrayBuffer>): void
+export function derive (prv: string | Uint8Array<ArrayBuffer>, out?: Uint8Array<ArrayBuffer>): string | Uint8Array<ArrayBuffer> | void {
+ return nano25519.derive(prv, out)
+}
+
+/**
+ * Signing using WebAssembly. To sign Nano blocks, the message should be a
+ * 32-byte block hash.
+ * @param {Uint8Array<ArrayBuffer>} msg - Variable-byte-length message up to 32 KiB
+ * @param {Uint8Array<ArrayBuffer>} prv - 32-byte private key
+ * @param {Uint8Array<ArrayBuffer>} pub - 32-byte public key
+ * @returns 64-byte detached signature
+ */
+export function sign (msg: Uint8Array<ArrayBuffer>, prv: Uint8Array<ArrayBuffer>, pub: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>
+/**
+ * Signing using WebAssembly. To sign Nano blocks, the message should be a
+ * 64-character hexadecimal block hash.
+ * @param {string} msg - Variable-length hexadecimal message up to 32 KiB
+ * @param {string} prv - 64-character hexadecimal private key
+ * @param {string} pub - 64-character hexadecimal public key
+ * @returns 128-character hexadecimal detached signature
+ */
+export function sign (msg: string, prv: string, pub: string): string
+/**
+ * Signing using WebAssembly. To sign Nano blocks, the message should be a
+ * 32-byte block hash. Instead of allocating an output buffer internally for the
+ * return value, signature bytes are written to the the user-supplied output
+ * buffer.
+ * @param {Uint8Array<ArrayBuffer>} msg - Variable-byte-length message up to 32 KiB
+ * @param {Uint8Array<ArrayBuffer>} prv - 32-byte private key
+ * @param {Uint8Array<ArrayBuffer>} pub - 32-byte public key
+ * @param {Uint8Array<ArrayBuffer>} out - buffer to receive 64-byte detached signature
+ */
+export function sign (msg: Uint8Array<ArrayBuffer>, prv: Uint8Array<ArrayBuffer>, pub: Uint8Array<ArrayBuffer>, out: Uint8Array<ArrayBuffer>): void
+export function sign (msg: string | Uint8Array<ArrayBuffer>, prv: string | Uint8Array<ArrayBuffer>, pub: string | Uint8Array<ArrayBuffer>, out?: Uint8Array<ArrayBuffer>): string | Uint8Array<ArrayBuffer> | void {
+ return nano25519.sign(msg, prv, pub, out)
+}
+
+/**
+ * Signature verification using WebAssembly. To verify Nano block signatures,
+ * use `verify_blocks()` instead.
+ * @param {Uint8Array<ArrayBuffer>} sig - 64-byte detached signature
+ * @param {Uint8Array<ArrayBuffer>} msg - Variable-byte-length message up to 32 KiB
+ * @param {Uint8Array<ArrayBuffer>} pub - 32-byte public key
+ * @returns true if signature matches block hash and public key, else false
+ */
+export function verify (sign: Uint8Array<ArrayBuffer>, msg: Uint8Array<ArrayBuffer>, pub: Uint8Array<ArrayBuffer>): boolean
+/**
+ * Signature verification using WebAssembly. To verify Nano block signatures,
+ * the message should be a 64-character block hash.
+ * @param {string} sig - 128-character hexadecimal detached signature
+ * @param {string} msg - Variable-length message up to 32 KiB
+ * @param {string} pub - 64-character hexadecimal public key
+ * @returns true if signature matches block hash and public key, else false
+ */
+export function verify (sign: string, msg: string, pub: string): boolean
+export function verify (sig: string | Uint8Array<ArrayBuffer>, msg: string | Uint8Array<ArrayBuffer>, pub: string | Uint8Array<ArrayBuffer>): boolean {
+ return nano25519.verify(sig, msg, pub)
+}
+
+/**
+ * Nano block signature verification using WebAssembly.
+ * @param {Uint8Array<ArrayBuffer>} sig - Buffer of up to 1024 64-byte detached signature (64 KiB)
+ * @param {Uint8Array<ArrayBuffer>} msg - Buffer of up to 1024 32-byte block hashes (32 KiB)
+ * @param {Uint8Array<ArrayBuffer>} pub - 32-byte public key
+ * @returns true if signature matches block hash and public key, else false
+ */
+export function verify_blocks (sign: Uint8Array<ArrayBuffer>, msg: Uint8Array<ArrayBuffer>, pub: Uint8Array<ArrayBuffer>): boolean
+/**
+ * Signature verification using WebAssembly. To verify Nano block signatures,
+ * the message should be a 64-character block hash.
+ * @param {string} sig - 128-character hexadecimal detached signature
+ * @param {string} msg - Variable-length message up to 32 KiB
+ * @param {string} pub - 64-character hexadecimal public key
+ * @returns true if signature matches block hash and public key, else false
+ */
+export function verify_blocks (sign: string, msg: string, pub: string): boolean
+export function verify_blocks (sig: string | Uint8Array<ArrayBuffer>, msg: string | Uint8Array<ArrayBuffer>, pub: string | Uint8Array<ArrayBuffer>): boolean {
+ return nano25519.verify_blocks(sig, msg, pub)
+}
+
--- /dev/null
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+import { clear, exports, isBytes, KEY_LEN, normalize, OUT_PTR, PRV_PTR } from './wasm'
+
+export function derive (prv: unknown, out?: unknown): string | Uint8Array<ArrayBuffer> | void {
+ if (typeof out !== 'undefined' && !(isBytes(out) && out.byteLength === KEY_LEN)) {
+ throw new TypeError('Derive output buffer must be 32-byte Uint8Array<ArrayBuffer>')
+ }
+ const privateKey = normalize('private key', KEY_LEN, KEY_LEN, prv)
+ const publicKey = new Uint8Array(KEY_LEN)
+ let buffer = new Uint8Array(exports.memory.buffer)
+ try {
+ for (let i = 0; i < KEY_LEN; i++) {
+ buffer[PRV_PTR + i] = privateKey[i]
+ }
+ exports.derive()
+ buffer = new Uint8Array(exports.memory.buffer)
+ for (let i = 0; i < KEY_LEN; i++) {
+ publicKey[i] = buffer[OUT_PTR + i]
+ }
+ if (typeof prv === 'string') {
+ let hex = ''
+ for (const byte of publicKey) {
+ hex += byte.toString(16).padStart(2, '0')
+ }
+ return hex
+ } else if (isBytes(prv) && out != null) {
+ out.set(publicKey)
+ publicKey.fill(0)
+ return
+ } else {
+ return publicKey
+ }
+ } finally {
+ clear(buffer)
+ privateKey.fill(0)
+ }
+}
+++ /dev/null
-//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
-//! SPDX-License-Identifier: GPL-3.0-or-later
-
-import { UUID } from 'node:crypto'
-import { Worker as NodeWorker } from 'node:worker_threads'
-
-type Action = 'derive' | 'sign' | 'start' | 'verify'
-
-//@ts-expect-error
-const nano25519_worker = NANO25519_WORKER
-
-/**
- * Host code for asynchronous Web Worker
- */
-let isWorkerReady: boolean = false
-let starting: Promise<unknown> | undefined
-let tasks: Map<string, Parameters<ConstructorParameters<PromiseConstructor>[0]>> = new Map()
-let worker: Worker | NodeWorker
-let url: string
-
-function isBytes (a: unknown): a is Uint8Array<ArrayBuffer> {
- return a instanceof Uint8Array && a.buffer instanceof ArrayBuffer
-}
-
-// Create worker module
-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
- worker.onerror = reset
- }
- NODE: {
- worker = new NodeWorker(nano25519_worker, {
- eval: true,
- stderr: false,
- stdout: false
- })
- worker.on('message', report)
- worker.on('error', reset)
- }
- console.log(`nano25519/async initialized`)
- isWorkerReady = true
- } catch (e: any) {
- isWorkerReady = false
- throw new Nano25519WorkerError(e)
- }
-}
-
-// Helper for environment-specific messaging to worker
-function post (id: UUID, data: Record<string, any> & Record<'action', Action>, transfer?: ArrayBuffer[]): void {
- data.id = id
- BROWSER: worker.postMessage(data, transfer)
- NODE: worker.postMessage({ data }, transfer)
-}
-
-// Parse and validate worker message
-function report (msg: { data: Record<string, unknown> }): void {
- const { data } = msg
- if (!('id' in data) || typeof data.id !== 'string') return
-
- const executor = tasks.get(data.id)
- if (executor == null) return
- const [ok, err] = executor
- tasks.delete(data.id)
-
- const { result } = data
- console.log('received result from worker')
- if (typeof result !== 'boolean' && typeof result !== 'string' && !isBytes(result)) {
- return err(`expected boolean, string, or bytes; received ${result?.constructor?.name ?? typeof result} '${result}`)
- }
- return ok(result)
-}
-
-// Reconstruct worker when errors occur
-function reset (): void {
- console.warn(`nano25519 encountered an error. Reinitializing...`)
- starting = undefined
- for (const [_, err] of tasks.values()) {
- err(new Nano25519WorkerError('worker reset, try again'))
- }
- tasks.clear()
- worker.terminate()
- init()
-}
-
-// Check that the worker is running and listening before sending messages
-async function start (): Promise<unknown> {
- return starting ??= new Promise((resolve, reject): void => {
- console.log('starting worker')
- if (!isWorkerReady) init()
- const id = crypto.randomUUID()
- tasks.set(id, [resolve, reject])
- post(id, { action: 'start' })
- })
-}
-
-// Send command and relevant data to nano25519 worker
-async function dispatch (data: Record<'action', Action> & Record<string, string | ArrayBuffer | Uint8Array<ArrayBuffer>>): Promise<unknown> {
- const id = crypto.randomUUID()
- const transfer: ArrayBuffer[] = []
- for (let k of Object.keys(data)) {
- if (isBytes(data[k])) {
- data[k] = data[k].slice().buffer
- transfer.push(data[k])
- }
- }
- console.log('sending data to worker')
- return new Promise((resolve, reject) => {
- tasks.set(id, [resolve, reject])
- post(id, data, transfer)
- })
-}
-
-export async function run (data: Record<'action', 'derive'> & Record<string, string | Uint8Array<ArrayBuffer>>): Promise<string | Uint8Array<ArrayBuffer>>
-export async function run (data: Record<'action', 'sign'> & Record<string, string | Uint8Array<ArrayBuffer>>): Promise<string | Uint8Array<ArrayBuffer>>
-export async function run (data: Record<'action', 'verify'> & Record<string, string | Uint8Array<ArrayBuffer>>): Promise<boolean>
-export async function run (data: Record<'action', 'derive' | 'sign' | 'verify'> & Record<string, string | Uint8Array<ArrayBuffer>>): Promise<boolean | string | Uint8Array<ArrayBuffer>> {
- try {
- 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) {
- starting = undefined
- throw new Nano25519WorkerError(e)
- }
-
- 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
- }
- break
- }
- case 'sign': {
- if ((isBytes(result) && result.byteLength === 64) || (typeof result === 'string' && /^[0-9a-f]{128}$/i.test(result))) {
- return result
- }
- break
- }
- case 'verify': {
- if (typeof result === 'boolean') {
- return result
- }
- break
- }
- }
- throw new Nano25519ResultError(data.action, result)
-}
-
-class Nano25519WorkerError extends Error {
- constructor (cause?: unknown) {
- super(`async process error`, { cause })
- }
-}
-class Nano25519ResultError extends Error {
- constructor (action: string, cause?: unknown) {
- super(`${action} result invalid`, { cause })
- }
-}
--- /dev/null
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+export { derive } from './derive'
+export { sign } from './sign'
+export { verify, verify_blocks } from './verify'
+
+++ /dev/null
-//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
-//! SPDX-License-Identifier: GPL-3.0-or-later
-
-//@ts-expect-error
-import nano25519_wasm from '../../build/nano25519.wasm'
-
-type Exports = {
- exports: {
- derive: () => void
- sign: (mlen: number) => void
- verify: (mlen: number) => number
- getInputPointer: () => number
- getMessagePointer: () => number
- getOutputPointer: () => number
- INPUT_BUFFER_BYTES: WebAssembly.Global
- MESSAGE_BUFFER_BYTES: WebAssembly.Global
- OUTPUT_BUFFER_BYTES: WebAssembly.Global
- memory: WebAssembly.Memory
- }
-}
-
-const mLen = 32768
-const wasm: Uint8Array<ArrayBuffer> = Uint8Array.from(nano25519_wasm)
-const module = new WebAssembly.Module(wasm)
-const { exports } = new WebAssembly.Instance(module, {
- env: {
- abort: (msg: any, file: any, row: any, col: any) => {
- const getString = (pointer: number): string | null => {
- const end = pointer + new Uint32Array(exports.memory.buffer)[pointer - 4 >>> 2] >>> 1
- const buf = new Uint16Array(exports.memory.buffer)
- let start = pointer >>> 1
- let string = ''
- while (end - start > 1024) {
- string += String.fromCharCode(...buf.subarray(start, start += 1024))
- }
- return string + String.fromCharCode(...buf.subarray(start, end))
- }
- // ~lib/builtins/abort(~lib/string/String | null?, ~lib/string/String | null?, u32?, u32?) => void
- msg >>>= 0
- file >>>= 0
- row >>>= 0
- col >>>= 0
- const message = `Nano25519WasmError: ${getString(msg)}, ${getString(file)}, row ${row}, col ${col}`
- throw new Error(message)
- }
- }
-}) as Exports
-const IN_LEN = exports.INPUT_BUFFER_BYTES.value
-const MSG_LEN = exports.MESSAGE_BUFFER_BYTES.value
-const OUT_LEN = exports.OUTPUT_BUFFER_BYTES.value
-const IN_PTR = exports.getInputPointer()
-const MSG_PTR = exports.getMessagePointer()
-const OUT_PTR = exports.getOutputPointer()
-
-export function derive (k: unknown, out?: unknown): string | Uint8Array<ArrayBuffer> | void {
- if (typeof out !== 'undefined' && !(isBytes(out) && out.byteLength === 32)) {
- throw new TypeError('Derive output buffer must be 32-byte Uint8Array<ArrayBuffer>')
- }
- const privateKey = new Uint8Array(32)
- const publicKey = new Uint8Array(32)
- let buffer = new Uint8Array(exports.memory.buffer)
- try {
- privateKey.set(normalize('private key', 32, 32, k))
- for (let i = 0; i < 32; i++) {
- buffer[IN_PTR + i] = privateKey[i]
- }
- exports.derive()
- buffer = new Uint8Array(exports.memory.buffer)
- for (let i = 0; i < 32; i++) {
- publicKey[i] = buffer[OUT_PTR + i]
- }
- if (typeof k === 'string') {
- let hex = ''
- for (const byte of publicKey) {
- hex += byte.toString(16).padStart(2, '0')
- }
- return hex
- } else if (isBytes(k) && out != null) {
- out.set(publicKey)
- publicKey.fill(0)
- return
- } else {
- return publicKey
- }
- } finally {
- clear(buffer)
- privateKey.fill(0)
- }
-}
-
-export function sign (m: unknown, k: unknown, s?: unknown): string | Uint8Array<ArrayBuffer> | void {
- if (typeof s !== 'undefined' && !(isBytes(s) && s.byteLength === 64)) {
- throw new TypeError('Sign output buffer must be 64-byte Uint8Array<ArrayBuffer>')
- }
- const secretKey = new Uint8Array(64)
- const signature = new Uint8Array(64)
- let buffer = new Uint8Array(exports.memory.buffer)
- try {
- secretKey.set(normalize('secret key', 64, 64, k))
- for (let i = 0; i < 64; i++) {
- buffer[IN_PTR + i] = secretKey[i]
- }
- const message = normalize('message', 0, mLen, m)
- for (let i = 0; i < message.byteLength; i++) {
- buffer[MSG_PTR + i] = message[i]
- }
- exports.sign(message.byteLength)
- buffer = new Uint8Array(exports.memory.buffer)
- for (let i = 0; i < 64; i++) {
- signature[i] = buffer[OUT_PTR + i]
- }
- if (typeof k === 'string') {
- let hex = ''
- for (const byte of signature) {
- hex += byte.toString(16).padStart(2, '0')
- }
- return hex
- } else if (isBytes(k) && s != null) {
- s.set(signature)
- signature.fill(0)
- return
- } else {
- return signature
- }
- } finally {
- clear(buffer)
- secretKey.fill(0)
- }
-}
-
-export function verify (s: unknown, m: unknown, k: unknown): boolean {
- let buffer = new Uint8Array(exports.memory.buffer)
- try {
- const signature = normalize('signature', 64, 64, s)
- const message = normalize('message', 0, mLen, m)
- const publicKey = normalize('public key', 32, 32, k)
- for (let i = 0; i < message.byteLength; i++) {
- buffer[MSG_PTR + i] = message[i]
- }
- for (let i = 0; i < 64; i++) {
- buffer[IN_PTR + i] = signature[i]
- }
- for (let i = 0; i < 32; i++) {
- buffer[IN_PTR + 64 + i] = publicKey[i]
- }
- const v = exports.verify(message.byteLength)
- return v === 0
- } finally {
- clear(buffer)
- }
-}
-
-function clear (memory: Uint8Array): void {
- memory.fill(0, IN_PTR, IN_LEN)
- memory.fill(0, MSG_PTR, MSG_LEN)
- memory.fill(0, OUT_PTR, OUT_LEN)
-}
-
-function isBytes (a: unknown): a is Uint8Array<ArrayBuffer> {
- return a instanceof Uint8Array && a.buffer instanceof ArrayBuffer
-}
-
-function normalize (name: string, byteLengthMin: number, byteLengthMax: number, value: unknown): Uint8Array<ArrayBuffer> {
- if (typeof name !== 'string') {
- throw new TypeError(`Invalid name ${name}`)
- }
- if (typeof byteLengthMin !== 'number') {
- throw new TypeError(`Invalid minimum byte length for ${name}`)
- }
- if (typeof byteLengthMax !== 'number') {
- throw new TypeError(`Invalid maximum byte length for ${name}`)
- }
- if (typeof value === 'string') {
- if (/[^0-9a-f]/i.test(value)) {
- throw new TypeError(`Invalid hexadecimal characters in ${name}`)
- }
- if (value.length & 1 || value.length < (byteLengthMin << 1) || value.length > (byteLengthMax << 1)) {
- throw new TypeError(`Invalid hexadecimal length ${value.length} for ${name}`)
- }
- value = new Uint8Array(value.match(/[0-9a-f]{2}/gi)?.map(b => parseInt(b, 16)) || [])
- }
- if (value instanceof ArrayBuffer) {
- value = new Uint8Array(value)
- }
- if (!(value instanceof Uint8Array)) {
- throw new TypeError(`${name} must be Uint8Array`)
- }
- if (!('buffer' in value && value.buffer instanceof ArrayBuffer)) {
- throw new TypeError(`${name} must be backed by an ArrayBuffer`)
- }
- if (value.byteLength < byteLengthMin) {
- throw new TypeError(`${name} must be at least ${byteLengthMin} bytes`)
- }
- if (value.byteLength > byteLengthMax) {
- throw new TypeError(`${name} must be no more than ${byteLengthMax} bytes`)
- }
- return value as Uint8Array<ArrayBuffer>
-}
--- /dev/null
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+import { clear, exports, isBytes, KEY_LEN, MSG_BUF_LEN, MSG_PTR, normalize, OUT_PTR, PRV_PTR, PUB_PTR, SIG_LEN } from './wasm'
+
+export function sign (msg: unknown, prv: unknown, pub: unknown, out?: unknown): string | Uint8Array<ArrayBuffer> | void {
+ if (typeof out !== 'undefined' && !(isBytes(out) && out.byteLength === 64)) {
+ throw new TypeError('Sign output buffer must be 64-byte Uint8Array<ArrayBuffer>')
+ }
+ const message = normalize('message', 0, MSG_BUF_LEN, msg)
+ const privateKey = normalize('private key', KEY_LEN, KEY_LEN, prv)
+ const publicKey = normalize('public key', KEY_LEN, KEY_LEN, pub)
+ const signature = new Uint8Array(SIG_LEN)
+ let buffer = new Uint8Array(exports.memory.buffer)
+ try {
+ for (let i = 0; i < message.byteLength; i++) {
+ buffer[MSG_PTR + i] = message[i]
+ }
+ for (let i = 0; i < KEY_LEN; i++) {
+ buffer[PRV_PTR + i] = privateKey[i]
+ }
+ for (let i = 0; i < KEY_LEN; i++) {
+ buffer[PUB_PTR + i] = publicKey[i]
+ }
+ exports.sign(message.byteLength)
+ buffer = new Uint8Array(exports.memory.buffer)
+ for (let i = 0; i < SIG_LEN; i++) {
+ signature[i] = buffer[OUT_PTR + i]
+ }
+ if (typeof prv === 'string') {
+ let hex = ''
+ for (const byte of signature) {
+ hex += byte.toString(16).padStart(2, '0')
+ }
+ return hex
+ } else if (isBytes(prv) && out != null) {
+ out.set(signature)
+ signature.fill(0)
+ return
+ } else {
+ return signature
+ }
+ } finally {
+ clear(buffer)
+ privateKey.fill(0)
+ publicKey.fill(0)
+ }
+}
--- /dev/null
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+import { clear, exports, KEY_LEN, MSG_BUF_LEN, MSG_PTR, normalize, PUB_PTR, SIG_LEN, SIG_PTR } from './wasm'
+
+export function verify (sig: unknown, msg: unknown, pub: unknown): boolean {
+ let buffer = new Uint8Array(exports.memory.buffer)
+ try {
+ const message = normalize('message', 0, MSG_BUF_LEN, msg)
+ const publicKey = normalize('public key', KEY_LEN, KEY_LEN, pub)
+ const signature = normalize('signature', SIG_LEN, SIG_LEN, sig)
+ for (let i = 0; i < message.byteLength; i++) {
+ buffer[MSG_PTR + i] = message[i]
+ }
+ for (let i = 0; i < KEY_LEN; i++) {
+ buffer[PUB_PTR + i] = publicKey[i]
+ }
+ for (let i = 0; i < SIG_LEN; i++) {
+ buffer[SIG_PTR + i] = signature[i]
+ }
+ const v = exports.verify(message.byteLength)
+ return v === 0
+ } finally {
+ clear(buffer)
+ }
+}
+
+export function verify_blocks (sig: unknown, msg: unknown, pub: unknown): boolean {
+ let buffer = new Uint8Array(exports.memory.buffer)
+ try {
+ const message = normalize('message', 0, MSG_BUF_LEN, msg)
+ const publicKey = normalize('public key', KEY_LEN, KEY_LEN, pub)
+ const signature = normalize('signature', SIG_LEN, SIG_LEN, sig)
+ for (let i = 0; i < message.byteLength; i++) {
+ buffer[MSG_PTR + i] = message[i]
+ }
+ for (let i = 0; i < KEY_LEN; i++) {
+ buffer[PUB_PTR + i] = publicKey[i]
+ }
+ for (let i = 0; i < SIG_LEN; i++) {
+ buffer[SIG_PTR + i] = signature[i]
+ }
+ const v = exports.verify(message.byteLength)
+ return v === 0
+ } finally {
+ clear(buffer)
+ }
+}
--- /dev/null
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+//@ts-expect-error
+import nano25519_wasm from '../../build/nano25519.wasm'
+
+type Exports = {
+ exports: {
+ derive: () => void
+ sign: (mlen: number) => void
+ verify: (mlen: number) => number
+ getMessagePointer: () => number
+ getOutputPointer: () => number
+ getPrivateKeyPointer: () => number
+ getPublicKeyPointer: () => number
+ getSignaturePointer: () => number
+ BLOCKHASH_BYTELENGTH: WebAssembly.Global
+ KEY_BYTELENGTH: WebAssembly.Global
+ SIGNATURE_BYTELENGTH: WebAssembly.Global
+ MESSAGE_BUFFER_BYTELENGTH: WebAssembly.Global
+ OUTPUT_BUFFER_BYTELENGTH: WebAssembly.Global
+ PRV_BUFFER_BYTELENGTH: WebAssembly.Global
+ PUB_BUFFER_BYTELENGTH: WebAssembly.Global
+ SIGNATURE_BUFFER_BYTELENGTH: WebAssembly.Global
+ memory: WebAssembly.Memory
+ }
+}
+
+const wasm: Uint8Array<ArrayBuffer> = Uint8Array.from(nano25519_wasm)
+const module = new WebAssembly.Module(wasm)
+
+export const { exports } = new WebAssembly.Instance(module, {
+ env: {
+ abort: (msg: any, file: any, row: any, col: any): never => {
+ const getString = (pointer: number): string | null => {
+ const end = pointer + new Uint32Array(exports.memory.buffer)[pointer - 4 >>> 2] >>> 1
+ const buf = new Uint16Array(exports.memory.buffer)
+ let start = pointer >>> 1
+ let string = ''
+ while (end - start > 1024) {
+ string += String.fromCharCode(...buf.subarray(start, start += 1024))
+ }
+ return string + String.fromCharCode(...buf.subarray(start, end))
+ }
+ // ~lib/builtins/abort(~lib/string/String | null?, ~lib/string/String | null?, u32?, u32?) => void
+ msg >>>= 0
+ file >>>= 0
+ row >>>= 0
+ col >>>= 0
+ const message = `Nano25519WasmError: ${getString(msg)}, ${getString(file)}, row ${row}, col ${col}`
+ throw new Error(message)
+ }
+ }
+}) as Exports
+
+export const BLK_LEN = exports.BLOCKHASH_BYTELENGTH.value
+export const KEY_LEN = exports.KEY_BYTELENGTH.value
+export const SIG_LEN = exports.SIGNATURE_BYTELENGTH.value
+
+export const MSG_BUF_LEN = exports.MESSAGE_BUFFER_BYTELENGTH.value
+export const OUT_BUF_LEN = exports.OUTPUT_BUFFER_BYTELENGTH.value
+export const PRV_BUF_LEN = exports.PRV_BUFFER_BYTELENGTH.value
+export const PUB_BUF_LEN = exports.PUB_BUFFER_BYTELENGTH.value
+export const SIG_BUF_LEN = exports.SIGNATURE_BUFFER_BYTELENGTH.value
+
+export const MSG_PTR = exports.getMessagePointer()
+export const OUT_PTR = exports.getOutputPointer()
+export const PRV_PTR = exports.getPrivateKeyPointer()
+export const PUB_PTR = exports.getPublicKeyPointer()
+export const SIG_PTR = exports.getSignaturePointer()
+
+export function clear (memory: Uint8Array): void {
+ memory.fill(0, MSG_PTR, MSG_BUF_LEN)
+ memory.fill(0, OUT_PTR, OUT_BUF_LEN)
+ memory.fill(0, PRV_PTR, KEY_LEN)
+ memory.fill(0, PUB_PTR, KEY_LEN)
+ memory.fill(0, SIG_PTR, SIG_LEN)
+}
+
+export function isBytes (a: unknown): a is Uint8Array<ArrayBuffer> {
+ return a instanceof Uint8Array && a.buffer instanceof ArrayBuffer
+}
+
+export function normalize (name: string, byteLengthMin: number, byteLengthMax: number, value: unknown): Uint8Array<ArrayBuffer> {
+ if (typeof name !== 'string') {
+ throw new TypeError(`Invalid name ${name}`)
+ }
+ if (typeof byteLengthMin !== 'number') {
+ throw new TypeError(`Invalid minimum byte length for ${name}`)
+ }
+ if (typeof byteLengthMax !== 'number') {
+ throw new TypeError(`Invalid maximum byte length for ${name}`)
+ }
+
+ if (typeof value === 'string') {
+ if (/[^0-9a-f]/i.test(value)) {
+ throw new TypeError(`Invalid hexadecimal characters in ${name}`)
+ }
+ if (value.length & 1 || value.length < (byteLengthMin << 1) || value.length > (byteLengthMax << 1)) {
+ throw new TypeError(`Invalid hexadecimal length ${value.length} for ${name}`)
+ }
+ return new Uint8Array(value.match(/[0-9a-f]{2}/gi)?.map(b => parseInt(b, 16)) || [])
+ }
+
+ if (!(value instanceof ArrayBuffer || value instanceof Uint8Array)) {
+ throw new TypeError(`${name} must be Uint8Array or ArrayBuffer`)
+ }
+ const bytes = new Uint8Array(value.slice())
+ if (bytes.byteLength < byteLengthMin) {
+ throw new TypeError(`${name} must be at least ${byteLengthMin} bytes`)
+ }
+ if (bytes.byteLength > byteLengthMax) {
+ throw new TypeError(`${name} must be no more than ${byteLengthMax} bytes`)
+ }
+ return bytes
+}
+++ /dev/null
-//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
-//! SPDX-License-Identifier: GPL-3.0-or-later
-
-import { UUID } from 'node:crypto'
-import { MessagePort as NodeMessagePort } from 'node:worker_threads'
-import { derive, sign, verify } from './nano25519'
-
-type Action = 'derive' | 'sign' | 'start' | 'verify'
-
-type Data = {
- id: UUID
- action: string
- message?: string | ArrayBuffer
- privateKey?: string
- publicKey?: string
- secretKey?: string
- signature?: string
-}
-
-let host: NodeMessagePort | null = null
-
-/**
- * Parses inbound data when nano25519 is started as a Web Worker. Only called
- * by functions in `async` module.
- * @param {object} message.data - Worker commands and related data
- */
-function listener (message: unknown): void {
- NODE: if (host == null) return queueMicrotask(() => listener(message))
- if (message == null
- || typeof message !== 'object'
- || !('data' in message)
- || message.data == null
- || typeof message.data !== 'object'
- || !('id' in message.data)
- || typeof message.data.id !== 'string'
- || !('action' in message.data)
- || typeof message.data.action !== 'string'
- ) return
- let result: undefined | boolean | string | Uint8Array<ArrayBuffer>
- let id: undefined | UUID
- try {
- const data: Data = message.data as object & { id: UUID, action: Action }
- id = data.id
-
- 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
- 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
- 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: any) {
- result = JSON.stringify(err?.message ?? err ?? 'unknown error in nano25519 worker listener')
- } finally {
- const data = { id, result }
- BROWSER: postMessage(data)
- NODE: host?.postMessage({ data })
- }
-}
-
-BROWSER: addEventListener('message', listener)
-NODE: {
- if (host == null) {
- import('node:worker_threads')
- .then(({ parentPort }): void => {
- host = parentPort
- host?.on('message', listener)
- })
- }
-}
+++ /dev/null
-//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
-//! SPDX-License-Identifier: GPL-3.0-or-later
-
-import * as nano25519 from './lib/nano25519'
-
-/**
- * Nano public key derivation using WebAssembly.
- * @param {string} k - 64-character hexadecimal private key
- * @returns 64-character hexadecimal public key
- */
-export function derive (k: string): string
-/**
- * Nano public key derivation using WebAssembly.
- * @param {Uint8Array<ArrayBuffer>} k - 32-byte private key
- * @returns 32-byte public key
- */
-export function derive (k: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>
-/**
- * Nano public key derivation using WebAssembly. Instead of allocating an output
- * buffer internally for the return value, public key bytes are written to the
- * the user-supplied output buffer.
- * @param {Uint8Array<ArrayBuffer>} prv - 32-byte private key
- * @param {Uint8Array<ArrayBuffer>} pub - buffer to receive 32-byte public key
- */
-export function derive (k: Uint8Array<ArrayBuffer>, out: Uint8Array<ArrayBuffer>): void
-export function derive (k: string | Uint8Array<ArrayBuffer>, out?: Uint8Array<ArrayBuffer>): string | Uint8Array<ArrayBuffer> | void {
- return nano25519.derive(k, out)
-}
-
-/**
- * Signing using WebAssembly. To sign Nano blocks, the message should be a
- * 64-character hexadecimal block hash.
- * @param {string} m - Variable-length hexadecimal message up to 32 KiB
- * @param {string} k - 128-character hexadecimal secret key (prv + pub)
- * @returns 128-character hexadecimal detached signature
- */
-export function sign (m: string, k: string): string
-/**
- * Signing using WebAssembly. To sign Nano blocks, the message should be a
- * 32-byte block hash.
- * @param {Uint8Array<ArrayBuffer>} m - Variable-byte-length message up to 32 KiB
- * @param {Uint8Array<ArrayBuffer>} k - 64-byte secret key (prv + pub)
- * @returns 64-byte detached signature
- */
-export function sign (m: Uint8Array<ArrayBuffer>, k: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>
-/**
- * Signing using WebAssembly. To sign Nano blocks, the message should be a
- * 32-byte block hash. Instead of allocating an output buffer internally for the
- * return value, signature bytes are written to the the user-supplied output
- * buffer.
- * @param {Uint8Array<ArrayBuffer>} m - Variable-byte-length message up to 32 KiB
- * @param {Uint8Array<ArrayBuffer>} k - 64-byte secret key (prv + pub)
- * @param {Uint8Array<ArrayBuffer>} out - buffer to receive 64-byte detached signature
- */
-export function sign (m: Uint8Array<ArrayBuffer>, k: Uint8Array<ArrayBuffer>, out: Uint8Array<ArrayBuffer>): void
-export function sign (m: string | Uint8Array<ArrayBuffer>, k: string | Uint8Array<ArrayBuffer>, out?: Uint8Array<ArrayBuffer>): string | Uint8Array<ArrayBuffer> | void {
- return nano25519.sign(m, k, out)
-}
-
-/**
- * Signature verification using WebAssembly. To verify Nano block signatures,
- * the message should be a 64-character block hash.
- * @param {string} s - 128-character hexadecimal detached signature
- * @param {string} m - Variable-length message up to 32 KiB
- * @param {string} k - 64-character hexadecimal public key
- * @returns true if signature matches block hash and public key, else false
- */
-export function verify (s: string, m: string, k: string): boolean
-/**
- * Signature verification using WebAssembly. To verify Nano block signatures,
- * the message should be a 32-byte block hash.
- * @param {Uint8Array<ArrayBuffer>} s - 64-byte detached signature
- * @param {Uint8Array<ArrayBuffer>} m - Variable-byte-length message up to 32 KiB
- * @param {Uint8Array<ArrayBuffer>} k - 32-byte public key
- * @returns true if signature matches block hash and public key, else false
- */
-export function verify (s: Uint8Array<ArrayBuffer>, m: Uint8Array<ArrayBuffer>, k: Uint8Array<ArrayBuffer>): boolean
-export function verify (s: string | Uint8Array<ArrayBuffer>, m: string | Uint8Array<ArrayBuffer>, k: string | Uint8Array<ArrayBuffer>): boolean {
- return nano25519.verify(s, m, k)
-}
//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
-import * as Nano25519 from 'nano25519'
-import { deriveAsync, signAsync, verifyAsync } from 'nano25519/async'
-import { derive, sign, verify } from 'nano25519/sync'
+import { derive, sign, verify } from 'nano25519'
import { NANO_ORG_VECTOR, PROBLEM_VECTOR, PYTHON_ED25519_BLAKE2B_VECTORS } from './vectors.mjs'
/**
passes += +test
failures += +!test
-// Check combined imports
-result = await Nano25519.deriveAsync(NANO_ORG_VECTOR.privateKeyBytes)
-test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
-check(`async derive from ${NANO_ORG_VECTOR.privateKey}`, test)
-passes += +test
-failures += +!test
-
-result = await Nano25519.signAsync(NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.secretKeyBytes)
-test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
-check(`async sign message ${NANO_ORG_VECTOR.blockHash}`, test)
-passes += +test
-failures += +!test
-
-result = await Nano25519.verifyAsync(NANO_ORG_VECTOR.signatureBytes, NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.publicKeyBytes)
-test = result === true
-check(`async verify signature ${NANO_ORG_VECTOR.signature}`, test)
-passes += +test
-failures += +!test
-
-result = Nano25519.derive(NANO_ORG_VECTOR.privateKeyBytes)
-test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
-check(`derive from ${NANO_ORG_VECTOR.privateKey}`, test)
-passes += +test
-failures += +!test
-
-result = Nano25519.sign(NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.secretKeyBytes)
-test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
-check(`sign message ${NANO_ORG_VECTOR.blockHash}`, test)
-passes += +test
-failures += +!test
-
-result = Nano25519.verify(NANO_ORG_VECTOR.signatureBytes, NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.publicKeyBytes)
-test = result === true
-check(`verify signature ${NANO_ORG_VECTOR.signature}`, test)
-passes += +test
-failures += +!test
-
-// Check async imports with string inputs
-result = await deriveAsync(NANO_ORG_VECTOR.privateKey)
-test = result.toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
-check(`async derive from private key string ${NANO_ORG_VECTOR.privateKey}`, test)
-passes += +test
-failures += +!test
-
-result = await signAsync(NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.secretKey)
-test = result.toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
-check(`async sign message string ${NANO_ORG_VECTOR.blockHash}`, test)
-passes += +test
-failures += +!test
-
-result = await verifyAsync(NANO_ORG_VECTOR.signature, NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.publicKey)
-test = result === true
-check(`async verify signature string ${NANO_ORG_VECTOR.signature}`, test)
-passes += +test
-failures += +!test
-
-// Check async imports with byte inputs
-result = await deriveAsync(NANO_ORG_VECTOR.privateKeyBytes)
-test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
-check(`async derive from private key bytes ${NANO_ORG_VECTOR.privateKey}`, test)
-passes += +test
-failures += +!test
-
-result = await signAsync(NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.secretKeyBytes)
-test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
-check(`async sign message bytes ${NANO_ORG_VECTOR.blockHash}`, test)
-passes += +test
-failures += +!test
-
-result = await verifyAsync(NANO_ORG_VECTOR.signatureBytes, NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.publicKeyBytes)
-test = result === true
-check(`async verify signature bytes ${NANO_ORG_VECTOR.signature}`, test)
-passes += +test
-failures += +!test
-
// Check sync imports with string inputs
result = derive(NANO_ORG_VECTOR.privateKey)
test = result.toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
]
},
"include": [
- "src/index.ts",
- "src/async.ts",
- "src/sync.ts"
+ "src/index.ts"
],
"exclude": [
"assembly",