From: Chris Duncan Date: Wed, 26 Aug 2026 07:52:54 +0000 (-0700) Subject: Start building out bulk Nano block verification, inserting all hash/signature pairs... X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=ccee79593911de8ba3d62a19ebe73787efb15196;p=nano25519.git Start building out bulk Nano block verification, inserting all hash/signature pairs into the message buffer and parsing them within WASM. --- diff --git a/src/assembly/index.ts b/src/assembly/index.ts index 522f278..27aff69 100644 --- a/src/assembly/index.ts +++ b/src/assembly/index.ts @@ -27,7 +27,7 @@ export function getMessagePointer (): usize { return changetype(MESSAGE_BUFFER) } -/** Returns the pointer to the static output buffer (1024 bytes). */ +/** Returns the pointer to the static output buffer (512 bytes). */ export function getOutputPointer (): usize { return changetype(OUTPUT_BUFFER) } @@ -130,10 +130,10 @@ const verify_sig = new StaticArray(SIGNATURE_BYTELENGTH) /** * Verify a 64-byte detached signature for a variable-length message against a * 32-byte public key. Input parameters are read as bytes from separate buffers. - * @param {u64} mlen Byte length of message that was signed, up to 32768 + * @param {i32} mlen Byte length of message that was signed, up to 32768 * @returns {boolean} True if message was signed by public key's private key */ -export function verify (mlen: i32): i32 { +export function verify (mlen: i32): void { if (mlen < 0 || mlen > MESSAGE_BUFFER_BYTELENGTH) { PRV_BUFFER.fill(0) PUB_BUFFER.fill(0) @@ -157,24 +157,34 @@ export function verify (mlen: i32): i32 { verify_pub.fill(0) verify_sig.fill(0) - // Output buffer not used, so just return - return verified + // Clear output buffer of prior data, then copy local result to output buffer + OUTPUT_BUFFER.fill(0) + OUTPUT_BUFFER[0] = u8(verified) + + // Clear local result + sign_sig.fill(0) } +const verify_blocks_msg = new StaticArray(BLOCKHASH_BYTELENGTH) const verify_blocks_pub = new StaticArray(KEY_BYTELENGTH) const verify_blocks_sig = new StaticArray(SIGNATURE_BYTELENGTH) +const verify_blocks_out = new StaticArray(256) /** * Verify a 64-byte detached signature for a 32-byte Nano block hash against a * 32-byte public key. Input parameters are read as bytes from separate buffers. * - * The message buffer and the signature buffer can hold up 1024 block hashes and - * signatures to verify many blocks for a single public key. - * @param {u64} mlen Byte length of message that was signed, up to 32768 - * @returns {boolean} True if message was signed by public key's private key + * The message buffer holds both the block hash and the signature buffer as a + * 96-byte concatenated value. The 32 KiB message buffer cannot be divided + * evenly by 96, so a conservative 24 KiB is used which supports up to 256 block + * hash/signature pairs. + * + * All pairs are verified against a single public key. The results are written + * to the output buffer in the same order they were received. + * @param {i32} count Number of signatures to verify, up to 256 */ -export function verify_blocks (mlen: i32): i32 { - if (mlen < 0 || mlen > MESSAGE_BUFFER_BYTELENGTH) { +export function verify_blocks (count: i32): void { + if (count < 0 || count > 256) { PRV_BUFFER.fill(0) PUB_BUFFER.fill(0) MESSAGE_BUFFER.fill(0) @@ -183,20 +193,28 @@ export function verify_blocks (mlen: i32): i32 { // Clear local buffers verify_blocks_pub.fill(0) - verify_blocks_sig.fill(0) // Copy input buffer to local parameters, then clear input buffer memory.copy(changetype(verify_blocks_pub), changetype(PUB_BUFFER), KEY_BYTELENGTH) PUB_BUFFER.fill(0) - memory.copy(changetype(verify_blocks_sig), changetype(SIGNATURE_BUFFER), SIGNATURE_BYTELENGTH) - SIGNATURE_BUFFER.fill(0) - // Verify message from buffer, then clear local input - const verified = crypto_verify_relaxed(verify_blocks_sig, MESSAGE_BUFFER, mlen, verify_blocks_pub) + // Iterate over block hash/signature pairs + for (let i = 0; i < count; i++) { + // Copy message buffer to local block hash/signature buffers + memory.copy(changetype(verify_blocks_msg), changetype(MESSAGE_BUFFER) + (96 * i), BLOCKHASH_BYTELENGTH) + memory.copy(changetype(verify_blocks_sig), changetype(MESSAGE_BUFFER) + BLOCKHASH_BYTELENGTH + (96 * i), SIGNATURE_BYTELENGTH) + + // Verify hash and signature, then copy local result to output buffer + const verified = crypto_verify_relaxed(verify_blocks_sig, verify_blocks_msg, BLOCKHASH_BYTELENGTH, verify_blocks_pub) + verify_blocks_out[i] = u8(verified) + } + + // Clear local input MESSAGE_BUFFER.fill(0) verify_blocks_pub.fill(0) verify_blocks_sig.fill(0) - // Output buffer not used, so just return - return verified + // Clear output buffer of prior data, then copy local result to output buffer + OUTPUT_BUFFER.fill(0) + memory.copy(changetype(OUTPUT_BUFFER), changetype(verify_blocks_out), count * 32) } diff --git a/src/index.ts b/src/index.ts index 5ba0473..682d81d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -83,23 +83,24 @@ export function verify (sig: string | Uint8Array, msg: string | Uin } /** - * Nano block signature verification using WebAssembly. - * @param {Uint8Array} sig - Buffer of up to 1024 64-byte detached signature (64 KiB) - * @param {Uint8Array} msg - Buffer of up to 1024 32-byte block hashes (32 KiB) + * Nano block signature bulk verification using WebAssembly. * @param {Uint8Array} pub - 32-byte public key - * @returns true if signature matches block hash and public key, else false + * @param {object[]} blocks - Array of up to 256 block hash/signature pairs + * @param {Uint8Array} blocks.hash - 32-byte block hash + * @param {Uint8Array} blocks.signature - 64-byte block signature + * @returns Array of results with, for each case, true if signature matches block hash and public key, else false */ -export function verify_blocks (sig: Uint8Array, msg: Uint8Array, pub: Uint8Array): boolean +export function verify_blocks (pub: Uint8Array, blocks: { hash: Uint8Array, signature: Uint8Array }[]): boolean[] /** - * Signature verification using WebAssembly. To verify Nano block signatures, - * the message should be a 64-character block hash. - * @param {string} sig - 128-character hexadecimal detached signature - * @param {string} msg - Variable-length message up to 32 KiB + * Nano block signature bulk verification using WebAssembly. * @param {string} pub - 64-character hexadecimal public key - * @returns true if signature matches block hash and public key, else false + * @param {object[]} blocks - Array of up to 256 block hash/signature pairs + * @param {string} blocks.hash - 64-character hexadecimal block hash + * @param {string} blocks.signature - 128-character hexadecimal block signature + * @returns Array of results with, for each case, true if signature matches block hash and public key, else false */ -export function verify_blocks (sig: string, msg: string, pub: string): boolean -export function verify_blocks (sig: string | Uint8Array, msg: string | Uint8Array, pub: string | Uint8Array): boolean { - return nano25519.verify_blocks(sig, msg, pub) +export function verify_blocks (pub: string, blocks: { hash: string, signature: string }[]): boolean[] +export function verify_blocks (pub: string | Uint8Array, blocks: { hash: string | Uint8Array, signature: string | Uint8Array }[]): boolean[] { + return nano25519.verify_blocks(pub, blocks) } diff --git a/src/lib/verify.ts b/src/lib/verify.ts index 0643820..a0d53c9 100644 --- a/src/lib/verify.ts +++ b/src/lib/verify.ts @@ -1,7 +1,7 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { clear, exports, KEY_LEN, MSG_BUF_LEN, MSG_PTR, normalize, PUB_PTR, SIG_LEN, SIG_PTR } from './wasm' +import { BLK_LEN, KEY_LEN, MSG_BUF_LEN, MSG_PTR, OUT_PTR, PUB_PTR, SIG_LEN, SIG_PTR, clear, exports, normalize } from './wasm' export function verify (sig: unknown, msg: unknown, pub: unknown): boolean { let buffer = new Uint8Array(exports.memory.buffer) @@ -18,30 +18,46 @@ export function verify (sig: unknown, msg: unknown, pub: unknown): boolean { for (let i = 0; i < SIG_LEN; i++) { buffer[SIG_PTR + i] = signature[i] } - const v = exports.verify(message.byteLength) - return v === 0 + exports.verify(message.byteLength) + buffer = new Uint8Array(exports.memory.buffer) + const verified = buffer[OUT_PTR] + return verified === 0 } finally { clear(buffer) } } -export function verify_blocks (sig: unknown, msg: unknown, pub: unknown): boolean { +export function verify_blocks (pub: unknown, data: unknown): boolean[] { + const blocks: unknown[] = Array.isArray(data) ? data : [data] + const count = blocks.length + const verified = new Uint8Array(count) let buffer = new Uint8Array(exports.memory.buffer) try { - const message = normalize('message', 0, MSG_BUF_LEN, msg) const publicKey = normalize('public key', KEY_LEN, KEY_LEN, pub) - const signature = normalize('signature', SIG_LEN, SIG_LEN, sig) - for (let i = 0; i < message.byteLength; i++) { - buffer[MSG_PTR + i] = message[i] - } for (let i = 0; i < KEY_LEN; i++) { buffer[PUB_PTR + i] = publicKey[i] } + for (let b = 0, p = MSG_PTR; b < count; b++) { + const block = blocks[b] + if (!(block != null && typeof block === 'object' && 'hash' in block && 'signature' in block)) { + throw new TypeError('invalid block', { cause: block }) + } + const hash = normalize('hash', BLK_LEN, BLK_LEN, block.hash) + const signature = normalize('signature', SIG_LEN, SIG_LEN, block.signature) + for (let i = 0; i < BLK_LEN; i++) { + buffer[p + i] = hash[i] + } + p += BLK_LEN + for (let i = 0; i < SIG_LEN; i++) { + buffer[p + i] = signature[i] + } + p += SIG_LEN + } + exports.verify_blocks(blocks.length) for (let i = 0; i < SIG_LEN; i++) { - buffer[SIG_PTR + i] = signature[i] + verified[i] = buffer[OUT_PTR + i] } - const v = exports.verify(message.byteLength) - return v === 0 + return [...verified].map(v => v === 0) } finally { clear(buffer) } diff --git a/src/lib/wasm.ts b/src/lib/wasm.ts index 56b0ee0..c25c24d 100644 --- a/src/lib/wasm.ts +++ b/src/lib/wasm.ts @@ -8,7 +8,8 @@ type Exports = { exports: { derive: () => void sign: (mlen: number) => void - verify: (mlen: number) => number + verify: (mlen: number) => void + verify_blocks: (count: number) => void getMessagePointer: () => number getOutputPointer: () => number getPrivateKeyPointer: () => number