]> git.codecow.com Git - nano25519.git/commitdiff
Document expected output.
authorChris Duncan <chris@zoso.dev>
Sun, 16 Aug 2026 08:41:22 +0000 (01:41 -0700)
committerChris Duncan <chris@zoso.dev>
Sun, 16 Aug 2026 08:41:22 +0000 (01:41 -0700)
src/assembly/ge.ts
src/assembly/index.ts

index 1be4e3784bfa899ac49a86e98b7e8980a4c86c36..63f1d56834905f1b7c163067e3513e37559d27e8 100644 (file)
@@ -148,6 +148,7 @@ export function ge_frombytes (h: ge_p3, s: StaticArray<u8>): 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
index 5d2a4a17729abdadb63f2f13b37f683c797e328b..1af829fa2552bf31e77c222f752dd2b6ca1275ac 100644 (file)
-//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>\r
-//! SPDX-License-Identifier: GPL-3.0-or-later\r
-\r
-import { Blake2b } from './blake2b'\r
-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'\r
-import { ge_p3, ge_sub_p3 } from './p'\r
-import { sc_is_canonical, sc_muladd, sc_reduce } from './sc'\r
-\r
-const MESSAGE_BYTES: i32 = 32\r
-const PRIVATEKEY_BYTES: i32 = 32\r
-const PUBLICKEY_BYTES: i32 = 32\r
-const SECRETKEY_BYTES: i32 = PRIVATEKEY_BYTES + PUBLICKEY_BYTES\r
-const SIGNATURE_BYTES: i32 = 64\r
-const SIGNEDMESSAGE_BYTES: i32 = SIGNATURE_BYTES + MESSAGE_BYTES\r
-\r
-// Static I/O buffers\r
-const OUTPUT_BUFFER = memory.data(64)\r
-const INPUT_BUFFER = memory.data(128)\r
-const MESSAGE_BUFFER = memory.data(32768)\r
-\r
-// crypto_hash function\r
-const blake2b = new Blake2b()\r
-\r
-/**\r
- * Clears the 3 least significant bits to guarantee multiple of 8 and prevent\r
- * small-subgroup attacks based on Curve25519's cofactor of 8. Also sets bit 254\r
- * and clears bit 255 to guarantee a fixed bit length for constant time\r
- * performance.\r
- * @param k 32-byte scalar\r
- */\r
-//@ts-expect-error\r
-@inline\r
-function clamp (k: StaticArray<u8>): void {\r
-       k[0] &= 248\r
-       k[31] &= 127\r
-       k[31] |= 64\r
-}\r
-\r
-const crypto_derive_a: StaticArray<u8> = new StaticArray<u8>(PRIVATEKEY_BYTES)\r
-const crypto_derive_az: StaticArray<u8> = new StaticArray<u8>(SECRETKEY_BYTES)\r
-/**\r
- * Hash seed to 32-byte scalar `a`, clamp it, then point-multiply it by the\r
- * Ed25519 base point.\r
- * @param pk 32-byte output buffer for the compressed public key\r
- * @param seed 32-byte input buffer for the securely-random secret seed value\r
- */\r
-function crypto_derive (pk: StaticArray<u8>, seed: StaticArray<u8>): void {\r
-       const a = crypto_derive_a\r
-       const az = crypto_derive_az\r
-       blake2b.init().update(changetype<usize>(seed), 32).digest(az)\r
-       memory.copy(changetype<usize>(a), changetype<usize>(az), 32)\r
-       az.fill(0)\r
-       clamp(a)\r
-       ge_scalarmult_base_tobytes(pk, a)\r
-       a.fill(0)\r
-}\r
-\r
-const crypto_sign_az = new StaticArray<u8>(64)\r
-const crypto_sign_nonce = new StaticArray<u8>(64)\r
-const crypto_sign_hram = new StaticArray<u8>(64)\r
-const crypto_sign_t = new StaticArray<u8>(PRIVATEKEY_BYTES)\r
-function crypto_sign (s: StaticArray<u8>, m: usize, mlen: i32, sk: StaticArray<u8>): void {\r
-       const az = crypto_sign_az\r
-       const nonce = crypto_sign_nonce\r
-       const hram = crypto_sign_hram\r
-       const t = changetype<usize>(crypto_sign_t)\r
-\r
-       // Hash secret key to private scalar `a` and prefix for nonce derivation `z`\r
-       memory.copy(t, changetype<usize>(sk), PRIVATEKEY_BYTES)\r
-       blake2b.init().update(t, PRIVATEKEY_BYTES).digest(az)\r
-       clamp(az)\r
-\r
-       // Derive nonce from prefix `z` and message `m`\r
-       blake2b.init().update(changetype<usize>(az) + 32, 32).update(m, mlen).digest(nonce)\r
-       sc_reduce(nonce)\r
-\r
-       // Compute R = rB, output to bytes s\r
-       ge_scalarmult_base_tobytes(s, nonce)\r
-\r
-       // Concatenate public key `A` and message `M` from parameter arguments:\r
-       // `A = sk[32,63], M = m`\r
-       // Compute challenge hash using `s = (R || A || M)`\r
-       blake2b.init().update(changetype<usize>(s), 32).update(changetype<usize>(sk) + 32, 32).update(m, mlen).digest(hram)\r
-       sc_reduce(hram)\r
-\r
-       // Compute `S = (r + h*a) mod L` and construct final signature `s = (R || S)`\r
-       sc_muladd(changetype<StaticArray<u8>>(changetype<usize>(s) + 32), changetype<StaticArray<u8>>(az), hram, nonce)\r
-\r
-       // Clean up sensitive data\r
-       az.fill(0)\r
-       hram.fill(0)\r
-       nonce.fill(0)\r
-       memory.fill(t, 0, PRIVATEKEY_BYTES)\r
-}\r
-\r
-const crypto_verify_h = new StaticArray<u8>(64)\r
-const crypto_verify_check = new ge_p3()\r
-const crypto_verify_expected_r = new ge_p3()\r
-const crypto_verify_A = new ge_p3()\r
-const crypto_verify_sb_ah = new ge_p3()\r
-const crypto_verify_S = new StaticArray<u8>(32)\r
-/**\r
- * Verify signature `s` was made by signing message `m` using public key `pk`.\r
- */\r
-function crypto_verify (s: StaticArray<u8>, m: usize, mlen: i32, pk: StaticArray<u8>): i32 {\r
-       const h = crypto_verify_h\r
-       const check = crypto_verify_check\r
-       const expected_r = crypto_verify_expected_r\r
-       const A = crypto_verify_A\r
-       const sb_ah = crypto_verify_sb_ah\r
-       const S = crypto_verify_S\r
-\r
-       // fail if public key `k` is non-canonical (`p = 2²⁵⁵-19 <= k`)\r
-       if (!ge_is_canonical(pk)) return -1\r
-\r
-       // fail if private scalar `S` is non-canonical (`L <= S`)\r
-       memory.copy(changetype<usize>(S), changetype<usize>(s) + 32, 32)\r
-       if (!sc_is_canonical(S)) return -1\r
-\r
-       if (ge_frombytes_negate_vartime(A, pk) != 0) return -1\r
-       if (ge_has_small_order(A) != 0) return -1\r
-\r
-       if (ge_frombytes(expected_r, s) != 0) return -1\r
-       if (ge_has_small_order(expected_r) != 0) return -1\r
-\r
-       // signature is nonce point R and scalar S (R || S)\r
-       // data to hash is nonce point R, public key A, and message M\r
-       // from parameter arguments: R = s[0,32], A = pk, M = m\r
-       // R, S, A, and M are all 32-byte values in this implementation\r
-       blake2b.init().update(changetype<usize>(s), 32).update(changetype<usize>(pk), 32).update(m, mlen).digest(h)\r
-       sc_reduce(h)\r
-\r
-       ge_double_scalarmult_vartime_to_p3(sb_ah, h, A, S)\r
-       ge_sub_p3(check, expected_r, sb_ah)\r
-\r
-       return ge_has_small_order(check) - 1\r
-}\r
-\r
-// Returns the pointer to the static output buffer (64 bytes).\r
-export function getOutputPointer (): usize {\r
-       return OUTPUT_BUFFER\r
-}\r
-\r
-// Returns the pointer to the static input buffer (128 bytes).\r
-export function getInputPointer (): usize {\r
-       return INPUT_BUFFER\r
-}\r
-\r
-// Returns the pointer to the static message buffer (32 KiB).\r
-export function getMessagePointer (): usize {\r
-       return MESSAGE_BUFFER\r
-}\r
-\r
-/**\r
- * Uses a private key to derive a Nano public key which is then written to the\r
- * static output buffer.\r
- */\r
-export function derive (): void {\r
-       const pk = changetype<StaticArray<u8>>(OUTPUT_BUFFER)\r
-       const seed = changetype<StaticArray<u8>>(INPUT_BUFFER)\r
-       memory.fill(OUTPUT_BUFFER, 0, SECRETKEY_BYTES)\r
-       crypto_derive(pk, seed)\r
-       memory.fill(INPUT_BUFFER, 0, PRIVATEKEY_BYTES)\r
-}\r
-\r
-const sign_s = new StaticArray<u8>(SIGNEDMESSAGE_BYTES)\r
-/**\r
- * Sign a message using a private key. The signature is written to the static\r
- * output buffer. This mirrors the functionality of `nacl.sign.detached()`.\r
- *\r
- * @param {u64} m - Message to sign (variable byte length up to 32 KiB)\r
- * @param {i32} mlen - Byte length of message\r
- * @param {u64} sk - 64-byte secret key (32-byte private key + 32-byte public key)\r
- */\r
-export function sign (mlen: i32): void {\r
-       if (mlen < 0 || mlen > 32768) throw new Error()\r
-       const s = sign_s\r
-       const m = MESSAGE_BUFFER\r
-       const sk = changetype<StaticArray<u8>>(INPUT_BUFFER)\r
-\r
-       memory.fill(OUTPUT_BUFFER, 0, SIGNATURE_BYTES)\r
-       crypto_sign(s, m, mlen, sk)\r
-       memory.copy(OUTPUT_BUFFER, changetype<usize>(s), SIGNATURE_BYTES)\r
-       memory.fill(INPUT_BUFFER, 0, SECRETKEY_BYTES)\r
-}\r
-\r
-const verify_s = new StaticArray<u8>(SIGNATURE_BYTES)\r
-const verify_k = new StaticArray<u8>(PUBLICKEY_BYTES)\r
-/**\r
- * Verify a signature on a message against a public key. This mirrors the\r
- * functionality of `nacl.sign.detached.verify()`.\r
- *\r
- * @param {u64} s - 64-byte signature\r
- * @param {u64} m - Message that was signed (variable byte-length up to 32 KiB)\r
- * @param {u64} k - 32-byte public key\r
- */\r
-export function verify (mlen: i32): void {\r
-       if (mlen < 0 || mlen > 32768) throw new Error('invalid message length')\r
-       const s = verify_s\r
-       const m = MESSAGE_BUFFER\r
-       const k = verify_k\r
-\r
-       memory.fill(OUTPUT_BUFFER, 0, SIGNATURE_BYTES)\r
-       memory.copy(changetype<usize>(s), INPUT_BUFFER, SIGNATURE_BYTES)\r
-       memory.copy(changetype<usize>(k), INPUT_BUFFER + SIGNATURE_BYTES, PUBLICKEY_BYTES)\r
-       const v = crypto_verify(s, m, mlen, k)\r
-       store<u8>(OUTPUT_BUFFER, v)\r
-       memory.fill(INPUT_BUFFER, 0, SIGNATURE_BYTES)\r
-}\r
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! 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<u8>): void {
+       k[0] &= 248
+       k[31] &= 127
+       k[31] |= 64
+}
+
+const crypto_derive_a: StaticArray<u8> = new StaticArray<u8>(PRIVATEKEY_BYTES)
+const crypto_derive_az: StaticArray<u8> = new StaticArray<u8>(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<u8>, seed: StaticArray<u8>): void {
+       const a = crypto_derive_a
+       const az = crypto_derive_az
+       blake2b.init().update(changetype<usize>(seed), 32).digest(az)
+       memory.copy(changetype<usize>(a), changetype<usize>(az), 32)
+       az.fill(0)
+       clamp(a)
+       ge_scalarmult_base_tobytes(pk, a)
+       a.fill(0)
+}
+
+const crypto_sign_az = new StaticArray<u8>(64)
+const crypto_sign_nonce = new StaticArray<u8>(64)
+const crypto_sign_hram = new StaticArray<u8>(64)
+const crypto_sign_t = new StaticArray<u8>(PRIVATEKEY_BYTES)
+function crypto_sign (s: StaticArray<u8>, m: usize, mlen: i32, sk: StaticArray<u8>): void {
+       const az = crypto_sign_az
+       const nonce = crypto_sign_nonce
+       const hram = crypto_sign_hram
+       const t = changetype<usize>(crypto_sign_t)
+
+       // Hash secret key to private scalar `a` and prefix for nonce derivation `z`
+       memory.copy(t, changetype<usize>(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<usize>(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<usize>(s), 32).update(changetype<usize>(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<StaticArray<u8>>(changetype<usize>(s) + 32), changetype<StaticArray<u8>>(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<u8>(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<u8>(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<u8>, m: usize, mlen: i32, pk: StaticArray<u8>): 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<usize>(S), changetype<usize>(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<usize>(s), 32).update(changetype<usize>(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<StaticArray<u8>>(OUTPUT_BUFFER)
+       const seed = changetype<StaticArray<u8>>(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<u8>(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<StaticArray<u8>>(INPUT_BUFFER)
+
+       memory.fill(OUTPUT_BUFFER, 0, SIGNATURE_BYTES)
+       crypto_sign(s, m, mlen, sk)
+       memory.copy(OUTPUT_BUFFER, changetype<usize>(s), SIGNATURE_BYTES)
+       memory.fill(INPUT_BUFFER, 0, SECRETKEY_BYTES)
+}
+
+const verify_s = new StaticArray<u8>(SIGNATURE_BYTES)
+const verify_k = new StaticArray<u8>(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<usize>(s), INPUT_BUFFER, SIGNATURE_BYTES)
+       memory.copy(changetype<usize>(k), INPUT_BUFFER + SIGNATURE_BYTES, PUBLICKEY_BYTES)
+       const v = crypto_verify(s, m, mlen, k)
+       store<u8>(OUTPUT_BUFFER, v)
+       memory.fill(INPUT_BUFFER, 0, SIGNATURE_BYTES)
+}