return changetype<usize>(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<usize>(OUTPUT_BUFFER)
}
/**
* 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)
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<u8>(BLOCKHASH_BYTELENGTH)
const verify_blocks_pub = new StaticArray<u8>(KEY_BYTELENGTH)
const verify_blocks_sig = new StaticArray<u8>(SIGNATURE_BYTELENGTH)
+const verify_blocks_out = new StaticArray<u8>(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)
// 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<usize>(verify_blocks_pub), changetype<usize>(PUB_BUFFER), KEY_BYTELENGTH)
PUB_BUFFER.fill(0)
- memory.copy(changetype<usize>(verify_blocks_sig), changetype<usize>(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<usize>(verify_blocks_msg), changetype<usize>(MESSAGE_BUFFER) + (96 * i), BLOCKHASH_BYTELENGTH)
+ memory.copy(changetype<usize>(verify_blocks_sig), changetype<usize>(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<usize>(OUTPUT_BUFFER), changetype<usize>(verify_blocks_out), count * 32)
}
}
/**
- * Nano block signature verification using WebAssembly.
- * @param {Uint8Array<ArrayBuffer>} sig - Buffer of up to 1024 64-byte detached signature (64 KiB)
- * @param {Uint8Array<ArrayBuffer>} msg - Buffer of up to 1024 32-byte block hashes (32 KiB)
+ * Nano block signature bulk verification using WebAssembly.
* @param {Uint8Array<ArrayBuffer>} 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<ArrayBuffer>} blocks.hash - 32-byte block hash
+ * @param {Uint8Array<ArrayBuffer>} 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<ArrayBuffer>, msg: Uint8Array<ArrayBuffer>, pub: Uint8Array<ArrayBuffer>): boolean
+export function verify_blocks (pub: Uint8Array<ArrayBuffer>, blocks: { hash: Uint8Array<ArrayBuffer>, signature: Uint8Array<ArrayBuffer> }[]): 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<ArrayBuffer>, msg: string | Uint8Array<ArrayBuffer>, pub: string | Uint8Array<ArrayBuffer>): 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<ArrayBuffer>, blocks: { hash: string | Uint8Array<ArrayBuffer>, signature: string | Uint8Array<ArrayBuffer> }[]): boolean[] {
+ return nano25519.verify_blocks(pub, blocks)
}
//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
//! 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)
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)
}
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