From e2d076d1089e32fa4725c0bdd05db35c2e91a842 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Fri, 25 Sep 2026 09:33:43 -0700 Subject: [PATCH] Refactor input copying from host to WASM to batch bytes and vastly improve performance for large message values at the boundary crossing. --- src/assembly/index.ts | 90 +++++++++------------ src/lib/derive.ts | 8 +- src/lib/errors.ts | 1 + src/lib/sign.ts | 16 ++-- src/lib/verify.ts | 41 +++------- src/lib/wasm.ts | 183 +++++++++++++++++++++++++++++++----------- 6 files changed, 199 insertions(+), 140 deletions(-) diff --git a/src/assembly/index.ts b/src/assembly/index.ts index 1da0d8a..b82b2d0 100644 --- a/src/assembly/index.ts +++ b/src/assembly/index.ts @@ -21,6 +21,23 @@ const OUTPUT_DERIVE = new StaticArray(KEY_BYTELENGTH) const OUTPUT_SIGN = new StaticArray(SIGNATURE_BYTELENGTH) const OUTPUT_VERIFY = new StaticArray(MAX_VERIFY_BLOCKS) +/** + * Sets 32 bytes at a time in a buffer memory address specified by `p`. + * + * A host-to-module crossing costs roughly the same regardless of payload width, + * so batching 32 bytes is much more efficient than writing individual bytes. + */ +function set32Bytes (p: usize, v0: u32, v1: u32, v2: u32, v3: u32, v4: u32, v5: u32, v6: u32, v7: u32): void { + store(p, v0) + store(p + 4, v1) + store(p + 8, v2) + store(p + 12, v3) + store(p + 16, v4) + store(p + 20, v5) + store(p + 24, v6) + store(p + 28, v7) +} + /** Clears all input and output buffers. */ export function clearMemory (): void { INPUT_MSG.fill(0) @@ -32,65 +49,36 @@ export function clearMemory (): void { OUTPUT_VERIFY.fill(0) } -/** Sets a byte in the static message input buffer. */ -export function setInputMsgByte (index: i32, v0: i32): void { - if (index < 0 || MAX_MESSAGE_BYTELENGTH <= index) { +/** + * Sets 32 bytes in the static message input buffer. Index range check accounts + * for a full batch width. The host code must pack the input bytes in + * little-endian order since WASM is little-endian by definition. + */ +export function setInputMsg (index: i32, v0: u32, v1: u32, v2: u32, v3: u32, v4: u32, v5: u32, v6: u32, v7: u32): void { + if (index < 0 || MAX_MESSAGE_BYTELENGTH - 32 < index) { raise(ERROR_INDEX_OUT_OF_RANGE, BUFFER_INPUT_MSG, index) } - if (v0 < 0 || 0xff < v0) { - raise(ERROR_BYTE_OUT_OF_RANGE, BUFFER_INPUT_MSG, v0) - } - INPUT_MSG[index] = u8(v0) + const p: usize = changetype(INPUT_MSG) + index + set32Bytes(p, v0, v1, v2, v3, v4, v5, v6, v7) } -/** Sets 4 bytes in the static message input buffer. */ -export function setInputMsg32 (index: i32, v0: u32, v1: u32, v2: u32, v3: u32, v4: u32, v5: u32, v6: u32, v7: u32): void { - if (index < 0 || MAX_MESSAGE_BYTELENGTH <= index) { - raise(ERROR_INDEX_OUT_OF_RANGE, BUFFER_INPUT_MSG, index) - } - const v = [v0, v1, v2, v3, v4, v5, v6, v7] - for (let i = 0; i < 8; i++) { - if (v[i] < 0 || 0xffffffff < v[i]) { - raise(ERROR_BYTE_OUT_OF_RANGE, BUFFER_INPUT_MSG, v[i]) - } - INPUT_MSG[index + (i << 2)] = u8((v[i] >>> 24) & 255) - INPUT_MSG[index + (i << 2) + 1] = u8((v[i] >>> 16) & 255) - INPUT_MSG[index + (i << 2) + 2] = u8((v[i] >>> 8) & 255) - INPUT_MSG[index + (i << 2) + 3] = u8(v[i] & 255) - } +/** Sets all 32 bytes in the static private key input buffer. */ +export function setInputPrv (v0: u32, v1: u32, v2: u32, v3: u32, v4: u32, v5: u32, v6: u32, v7: u32): void { + const p: usize = changetype(INPUT_PRV) + set32Bytes(p, v0, v1, v2, v3, v4, v5, v6, v7) } -/** Sets a byte in the static private key input buffer (32 bytes). */ -export function setInputPrvByte (index: i32, value: i32): void { - if (index < 0 || KEY_BYTELENGTH <= index) { - raise(ERROR_INDEX_OUT_OF_RANGE, BUFFER_INPUT_PRV, index) - } - if (value < 0 || 0xff < value) { - raise(ERROR_BYTE_OUT_OF_RANGE, BUFFER_INPUT_PRV, value) - } - INPUT_PRV[index] = u8(value) +/** Sets all 32 bytes of the static public key input buffer. */ +export function setInputPub (v0: u32, v1: u32, v2: u32, v3: u32, v4: u32, v5: u32, v6: u32, v7: u32): void { + const p: usize = changetype(INPUT_PUB) + set32Bytes(p, v0, v1, v2, v3, v4, v5, v6, v7) } -/** Sets a byte in the static public key input buffer (32 bytes). */ -export function setInputPubByte (index: i32, value: i32): void { - if (index < 0 || KEY_BYTELENGTH <= index) { - raise(ERROR_INDEX_OUT_OF_RANGE, BUFFER_INPUT_PUB, index) - } - if (value < 0 || 0xff < value) { - raise(ERROR_BYTE_OUT_OF_RANGE, BUFFER_INPUT_PUB, value) - } - INPUT_PUB[index] = u8(value) -} - -/** Sets a byte in the static signature input buffer (64 bytes). */ -export function setInputSigByte (index: i32, value: i32): void { - if (index < 0 || SIGNATURE_BYTELENGTH <= index) { - raise(ERROR_INDEX_OUT_OF_RANGE, BUFFER_INPUT_SIG, index) - } - if (value < 0 || 0xff < value) { - raise(ERROR_BYTE_OUT_OF_RANGE, BUFFER_INPUT_SIG, value) - } - INPUT_SIG[index] = u8(value) +/** Sets all 64 bytes of the static signature input buffer. */ +export function setInputSig (v0: u32, v1: u32, v2: u32, v3: u32, v4: u32, v5: u32, v6: u32, v7: u32, v8: u32, v9: u32, v10: u32, v11: u32, v12: u32, v13: u32, v14: u32, v15: u32): void { + const p: usize = changetype(INPUT_SIG) + set32Bytes(p, v0, v1, v2, v3, v4, v5, v6, v7) + set32Bytes(p + 32, v8, v9, v10, v11, v12, v13, v14, v15) } /** Gets a byte from the derive() static output buffer (32 bytes). */ diff --git a/src/lib/derive.ts b/src/lib/derive.ts index 4456922..160fa9b 100644 --- a/src/lib/derive.ts +++ b/src/lib/derive.ts @@ -2,7 +2,7 @@ //! SPDX-License-Identifier: GPL-3.0-or-later import { allocate, byteLength, bytesToHex, copy, fill, isBytes, normalize } from './primordials' -import { MemoryBuffers, Mutex, clearMemory, constants, derive as wasm_derive, } from './wasm' +import { clearMemory, constants, getOutput, Mutex, setInput, derive as wasm_derive, } from './wasm' const { KEY_BYTELENGTH, } = constants @@ -14,12 +14,10 @@ export function derive (prv: unknown, out?: unknown): string | void { const publicKey = allocate(KEY_BYTELENGTH) Mutex.lock() try { - for (let i = 0; i < KEY_BYTELENGTH; i++) { - MemoryBuffers.INPUT_PRV(i, privateKey[i]) - } + setInput('prv', privateKey) wasm_derive() for (let i = 0; i < KEY_BYTELENGTH; i++) { - publicKey[i] = MemoryBuffers.OUTPUT_DERIVE(i) + publicKey[i] = getOutput('derive', i) } if (out != null) { copy(out, publicKey) diff --git a/src/lib/errors.ts b/src/lib/errors.ts index a74d7d6..aaa1011 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -1,6 +1,7 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later +export class Nano25519MutexError extends RangeError { } export class Nano25519TypeError extends TypeError { } export class Nano25519WasmError extends Error { } diff --git a/src/lib/sign.ts b/src/lib/sign.ts index 812a2d9..85d0710 100644 --- a/src/lib/sign.ts +++ b/src/lib/sign.ts @@ -2,7 +2,7 @@ //! SPDX-License-Identifier: GPL-3.0-or-later import { allocate, byteLength, bytesToHex, copy, fill, isBytes, normalize } from './primordials' -import { MemoryBuffers, Mutex, clearMemory, constants, sign as wasm_sign } from './wasm' +import { Mutex, clearMemory, constants, getOutput, setInput, sign as wasm_sign } from './wasm' const { KEY_BYTELENGTH, MAX_MESSAGE_BYTELENGTH, SIGNATURE_BYTELENGTH, } = constants @@ -16,18 +16,12 @@ export function sign (msg: unknown, prv: unknown, pub: unknown, out?: unknown): const signature = allocate(SIGNATURE_BYTELENGTH) Mutex.lock() try { - for (let i = 0; i < message.byteLength; i++) { - MemoryBuffers.INPUT_MSG(i, message[i]) - } - for (let i = 0; i < KEY_BYTELENGTH; i++) { - MemoryBuffers.INPUT_PRV(i, privateKey[i]) - } - for (let i = 0; i < KEY_BYTELENGTH; i++) { - MemoryBuffers.INPUT_PUB(i, publicKey[i]) - } + setInput('msg', message) + setInput('prv', privateKey) + setInput('pub', publicKey) wasm_sign(message.byteLength) for (let i = 0; i < SIGNATURE_BYTELENGTH; i++) { - signature[i] = MemoryBuffers.OUTPUT_SIGN(i) + signature[i] = getOutput('sign', i) } if (out != null) { copy(out, signature) diff --git a/src/lib/verify.ts b/src/lib/verify.ts index b87d226..1307276 100644 --- a/src/lib/verify.ts +++ b/src/lib/verify.ts @@ -2,7 +2,7 @@ //! SPDX-License-Identifier: GPL-3.0-or-later import { isArray, normalize } from './primordials' -import { MemoryBuffers, Mutex, clearMemory, constants, verify as wasm_verify, verify_blocks as wasm_verify_blocks, } from './wasm' +import { Mutex, clearMemory, constants, getOutput, setInput, verify as wasm_verify, verify_blocks as wasm_verify_blocks, } from './wasm' const { BLOCKHASH_BYTELENGTH, KEY_BYTELENGTH, MAX_MESSAGE_BYTELENGTH, MAX_VERIFY_BLOCKS, SIGNATURE_BYTELENGTH, } = constants @@ -12,21 +12,11 @@ export function verify (sig: unknown, msg: unknown, pub: unknown): boolean { const message = normalize('message', 0, MAX_MESSAGE_BYTELENGTH, msg) const publicKey = normalize('public key', KEY_BYTELENGTH, KEY_BYTELENGTH, pub) const signature = normalize('signature', SIGNATURE_BYTELENGTH, SIGNATURE_BYTELENGTH, sig) - for (let i = 0; i < message.byteLength; i += 32) { - const u32: number[] = [] - for (let j = 0; j < 32; j += 4) { - u32[j >> 2] = ((message[i + j] ?? 0) << 24) | ((message[i + j + 1] ?? 0) << 16) | ((message[i + j + 2] ?? 0) << 8) | (message[i + j + 3] ?? 0) - } - MemoryBuffers.INPUT_MSG_32(i, u32[0], u32[1], u32[2], u32[3], u32[4], u32[5], u32[6], u32[7]) - } - for (let i = 0; i < KEY_BYTELENGTH; i++) { - MemoryBuffers.INPUT_PUB(i, publicKey[i]) - } - for (let i = 0; i < SIGNATURE_BYTELENGTH; i++) { - MemoryBuffers.INPUT_SIG(i, signature[i]) - } + setInput('msg', message) + setInput('pub', publicKey) + setInput('sig', signature) wasm_verify(message.byteLength) - const verified = MemoryBuffers.OUTPUT_VERIFY(0) + const verified = getOutput('verify', 0) return verified === 1 } finally { clearMemory() @@ -46,10 +36,9 @@ export function verify_blocks (pub: unknown, data: unknown): boolean[] { Mutex.lock() try { const publicKey = normalize('public key', KEY_BYTELENGTH, KEY_BYTELENGTH, pub) - for (let i = 0; i < KEY_BYTELENGTH; i++) { - MemoryBuffers.INPUT_PUB(i, publicKey[i]) - } - for (let b = 0, p = 0; b < count; b++) { + setInput('pub', publicKey) + const message = new Uint8Array(count * (SIGNATURE_BYTELENGTH + BLOCKHASH_BYTELENGTH)) + for (let b = 0; b < count; b++) { const block = blocks[b] if (block == null || typeof block !== 'object') { throw new TypeError('Invalid block', { cause: block }) @@ -62,18 +51,14 @@ export function verify_blocks (pub: unknown, data: unknown): boolean[] { } const signature = normalize('signature', SIGNATURE_BYTELENGTH, SIGNATURE_BYTELENGTH, block.signature) const hash = normalize('hash', BLOCKHASH_BYTELENGTH, BLOCKHASH_BYTELENGTH, block.hash) - for (let i = 0; i < SIGNATURE_BYTELENGTH; i++) { - MemoryBuffers.INPUT_MSG(p + i, signature[i]) - } - p += SIGNATURE_BYTELENGTH - for (let i = 0; i < BLOCKHASH_BYTELENGTH; i++) { - MemoryBuffers.INPUT_MSG(p + i, hash[i]) - } - p += BLOCKHASH_BYTELENGTH + const offset = b * 96 + message.set(signature, offset) + message.set(hash, offset + SIGNATURE_BYTELENGTH) } + setInput('msg', message) wasm_verify_blocks(count) for (let i = 0; i < count; i++) { - verified[i] = MemoryBuffers.OUTPUT_VERIFY(i) === 1 + verified[i] = getOutput('verify', i) === 1 } return verified } finally { diff --git a/src/lib/wasm.ts b/src/lib/wasm.ts index 64fb24d..b4fe760 100644 --- a/src/lib/wasm.ts +++ b/src/lib/wasm.ts @@ -3,8 +3,8 @@ //@ts-expect-error import nano25519_wasm from '../../build/nano25519.wasm' -import { Nano25519TypeError, abort, errorCodes } from './errors' -import { copy } from './primordials' +import { Nano25519MutexError, Nano25519TypeError, abort, errorCodes, } from './errors' +import { copy, } from './primordials' type Exports = WebAssembly.Instance['exports'] & { clearMemory: () => void @@ -12,11 +12,10 @@ type Exports = WebAssembly.Instance['exports'] & { sign: (mlen: number) => void verify: (mlen: number) => void verify_blocks: (count: number) => void - setInputMsgByte: (index: number, value: number) => void - setInputMsg32: (index: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number) => void - setInputPrvByte: (index: number, value: number) => void - setInputPubByte: (index: number, value: number) => void - setInputSigByte: (index: number, value: number) => void + setInputMsg: (index: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number) => void + setInputPrv: (v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number) => void + setInputPub: (v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number) => void + setInputSig: (v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number, v14: number, v15: number) => void getOutputDeriveByte: (index: number) => number getOutputSignByte: (index: number) => number getOutputVerifyByte: (index: number) => number @@ -83,6 +82,105 @@ 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 +/** + * Copies the bytes of a message for signing, or signature verification, into + * the static message input buffer of the WASM module. Bytes are packed in + * little-endian order to align with the WASM spec. The entire input is written + * in one call rather than looped over an intermediate array because allocating + * costs more than the copy itself. Each byte must be protected with a null + * coalescing operator in order to fall back on a zero value since the message + * length can vary and not be a multiple of 32 bytes. + */ +function setInputMsg (message: Uint8Array): void { + const mlen = message.byteLength + for (let i = 0; i < mlen; i += 32) { + exports.setInputMsg(i, + (message[i] ?? 0) | ((message[i + 1] ?? 0) << 8) | ((message[i + 2] ?? 0) << 16) | ((message[i + 3] ?? 0) << 24), + (message[i + 4] ?? 0) | ((message[i + 5] ?? 0) << 8) | ((message[i + 6] ?? 0) << 16) | ((message[i + 7] ?? 0) << 24), + (message[i + 8] ?? 0) | ((message[i + 9] ?? 0) << 8) | ((message[i + 10] ?? 0) << 16) | ((message[i + 11] ?? 0) << 24), + (message[i + 12] ?? 0) | ((message[i + 13] ?? 0) << 8) | ((message[i + 14] ?? 0) << 16) | ((message[i + 15] ?? 0) << 24), + (message[i + 16] ?? 0) | ((message[i + 17] ?? 0) << 8) | ((message[i + 18] ?? 0) << 16) | ((message[i + 19] ?? 0) << 24), + (message[i + 20] ?? 0) | ((message[i + 21] ?? 0) << 8) | ((message[i + 22] ?? 0) << 16) | ((message[i + 23] ?? 0) << 24), + (message[i + 24] ?? 0) | ((message[i + 25] ?? 0) << 8) | ((message[i + 26] ?? 0) << 16) | ((message[i + 27] ?? 0) << 24), + (message[i + 28] ?? 0) | ((message[i + 29] ?? 0) << 8) | ((message[i + 30] ?? 0) << 16) | ((message[i + 31] ?? 0) << 24) + ) + } +} + +/** + * Copies the bytes of a private key into the static private key input buffer of + * the WASM module. Bytes are packed in little-endian order to align with the + * WASM spec. The entire input is written in one call rather than looped over an + * intermediate array because allocating costs more than the copy itself. + */ +function setInputPrv (prv: Uint8Array): void { + exports.setInputPrv( + prv[0] | (prv[1] << 8) | (prv[2] << 16) | (prv[3] << 24), + prv[4] | (prv[5] << 8) | (prv[6] << 16) | (prv[7] << 24), + prv[8] | (prv[9] << 8) | (prv[10] << 16) | (prv[11] << 24), + prv[12] | (prv[13] << 8) | (prv[14] << 16) | (prv[15] << 24), + prv[16] | (prv[17] << 8) | (prv[18] << 16) | (prv[19] << 24), + prv[20] | (prv[21] << 8) | (prv[22] << 16) | (prv[23] << 24), + prv[24] | (prv[25] << 8) | (prv[26] << 16) | (prv[27] << 24), + prv[28] | (prv[29] << 8) | (prv[30] << 16) | (prv[31] << 24) + ) +} + +/** + * Copies the bytes of a public key into the static public key input buffer of + * the WASM module. Bytes are packed in little-endian order to align with the + * WASM spec. The entire input is written in one call rather than looped over an + * intermediate array because allocating costs more than the copy itself. + */ +function setInputPub (pub: Uint8Array): void { + exports.setInputPub( + pub[0] | (pub[1] << 8) | (pub[2] << 16) | (pub[3] << 24), + pub[4] | (pub[5] << 8) | (pub[6] << 16) | (pub[7] << 24), + pub[8] | (pub[9] << 8) | (pub[10] << 16) | (pub[11] << 24), + pub[12] | (pub[13] << 8) | (pub[14] << 16) | (pub[15] << 24), + pub[16] | (pub[17] << 8) | (pub[18] << 16) | (pub[19] << 24), + pub[20] | (pub[21] << 8) | (pub[22] << 16) | (pub[23] << 24), + pub[24] | (pub[25] << 8) | (pub[26] << 16) | (pub[27] << 24), + pub[28] | (pub[29] << 8) | (pub[30] << 16) | (pub[31] << 24) + ) +} + +/** + * Copies the bytes of a detached signature into the static signature input + * buffer of the WASM module. Bytes are packed in little-endian order to align + * with the WASM spec. The entire input is written in one call rather than looped over an + * intermediate array because allocating costs more than the copy itself. + */ +function setInputSig (sig: Uint8Array): void { + exports.setInputSig( + sig[0] | (sig[1] << 8) | (sig[2] << 16) | (sig[3] << 24), + sig[4] | (sig[5] << 8) | (sig[6] << 16) | (sig[7] << 24), + sig[8] | (sig[9] << 8) | (sig[10] << 16) | (sig[11] << 24), + sig[12] | (sig[13] << 8) | (sig[14] << 16) | (sig[15] << 24), + sig[16] | (sig[17] << 8) | (sig[18] << 16) | (sig[19] << 24), + sig[20] | (sig[21] << 8) | (sig[22] << 16) | (sig[23] << 24), + sig[24] | (sig[25] << 8) | (sig[26] << 16) | (sig[27] << 24), + sig[28] | (sig[29] << 8) | (sig[30] << 16) | (sig[31] << 24), + sig[32] | (sig[33] << 8) | (sig[34] << 16) | (sig[35] << 24), + sig[36] | (sig[37] << 8) | (sig[38] << 16) | (sig[39] << 24), + sig[40] | (sig[41] << 8) | (sig[42] << 16) | (sig[43] << 24), + sig[44] | (sig[45] << 8) | (sig[46] << 16) | (sig[47] << 24), + sig[48] | (sig[49] << 8) | (sig[50] << 16) | (sig[51] << 24), + sig[52] | (sig[53] << 8) | (sig[54] << 16) | (sig[55] << 24), + sig[56] | (sig[57] << 8) | (sig[58] << 16) | (sig[59] << 24), + sig[60] | (sig[61] << 8) | (sig[62] << 16) | (sig[63] << 24) + ) +} + +/** + * Tells the WASM module to clear its own I/O buffers internally, and then + * releases the mutex lock. + */ +export function clearMemory (): void { + exports.clearMemory() + locked = false +} + export const constants = { BLOCKHASH_BYTELENGTH, KEY_BYTELENGTH, @@ -92,47 +190,42 @@ export const constants = { MAX_MESSAGE_BYTELENGTH } -export class MemoryBuffers { - static #check (): void { - if (!locked) throw new Nano25519TypeError('Mutex not acquired') - } - static get INPUT_MSG () { - this.#check() - return exports.setInputMsgByte - } - static get INPUT_MSG_32 () { - this.#check() - return exports.setInputMsg32 - } - static get INPUT_PRV () { - this.#check() - return exports.setInputPrvByte - } - static get INPUT_PUB () { - this.#check() - return exports.setInputPubByte - } - static get INPUT_SIG () { - this.#check() - return exports.setInputSigByte - } - static get OUTPUT_DERIVE () { - this.#check() - return exports.getOutputDeriveByte - } - static get OUTPUT_SIGN () { - this.#check() - return exports.getOutputSignByte - } - static get OUTPUT_VERIFY () { - this.#check() - return exports.getOutputVerifyByte +/** + * Copies the bytes of a specified input parameter into the associated static + * input buffer of the WASM module. Called internal functions must pack input + * bytes in little-endian order to align with the WASM spec. + * + * A mutex lock must first be acquired by called `Mutex.lock()`. The lock is + * only released when `clearMemory()` is called in order to prevent re-entry + * while WASM memory holds sensitive data. + */ +export function setInput (name: 'msg' | 'prv' | 'pub' | 'sig', input: Uint8Array): void { + if (!locked) throw new Nano25519MutexError('Mutex not acquired') + switch (name) { + case 'msg': return setInputMsg(input) + case 'prv': return setInputPrv(input) + case 'pub': return setInputPub(input) + case 'sig': return setInputSig(input) + default: throw new Nano25519TypeError('Invalid input name', { cause: name }) } } -export function clearMemory (): void { - exports.clearMemory() - locked = false +/** + * Copies the bytes of a specified function result from the associated static + * output buffer of the WASM module. + * + * A mutex lock must first be acquired by called `Mutex.lock()`. The lock is + * only released when `clearMemory()` is called in order to prevent re-entry + * while WASM memory holds sensitive data. + */ +export function getOutput (name: 'derive' | 'sign' | 'verify', index: number): number { + if (!locked) throw new Nano25519MutexError('Mutex not acquired') + switch (name) { + case 'derive': return exports.getOutputDeriveByte(index) + case 'sign': return exports.getOutputSignByte(index) + case 'verify': return exports.getOutputVerifyByte(index) + default: throw new Nano25519TypeError('Invalid output name', { cause: name }) + } } export const { derive, sign, verify, verify_blocks } = exports -- 2.52.0