ml_dsa_65.nx source
↩ module page · 106 lines · 3681 B
1// ml_dsa_65.nx -- ML-DSA-65 post-quantum digital signature.
2//
3// NIST FIPS 204 (August 2024). Lattice-based (Module-LWE +
4// rejection sampling). Drop-in replacement for Ed25519 once
5// fault-tolerant quantum computers exist -- Ed25519 is broken
6// by Shor's algorithm.
7//
8// Parameters (FIPS 204 Table 2, ML-DSA-65 = NIST security
9// category 3, AES-192 equivalent):
10// n = 256 polynomial degree
11// q = 8380417 prime modulus
12// d = 13 dropped-bits parameter
13// tau = 49 signing challenge weight
14// lambda = 192 collision-resistance security parameter
15// gamma1 = 524288
16// gamma2 = 261888
17// (k, l) = (6, 5) matrix dimensions
18// eta = 4
19// beta = tau * eta = 196
20// omega = 55
21//
22// Key / signature sizes:
23// public key = 1952 bytes
24// secret key = 4032 bytes
25// signature = 3309 bytes
26//
27// Status (2026-04-23): SKELETON. Same scaffolded-then-filled
28// approach as ml_kem_768.nx -- API locked so TLS / X.509 / OTR
29// / email crypto callers can write against the signatures now;
30// the NTT + ExpandA + Keccak-streamed sampling core lands as a
31// separate push (~800 LoC).
32//
33// Dependencies (pending full impl):
34// - sha3.nx: SHAKE-256 for ExpandA, ExpandS, challenge c
35// - rand.nx: 32-byte seed at keygen
36// - ct.nx: constant-time rejection sampling
37//
38// Invariants:
39// DSA1 Output bytes match FIPS 204 Appendix A test vectors.
40// DSA2 Verify ALWAYS accepts a genuine signature from the
41// same (msg, pk) pair; rejects any bit-flip in ct mode.
42// DSA3 Sign uses deterministic (kappa-based) randomness to
43// avoid Sony-PS3-ECDSA class nonce-reuse attacks.
44
45import "syscalls.nx"
46
47const MLDSA_N: i64 = 256
48const MLDSA_Q: i64 = 8380417
49const MLDSA_D: i64 = 13
50const MLDSA_TAU: i64 = 49
51const MLDSA_LAMBDA: i64 = 192
52const MLDSA_K: i64 = 6
53const MLDSA_L: i64 = 5
54const MLDSA_ETA: i64 = 4
55const MLDSA_BETA: i64 = 196
56const MLDSA_OMEGA: i64 = 55
57
58const MLDSA_PK_BYTES: i64 = 1952
59const MLDSA_SK_BYTES: i64 = 4032
60const MLDSA_SIG_BYTES: i64 = 3309
61
62const MLDSA_ERR_PENDING: i64 = -1
63const MLDSA_ERR_RANDOM: i64 = -2
64const MLDSA_ERR_VERIFY: i64 = -3
65
66// KeyGen() -> (pk, sk)
67func ml_dsa_65_keygen(pk_out: *u8, sk_out: *u8) -> i64 {
68 var i: i64 = 0
69 while i < MLDSA_PK_BYTES { pk_out[i] = 0; i = i + 1 }
70 i = 0
71 while i < MLDSA_SK_BYTES { sk_out[i] = 0; i = i + 1 }
72 return MLDSA_ERR_PENDING
73}
74
75// Sign(sk, msg, msg_len) -> sig
76// FIPS 204 ยง5.2 deterministic signing (kappa counter-based nonce).
77func ml_dsa_65_sign(sk_in: *u8,
78 msg: *u8, msg_len: i64,
79 sig_out: *u8) -> i64 {
80 var i: i64 = 0
81 while i < MLDSA_SIG_BYTES { sig_out[i] = 0; i = i + 1 }
82 return MLDSA_ERR_PENDING
83}
84
85// Verify(pk, msg, msg_len, sig) -> 0 if good, MLDSA_ERR_VERIFY
86// if bad. Constant-time in the SIGNATURE; message can branch.
87func ml_dsa_65_verify(pk_in: *u8,
88 msg: *u8, msg_len: i64,
89 sig_in: *u8) -> i64 {
90 return MLDSA_ERR_PENDING
91}
92
93// Compile-only smoke.
94func main() -> i64 {
95 let pk: *u8 = sys_mmap(MLDSA_PK_BYTES)
96 let sk: *u8 = sys_mmap(MLDSA_SK_BYTES)
97 let sig: *u8 = sys_mmap(MLDSA_SIG_BYTES)
98 if ml_dsa_65_keygen(pk, sk) != MLDSA_ERR_PENDING { return 1 }
99 if ml_dsa_65_sign(sk, "hello", 5, sig) != MLDSA_ERR_PENDING {
100 return 2
101 }
102 if ml_dsa_65_verify(pk, "hello", 5, sig) != MLDSA_ERR_PENDING {
103 return 3
104 }
105 return 0
106}