From: Chris Duncan Date: Sun, 16 Aug 2026 08:41:22 +0000 (-0700) Subject: Document expected output. X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=9359f17fb0578a7e689c6d1888ffabcf975b6327;p=nano25519.git Document expected output. --- diff --git a/src/assembly/ge.ts b/src/assembly/ge.ts index 1be4e37..63f1d56 100644 --- a/src/assembly/ge.ts +++ b/src/assembly/ge.ts @@ -148,6 +148,7 @@ export function ge_frombytes (h: ge_p3, s: StaticArray): i32 { const ge_has_small_order_y_sqrtm1: FieldElement = fe() const ge_has_small_order_c: FieldElement = fe() +/** return 1 if p has small order else return 0 */ export function ge_has_small_order (p: ge_p3): i32 { const y_sqrtm1 = ge_has_small_order_y_sqrtm1 const c = ge_has_small_order_c diff --git a/src/assembly/index.ts b/src/assembly/index.ts index 5d2a4a1..1af829f 100644 --- a/src/assembly/index.ts +++ b/src/assembly/index.ts @@ -1,209 +1,210 @@ -//! SPDX-FileCopyrightText: 2026 Chris Duncan -//! SPDX-License-Identifier: GPL-3.0-or-later - -import { Blake2b } from './blake2b' -import { ge_double_scalarmult_vartime_to_p3, ge_frombytes, ge_frombytes_negate_vartime, ge_has_small_order, ge_is_canonical, ge_scalarmult_base_tobytes } from './ge' -import { ge_p3, ge_sub_p3 } from './p' -import { sc_is_canonical, sc_muladd, sc_reduce } from './sc' - -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 SIGNEDMESSAGE_BYTES: i32 = SIGNATURE_BYTES + MESSAGE_BYTES - -// Static I/O buffers -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() - -/** - * Clears the 3 least significant bits to guarantee multiple of 8 and prevent - * small-subgroup attacks based on Curve25519's cofactor of 8. Also sets bit 254 - * and clears bit 255 to guarantee a fixed bit length for constant time - * performance. - * @param k 32-byte scalar - */ -//@ts-expect-error -@inline -function clamp (k: StaticArray): void { - k[0] &= 248 - k[31] &= 127 - k[31] |= 64 -} - -const crypto_derive_a: StaticArray = new StaticArray(PRIVATEKEY_BYTES) -const crypto_derive_az: StaticArray = new StaticArray(SECRETKEY_BYTES) -/** - * Hash seed to 32-byte scalar `a`, clamp it, then point-multiply it by the - * Ed25519 base point. - * @param pk 32-byte output buffer for the compressed public key - * @param seed 32-byte input buffer for the securely-random secret seed value - */ -function crypto_derive (pk: StaticArray, seed: StaticArray): void { - const a = crypto_derive_a - const az = crypto_derive_az - blake2b.init().update(changetype(seed), 32).digest(az) - memory.copy(changetype(a), changetype(az), 32) - az.fill(0) - clamp(a) - ge_scalarmult_base_tobytes(pk, a) - a.fill(0) -} - -const crypto_sign_az = new StaticArray(64) -const crypto_sign_nonce = new StaticArray(64) -const crypto_sign_hram = new StaticArray(64) -const crypto_sign_t = new StaticArray(PRIVATEKEY_BYTES) -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 t = changetype(crypto_sign_t) - - // Hash secret key to private scalar `a` and prefix for nonce derivation `z` - 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` - 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[32,63], 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)` - sc_muladd(changetype>(changetype(s) + 32), changetype>(az), hram, nonce) - - // Clean up sensitive data - az.fill(0) - hram.fill(0) - nonce.fill(0) - memory.fill(t, 0, PRIVATEKEY_BYTES) -} - -const crypto_verify_h = new StaticArray(64) -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_S = new StaticArray(32) -/** - * Verify signature `s` was made by signing message `m` using public key `pk`. - */ -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 S = crypto_verify_S - - // fail if public key `k` is non-canonical (`p = 2²⁵⁵-19 <= k`) - if (!ge_is_canonical(pk)) return -1 - - // fail if private scalar `S` is non-canonical (`L <= S`) - memory.copy(changetype(S), changetype(s) + 32, 32) - if (!sc_is_canonical(S)) return -1 - - if (ge_frombytes_negate_vartime(A, pk) != 0) return -1 - if (ge_has_small_order(A) != 0) return -1 - - if (ge_frombytes(expected_r, s) != 0) return -1 - if (ge_has_small_order(expected_r) != 0) return -1 - - // signature is nonce point R and scalar S (R || S) - // 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 - 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) - ge_sub_p3(check, expected_r, sb_ah) - - 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 message buffer (32 KiB). -export function getMessagePointer (): usize { - return MESSAGE_BUFFER -} - -/** - * Uses a private key to derive a Nano public key which is then written to the - * static output buffer. - */ -export function derive (): void { - const pk = changetype>(OUTPUT_BUFFER) - const seed = changetype>(INPUT_BUFFER) - memory.fill(OUTPUT_BUFFER, 0, SECRETKEY_BYTES) - crypto_derive(pk, seed) - memory.fill(INPUT_BUFFER, 0, PRIVATEKEY_BYTES) -} - -const sign_s = new StaticArray(SIGNEDMESSAGE_BYTES) -/** - * 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} 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 (mlen: i32): void { - if (mlen < 0 || mlen > 32768) throw new Error() - const s = sign_s - const m = MESSAGE_BUFFER - const sk = changetype>(INPUT_BUFFER) - - memory.fill(OUTPUT_BUFFER, 0, SIGNATURE_BYTES) - crypto_sign(s, m, mlen, sk) - memory.copy(OUTPUT_BUFFER, changetype(s), SIGNATURE_BYTES) - memory.fill(INPUT_BUFFER, 0, SECRETKEY_BYTES) -} - -const verify_s = new StaticArray(SIGNATURE_BYTES) -const verify_k = new StaticArray(PUBLICKEY_BYTES) -/** - * Verify a signature on a message against a public key. This mirrors the - * functionality of `nacl.sign.detached.verify()`. - * - * @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 (mlen: i32): void { - if (mlen < 0 || mlen > 32768) throw new Error('invalid message length') - const s = verify_s - const m = MESSAGE_BUFFER - const k = verify_k - - memory.fill(OUTPUT_BUFFER, 0, SIGNATURE_BYTES) - memory.copy(changetype(s), INPUT_BUFFER, SIGNATURE_BYTES) - memory.copy(changetype(k), INPUT_BUFFER + SIGNATURE_BYTES, PUBLICKEY_BYTES) - const v = crypto_verify(s, m, mlen, k) - store(OUTPUT_BUFFER, v) - memory.fill(INPUT_BUFFER, 0, SIGNATURE_BYTES) -} +//! SPDX-FileCopyrightText: 2026 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +import { Blake2b } from './blake2b' +import { ge_double_scalarmult_vartime_to_p3, ge_frombytes, ge_frombytes_negate_vartime, ge_has_small_order, ge_is_canonical, ge_scalarmult_base_tobytes } from './ge' +import { ge_p3, ge_sub_p3 } from './p' +import { sc_is_canonical, sc_muladd, sc_reduce } from './sc' + +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 SIGNEDMESSAGE_BYTES: i32 = SIGNATURE_BYTES + MESSAGE_BYTES + +// Static I/O buffers +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() + +/** + * Clears the 3 least significant bits to guarantee multiple of 8 and prevent + * small-subgroup attacks based on Curve25519's cofactor of 8. Also sets bit 254 + * and clears bit 255 to guarantee a fixed bit length for constant time + * performance. + * @param k 32-byte scalar + */ +//@ts-expect-error +@inline +function clamp (k: StaticArray): void { + k[0] &= 248 + k[31] &= 127 + k[31] |= 64 +} + +const crypto_derive_a: StaticArray = new StaticArray(PRIVATEKEY_BYTES) +const crypto_derive_az: StaticArray = new StaticArray(SECRETKEY_BYTES) +/** + * Hash seed to 32-byte scalar `a`, clamp it, then point-multiply it by the + * Ed25519 base point. + * @param pk 32-byte output buffer for the compressed public key + * @param seed 32-byte input buffer for the securely-random secret seed value + */ +function crypto_derive (pk: StaticArray, seed: StaticArray): void { + const a = crypto_derive_a + const az = crypto_derive_az + blake2b.init().update(changetype(seed), 32).digest(az) + memory.copy(changetype(a), changetype(az), 32) + az.fill(0) + clamp(a) + ge_scalarmult_base_tobytes(pk, a) + a.fill(0) +} + +const crypto_sign_az = new StaticArray(64) +const crypto_sign_nonce = new StaticArray(64) +const crypto_sign_hram = new StaticArray(64) +const crypto_sign_t = new StaticArray(PRIVATEKEY_BYTES) +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 t = changetype(crypto_sign_t) + + // Hash secret key to private scalar `a` and prefix for nonce derivation `z` + 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` + 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[32,63], 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)` + sc_muladd(changetype>(changetype(s) + 32), changetype>(az), hram, nonce) + + // Clean up sensitive data + az.fill(0) + hram.fill(0) + nonce.fill(0) + memory.fill(t, 0, PRIVATEKEY_BYTES) +} + +const crypto_verify_h = new StaticArray(64) +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_S = new StaticArray(32) +/** + * Verify signature `s` was made by signing message `m` using public key `pk`. + * @returns -1 if signature fails to verify, else return 0 if signature is good + */ +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 S = crypto_verify_S + + // fail if public key `k` is non-canonical (`p = 2²⁵⁵-19 <= k`) + if (!ge_is_canonical(pk)) return -1 + + // fail if private scalar `S` is non-canonical (`L <= S`) + memory.copy(changetype(S), changetype(s) + 32, 32) + if (!sc_is_canonical(S)) return -1 + + if (ge_frombytes_negate_vartime(A, pk) != 0) return -1 + if (ge_has_small_order(A) != 0) return -1 + + if (ge_frombytes(expected_r, s) != 0) return -1 + if (ge_has_small_order(expected_r) != 0) return -1 + + // signature is nonce point R and scalar S (R || S) + // 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 + 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) + ge_sub_p3(check, expected_r, sb_ah) + + 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 message buffer (32 KiB). +export function getMessagePointer (): usize { + return MESSAGE_BUFFER +} + +/** + * Uses a private key to derive a Nano public key which is then written to the + * static output buffer. + */ +export function derive (): void { + const pk = changetype>(OUTPUT_BUFFER) + const seed = changetype>(INPUT_BUFFER) + memory.fill(OUTPUT_BUFFER, 0, SECRETKEY_BYTES) + crypto_derive(pk, seed) + memory.fill(INPUT_BUFFER, 0, PRIVATEKEY_BYTES) +} + +const sign_s = new StaticArray(SIGNEDMESSAGE_BYTES) +/** + * 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} 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 (mlen: i32): void { + if (mlen < 0 || mlen > 32768) throw new Error() + const s = sign_s + const m = MESSAGE_BUFFER + const sk = changetype>(INPUT_BUFFER) + + memory.fill(OUTPUT_BUFFER, 0, SIGNATURE_BYTES) + crypto_sign(s, m, mlen, sk) + memory.copy(OUTPUT_BUFFER, changetype(s), SIGNATURE_BYTES) + memory.fill(INPUT_BUFFER, 0, SECRETKEY_BYTES) +} + +const verify_s = new StaticArray(SIGNATURE_BYTES) +const verify_k = new StaticArray(PUBLICKEY_BYTES) +/** + * Verify a signature on a message against a public key. This mirrors the + * functionality of `nacl.sign.detached.verify()`. + * + * @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 (mlen: i32): void { + if (mlen < 0 || mlen > 32768) throw new Error('invalid message length') + const s = verify_s + const m = MESSAGE_BUFFER + const k = verify_k + + memory.fill(OUTPUT_BUFFER, 0, SIGNATURE_BYTES) + memory.copy(changetype(s), INPUT_BUFFER, SIGNATURE_BYTES) + memory.copy(changetype(k), INPUT_BUFFER + SIGNATURE_BYTES, PUBLICKEY_BYTES) + const v = crypto_verify(s, m, mlen, k) + store(OUTPUT_BUFFER, v) + memory.fill(INPUT_BUFFER, 0, SIGNATURE_BYTES) +}