Skip to main content

slh_dsa_sign_verify/
slh_dsa_sign_verify.rs

1// SLH-DSA Sign / Verify Example
2//
3// Demonstrates the SLH-DSA stateless hash-based signature scheme (FIPS 205)
4// using the SHAKE-128f parameter set (NIST Security Category 1, fast variant).
5//
6// Unlike lattice-based schemes (ML-KEM, ML-DSA), SLH-DSA relies *only* on the
7// security of cryptographic hash functions (SHAKE/SHA-3). It has no algebraic
8// structure that could be exploited by future mathematical advances, making it
9// the most conservative post-quantum signature choice.
10//
11// The trade-off is signature size: SLH-DSA-SHAKE-128f produces 17,088-byte
12// signatures (compared to ~2,420 for ML-DSA-44). Key sizes are tiny (32/64
13// bytes), but signing is slower due to the deep Merkle-tree traversal.
14//
15// Architecture of an SLH-DSA signature:
16//   - A randomizer R (16 bytes for 128f)
17//   - A FORS few-time signature (Forest of Random Subsets)
18//   - A hypertree signature (d layers of XMSS Merkle trees with WOTS+ leaves)
19//
20// Run:
21//   cargo run --example sign_verify -p slh_dsa
22
23use quantica::slh_dsa::*;
24use std::time::Instant;
25
26/// Pretty-print a byte slice as a hex string.
27fn hex(bytes: &[u8]) -> String {
28    bytes.iter().map(|b| format!("{:02x}", b)).collect()
29}
30
31fn main() {
32    println!("=== SLH-DSA (FIPS 205) Sign / Verify Example ===\n");
33
34    let mut rng = OsRng;
35
36    // ---------------------------------------------------------------
37    // Step 1: Key generation (SHAKE-128f)
38    // ---------------------------------------------------------------
39    // SHAKE-128f is the fastest 128-bit security parameter set.
40    // Keys are very small: sk = 64 bytes, pk = 32 bytes.
41    let t0 = Instant::now();
42    let (sk, pk) = SlhDsa::<Shake128f>::keygen(&mut rng).expect("keygen failed");
43    let keygen_us = t0.elapsed().as_micros();
44
45    println!("--- Key Generation (SLH-DSA-SHAKE-128f) ---");
46    println!("  secret key:  {} bytes", sk.len());
47    println!("  public key:  {} bytes", pk.len());
48    println!("  pk (hex):    {}", hex(&pk));
49    println!("  time:        {} us", keygen_us);
50    println!();
51
52    // ---------------------------------------------------------------
53    // Step 2: Sign a message
54    // ---------------------------------------------------------------
55    // SLH-DSA signing traverses a hypertree of Merkle trees, which is
56    // computationally intensive. The "f" (fast) variants are optimized
57    // for speed at the cost of larger signatures.
58    let message = b"Hash-based signatures: conservative post-quantum security.";
59
60    let t0 = Instant::now();
61    let sig = SlhDsa::<Shake128f>::sign(message, &sk, &mut rng).expect("sign failed");
62    let sign_ms = t0.elapsed().as_millis();
63
64    println!("--- Signing ---");
65    println!("  message:     {:?}", std::str::from_utf8(message).unwrap());
66    println!("  signature:   {} bytes", sig.len());
67    println!("  sig[0..32]:  {}", hex(&sig[..32]));
68    println!("  time:        {} ms", sign_ms);
69    println!();
70
71    // ---------------------------------------------------------------
72    // Step 3: Verify the signature
73    // ---------------------------------------------------------------
74    let t0 = Instant::now();
75    let valid = SlhDsa::<Shake128f>::verify(message, &sig, &pk).expect("verify call failed");
76    let verify_ms = t0.elapsed().as_millis();
77
78    println!("--- Verification ---");
79    println!("  valid:       {}", valid);
80    println!("  time:        {} ms", verify_ms);
81    assert!(valid, "Signature should be valid!");
82    println!();
83
84    // ---------------------------------------------------------------
85    // Step 4: Tamper test
86    // ---------------------------------------------------------------
87    let mut tampered_bytes = sig.as_bytes().to_vec();
88    tampered_bytes[100] ^= 0x01;
89    let tampered_sig = quantica::slh_dsa::Signature::<Shake128f>::from_bytes(&tampered_bytes).expect("from_bytes");
90
91    let tampered_valid = SlhDsa::<Shake128f>::verify(message, &tampered_sig, &pk).expect("verify call failed");
92
93    println!("--- Tamper Test ---");
94    println!("  flipped bit in sig[100]");
95    println!("  valid:       {} (expected: false)", tampered_valid);
96    assert!(!tampered_valid, "Tampered signature should be rejected!");
97    println!();
98
99    // ---------------------------------------------------------------
100    // Summary: signature sizes across parameter sets
101    // ---------------------------------------------------------------
102    println!("--- SLH-DSA Signature Sizes (SHAKE-based) ---");
103    println!(
104        "  SHAKE-128f: sig={:>5} bytes, pk={:>2}, sk={:>3}",
105        SlhDsa::<Shake128f>::signature_size(),
106        SlhDsa::<Shake128f>::public_key_size(),
107        SlhDsa::<Shake128f>::secret_key_size(),
108    );
109    println!(
110        "  SHAKE-128s: sig={:>5} bytes, pk={:>2}, sk={:>3}",
111        SlhDsa::<Shake128s>::signature_size(),
112        SlhDsa::<Shake128s>::public_key_size(),
113        SlhDsa::<Shake128s>::secret_key_size(),
114    );
115    println!(
116        "  SHAKE-192f: sig={:>5} bytes, pk={:>2}, sk={:>3}",
117        SlhDsa::<Shake192f>::signature_size(),
118        SlhDsa::<Shake192f>::public_key_size(),
119        SlhDsa::<Shake192f>::secret_key_size(),
120    );
121    println!(
122        "  SHAKE-192s: sig={:>5} bytes, pk={:>2}, sk={:>3}",
123        SlhDsa::<Shake192s>::signature_size(),
124        SlhDsa::<Shake192s>::public_key_size(),
125        SlhDsa::<Shake192s>::secret_key_size(),
126    );
127    println!(
128        "  SHAKE-256f: sig={:>5} bytes, pk={:>2}, sk={:>3}",
129        SlhDsa::<Shake256f>::signature_size(),
130        SlhDsa::<Shake256f>::public_key_size(),
131        SlhDsa::<Shake256f>::secret_key_size(),
132    );
133    println!(
134        "  SHAKE-256s: sig={:>5} bytes, pk={:>2}, sk={:>3}",
135        SlhDsa::<Shake256s>::signature_size(),
136        SlhDsa::<Shake256s>::public_key_size(),
137        SlhDsa::<Shake256s>::secret_key_size(),
138    );
139    println!();
140
141    println!("All checks passed.");
142}