From 3934cb5d5118bb603b96c4d0dfe958ec6d952a2c Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Mon, 31 Aug 2026 23:02:16 -0700 Subject: [PATCH] Import initial memory instead of exporting so that build tests can run and throw helpful messages instead of just crashing exported memory. --- asconfig.json | 1 + src/assembly/utils.ts | 4 +++- src/lib/derive.ts | 6 +++--- src/lib/sign.ts | 6 +++--- src/lib/verify.ts | 10 +++++----- src/lib/wasm.ts | 31 ++++++++++++++++++++----------- 6 files changed, 35 insertions(+), 23 deletions(-) diff --git a/asconfig.json b/asconfig.json index f75849a..5aad30a 100644 --- a/asconfig.json +++ b/asconfig.json @@ -11,6 +11,7 @@ "sourceMap": false, "debug": false, "initialMemory": 4, + "importMemory": true, "runtime": "stub", "exportRuntime": false, "enable": [ diff --git a/src/assembly/utils.ts b/src/assembly/utils.ts index d8cb54f..91140a0 100644 --- a/src/assembly/utils.ts +++ b/src/assembly/utils.ts @@ -83,7 +83,9 @@ const testbytesB = new StaticArray(32).fill(0) for (let i = 0; i < 32; i++) { for (let j: u8 = 0; j < 8; j++) { testbytesA[i] = 1 << j - if (equalbytes(testbytesA, testbytesB, 32)) { throw new Error('equalbytes failed unit test') } + if (equalbytes(testbytesA, testbytesB, 32)) { + throw new Error('equalbytes failed unit test') + } testbytesA[i] = 0 } } diff --git a/src/lib/derive.ts b/src/lib/derive.ts index ae9f834..1a1288f 100644 --- a/src/lib/derive.ts +++ b/src/lib/derive.ts @@ -1,7 +1,7 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { Mutex, Nano25519TypeError, Pointers, clearMemory, constants, exports, isBytes, normalize } from './wasm' +import { Mutex, Nano25519TypeError, Pointers, clearMemory, constants, exports, isBytes, memory, normalize } from './wasm' const { KEY_BYTELENGTH, } = constants @@ -11,14 +11,14 @@ export function derive (prv: unknown, out?: unknown): string | Uint8Array //! SPDX-License-Identifier: GPL-3.0-or-later -import { Mutex, Nano25519TypeError, Pointers, clearMemory, constants, exports, isBytes, normalize } from './wasm' +import { Mutex, Nano25519TypeError, Pointers, clearMemory, constants, exports, isBytes, memory, normalize } from './wasm' const { KEY_BYTELENGTH, MAX_MESSAGE_BYTELENGTH, SIGNATURE_BYTELENGTH, } = constants @@ -13,7 +13,7 @@ export function sign (msg: unknown, prv: unknown, pub: unknown, out?: unknown): const privateKey = normalize('private key', KEY_BYTELENGTH, KEY_BYTELENGTH, prv) const publicKey = normalize('public key', KEY_BYTELENGTH, KEY_BYTELENGTH, pub) const signature = new Uint8Array(SIGNATURE_BYTELENGTH) - let buffer = new Uint8Array(exports.memory.buffer) + let buffer = new Uint8Array(memory.buffer) Mutex.lock() try { for (let i = 0; i < message.byteLength; i++) { @@ -26,7 +26,7 @@ export function sign (msg: unknown, prv: unknown, pub: unknown, out?: unknown): buffer[Pointers.INPUT_PUB + i] = publicKey[i] } exports.sign(message.byteLength) - if (exports.memory.buffer !== buffer.buffer) { + if (memory.buffer !== buffer.buffer) { throw new Nano25519TypeError('WASM memory buffer detached') } for (let i = 0; i < SIGNATURE_BYTELENGTH; i++) { diff --git a/src/lib/verify.ts b/src/lib/verify.ts index c001ea7..34dfd03 100644 --- a/src/lib/verify.ts +++ b/src/lib/verify.ts @@ -1,12 +1,12 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { Mutex, Nano25519TypeError, Pointers, clearMemory, constants, exports, normalize, } from './wasm' +import { Mutex, Nano25519TypeError, Pointers, clearMemory, constants, exports, memory, normalize } from './wasm' const { BLOCKHASH_BYTELENGTH, KEY_BYTELENGTH, MAX_MESSAGE_BYTELENGTH, MAX_VERIFY_BLOCKS, SIGNATURE_BYTELENGTH, } = constants export function verify (sig: unknown, msg: unknown, pub: unknown): boolean { - let buffer = new Uint8Array(exports.memory.buffer) + let buffer = new Uint8Array(memory.buffer) Mutex.lock() try { const message = normalize('message', 0, MAX_MESSAGE_BYTELENGTH, msg) @@ -22,7 +22,7 @@ export function verify (sig: unknown, msg: unknown, pub: unknown): boolean { buffer[Pointers.INPUT_SIG + i] = signature[i] } exports.verify(message.byteLength) - if (exports.memory.buffer !== buffer.buffer) { + if (memory.buffer !== buffer.buffer) { throw new Nano25519TypeError('WASM memory buffer detached') } const verified = buffer[Pointers.OUTPUT_VERIFY] @@ -42,7 +42,7 @@ export function verify_blocks (pub: unknown, data: unknown): boolean[] { throw new RangeError(`Bulk Nano block verification must be no more than ${MAX_VERIFY_BLOCKS} blocks`, { cause: count }) } const verified = new Uint8Array(count) - let buffer = new Uint8Array(exports.memory.buffer) + let buffer = new Uint8Array(memory.buffer) Mutex.lock() try { const publicKey = normalize('public key', KEY_BYTELENGTH, KEY_BYTELENGTH, pub) @@ -72,7 +72,7 @@ export function verify_blocks (pub: unknown, data: unknown): boolean[] { p += BLOCKHASH_BYTELENGTH } exports.verify_blocks(count) - if (exports.memory.buffer !== buffer.buffer) { + if (memory.buffer !== buffer.buffer) { throw new Nano25519TypeError('WASM memory buffer detached') } for (let i = 0; i < count; i++) { diff --git a/src/lib/wasm.ts b/src/lib/wasm.ts index 61e332a..50f4bf1 100644 --- a/src/lib/wasm.ts +++ b/src/lib/wasm.ts @@ -23,7 +23,6 @@ type Exports = { MAX_VERIFY_BLOCKS: WebAssembly.Global MAX_VERIFY_BLOCKS_BYTELENGTH: WebAssembly.Global MAX_MESSAGE_BYTELENGTH: WebAssembly.Global - memory: WebAssembly.Memory } } @@ -37,15 +36,25 @@ export const Mutex = { } } +/** + * WASM memory created here and imported, instead of building and exporting, so + * that top-level WASM abort calls work while `exports` is still instantiating. + * Host-owned memory means abort handler can always read the error message. + * + * NOTE: `initial` must match `initialMemory` in `asconfig.json`. + */ +export const memory = new WebAssembly.Memory({ initial: 4 }) + const wasm: Uint8Array = Uint8Array.from(nano25519_wasm) const module = new WebAssembly.Module(wasm) export const { exports } = new WebAssembly.Instance(module, { env: { + memory, 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) + const end = pointer + new Uint32Array(memory.buffer)[pointer - 4 >>> 2] >>> 1 + const buf = new Uint16Array(memory.buffer) let start = pointer >>> 1 let string = '' while (end - start > 1024) { @@ -104,14 +113,14 @@ export class Pointers { } export function clearMemory (): void { - const memory = new Uint8Array(exports.memory.buffer) - memory.fill(0, INPUT_MSG, INPUT_MSG + MAX_MESSAGE_BYTELENGTH) - memory.fill(0, INPUT_PRV, INPUT_PRV + KEY_BYTELENGTH) - memory.fill(0, INPUT_PUB, INPUT_PUB + KEY_BYTELENGTH) - memory.fill(0, INPUT_SIG, INPUT_SIG + SIGNATURE_BYTELENGTH) - memory.fill(0, OUTPUT_DERIVE, OUTPUT_DERIVE + KEY_BYTELENGTH) - memory.fill(0, OUTPUT_SIGN, OUTPUT_SIGN + SIGNATURE_BYTELENGTH) - memory.fill(255, OUTPUT_VERIFY, OUTPUT_VERIFY + MAX_VERIFY_BLOCKS) + const bytes = new Uint8Array(memory.buffer) + bytes.fill(0, INPUT_MSG, INPUT_MSG + MAX_MESSAGE_BYTELENGTH) + bytes.fill(0, INPUT_PRV, INPUT_PRV + KEY_BYTELENGTH) + bytes.fill(0, INPUT_PUB, INPUT_PUB + KEY_BYTELENGTH) + bytes.fill(0, INPUT_SIG, INPUT_SIG + SIGNATURE_BYTELENGTH) + bytes.fill(0, OUTPUT_DERIVE, OUTPUT_DERIVE + KEY_BYTELENGTH) + bytes.fill(0, OUTPUT_SIGN, OUTPUT_SIGN + SIGNATURE_BYTELENGTH) + bytes.fill(255, OUTPUT_VERIFY, OUTPUT_VERIFY + MAX_VERIFY_BLOCKS) locked = false } -- 2.52.0