]> git.codecow.com Git - nano25519.git/commitdiff
Refactor error code handling, eliminate dead code, and remove redundant currying.
authorChris Duncan <chris@zoso.dev>
Thu, 24 Sep 2026 06:42:39 +0000 (23:42 -0700)
committerChris Duncan <chris@zoso.dev>
Thu, 24 Sep 2026 06:42:39 +0000 (23:42 -0700)
.gitignore
src/assembly/env.ts [new file with mode: 0644]
src/lib/derive.ts
src/lib/errors.ts
src/lib/primordials.ts
src/lib/sign.ts
src/lib/verify.ts
src/lib/wasm.ts
test/node.mjs

index 15adba0e66716923b4c9bce6ebcd26a69e203e65..64477c8887f6b498b6c5743c017d306b793393fa 100644 (file)
@@ -3,7 +3,6 @@
 \r
 # dotenv environment variables file\r
 .env\r
-env.*\r
 \r
 # Dependency directories\r
 node_modules/\r
diff --git a/src/assembly/env.ts b/src/assembly/env.ts
new file mode 100644 (file)
index 0000000..67d0fa9
--- /dev/null
@@ -0,0 +1,53 @@
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! 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()
+}
index 74d03979b110a3cf09aee3d1ac86b28ce8933007..44569228dcd71ea2ac6503414bd4d6bd46abf5c8 100644 (file)
@@ -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++) {
index 9ebeb1ad30b2e69954d3d35bf0b7c07d049e34cd..a8ce1585e1e07a9b85fbee91c8b7792c904cf271 100644 (file)
@@ -1,7 +1,8 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! 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<string, number> = {
+       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<number, string> = {
-       [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. */
index cab2ca8cc6c3f609b8ad9cd9a3b26bff292cd9e6..507deb5e5232d646f54a16e6b02e4613011f2473 100644 (file)
  * 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<ArrayBuffer>
+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<ArrayBufferLike>(TypedArray, 'buffer')
 const getBufferByteLength = get<number>(ArrayBuffer.prototype, 'byteLength')
 /** Reads internal slot `[[ByteLength]]`. */
 const getByteLength = get<number>(TypedArray, 'byteLength')
-/** Reads the live backing store of a `WebAssembly.Memory`. */
-const getMemoryBuffer = get<ArrayBuffer>(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 }
index b5d069bb0b5238716b69268826b8700e456cc160..812a2d9e463a155820a7c6fa9e799773dfc41a28 100644 (file)
@@ -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++) {
index bee6f208b0d60bedbb68a7b906fe05adca93d36e..cccaac6871fe4f3eb012b6d67676aff4aacaf5a5 100644 (file)
@@ -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
                }
index 7fd6c7c2ac2a9da92de56dd27bd0ce1da422ebd8..363ff98ad0c6d0b020255bcb709af77a85e7b564 100644 (file)
@@ -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<string, unknown>)[name]) {
-               throw new Nano25519TypeError(`Error code ${name} is ${global.value} in wasm, ${(errors as Record<string, unknown>)[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<string, unknown>)[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
        }
 }
 
index e22a5a9e2e43c4f91dc432c4387bbd6bb064a4b5..aaf4385ab28061a1c6b91e5a4e70585bd8d5aa27 100644 (file)
@@ -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
        }