--- /dev/null
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+import { Blake2b } from './blake2b'
+import { ge_scalarmult_base_tobytes } from './ge'
+import { clamp } from './utils'
+
+const PRIVATEKEY_BYTES: i32 = 32
+const PUBLICKEY_BYTES: i32 = 32
+const SECRETKEY_BYTES: i32 = PRIVATEKEY_BYTES + PUBLICKEY_BYTES
+
+// crypto_hash function
+const blake2b = new Blake2b()
+
+const crypto_derive_h: StaticArray<u8> = new StaticArray<u8>(SECRETKEY_BYTES)
+const crypto_derive_s: StaticArray<u8> = new StaticArray<u8>(PRIVATEKEY_BYTES)
+/**
+ * Hash seed to 32-byte scalar `a`, clamp it, then point-multiply it by the
+ * Ed25519 base point. The Nano specification uses BLAKE2b as the hash function
+ * instead of SHA-512 specified by RFC 8032.
+ *
+ * https://www.rfc-editor.org/info/rfc8032/#section-5.1.5
+ *
+ * @param A 32-byte output buffer for the compressed public key
+ * @param key 32-byte private key "seed" of cryptographically secure random data
+ */
+export function crypto_derive (A: StaticArray<u8>, key: StaticArray<u8>): void {
+ const h = crypto_derive_h
+ const s = crypto_derive_s
+ // Hash private key to 64-byte buffer `h`
+ blake2b.init().update(key, 32).digest(h)
+ // Keep only the lower 32 bytes as secret scalar `s` for deriving public key
+ memory.copy(changetype<usize>(s), changetype<usize>(h), 32)
+ h.fill(0)
+ // Clear lowest three bits and highest bit, then set second highest bit
+ clamp(s)
+ // Perform fixed-base scalar multiplication `[s]B`, output to public key `A`
+ ge_scalarmult_base_tobytes(A, s)
+ s.fill(0)
+}
--- /dev/null
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+import { Blake2b } from './blake2b'
+import { ge_scalarmult_base_tobytes } from './ge'
+import { sc_muladd, sc_reduce } from './sc'
+import { clamp } from './utils'
+
+const PRIVATEKEY_BYTES: i32 = 32
+
+// crypto_hash function
+const blake2b = new Blake2b()
+
+const crypto_sign_h = new StaticArray<u8>(64)
+const crypto_sign_s = new StaticArray<u8>(32)
+const crypto_sign_prefix = new StaticArray<u8>(32)
+const crypto_sign_r = new StaticArray<u8>(64)
+const crypto_sign_R = new StaticArray<u8>(64)
+const crypto_sign_k = new StaticArray<u8>(64)
+const crypto_sign_S = new StaticArray<u8>(64)
+/**
+ * Sign a message with a secret key. The Nano specification uses BLAKE2b as the
+ * hash function instead of SHA-512 specified by RFC 8032.
+ *
+ * In this implementation, the secret key is a 64-byte concatenation of the
+ * private key and its public key in order to (1) ensure the user has a full
+ * correct keypair for data integrity, and (2) improve performance by avoiding
+ * expensive point multiplication and instead taking a public key that can be
+ * computed once and cached. This behavior deviates from RFC 8032 which
+ * indicates the public key should be recomputed.
+ *
+ * https://www.rfc-editor.org/info/rfc8032/#section-5.1.6
+ *
+ * @param {StaticArray<u8>} RS 64-byte output buffer for detached signature
+ * @param {StaticArray<u8>} M variable-length message to be signed
+ * @param {i32} mlen bytelength of `m`
+ * @param {StaticArray<u8>} key 32-byte private key from input buffer
+ * @param {StaticArray<u8>} A 32-byte public key from input buffer
+ */
+export function crypto_sign (RS: StaticArray<u8>, M: StaticArray<u8>, mlen: i32, key: StaticArray<u8>, A: StaticArray<u8>): void {
+ const h = crypto_sign_h
+ const s = crypto_sign_s
+ const prefix = crypto_sign_prefix
+ const r = crypto_sign_r
+ const R = crypto_sign_R
+ const k = crypto_sign_k
+ const S = crypto_sign_S
+
+ // Hash private key to `h`
+ blake2b.init().update(key, PRIVATEKEY_BYTES).digest(h)
+ key.fill(0)
+
+ // Split `h` into clamped secret scalar `s` and nonce prefix
+ memory.copy(changetype<usize>(s), changetype<usize>(h), 32)
+ clamp(s)
+ memory.copy(changetype<usize>(prefix), changetype<usize>(h) + 32, 32)
+ h.fill(0)
+
+ // Hash prefix and message `M` to nonce `r`
+ blake2b.init().update(prefix, 32).update(M, mlen).digest(r)
+ prefix.fill(0)
+
+ // Reduce `r` modulo `L`, the group order of the base point `B`
+ sc_reduce(r)
+
+ // Perform fixed-base scalar multiplication `[r]B`, output to point `R`
+ ge_scalarmult_base_tobytes(R, r)
+
+ // Compute challenge hash `blake2b(R || A || M)`, output to `k`
+ blake2b.init().update(R, 32).update(A, 32).update(M, mlen).digest(k)
+ A.fill(0)
+
+ // Reduce `k` modulo `L` for efficiency
+ sc_reduce(k)
+
+ // Compute `S = (k * s + r) mod L`
+ sc_muladd(S, k, s, r)
+ k.fill(0)
+ s.fill(0)
+ r.fill(0)
+
+ // Construct final signature `(R || S)`
+ memory.copy(changetype<usize>(RS), changetype<usize>(R), 32)
+ R.fill(0)
+ memory.copy(changetype<usize>(RS) + 32, changetype<usize>(S), 32)
+ S.fill(0)
+}
--- /dev/null
+//! 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 } from './ge'
+import { ge_p3, ge_sub_p3 } from './p'
+import { sc_is_canonical, sc_reduce } from './sc'
+
+// crypto_hash function
+const blake2b = new Blake2b()
+
+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
+ */
+export function crypto_verify (s: StaticArray<u8>, m: usize, mlen: i32, pub: StaticArray<u8>): i32 {
+ const M = changetype<StaticArray<u8>>(m)
+ 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(pub)) 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, pub) != 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(s, 32).update(pub, 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
+}
//! 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'
+import { crypto_derive } from './crypto_derive'
+import { crypto_sign } from './crypto_sign'
+import { crypto_verify } from './crypto_verify'
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
// Static I/O buffers
const OUTPUT_BUFFER = memory.data(OUTPUT_BUFFER_BYTES)
const MESSAGE_BUFFER = memory.data(MESSAGE_BUFFER_BYTES)
-// 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_h: StaticArray<u8> = new StaticArray<u8>(SECRETKEY_BYTES)
-const crypto_derive_s: StaticArray<u8> = new StaticArray<u8>(PRIVATEKEY_BYTES)
-/**
- * Hash seed to 32-byte scalar `a`, clamp it, then point-multiply it by the
- * Ed25519 base point. The Nano specification uses BLAKE2b as the hash function
- * instead of SHA-512 specified by RFC 8032.
- *
- * https://www.rfc-editor.org/info/rfc8032/#section-5.1.5
- *
- * @param A 32-byte output buffer for the compressed public key
- * @param key 32-byte private key "seed" of cryptographically secure random data
- */
-function crypto_derive (A: StaticArray<u8>, key: StaticArray<u8>): void {
- const h = crypto_derive_h
- const s = crypto_derive_s
- // Hash private key to 64-byte buffer `h`
- blake2b.init().update(key, 32).digest(h)
- // Keep only the lower 32 bytes as secret scalar `s` for deriving public key
- memory.copy(changetype<usize>(s), changetype<usize>(h), 32)
- h.fill(0)
- // Clear lowest three bits and highest bit, then set second highest bit
- clamp(s)
- // Perform fixed-base scalar multiplication `[s]B`, output to public key `A`
- ge_scalarmult_base_tobytes(A, s)
- s.fill(0)
-}
-
-const crypto_sign_h = new StaticArray<u8>(64)
-const crypto_sign_s = new StaticArray<u8>(32)
-const crypto_sign_prefix = new StaticArray<u8>(32)
-const crypto_sign_r = new StaticArray<u8>(64)
-const crypto_sign_R = new StaticArray<u8>(64)
-const crypto_sign_k = new StaticArray<u8>(64)
-const crypto_sign_S = new StaticArray<u8>(64)
-/**
- * Sign a message with a secret key. The Nano specification uses BLAKE2b as the
- * hash function instead of SHA-512 specified by RFC 8032.
- *
- * In this implementation, the secret key is a 64-byte concatenation of the
- * private key and its public key in order to (1) ensure the user has a full
- * correct keypair for data integrity, and (2) improve performance by avoiding
- * expensive point multiplication and instead taking a public key that can be
- * computed once and cached. This behavior deviates from RFC 8032 which
- * indicates the public key should be recomputed.
- *
- * https://www.rfc-editor.org/info/rfc8032/#section-5.1.6
- *
- * @param {StaticArray<u8>} RS 64-byte output buffer for detached signature
- * @param {StaticArray<u8>} M variable-length message to be signed
- * @param {i32} mlen bytelength of `m`
- * @param {StaticArray<u8>} key 32-byte private key from input buffer
- * @param {StaticArray<u8>} A 32-byte public key from input buffer
- */
-function crypto_sign (RS: StaticArray<u8>, M: StaticArray<u8>, mlen: i32, key: StaticArray<u8>, A: StaticArray<u8>): void {
- const h = crypto_sign_h
- const s = crypto_sign_s
- const prefix = crypto_sign_prefix
- const r = crypto_sign_r
- const R = crypto_sign_R
- const k = crypto_sign_k
- const S = crypto_sign_S
-
- // Hash private key to `h`
- blake2b.init().update(key, PRIVATEKEY_BYTES).digest(h)
- key.fill(0)
-
- // Split `h` into clamped secret scalar `s` and nonce prefix
- memory.copy(changetype<usize>(s), changetype<usize>(h), 32)
- clamp(s)
- memory.copy(changetype<usize>(prefix), changetype<usize>(h) + 32, 32)
- h.fill(0)
-
- // Hash prefix and message `M` to nonce `r`
- blake2b.init().update(prefix, 32).update(M, mlen).digest(r)
- prefix.fill(0)
-
- // Reduce `r` modulo `L`, the group order of the base point `B`
- sc_reduce(r)
-
- // Perform fixed-base scalar multiplication `[r]B`, output to point `R`
- ge_scalarmult_base_tobytes(R, r)
-
- // Compute challenge hash `blake2b(R || A || M)`, output to `k`
- blake2b.init().update(R, 32).update(A, 32).update(M, mlen).digest(k)
- A.fill(0)
-
- // Reduce `k` modulo `L` for efficiency
- sc_reduce(k)
-
- // Compute `S = (k * s + r) mod L`
- sc_muladd(S, k, s, r)
- k.fill(0)
- s.fill(0)
- r.fill(0)
-
- // Construct final signature `(R || S)`
- memory.copy(changetype<usize>(RS), changetype<usize>(R), 32)
- R.fill(0)
- memory.copy(changetype<usize>(RS) + 32, changetype<usize>(S), 32)
- S.fill(0)
-}
-
-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, pub: StaticArray<u8>): i32 {
- const M = changetype<StaticArray<u8>>(m)
- 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(pub)) 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, pub) != 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(s, 32).update(pub, 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
//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
+/**
+ * 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
+export function clamp (k: StaticArray<u8>): void {
+ k[0] &= 248
+ k[31] &= 127
+ k[31] |= 64
+}
+
//@ts-expect-error
@inline
export function equal (b: i8, c: i8): u8 {