From 44fc6520407c562af0d9cc5106dc210dcca1b32f Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Fri, 28 Aug 2026 10:54:58 -0700 Subject: [PATCH] Export constants for consumer use. Increase minimum message buffer allocation to a full page of 64 KiB. Increase initial memory accordingly. --- asconfig.json | 2 +- src/assembly/crypto_verify.ts | 6 ++--- src/assembly/index.ts | 23 +++++++++------- src/assembly/utils.ts | 11 ++++++++ src/index.ts | 2 ++ src/lib/derive.ts | 5 +++- src/lib/index.ts | 1 + src/lib/sign.ts | 5 +++- src/lib/verify.ts | 5 +++- src/lib/wasm.ts | 50 ++++++++++++++++++++++++----------- test/index.html | 4 +-- test/node.mjs | 39 +++++++++------------------ 12 files changed, 93 insertions(+), 60 deletions(-) diff --git a/asconfig.json b/asconfig.json index efb473e..f75849a 100644 --- a/asconfig.json +++ b/asconfig.json @@ -10,7 +10,7 @@ "bindings": "esm", "sourceMap": false, "debug": false, - "initialMemory": 2, + "initialMemory": 4, "runtime": "stub", "exportRuntime": false, "enable": [ diff --git a/src/assembly/crypto_verify.ts b/src/assembly/crypto_verify.ts index b903b14..8b3e9b2 100644 --- a/src/assembly/crypto_verify.ts +++ b/src/assembly/crypto_verify.ts @@ -41,7 +41,7 @@ export function crypto_verify_decodepubkey (pub: StaticArray): i32 { * * - S < 2²⁵³, instead of S < L * - Skip canonical and small-order checks on public key - * - Skip checks on r and sb_ah + * - Byte-for-byte comparison of `r` to `s` * * IMPORTANT: Callers MUST call `crypto_verify_decodepubkey` first in order to * set `A` for the scalar multiplication step before checking `sB = R + hA`. @@ -64,7 +64,7 @@ export function crypto_verify_relaxed (s: StaticArray, M: StaticArray, p ge_double_scalarmult_vartime_to_p3(sb_ah, h, A, S) ge_p3_tobytes(check_r, sb_ah) - return equalbytes(s, check_r, KEY_BYTELENGTH) - 1 + return equalbytes(s, check_r, 32) - 1 } /** @@ -105,5 +105,5 @@ export function crypto_verify_strict (s: StaticArray, M: StaticArray, ml return ge_has_small_order(check) - 1 // ge_p3_tobytes(check_r, sb_ah) - // return equalbytes(s, check_r, KEY_BYTELENGTH) - 1 + // return equalbytes(s, check_r, 32) - 1 } diff --git a/src/assembly/index.ts b/src/assembly/index.ts index c67eaba..1a13bc0 100644 --- a/src/assembly/index.ts +++ b/src/assembly/index.ts @@ -7,10 +7,13 @@ import { crypto_verify_decodepubkey, crypto_verify_relaxed, crypto_verify_strict export const BLOCKHASH_BYTELENGTH: i32 = 32 export const KEY_BYTELENGTH: i32 = 32 -export const MAX_VERIFY_BLOCKS: i32 = 32 -export const MAX_MESSAGE_BYTELENGTH: i32 = 1 << 15 export const SIGNATURE_BYTELENGTH: i32 = 64 -export const SIGNEDBLOCK_BYTELENGTH: i32 = BLOCKHASH_BYTELENGTH + SIGNATURE_BYTELENGTH + +const PAGE_BYTELENGTH: i32 = 1 << 16 +const SIGNEDBLOCK_BYTELENGTH: i32 = BLOCKHASH_BYTELENGTH + SIGNATURE_BYTELENGTH +export const MAX_VERIFY_BLOCKS: i32 = 32 +export const MAX_VERIFY_BLOCKS_BYTELENGTH: i32 = MAX_VERIFY_BLOCKS * SIGNEDBLOCK_BYTELENGTH +export const MAX_MESSAGE_BYTELENGTH: i32 = MAX_VERIFY_BLOCKS_BYTELENGTH > PAGE_BYTELENGTH ? MAX_VERIFY_BLOCKS_BYTELENGTH : PAGE_BYTELENGTH // Static I/O buffers const INPUT_MSG = new StaticArray(MAX_MESSAGE_BYTELENGTH) @@ -22,37 +25,37 @@ const OUTPUT_DERIVE = new StaticArray(KEY_BYTELENGTH) const OUTPUT_SIGN = new StaticArray(SIGNATURE_BYTELENGTH) const OUTPUT_VERIFY = new StaticArray(MAX_VERIFY_BLOCKS) -/** Returns the pointer to the static message input buffer (32 KiB). */ +/** Returns a pointer to the static message input buffer. */ export function ptrInputMsg (): usize { return changetype(INPUT_MSG) } -/** Returns the pointer to the static private key input buffer (32 bytes). */ +/** Returns a pointer to the static private key input buffer (32 bytes). */ export function ptrInputPrv (): usize { return changetype(INPUT_PRV) } -/** Returns the pointer to the static public key input buffer (32 bytes). */ +/** Returns a pointer to the static public key input buffer (32 bytes). */ export function ptrInputPub (): usize { return changetype(INPUT_PUB) } -/** Returns the pointer to the static signature input buffer (64 bytes). */ +/** Returns a pointer to the static signature input buffer (64 bytes). */ export function ptrInputSig (): usize { return changetype(INPUT_SIG) } -/** Returns the pointer to the derive() static output buffer (64 bytes). */ +/** Returns a pointer to the derive() static output buffer (64 bytes). */ export function ptrOutputDerive (): usize { return changetype(OUTPUT_DERIVE) } -/** Returns the pointer to the sign() static output buffer (64 bytes). */ +/** Returns a pointer to the sign() static output buffer (64 bytes). */ export function ptrOutputSign (): usize { return changetype(OUTPUT_SIGN) } -/** Returns the pointer to the verify() static output buffer (64 bytes). */ +/** Returns a pointer to the verify() static output buffer (64 bytes). */ export function ptrOutputVerify (): usize { return changetype(OUTPUT_VERIFY) } diff --git a/src/assembly/utils.ts b/src/assembly/utils.ts index 3453677..4f9a007 100644 --- a/src/assembly/utils.ts +++ b/src/assembly/utils.ts @@ -75,3 +75,14 @@ export function sodium_is_zero (n: StaticArray, nlen: i32): u8 { d &= 255 return u8(((d - 1) >> 8) & 1) } + +/** Unit tests on initialization */ +const testbytesA = new StaticArray(32).fill(0) +const testbytesB = new StaticArray(32).fill(0) +for (let i = 0; i < 32; i++) { + testbytesA[i] = 1 + if (equalbytes(testbytesA, testbytesB, 32)) { + throw new Error('equalbytes failed unit test') + } + testbytesA[i] = 0 +} diff --git a/src/index.ts b/src/index.ts index 4c53918..c4ef6a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,8 @@ import * as nano25519 from './lib' +export { constants } from './lib' + /** * Nano public key derivation using WebAssembly. * @param {Uint8Array} prv - 32-byte private key diff --git a/src/lib/derive.ts b/src/lib/derive.ts index 6eed51e..5a831f8 100644 --- a/src/lib/derive.ts +++ b/src/lib/derive.ts @@ -1,7 +1,10 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { clear, exports, INPUT_PRV, isBytes, KEY_BYTELENGTH, normalize, OUTPUT_DERIVE } from './wasm' +import { clear, constants, exports, isBytes, normalize, pointers, } from './wasm' + +const { KEY_BYTELENGTH, } = constants +const { INPUT_PRV, OUTPUT_DERIVE, } = pointers export function derive (prv: unknown, out?: unknown): string | Uint8Array | void { if (typeof out !== 'undefined' && !(isBytes(out) && out.byteLength === KEY_BYTELENGTH)) { diff --git a/src/lib/index.ts b/src/lib/index.ts index 7b00dca..9e34dab 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -4,4 +4,5 @@ export { derive } from './derive' export { sign } from './sign' export { verify, verify_blocks } from './verify' +export { constants } from './wasm' diff --git a/src/lib/sign.ts b/src/lib/sign.ts index 402dea9..9b543e7 100644 --- a/src/lib/sign.ts +++ b/src/lib/sign.ts @@ -1,7 +1,10 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { clear, exports, INPUT_MSG, INPUT_PRV, INPUT_PUB, isBytes, KEY_BYTELENGTH, MAX_MESSAGE_BYTELENGTH, normalize, OUTPUT_SIGN, SIGNATURE_BYTELENGTH } from './wasm' +import { clear, constants, exports, isBytes, normalize, pointers, } from './wasm' + +const { KEY_BYTELENGTH, MAX_MESSAGE_BYTELENGTH, SIGNATURE_BYTELENGTH, } = constants +const { INPUT_MSG, INPUT_PRV, INPUT_PUB, OUTPUT_SIGN, } = pointers export function sign (msg: unknown, prv: unknown, pub: unknown, out?: unknown): string | Uint8Array | void { if (typeof out !== 'undefined' && !(isBytes(out) && out.byteLength === 64)) { diff --git a/src/lib/verify.ts b/src/lib/verify.ts index f2e433c..415bb34 100644 --- a/src/lib/verify.ts +++ b/src/lib/verify.ts @@ -1,7 +1,10 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { BLOCKHASH_BYTELENGTH, INPUT_MSG, INPUT_PUB, INPUT_SIG, KEY_BYTELENGTH, MAX_MESSAGE_BYTELENGTH, MAX_VERIFY_BLOCKS, OUTPUT_VERIFY, SIGNATURE_BYTELENGTH, clear, exports, normalize } from './wasm' +import { clear, constants, exports, normalize, pointers, } from './wasm' + +const { BLOCKHASH_BYTELENGTH, KEY_BYTELENGTH, MAX_MESSAGE_BYTELENGTH, MAX_VERIFY_BLOCKS, SIGNATURE_BYTELENGTH, } = constants +const { INPUT_MSG, INPUT_PUB, INPUT_SIG, OUTPUT_VERIFY, } = pointers export function verify (sig: unknown, msg: unknown, pub: unknown): boolean { let buffer = new Uint8Array(exports.memory.buffer) diff --git a/src/lib/wasm.ts b/src/lib/wasm.ts index 1bd81d3..7e51979 100644 --- a/src/lib/wasm.ts +++ b/src/lib/wasm.ts @@ -19,10 +19,10 @@ type Exports = { ptrOutputVerify: () => number BLOCKHASH_BYTELENGTH: WebAssembly.Global KEY_BYTELENGTH: WebAssembly.Global + SIGNATURE_BYTELENGTH: WebAssembly.Global MAX_VERIFY_BLOCKS: WebAssembly.Global + MAX_VERIFY_BLOCKS_BYTELENGTH: WebAssembly.Global MAX_MESSAGE_BYTELENGTH: WebAssembly.Global - SIGNATURE_BYTELENGTH: WebAssembly.Global - SIGNEDBLOCK_BYTELENGTH: WebAssembly.Global memory: WebAssembly.Memory } } @@ -54,20 +54,40 @@ export const { exports } = new WebAssembly.Instance(module, { } }) as Exports -export const BLOCKHASH_BYTELENGTH = exports.BLOCKHASH_BYTELENGTH.value -export const KEY_BYTELENGTH = exports.KEY_BYTELENGTH.value -export const MAX_MESSAGE_BYTELENGTH = exports.MAX_MESSAGE_BYTELENGTH.value -export const MAX_VERIFY_BLOCKS = exports.MAX_VERIFY_BLOCKS.value -export const SIGNATURE_BYTELENGTH = exports.SIGNATURE_BYTELENGTH.value -export const SIGNEDBLOCK_BYTELENGTH = exports.SIGNEDBLOCK_BYTELENGTH.value +const BLOCKHASH_BYTELENGTH = exports.BLOCKHASH_BYTELENGTH.value +const KEY_BYTELENGTH = exports.KEY_BYTELENGTH.value +const SIGNATURE_BYTELENGTH = exports.SIGNATURE_BYTELENGTH.value -export const INPUT_MSG = exports.ptrInputMsg() -export const INPUT_PRV = exports.ptrInputPrv() -export const INPUT_PUB = exports.ptrInputPub() -export const INPUT_SIG = exports.ptrInputSig() -export const OUTPUT_DERIVE = exports.ptrOutputDerive() -export const OUTPUT_SIGN = exports.ptrOutputSign() -export const OUTPUT_VERIFY = exports.ptrOutputVerify() +const MAX_VERIFY_BLOCKS = exports.MAX_VERIFY_BLOCKS.value +const MAX_VERIFY_BLOCKS_BYTELENGTH = exports.MAX_VERIFY_BLOCKS_BYTELENGTH.value +const MAX_MESSAGE_BYTELENGTH = exports.MAX_MESSAGE_BYTELENGTH.value + +export const constants = { + BLOCKHASH_BYTELENGTH, + KEY_BYTELENGTH, + SIGNATURE_BYTELENGTH, + MAX_VERIFY_BLOCKS, + MAX_VERIFY_BLOCKS_BYTELENGTH, + MAX_MESSAGE_BYTELENGTH +} + +const INPUT_MSG = exports.ptrInputMsg() +const INPUT_PRV = exports.ptrInputPrv() +const INPUT_PUB = exports.ptrInputPub() +const INPUT_SIG = exports.ptrInputSig() +const OUTPUT_DERIVE = exports.ptrOutputDerive() +const OUTPUT_SIGN = exports.ptrOutputSign() +const OUTPUT_VERIFY = exports.ptrOutputVerify() + +export const pointers = { + INPUT_MSG, + INPUT_PRV, + INPUT_PUB, + INPUT_SIG, + OUTPUT_DERIVE, + OUTPUT_SIGN, + OUTPUT_VERIFY, +} export function clear (memory: Uint8Array): void { memory.fill(0, INPUT_MSG, INPUT_MSG + MAX_MESSAGE_BYTELENGTH) diff --git a/test/index.html b/test/index.html index 99b4512..bb30ca9 100644 --- a/test/index.html +++ b/test/index.html @@ -380,7 +380,7 @@ SPDX-License-Identifier: GPL-3.0-or-later if (api === 'nano25519 with bulk block verification') { publicKey = derive(privateKey, 'nano25519') const blocks = [] - for (let b = 0; blockhash < 32; b++) { + for (let b = 0; b < nano25519.constants.MAX_VERIFY_BLOCKS; b++) { blockHash = random() signature = sign(blockHash, privateKey, publicKey, 'nano25519') blocks[b] = { hash: blockHash, signature } @@ -391,7 +391,7 @@ SPDX-License-Identifier: GPL-3.0-or-later if (verified.some(v => v !== true)) { throw new Error(`invalid result\nsignature: ${signature}\nblock hash: ${blockHash}\npublic key: ${publicKey}`) } - duration = (end - start) / 32 + duration = (end - start) / nano25519.constants.MAX_VERIFY_BLOCKS deriveTimes[i].push(0) signTimes[i].push(0) verifyTimes[i].push(duration) diff --git a/test/node.mjs b/test/node.mjs index 38b1f8e..bad4e11 100644 --- a/test/node.mjs +++ b/test/node.mjs @@ -1,7 +1,7 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { derive, sign, verify, verify_blocks } from 'nano25519' +import { constants, derive, sign, verify, verify_blocks } from 'nano25519' import { NANO_ORG_VECTOR, PROBLEM_VECTOR, PYTHON_ED25519_BLAKE2B_VECTORS } from './vectors.mjs' /** @@ -65,17 +65,18 @@ passes += +test failures += +!test try { - result = sign('1'.repeat(65538), NANO_ORG_VECTOR.privateKey, NANO_ORG_VECTOR.publicKey) + result = sign('1'.repeat((constants.MAX_MESSAGE_BYTELENGTH << 1) + 1), NANO_ORG_VECTOR.privateKey, NANO_ORG_VECTOR.publicKey) result = false } catch (err) { + console.log(err) if (err != null && typeof err === 'object' && 'message' in err) { - result = err.message === `Invalid hexadecimal length 65538 for message` + result = err.message === `Invalid hexadecimal length ${(constants.MAX_MESSAGE_BYTELENGTH << 1) + 1} for message` } else { result = false } } test = result === true -check(`sign message too long (65538 '1's)`, test) +check(`sign message too long (${(constants.MAX_MESSAGE_BYTELENGTH << 1) + 1} '1's)`, test) passes += +test failures += +!test @@ -145,32 +146,18 @@ passes += +test failures += +!test try { - result = sign(NANO_ORG_VECTOR.blockHash + '0', NANO_ORG_VECTOR.privateKey, NANO_ORG_VECTOR.publicKey) - result = false -} catch (err) { - if (err != null && typeof err === 'object' && 'message' in err) { - result = err.message === 'Invalid hexadecimal length 65 for message' - } else { - result = false - } -} -test = result === true -check(`sign message odd character count ${NANO_ORG_VECTOR.blockHash + '0'}`, test) -passes += +test -failures += +!test - -try { - result = sign('1'.repeat(65538), NANO_ORG_VECTOR.privateKey, NANO_ORG_VECTOR.publicKey) + result = sign(new Uint8Array(constants.MAX_MESSAGE_BYTELENGTH + 1).fill(1), NANO_ORG_VECTOR.privateKeyBytes, NANO_ORG_VECTOR.publicKeyBytes) result = false } catch (err) { + console.log(err) if (err != null && typeof err === 'object' && 'message' in err) { - result = err.message === 'Invalid hexadecimal length 65538 for message' + result = err.message === `message must be no more than ${constants.MAX_MESSAGE_BYTELENGTH} bytes` } else { result = false } } test = result === true -check(`sign message too long (65538 '1's)`, test) +check(`sign message too long (${constants.MAX_MESSAGE_BYTELENGTH + 1} bytes)`, test) passes += +test failures += +!test @@ -391,7 +378,7 @@ failures += +!test try { const blocks = [] - for (let i = 0; i < 32; i++) { + for (let i = 0; i < constants.MAX_VERIFY_BLOCKS; i++) { blocks[i] = { hash: NANO_ORG_VECTOR.blockHashBytes, signature: NANO_ORG_VECTOR.signatureBytes } } const verified = verify_blocks(NANO_ORG_VECTOR.publicKeyBytes, blocks) @@ -401,7 +388,7 @@ try { result = false } test = result === true -check(`verify_blocks 32 max`, test) +check(`verify_blocks ${constants.MAX_VERIFY_BLOCKS} max`, test) passes += +test failures += +!test @@ -426,14 +413,14 @@ failures += +!test try { const blocks = [] - for (let i = 0; i < 33; i++) { + for (let i = 0; i < constants.MAX_VERIFY_BLOCKS + 1; i++) { blocks[i] = { hash: NANO_ORG_VECTOR.blockHashBytes, signature: NANO_ORG_VECTOR.signatureBytes } } verify_blocks(NANO_ORG_VECTOR.publicKeyBytes, blocks) result = false } catch (err) { if (err != null && typeof err === 'object' && 'message' in err) { - result = err.message === 'Bulk Nano block verification must be no more than 32 blocks' + result = err.message === `Bulk Nano block verification must be no more than ${constants.MAX_VERIFY_BLOCKS} blocks` } else { result = false } -- 2.52.0