From: Chris Duncan Date: Thu, 24 Sep 2026 06:42:39 +0000 (-0700) Subject: Refactor error code handling, eliminate dead code, and remove redundant currying. X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=6412abb711466da76beb3cfe10f57a4c990104ca;p=nano25519.git Refactor error code handling, eliminate dead code, and remove redundant currying. --- diff --git a/.gitignore b/.gitignore index 15adba0..64477c8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ # dotenv environment variables file .env -env.* # Dependency directories node_modules/ diff --git a/src/assembly/env.ts b/src/assembly/env.ts new file mode 100644 index 0000000..67d0fa9 --- /dev/null +++ b/src/assembly/env.ts @@ -0,0 +1,53 @@ +//! SPDX-FileCopyrightText: 2026 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +import { ERROR_UNKNOWN, } from './errors' + +/** + * The module's only import, installed as AssemblyScript's `abort` by + * `use: ["abort=src/assembly/abort/abort"]` in `asconfig.json`. + * + * It takes four integers and no pointers so the host never reads WASM memory + * to report an error unlike the default `abort` which decodes a UTF-16 string + * from linear memory using pointer arithmetic. + */ +//@ts-expect-error +@external('env', 'abort') +declare function env_abort (code: i32, line: i32, detail: i32, context: i32): void + +let errorCode: i32 = ERROR_UNKNOWN +let errorDetail: i32 = 0 +let errorContext: i32 = 0 + +/** + * Reads the pending code, resets it, and hands it to the host. Resetting + * *before* the call matters: `env_abort` throws, so nothing after it runs, and + * a stale code would otherwise be attributed to the next uncoded abort. + */ +export function abort (message: usize, fileName: usize, line: u32, col: u32): void { + const code = errorCode + const detail = errorDetail + const context = errorContext + errorCode = ERROR_UNKNOWN + errorDetail = 0 + errorContext = 0 + env_abort(code, i32(line), detail, context) +} + +/** + * Throws, reporting `code` and up to two context integers to the host. + * + * **Must stay `@inline`.** AssemblyScript fills in the line number at the + * `throw`, so inlining is what makes the host report the caller's line rather + * than this file's. Also do not pass anything to `Error()` since that will flag + * to AssemblyScript that String functionality should be included in the runtime + * stub. + */ +//@ts-expect-error +@inline +export function raise (code: i32, a: i32 = 0, b: i32 = 0): void { + errorCode = code + errorDetail = a + errorContext = b + throw new Error() +} diff --git a/src/lib/derive.ts b/src/lib/derive.ts index 74d0397..4456922 100644 --- a/src/lib/derive.ts +++ b/src/lib/derive.ts @@ -15,7 +15,7 @@ export function derive (prv: unknown, out?: unknown): string | void { Mutex.lock() try { for (let i = 0; i < KEY_BYTELENGTH; i++) { - MemoryBuffers.INPUT_PRV(i)(privateKey[i]) + MemoryBuffers.INPUT_PRV(i, privateKey[i]) } wasm_derive() for (let i = 0; i < KEY_BYTELENGTH; i++) { diff --git a/src/lib/errors.ts b/src/lib/errors.ts index 9ebeb1a..a8ce158 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -1,7 +1,8 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { Nano25519WasmError } from './primordials' +export class Nano25519TypeError extends TypeError { } +export class Nano25519WasmError extends Error { } /** * Message text for the codes in `src/assembly/errors.ts`. The numbers are the @@ -13,37 +14,23 @@ import { Nano25519WasmError } from './primordials' * handler has to exist *before* instantiation: the `equalbytes` guard runs in * the start function, so it can abort while `exports` is still undefined. */ -export const ERROR_UNKNOWN = 0 -export const ERROR_INVALID_PUBLIC_KEY = 1 -export const ERROR_INVALID_MESSAGE_LENGTH = 2 -export const ERROR_INVALID_BLOCK_COUNT = 3 -export const ERROR_INDEX_OUT_OF_RANGE = 4 -export const ERROR_BYTE_OUT_OF_RANGE = 5 -export const ERROR_SELFTEST_XPASS = 6 -export const ERROR_SELFTEST_XFAIL_ONE = 7 -export const ERROR_SELFTEST_XFAIL_PAIR = 8 +export const errorCodes: Record = { + ERROR_UNKNOWN: 0, + ERROR_INVALID_PUBLIC_KEY: 1, + ERROR_INVALID_MESSAGE_LENGTH: 2, + ERROR_INVALID_BLOCK_COUNT: 3, + ERROR_INDEX_OUT_OF_RANGE: 4, + ERROR_BYTE_OUT_OF_RANGE: 5, + ERROR_SELFTEST_XPASS: 6, + ERROR_SELFTEST_XFAIL_ONE: 7, + ERROR_SELFTEST_XFAIL_PAIR: 8, +} const VECTORS = ['zeros', 'ones', 'spread'] /** Buffer ids from `src/assembly/errors.ts`, in value order. */ const BUFFERS = ['INPUT_MSG', 'INPUT_PRV', 'INPUT_PUB', 'INPUT_SIG', 'OUTPUT_DERIVE', 'OUTPUT_SIGN', 'OUTPUT_VERIFY'] -/** - * Source file per code. `env.abort` no longer receives a file pointer, so the - * code carries that instead — which holds only while **each code is raised - * from exactly one file**. Reusing a code across files makes this lie. - */ -const FILES: Record = { - [ERROR_INVALID_PUBLIC_KEY]: 'src/assembly/index.ts', - [ERROR_INVALID_MESSAGE_LENGTH]: 'src/assembly/index.ts', - [ERROR_INVALID_BLOCK_COUNT]: 'src/assembly/index.ts', - [ERROR_INDEX_OUT_OF_RANGE]: 'src/assembly/index.ts', - [ERROR_BYTE_OUT_OF_RANGE]: 'src/assembly/index.ts', - [ERROR_SELFTEST_XPASS]: 'src/assembly/tests.ts', - [ERROR_SELFTEST_XFAIL_ONE]: 'src/assembly/tests.ts', - [ERROR_SELFTEST_XFAIL_PAIR]: 'src/assembly/tests.ts' -} - /** * Rebuilds a self-test vector. These are the same three definitions as * `src/assembly/tests.ts`; keeping them here is what lets the host report the @@ -61,13 +48,13 @@ function selftest (code: number, v: number, detail: number, length: number): str const name = VECTORS[v] ?? `vector ${v}` const expected = vector(v, length) const actual = vector(v, length) - if (code === ERROR_SELFTEST_XFAIL_ONE) { + if (code === errorCodes.ERROR_SELFTEST_XFAIL_ONE) { const position = (detail >> 8) & 0xff const mask = detail & 0xff actual[position] ^= mask return `equalbytes build error from "${name}": expected fail [${expected}]; actual pass [${actual}] (byte ${position} ^ 0x${mask.toString(16).padStart(2, '0')})` } - if (code === ERROR_SELFTEST_XFAIL_PAIR) { + if (code === errorCodes.ERROR_SELFTEST_XFAIL_PAIR) { const i = (detail >> 8) & 0xff const j = detail & 0xff actual[i] ^= 0x80 @@ -77,39 +64,31 @@ function selftest (code: number, v: number, detail: number, length: number): str return `equalbytes build error from "${name}": expected pass [${expected}]; actual fail` } -/** Turns the four integers `env.abort` reports into a message. */ -export function describe (code: number, line: number, a: number, b: number): string { - let text: string +/** + * Turns the four integers reported by `env.abort` into a message. `line` only + * locates an ERROR_UNKNOWN abort: every coded site reports the `throw` inside + * `raise` because AssemblyScript fixes the line before inlining, so the code + * and its details are the location for coded errors. + */ +function describe (code: number, line: number, detail: number, context: number): string { switch (code) { - case ERROR_INVALID_PUBLIC_KEY: - text = 'Invalid public key' - break - case ERROR_INVALID_MESSAGE_LENGTH: - text = `Invalid message length: ${a} is not in [0, ${b}]` - break - case ERROR_INVALID_BLOCK_COUNT: - text = `Invalid block count: ${a} is not in [1, ${b}]` - break - case ERROR_INDEX_OUT_OF_RANGE: - text = `Buffer index out of range for ${BUFFERS[a] ?? `buffer ${a}`}: ${b}` - break - case ERROR_BYTE_OUT_OF_RANGE: - text = `Byte value out of range for ${BUFFERS[a] ?? `buffer ${a}`}: ${b} is not in [0, 255]` - break - case ERROR_SELFTEST_XPASS: - case ERROR_SELFTEST_XFAIL_ONE: - case ERROR_SELFTEST_XFAIL_PAIR: - text = selftest(code, a, b, 32) - break + case errorCodes.ERROR_INVALID_PUBLIC_KEY: + return 'Invalid public key' + case errorCodes.ERROR_INVALID_MESSAGE_LENGTH: + return `Invalid message length: ${detail} is not in [0, ${context}]` + case errorCodes.ERROR_INVALID_BLOCK_COUNT: + return `Invalid block count: ${detail} is not in [1, ${context}]` + case errorCodes.ERROR_INDEX_OUT_OF_RANGE: + return `Buffer index out of range for ${BUFFERS[detail] ?? `buffer ${detail}`}: ${context}` + case errorCodes.ERROR_BYTE_OUT_OF_RANGE: + return `Byte value out of range for ${BUFFERS[detail] ?? `buffer ${detail}`}: ${context} is not in [0, 255]` + case errorCodes.ERROR_SELFTEST_XPASS: + case errorCodes.ERROR_SELFTEST_XFAIL_ONE: + case errorCodes.ERROR_SELFTEST_XFAIL_PAIR: + return selftest(code, detail, context, 32) default: - text = `Unrecognized wasm abort (code ${code}, a ${a}, b ${b})` + return `Unrecognized wasm abort (code ${code}, a ${detail}, b ${context}), row ${line}` } - // `line` only locates an ERROR_UNKNOWN abort. Every coded site reports the - // `throw` inside `raise`, because AssemblyScript fixes the line before - // inlining — so for coded errors the code and its details are the location. - return code === ERROR_UNKNOWN - ? `${text}, row ${line}` - : `${text}, ${FILES[code] ?? 'unknown file'}` } /** The module's only import. Takes four integers and never reads wasm memory. */ diff --git a/src/lib/primordials.ts b/src/lib/primordials.ts index cab2ca8..507deb5 100644 --- a/src/lib/primordials.ts +++ b/src/lib/primordials.ts @@ -38,20 +38,11 @@ * bundler will order that dependency first, and any call it makes into here * during its own evaluation will read captures that are still unassigned. */ - export type Bytes = Uint8Array +export const isArray = Array.isArray -/** Declared here to avoid import loops. */ -export class Nano25519TypeError extends TypeError { } -export class Nano25519WasmError extends Error { } - -const isArray = Array.isArray - -/** `TypedArray` constructors, bound before anything can replace the global. */ +/** `TypedArray` constructor, bound before anything can replace the global. */ const Bytes = Uint8Array -const Words = Uint16Array -const Longs = Uint32Array - const TypedArray = Object.getPrototypeOf(Bytes.prototype) const bytesSet = Bytes.prototype.set @@ -73,29 +64,6 @@ const getBuffer = get(TypedArray, 'buffer') const getBufferByteLength = get(ArrayBuffer.prototype, 'byteLength') /** Reads internal slot `[[ByteLength]]`. */ const getByteLength = get(TypedArray, 'byteLength') -/** Reads the live backing store of a `WebAssembly.Memory`. */ -const getMemoryBuffer = get(WebAssembly.Memory.prototype, 'buffer') - -/** Reads one unsigned 32-bit word from `buffer` at byte `offset`. */ -function readLong (buffer: ArrayBufferLike, offset: number): number { - return new Longs(buffer)[offset >>> 2] -} - -const fromCharCode = String.fromCharCode -/** - * Reads a UTF-16 string of `length` code units from `buffer` at `offset`. - * Used only by the WASM abort handler. Builds the string one code unit at a - * time rather than spreading a subarray, which would run the iterator and - * risk a stack overflow on a long message. - */ -function utf16 (buffer: ArrayBufferLike, offset: number, length: number): string { - const words = new Words(buffer) - let string = '' - for (let i = 0; i < length; i++) { - string += fromCharCode(words[offset + i]) - } - return string -} /** True for a genuine `ArrayBuffer`, false for a `SharedArrayBuffer`. */ function isArrayBuffer (a: unknown): a is ArrayBuffer { @@ -111,34 +79,17 @@ function isArrayBuffer (a: unknown): a is ArrayBuffer { * Reads internal slot `[[TypedArrayName]]`, so a subclass overriding * `Symbol.toStringTag` cannot pass, and a look-alike object cannot either. */ -function isBytes (a: unknown): a is Bytes { +export function isBytes (a: unknown): a is Bytes { return getTag.call(a) === 'Uint8Array' && isArrayBuffer(getBuffer.call(a)) } -/** - * The live backing buffer of `memory`. Read through the captured accessor - * because every host read and write goes through it: a patched getter would - * redirect the whole I/O path to a decoy buffer, and the detachment check - * cannot notice because both of its operands come from the same getter. - */ -function memoryBuffer (memory: WebAssembly.Memory): ArrayBuffer { - return getMemoryBuffer.call(memory) -} - -/** Checks if WASM memory buffer was detached. */ -function validateMemoryBuffer (memory: WebAssembly.Memory, bytes: Uint8Array): void { - if (getMemoryBuffer.call(memory) !== getBuffer.call(bytes)) { - throw new Nano25519TypeError('WASM memory buffer detached') - } -} - /** Captured version of `new Uint8Array(byteLength)`. */ -function allocate (length: number): Bytes { +export function allocate (length: number): Bytes { return new Bytes(length) } /** Captured version of `Uint8Array.prototype.byteLength`. */ -function byteLength (bytes: Uint8Array): number { +export function byteLength (bytes: Uint8Array): number { return getByteLength.call(bytes) } @@ -147,10 +98,10 @@ function byteLength (bytes: Uint8Array): number { * `Symbol.species`, so it cannot hand control to a caller-supplied subclass, * and it sizes the copy from an internal slot rather than a spoofable `length`. */ -function copy (src: Uint8Array): Bytes +export function copy (src: Uint8Array): Bytes /** `target.set(source, offset)` through the captured method. */ -function copy (dst: Uint8Array, src: Uint8Array, offset?: number): void -function copy (a: Uint8Array, b?: Uint8Array, o?: number): void | Bytes { +export function copy (dst: Uint8Array, src: Uint8Array, offset?: number): void +export function copy (a: Uint8Array, b?: Uint8Array, o?: number): void | Bytes { if (b) { return bytesSet.call(a, b, o) } @@ -160,12 +111,12 @@ function copy (a: Uint8Array, b?: Uint8Array, o?: number): void | Bytes { } /** Captured version of `target.fill(value, start, end)`. */ -function fill (target: Uint8Array, value: number, start?: number, end?: number): void { +export function fill (target: Uint8Array, value: number, start?: number, end?: number): void { bytesFill.call(target, value, start, end) } /** Captured version of `new Uint8Array(buffer)`. */ -function view (buffer: ArrayBuffer): Bytes { +export function view (buffer: ArrayBuffer): Bytes { return new Bytes(buffer) } @@ -177,6 +128,17 @@ for (let i = 0; i < 16; i++) { NIBBLE[UPPER[i]] = i } +/** Lowercase hexadecimal encoding of `bytes`. */ +export function bytesToHex (bytes: Uint8Array): string { + const byteLength = getByteLength.call(bytes) + let hex = '' + for (let i = 0; i < byteLength; i++) { + const byte = bytes[i] + hex += LOWER[byte >> 4] + LOWER[byte & 15] + } + return hex +} + /** * Decodes hex string to Bytes. Validates input directly instead of using a * patchable `RegExp.prototype.test()`. @@ -200,18 +162,6 @@ function hexToBytes (hex: string): Bytes { } return bytes } - -/** Lowercase hexadecimal encoding of `bytes`. */ -function bytesToHex (bytes: Uint8Array): string { - const byteLength = getByteLength.call(bytes) - let hex = '' - for (let i = 0; i < byteLength; i++) { - const byte = bytes[i] - hex += LOWER[byte >> 4] + LOWER[byte & 15] - } - return hex -} - /** * Accepts an unknown input; checks that it is a representation of bytes using a * hex string, ArrayBuffer, or Uint8Array; and validates that the byte length of @@ -223,7 +173,7 @@ function bytesToHex (bytes: Uint8Array): string { * @param {unknown} value User input to validate * @returns User input bytes copied to a new Uint8Array for internal use. */ -function normalize (name: string, byteLengthMin: number, byteLengthMax: number, value: unknown): Bytes { +export function normalize (name: string, byteLengthMin: number, byteLengthMax: number, value: unknown): Bytes { if (typeof value === 'string') { if (value.length < (byteLengthMin << 1) || value.length > (byteLengthMax << 1)) { throw new TypeError(`Invalid hexadecimal length ${value.length} for ${name}`) @@ -247,5 +197,3 @@ function normalize (name: string, byteLengthMin: number, byteLengthMax: number, } return bytes } - -export { allocate, byteLength, bytesToHex, copy, fill, isArray, isBytes, memoryBuffer, normalize, readLong, utf16, validateMemoryBuffer, view } diff --git a/src/lib/sign.ts b/src/lib/sign.ts index b5d069b..812a2d9 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 { clearMemory, constants, MemoryBuffers, Mutex, sign as wasm_sign } from './wasm' +import { MemoryBuffers, Mutex, clearMemory, constants, sign as wasm_sign } from './wasm' const { KEY_BYTELENGTH, MAX_MESSAGE_BYTELENGTH, SIGNATURE_BYTELENGTH, } = constants @@ -17,13 +17,13 @@ export function sign (msg: unknown, prv: unknown, pub: unknown, out?: unknown): Mutex.lock() try { for (let i = 0; i < message.byteLength; i++) { - MemoryBuffers.INPUT_MSG(i)(message[i]) + MemoryBuffers.INPUT_MSG(i, message[i]) } for (let i = 0; i < KEY_BYTELENGTH; i++) { - MemoryBuffers.INPUT_PRV(i)(privateKey[i]) + MemoryBuffers.INPUT_PRV(i, privateKey[i]) } for (let i = 0; i < KEY_BYTELENGTH; i++) { - MemoryBuffers.INPUT_PUB(i)(publicKey[i]) + MemoryBuffers.INPUT_PUB(i, publicKey[i]) } wasm_sign(message.byteLength) for (let i = 0; i < SIGNATURE_BYTELENGTH; i++) { diff --git a/src/lib/verify.ts b/src/lib/verify.ts index bee6f20..cccaac6 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 { clearMemory, constants, MemoryBuffers, Mutex, verify as wasm_verify, verify_blocks as wasm_verify_blocks, } from './wasm' +import { MemoryBuffers, Mutex, clearMemory, constants, 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 @@ -13,13 +13,13 @@ export function verify (sig: unknown, msg: unknown, pub: unknown): boolean { 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++) { - MemoryBuffers.INPUT_MSG(i)(message[i]) + MemoryBuffers.INPUT_MSG(i, message[i]) } for (let i = 0; i < KEY_BYTELENGTH; i++) { - MemoryBuffers.INPUT_PUB(i)(publicKey[i]) + MemoryBuffers.INPUT_PUB(i, publicKey[i]) } for (let i = 0; i < SIGNATURE_BYTELENGTH; i++) { - MemoryBuffers.INPUT_SIG(i)(signature[i]) + MemoryBuffers.INPUT_SIG(i, signature[i]) } wasm_verify(message.byteLength) const verified = MemoryBuffers.OUTPUT_VERIFY(0) @@ -43,7 +43,7 @@ export function verify_blocks (pub: unknown, data: unknown): boolean[] { 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]) + MemoryBuffers.INPUT_PUB(i, publicKey[i]) } for (let b = 0, p = 0; b < count; b++) { const block = blocks[b] @@ -59,11 +59,11 @@ 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]) + 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]) + MemoryBuffers.INPUT_MSG(p + i, hash[i]) } p += BLOCKHASH_BYTELENGTH } diff --git a/src/lib/wasm.ts b/src/lib/wasm.ts index 7fd6c7c..363ff98 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, copy } from './primordials' -import * as errors from './errors' +import { Nano25519TypeError, abort, errorCodes } from './errors' +import { copy } from './primordials' type Exports = WebAssembly.Instance['exports'] & { clearMemory: () => void @@ -25,6 +25,22 @@ type Exports = WebAssembly.Instance['exports'] & { MAX_VERIFY_BLOCKS: WebAssembly.Global MAX_VERIFY_BLOCKS_BYTELENGTH: WebAssembly.Global MAX_MESSAGE_BYTELENGTH: WebAssembly.Global + BUFFER_INPUT_MSG: WebAssembly.Global + BUFFER_INPUT_PRV: WebAssembly.Global + BUFFER_INPUT_PUB: WebAssembly.Global + BUFFER_INPUT_SIG: WebAssembly.Global + BUFFER_OUTPUT_DERIVE: WebAssembly.Global + BUFFER_OUTPUT_SIGN: WebAssembly.Global + BUFFER_OUTPUT_VERIFY: WebAssembly.Global + ERROR_BYTE_OUT_OF_RANGE: WebAssembly.Global + ERROR_INDEX_OUT_OF_RANGE: WebAssembly.Global + ERROR_INVALID_BLOCK_COUNT: WebAssembly.Global + ERROR_INVALID_MESSAGE_LENGTH: WebAssembly.Global + ERROR_INVALID_PUBLIC_KEY: WebAssembly.Global + ERROR_SELFTEST_XFAIL_ONE: WebAssembly.Global + ERROR_SELFTEST_XFAIL_PAIR: WebAssembly.Global + ERROR_SELFTEST_XPASS: WebAssembly.Global + ERROR_UNKNOWN: WebAssembly.Global } let locked = false @@ -37,22 +53,24 @@ export const Mutex = { const wasm = copy(nano25519_wasm) const module = new WebAssembly.Module(wasm) - -const instance = new WebAssembly.Instance(module, { env: { abort: errors.abort } }) +const instance = new WebAssembly.Instance(module, { env: { abort } }) const exports = instance.exports as Exports /** * The error codes are a contract between `src/assembly/errors.ts` and * `src/lib/errors.ts`. The host table cannot be read from the module, because - * the handler has to exist before instantiation, so it is checked against the - * module here instead. A mismatch means one side was renumbered alone. + * the handler has to exist before instantiation, so it is checked that it + * matches against the module here instead. */ -for (const name of Object.keys(errors)) { - if (!name.startsWith('ERROR_')) continue - const global = exports[name] as WebAssembly.Global | undefined - if (global == null) throw new Nano25519TypeError(`Missing wasm error code ${name}`) - if (global.value !== (errors as Record)[name]) { - throw new Nano25519TypeError(`Error code ${name} is ${global.value} in wasm, ${(errors as Record)[name]} in host`) +for (const errorCode of Object.keys(errorCodes)) { + if (errorCode.startsWith('ERROR_')) { + const wasmErrorCode = exports[errorCode] + if (wasmErrorCode == null || !('value' in wasmErrorCode)) { + throw new Nano25519TypeError(`Missing WASM error code ${errorCode}`) + } + if (wasmErrorCode.value !== errorCodes[errorCode]) { + throw new Nano25519TypeError(`Error code ${errorCode} is ${wasmErrorCode.value} in wasm, ${(errorCodes as Record)[errorCode]} in host`) + } } } @@ -79,31 +97,31 @@ export class MemoryBuffers { } static get INPUT_MSG () { this.#check() - return (i: number) => (v: number) => exports.setInputMsgByte(i, v) + return exports.setInputMsgByte } static get INPUT_PRV () { this.#check() - return (i: number) => (v: number) => exports.setInputPrvByte(i, v) + return exports.setInputPrvByte } static get INPUT_PUB () { this.#check() - return (i: number) => (v: number) => exports.setInputPubByte(i, v) + return exports.setInputPubByte } static get INPUT_SIG () { this.#check() - return (i: number) => (v: number) => exports.setInputSigByte(i, v) + return exports.setInputSigByte } static get OUTPUT_DERIVE () { this.#check() - return (i: number) => exports.getOutputDeriveByte(i) + return exports.getOutputDeriveByte } static get OUTPUT_SIGN () { this.#check() - return (i: number) => exports.getOutputSignByte(i) + return exports.getOutputSignByte } static get OUTPUT_VERIFY () { this.#check() - return (i: number) => exports.getOutputVerifyByte(i) + return exports.getOutputVerifyByte } } diff --git a/test/node.mjs b/test/node.mjs index e22a5a9..aaf4385 100644 --- a/test/node.mjs +++ b/test/node.mjs @@ -161,10 +161,12 @@ failures += +!test try { result = sign(NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.privateKey, NANO_ORG_VECTOR.publicKey.replace('3', '1')) + console.log(result) result = false } catch (err) { + console.log(err) if (err != null && typeof err === 'object' && 'message' in err && typeof err.message === 'string') { - result = err.constructor.name === 'Nano25519WasmError' && err.message === 'Invalid public key, src/assembly/index.ts' + result = err.constructor.name === 'Nano25519WasmError' && err.message === 'Invalid public key' } else { result = false }