From: Chris Duncan Date: Fri, 20 Mar 2026 22:11:04 +0000 (-0700) Subject: Enable variable-length messages up to 32 KiB. X-Git-Tag: v1.0.0~4 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=324b0cad56a46e2b9003e365e55d1ad2b692621d;p=nano25519.git Enable variable-length messages up to 32 KiB. Add dedicated buffer for sign/verify message data and refactor references to use length variables. Convert terminology from "block hash" to "message" to reflect new capabilities and document how Nano block hashes should be handled. Deprecate crypto_hash and instead use blake2b directly for streaming hash updates. Update test webpage and NodeJS test to use full set of test vectors from the Python ed25519-blake2b library. Update workers to transfer bytes instead of copying them, and always specify worker commands with the 'action' property. --- diff --git a/README.md b/README.md index 4030ecd..a7c378f 100644 --- a/README.md +++ b/README.md @@ -78,8 +78,8 @@ const pub = await NanoNaCl.deriveAsync(prv); ### Sign ```javascript -// `hash` is a 32-byte Uint8Array hash of a valid Nano transaction block -const hash = new Uint8Array(32); +// `msg` is a 32-byte Uint8Array hash of a valid Nano transaction block +const msg = new Uint8Array(32); // `prv` is a 32-byte Uint8Array private key const prv = new Uint8Array(32); // `pub` is a 32-byte Uint8Array public key derived from `prv` @@ -87,15 +87,15 @@ const pub = NanoNaCl.derive(prv); // `sk` is a 64-byte Uint8Array secret key joining private and public keys const sk = new Uint8Array([...prv, ...pub]); -const sig = NanoNaCl.sign(hash, sk); +const sig = NanoNaCl.sign(msg, sk); // `sig` is a 64-byte Uint8Array signature for the block hash ``` ### Sign (async) ```javascript -// `hash` is a 64-char hex string hash of a valid Nano transaction block -const hash = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; +// `msg` is a 64-char hex string hash of a valid Nano transaction block +const msg = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; // `prv` is a 64-char hex string private key const prv = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; // `pub` is a 64-char hex string public key derived from `prv` @@ -103,37 +103,37 @@ const pub = await NanoNaCl.deriveAsync(prv); // `sk` is a 128-char hex string secret key joining private and public keys const sk = prv + pub; -const sig = await NanoNaCl.signAsync(hash, sk); -// `sig` is a 128-char hex string signature on the blockhash +const sig = await NanoNaCl.signAsync(msg, sk); +// `sig` is a 128-char hex string signature for the block hash ``` ### Verify ```javascript -// `sig` is a 64-byte Uint8Array signature on a block hash +// `sig` is a 64-byte Uint8Array signature const sig = new Uint8Array(64); -// `hash` is a 32-byte Uint8Array hash of a valid Nano transaction block -const hash = new Uint8Array(32); +// `msg` is a 32-byte Uint8Array hash of a valid Nano transaction block +const msg = new Uint8Array(32); // `pub` is a 32-byte Uint8Array public key for a Nano account const pub = new Uint8Array(32); -const v = NanoNaCl.verify(sig, hash, pub); -// `v` is a boolean 'true' if the same `prv` that derives `pub` was also used to create `sig` by signing `hash`, else 'false' +const v = NanoNaCl.verify(sig, msg, pub); +// `v` is a boolean 'true' if the same `prv` that derives `pub` was also used to create `sig` by signing `msg`, else 'false' ``` ### Verify (async) ```javascript -// `sig` is a 128-char hex string signature on a block hash +// `sig` is a 128-char hex string signature const sig = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; -// `hash` is a 64-char hex string hash of a valid Nano transaction block -const hash = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; +// `msg` is a 64-char hex string hash of a valid Nano transaction block +const msg = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; // `pub` is a 64-char hex string public key for a Nano account const pub = "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"; -const v = await NanoNaCl.verifyAsync(sig, hash, pub); -// `v` is a boolean 'true' if the same `prv` that derives `pub` was also used to create `sig` by signing `hash`, else 'false' +const v = await NanoNaCl.verifyAsync(sig, msg, pub); +// `v` is a boolean 'true' if the same `prv` that derives `pub` was also used to create `sig` by signing `msg`, else 'false' ``` ## Notes diff --git a/assembly/blake2b.ts b/assembly/blake2b.ts index 82c654b..272561a 100644 --- a/assembly/blake2b.ts +++ b/assembly/blake2b.ts @@ -82,9 +82,9 @@ export class Blake2b { return this } - // input: variable-length message data passed by user to be hashed - update (input: StaticArray): Blake2b { - for (let i = 0; i < input.length; i++) { + // input: variable-length message bytes passed by user to be hashed + update (input: usize, length: i32): Blake2b { + for (let i = 0; i < length; i++) { // is buffer full? if (this.c === this.b.length) { @@ -100,11 +100,12 @@ export class Blake2b { } // increment buffer counter and set byte - this.b[this.c++] = input[i] + this.b[this.c++] = load(input + i) } return this } + // output: 64-byte hash of original message input digest (output: StaticArray): void { // add final message block size to total bytes @@ -116,7 +117,7 @@ export class Blake2b { // set final block flag and compress this.COMPRESS(true) - // return byte array of 64-bit chain buffer + // return byte array of 64-byte chain buffer memory.copy(changetype(output), changetype(this.h), 64) } diff --git a/assembly/index.ts b/assembly/index.ts index 36fadc7..ff2246d 100644 --- a/assembly/index.ts +++ b/assembly/index.ts @@ -6,16 +6,20 @@ import { ge_double_scalarmult_vartime_to_p3, ge_frombytes, ge_frombytes_negate_v import { ge_p3, ge_sub_p3 } from './p' import { sc_is_canonical, sc_muladd, sc_reduce } from './sc' -const BLOCKHASH_BYTES: i32 = 32 +const MESSAGE_BYTES: i32 = 32 const PRIVATEKEY_BYTES: i32 = 32 const PUBLICKEY_BYTES: i32 = 32 const SECRETKEY_BYTES: i32 = PRIVATEKEY_BYTES + PUBLICKEY_BYTES const SIGNATURE_BYTES: i32 = 64 -const SIGNEDBLOCKHASH_BYTES: i32 = SIGNATURE_BYTES + BLOCKHASH_BYTES +const SIGNEDMESSAGE_BYTES: i32 = SIGNATURE_BYTES + MESSAGE_BYTES // Static I/O buffers -const INPUT_BUFFER = memory.data(128) const OUTPUT_BUFFER = memory.data(64) +const INPUT_BUFFER = memory.data(128) +const MESSAGE_BUFFER = memory.data(32768) + +// crypto_hash function +const blake2b = new Blake2b() //@ts-expect-error @inline @@ -25,13 +29,8 @@ function clamp (k: StaticArray): void { k[31] |= 64 } -const blake2b = new Blake2b() -function crypto_hash (o: StaticArray, i: StaticArray): void { - blake2b.init().update(i).digest(o) -} - function crypto_derive (pk: StaticArray, sk: StaticArray, seed: StaticArray): void { - crypto_hash(sk, seed) + blake2b.init().update(changetype(seed), seed.length).digest(sk) clamp(sk) ge_scalarmult_base_tobytes(pk, sk) @@ -44,34 +43,29 @@ const crypto_sign_nonce = new StaticArray(64) const crypto_sign_hram = new StaticArray(64) const crypto_sign_zm = new StaticArray(SIGNATURE_BYTES) const crypto_sign_t = new StaticArray(PRIVATEKEY_BYTES) -function crypto_sign (s: StaticArray, m: StaticArray, sk: StaticArray): void { +function crypto_sign (s: StaticArray, m: usize, mlen: i32, sk: StaticArray): void { const az = crypto_sign_az const nonce = crypto_sign_nonce const hram = crypto_sign_hram - const zm = crypto_sign_zm - const t = crypto_sign_t + const zm = changetype(crypto_sign_zm) + const t = changetype(crypto_sign_t) // Hash secret key to private scalar `a` and prefix for nonce derivation `z` - memory.copy(changetype(t), changetype(sk), PRIVATEKEY_BYTES) - crypto_hash(az, t) + memory.copy(t, changetype(sk), PRIVATEKEY_BYTES) + blake2b.init().update(t, PRIVATEKEY_BYTES).digest(az) clamp(az) // Derive nonce from prefix `z` and message `m` - memory.copy(changetype(zm), changetype(az) + 32, 32) - memory.copy(changetype(zm) + 32, changetype(m), 32) - crypto_hash(nonce, zm) + blake2b.init().update(changetype(az) + 32, 32).update(m, mlen).digest(nonce) sc_reduce(nonce) // Compute R = rB, output to bytes s ge_scalarmult_base_tobytes(s, nonce) - // Concatenate public key `A` and message `M` - // from parameter arguments: A = sk[0,32], M = m - memory.copy(changetype(s) + 32, changetype(sk) + 32, 32) - memory.copy(changetype(s) + 64, changetype(m), 32) - - // Compute challenge hash now that `s = (R || A || M)` - crypto_hash(hram, s) + // Concatenate public key `A` and message `M` from parameter arguments: + // `A = sk[0,32], M = m` + // Compute challenge hash using `s = (R || A || M)` + blake2b.init().update(changetype(s), 32).update(changetype(sk) + 32, 32).update(m, mlen).digest(hram) sc_reduce(hram) // Compute `S = (r + h*a) mod L` and construct final signature `s = (R || S)` @@ -87,18 +81,16 @@ const crypto_verify_check = new ge_p3() const crypto_verify_expected_r = new ge_p3() const crypto_verify_A = new ge_p3() const crypto_verify_sb_ah = new ge_p3() -const crypto_verify_ram = new StaticArray(SIGNEDBLOCKHASH_BYTES) const crypto_verify_S = new StaticArray(32) /** * Verify signature `s` was made by signing message `m` using public key `pk`. */ -function crypto_verify (s: StaticArray, m: StaticArray, pk: StaticArray): i32 { +function crypto_verify (s: StaticArray, m: usize, mlen: i32, pk: StaticArray): i32 { const h = crypto_verify_h const check = crypto_verify_check const expected_r = crypto_verify_expected_r const A = crypto_verify_A const sb_ah = crypto_verify_sb_ah - const ram = crypto_verify_ram const S = crypto_verify_S // fail if public key `k` is non-canonical (`p = 2²⁵⁵-19 <= k`) @@ -118,10 +110,7 @@ function crypto_verify (s: StaticArray, m: StaticArray, pk: StaticArray< // data to hash is nonce point R, public key A, and message M // from parameter arguments: R = s[0,32], A = pk, M = m // R, S, A, and M are all 32-byte values in this implementation - memory.copy(changetype(ram), changetype(s), 32) - memory.copy(changetype(ram) + 32, changetype(pk), 32) - memory.copy(changetype(ram) + 64, changetype(m), 32) - crypto_hash(h, ram) + blake2b.init().update(changetype(s), 32).update(changetype(pk), 32).update(m, mlen).digest(h) sc_reduce(h) ge_double_scalarmult_vartime_to_p3(sb_ah, h, A, S) @@ -130,14 +119,19 @@ function crypto_verify (s: StaticArray, m: StaticArray, pk: StaticArray< return ge_has_small_order(check) - 1 } +// Returns the pointer to the static output buffer (64 bytes). +export function getOutputPointer (): usize { + return OUTPUT_BUFFER +} + // Returns the pointer to the static input buffer (128 bytes). export function getInputPointer (): usize { return INPUT_BUFFER } -// Returns the pointer to the static output buffer (64 bytes). -export function getOutputPointer (): usize { - return OUTPUT_BUFFER +// Returns the pointer to the static message buffer (32 KiB). +export function getMessagePointer (): usize { + return MESSAGE_BUFFER } const derive_pk = new StaticArray(PUBLICKEY_BYTES) @@ -152,6 +146,7 @@ export function derive (): void { const pk = derive_pk const sk = derive_sk const seed = derive_seed + for (let i = 0; i < PRIVATEKEY_BYTES; i++) { seed[i] = load(INPUT_BUFFER + i) } @@ -162,64 +157,50 @@ export function derive (): void { } } -const sign_h = new StaticArray(BLOCKHASH_BYTES) const sign_sk = new StaticArray(SECRETKEY_BYTES) -const sign_s = new StaticArray(SIGNEDBLOCKHASH_BYTES) +const sign_s = new StaticArray(SIGNEDMESSAGE_BYTES) /** - * Signs the message using the private key and writes the signature to the static + * Sign a message using a private key. The signature is written to the static * output buffer. This mirrors the functionality of `nacl.sign.detached()`. * - * @param {u64} h0-h3 - Message to sign (32 bytes as 4 × u64) - * @param {u64} k0-k7 - Secret key (32 bytes private key + 32 bytes public key as 8 × u64) + * @param {u64} m - Message to sign (variable byte length up to 32 KiB) + * @param {i32} mlen - Byte length of message + * @param {u64} sk - 64-byte secret key (32-byte private key + 32-byte public key) */ -export function sign (): void { - const h = sign_h - const sk = sign_sk +export function sign (mlen: i32): void { const s = sign_s - let ptr = INPUT_BUFFER - for (let i = 0; i < BLOCKHASH_BYTES; i++) { - h[i] = load(usize(i) + ptr) - } - ptr += BLOCKHASH_BYTES - for (let i = 0; i < SECRETKEY_BYTES; i++) { - sk[i] = load(usize(i) + ptr) - } + const m = MESSAGE_BUFFER + const sk = sign_sk - crypto_sign(s, h, sk) - for (let i = 0; i < SIGNATURE_BYTES; i++) { - store(OUTPUT_BUFFER + i, s[i]) - } + memory.copy(changetype(sk), INPUT_BUFFER, SECRETKEY_BYTES) + crypto_sign(s, m, mlen, sk) + memory.copy(OUTPUT_BUFFER, changetype(s), SIGNATURE_BYTES) } -const verify_h = new StaticArray(BLOCKHASH_BYTES) const verify_s = new StaticArray(SIGNATURE_BYTES) const verify_k = new StaticArray(PUBLICKEY_BYTES) /** - * Verifies a signature on a block hash against a public key. This mirrors the + * Verify a signature on a message against a public key. This mirrors the * functionality of `nacl.sign.detached.verify()`. * - * @param {u64} s0-s7 - Signature (64 bytes as 8 × u64) - * @param {u64} h0-h3 - Blockhash (32 bytes as 4 × u64) - * @param {u64} k0-k3 - Public key (32 bytes as 4 × u64) + * @param {u64} s - 64-byte signature + * @param {u64} m - Message that was signed (variable byte-length up to 32 KiB) + * @param {u64} k - 32-byte public key */ -export function verify (): void { +export function verify (mlen: i32): void { const s = verify_s - const h = verify_h + const m = MESSAGE_BUFFER const k = verify_k let ptr = INPUT_BUFFER for (let i = 0; i < SIGNATURE_BYTES; i++) { s[i] = load(usize(i) + ptr) } - ptr += SIGNATURE_BYTES - for (let i = 0; i < BLOCKHASH_BYTES; i++) { - h[i] = load(usize(i) + ptr) - } - ptr += BLOCKHASH_BYTES + ptr = INPUT_BUFFER + SIGNATURE_BYTES for (let i = 0; i < PUBLICKEY_BYTES; i++) { k[i] = load(usize(i) + ptr) } - const v = crypto_verify(s, h, k) + const v = crypto_verify(s, m, mlen, k) store(OUTPUT_BUFFER, v) } diff --git a/esbuild/config.mjs b/esbuild/config.mjs index dabf9d1..344e640 100644 --- a/esbuild/config.mjs +++ b/esbuild/config.mjs @@ -44,7 +44,7 @@ export const nodeOptions = { platform: 'node', target: 'node22', entryPoints: [ - { in: './index.ts', out: 'node' } + { in: './index.ts', out: 'index' } ], packages: 'external', dropLabels: ['BROWSER'] diff --git a/index.html b/index.html index c299e53..db7ef49 100644 --- a/index.html +++ b/index.html @@ -15,7 +15,7 @@ SPDX-License-Identifier: GPL-3.0-or-later