From fae796f2b37ccffbc56d3ac868016732f64ceed1 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Wed, 19 Aug 2026 21:20:49 -0700 Subject: [PATCH] Declare I/O buffers as StaticArray for memory safety. Set memory with live value instead of ignored export. --- asconfig.json | 3 +- src/assembly/index.ts | 120 +++++++++++++++++++++++++++--------------- src/lib/nano25519.ts | 21 ++++---- test/index.html | 10 ++-- 4 files changed, 94 insertions(+), 60 deletions(-) diff --git a/asconfig.json b/asconfig.json index 1fa1753..331f04d 100644 --- a/asconfig.json +++ b/asconfig.json @@ -9,7 +9,8 @@ "uncheckedBehavior": "always", "bindings": "esm", "sourceMap": false, - "debug": true, + "debug": false, + "initialMemory": 8, "runtime": "stub", "exportRuntime": false, "enable": [ diff --git a/src/assembly/index.ts b/src/assembly/index.ts index 72e781a..bee7ad4 100644 --- a/src/assembly/index.ts +++ b/src/assembly/index.ts @@ -12,29 +12,29 @@ const SIGNATURE_BYTES: i32 = 64 // Static I/O buffers const INPUT_BUFFER_BYTES: i32 = SIGNATURE_BYTES + MESSAGE_BYTES -const OUTPUT_BUFFER_BYTES: i32 = SIGNATURE_BYTES const MESSAGE_BUFFER_BYTES: i32 = 32768 -const INPUT_BUFFER = memory.data(INPUT_BUFFER_BYTES) -const OUTPUT_BUFFER = memory.data(OUTPUT_BUFFER_BYTES) -const MESSAGE_BUFFER = memory.data(MESSAGE_BUFFER_BYTES) - -/** Returns the pointer to the static output buffer (64 bytes). */ -export function getOutputPointer (): usize { - return OUTPUT_BUFFER -} +const OUTPUT_BUFFER_BYTES: i32 = SIGNATURE_BYTES +const INPUT_BUFFER = new StaticArray(INPUT_BUFFER_BYTES) +const MESSAGE_BUFFER = new StaticArray(MESSAGE_BUFFER_BYTES) +const OUTPUT_BUFFER = new StaticArray(OUTPUT_BUFFER_BYTES) /** Returns the pointer to the static input buffer (128 bytes). */ export function getInputPointer (): usize { - return INPUT_BUFFER + return changetype(INPUT_BUFFER) } /** Returns the pointer to the static message buffer (32 KiB). */ export function getMessagePointer (): usize { - return MESSAGE_BUFFER + return changetype(MESSAGE_BUFFER) +} + +/** Returns the pointer to the static output buffer (64 bytes). */ +export function getOutputPointer (): usize { + return changetype(OUTPUT_BUFFER) } -const derive_pub = new StaticArray(PUBLICKEY_BYTES) const derive_prv = new StaticArray(PRIVATEKEY_BYTES) +const derive_pub = new StaticArray(PUBLICKEY_BYTES) /** * 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: @@ -44,20 +44,30 @@ const derive_prv = new StaticArray(PRIVATEKEY_BYTES) * The public key is written to the output buffer. */ export function derive (): void { - const pub = derive_pub - const prv = derive_prv + // Clear local buffers + derive_prv.fill(0) + derive_pub.fill(0) + + // Copy input buffer to local parameterss, then clear input buffer + memory.copy(changetype(derive_prv), changetype(INPUT_BUFFER), PRIVATEKEY_BYTES) + INPUT_BUFFER.fill(0) + + // Derive, then clear local input + crypto_derive(derive_pub, derive_prv) + derive_prv.fill(0) - memory.copy(changetype(prv), INPUT_BUFFER, PRIVATEKEY_BYTES) - memory.fill(INPUT_BUFFER, 0, INPUT_BUFFER_BYTES) + // Clear output buffer of prior data, then copy local result to output buffer + OUTPUT_BUFFER.fill(0) + memory.copy(changetype(OUTPUT_BUFFER), changetype(derive_pub), PUBLICKEY_BYTES) - crypto_derive(pub, prv) - memory.fill(OUTPUT_BUFFER, 0, OUTPUT_BUFFER_BYTES) - memory.copy(OUTPUT_BUFFER, changetype(pub), PUBLICKEY_BYTES) + // Clear local result + derive_pub.fill(0) } -const sign_sig = new StaticArray(SIGNATURE_BYTES) +const sign_msg = new StaticArray(MESSAGE_BUFFER_BYTES) const sign_prv = new StaticArray(PRIVATEKEY_BYTES) const sign_pub = new StaticArray(PUBLICKEY_BYTES) +const sign_sig = new StaticArray(SIGNATURE_BYTES) /** * 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: @@ -71,23 +81,39 @@ const sign_pub = new StaticArray(PUBLICKEY_BYTES) */ export function sign (mlen: i32): void { if (mlen < 0 || mlen > 32768) throw new Error() - const sig = sign_sig - const prv = sign_prv - const pub = sign_pub - - memory.copy(changetype(prv), INPUT_BUFFER, PRIVATEKEY_BYTES) - memory.copy(changetype(pub), INPUT_BUFFER + PRIVATEKEY_BYTES, PUBLICKEY_BYTES) - memory.fill(INPUT_BUFFER, 0, INPUT_BUFFER_BYTES) - const msg = changetype>(MESSAGE_BUFFER) - - crypto_sign(sig, msg, mlen, prv, pub) - memory.fill(OUTPUT_BUFFER, 0, OUTPUT_BUFFER_BYTES) - memory.copy(OUTPUT_BUFFER, changetype(sig), SIGNATURE_BYTES) + // Clear local buffers + sign_msg.fill(0) + sign_prv.fill(0) + sign_pub.fill(0) + sign_sig.fill(0) + + // Copy input buffer to local parameters, then clear input buffer + memory.copy(changetype(sign_prv), changetype(INPUT_BUFFER), PRIVATEKEY_BYTES) + memory.copy(changetype(sign_pub), changetype(INPUT_BUFFER) + PRIVATEKEY_BYTES, PUBLICKEY_BYTES) + INPUT_BUFFER.fill(0) + + // Copy message buffer to local variable, then clear message buffer + memory.copy(changetype(sign_msg), changetype(MESSAGE_BUFFER), mlen) + MESSAGE_BUFFER.fill(0) + + // Sign, then clear local input + crypto_sign(sign_sig, sign_msg, mlen, sign_prv, sign_pub) + sign_msg.fill(0) + sign_prv.fill(0) + sign_pub.fill(0) + + // Clear output buffer of prior data, then copy local result to output buffer + OUTPUT_BUFFER.fill(0) + memory.copy(changetype(OUTPUT_BUFFER), changetype(sign_sig), SIGNATURE_BYTES) + + // Clear local result + sign_sig.fill(0) } -const verify_sig = new StaticArray(SIGNATURE_BYTES) +const verify_msg = new StaticArray(MESSAGE_BUFFER_BYTES) const verify_pub = new StaticArray(PUBLICKEY_BYTES) +const verify_sig = new StaticArray(SIGNATURE_BYTES) /** * 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 @@ -102,15 +128,27 @@ const verify_pub = new StaticArray(PUBLICKEY_BYTES) */ export function verify (mlen: i32): i32 { if (mlen < 0 || mlen > 32768) throw new Error('invalid message length') - const sig = verify_sig - const pub = verify_pub - memory.copy(changetype(sig), INPUT_BUFFER, SIGNATURE_BYTES) - memory.copy(changetype(pub), INPUT_BUFFER + SIGNATURE_BYTES, PUBLICKEY_BYTES) - memory.fill(INPUT_BUFFER, 0, INPUT_BUFFER_BYTES) + // Clear local buffers + verify_msg.fill(0) + verify_pub.fill(0) + verify_sig.fill(0) + + // Copy input buffer to local parameters, then clear input buffer + memory.copy(changetype(verify_sig), changetype(INPUT_BUFFER), SIGNATURE_BYTES) + memory.copy(changetype(verify_pub), changetype(INPUT_BUFFER) + SIGNATURE_BYTES, PUBLICKEY_BYTES) + INPUT_BUFFER.fill(0) + + // Copy message buffer to local variable, then clear message buffer + memory.copy(changetype(verify_msg), changetype(MESSAGE_BUFFER), mlen) + MESSAGE_BUFFER.fill(0) - const msg = changetype>(MESSAGE_BUFFER) + // Verify, then clear local input + const verified = crypto_verify(verify_sig, verify_msg, mlen, verify_pub) + verify_msg.fill(0) + verify_pub.fill(0) + verify_sig.fill(0) - memory.fill(OUTPUT_BUFFER, 0, OUTPUT_BUFFER_BYTES) - return crypto_verify(sig, msg, mlen, pub) + // Output buffer not used, so just return + return verified } diff --git a/src/lib/nano25519.ts b/src/lib/nano25519.ts index 000c72e..13a31c5 100644 --- a/src/lib/nano25519.ts +++ b/src/lib/nano25519.ts @@ -6,12 +6,12 @@ import nano25519_wasm from '../../build/nano25519.wasm' type Exports = { exports: { - derive: () => void, - sign: (mlen: number) => void, - verify: (mlen: number) => number, - getOutputPointer: () => number, - getInputPointer: () => number, - getMessagePointer: () => number, + derive: () => void + sign: (mlen: number) => void + verify: (mlen: number) => number + getInputPointer: () => number + getMessagePointer: () => number + getOutputPointer: () => number memory: WebAssembly.Memory } } @@ -38,8 +38,7 @@ const { exports } = new WebAssembly.Instance(module, { col >>>= 0 const message = `Nano25519WasmError: ${getString(msg)}, ${getString(file)}, row ${row}, col ${col}` throw new Error(message) - }, - memory: new WebAssembly.Memory({ initial: 1, maximum: 1 }) + } } }) as Exports @@ -149,11 +148,11 @@ export function verify (s: unknown, m: unknown, k: unknown): boolean { } } -function clear (buffer: Uint8Array): void { +function clear (memory: Uint8Array): void { let inPtr = exports.getInputPointer() let outPtr = exports.getOutputPointer() - buffer.fill(0, inPtr, inPtr + 96) - buffer.fill(0, outPtr, outPtr + 64) + memory.fill(0, inPtr, inPtr + 96) + memory.fill(0, outPtr, outPtr + 64) } function isBytes (a: unknown): a is Uint8Array { diff --git a/test/index.html b/test/index.html index 8c6c447..1dbf0a7 100644 --- a/test/index.html +++ b/test/index.html @@ -31,11 +31,7 @@ SPDX-License-Identifier: GPL-3.0-or-later } } - +