]> git.codecow.com Git - nano25519.git/commitdiff
Start building out bulk Nano block verification, inserting all hash/signature pairs...
authorChris Duncan <chris@zoso.dev>
Wed, 26 Aug 2026 07:52:54 +0000 (00:52 -0700)
committerChris Duncan <chris@zoso.dev>
Wed, 26 Aug 2026 07:52:54 +0000 (00:52 -0700)
src/assembly/index.ts
src/index.ts
src/lib/verify.ts
src/lib/wasm.ts

index 522f2783f1f709059ac5a4a2abe5f6277d958675..27aff69aa7d50bfd8ae7180d2d944dbf96cac42b 100644 (file)
@@ -27,7 +27,7 @@ export function getMessagePointer (): usize {
        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)
 }
@@ -130,10 +130,10 @@ const verify_sig = new StaticArray<u8>(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<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)
@@ -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<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)
 }
index 5ba0473d0f0f3f4fee369a12fa4f669340fd9aac..682d81dbdab95baf7c25c9bc46963012361f55bb 100644 (file)
@@ -83,23 +83,24 @@ export function verify (sig: string | Uint8Array<ArrayBuffer>, msg: string | Uin
 }
 
 /**
- * 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)
 }
 
index 0643820f79725d6a06050dfe66f3b5d288b58482..a0d53c92fb3e14c0c4c9f2538dc029d876004abd 100644 (file)
@@ -1,7 +1,7 @@
 //! 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)
@@ -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)
        }
index 56b0ee0347c7237fe717e84234c522a7b01c5f25..c25c24d9178089cedc0d97f19008409ddeaab131 100644 (file)
@@ -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