]> git.codecow.com Git - nano25519.git/commitdiff
Extract crypto functions from API definition file.
authorChris Duncan <chris@codecow.com>
Wed, 19 Aug 2026 23:24:38 +0000 (16:24 -0700)
committerChris Duncan <chris@codecow.com>
Wed, 19 Aug 2026 23:24:38 +0000 (16:24 -0700)
src/assembly/crypto_derive.ts [new file with mode: 0644]
src/assembly/crypto_sign.ts [new file with mode: 0644]
src/assembly/crypto_verify.ts [new file with mode: 0644]
src/assembly/index.ts
src/assembly/utils.ts

diff --git a/src/assembly/crypto_derive.ts b/src/assembly/crypto_derive.ts
new file mode 100644 (file)
index 0000000..f1bb450
--- /dev/null
@@ -0,0 +1,40 @@
+//! 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)
+}
diff --git a/src/assembly/crypto_sign.ts b/src/assembly/crypto_sign.ts
new file mode 100644 (file)
index 0000000..df81ade
--- /dev/null
@@ -0,0 +1,87 @@
+//! 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)
+}
diff --git a/src/assembly/crypto_verify.ts b/src/assembly/crypto_verify.ts
new file mode 100644 (file)
index 0000000..9dda4a6
--- /dev/null
@@ -0,0 +1,55 @@
+//! 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
+}
index 87448d0a2d13ac5681f240f82fb19da2d0739383..eb18a1d43c032ede1fad932a5cd2ffcfa2a84b0d 100644 (file)
@@ -1,15 +1,13 @@
 //! 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
@@ -20,171 +18,6 @@ const INPUT_BUFFER = memory.data(INPUT_BUFFER_BYTES)
 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
index 13c535723595220a80549c804b9015d7f7f286d0..bee890ea938ac52cdcaaa84c288d096f1655e16e 100644 (file)
@@ -1,6 +1,21 @@
 //! 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 {