Skip to main content

slh_dsa_sign_verify/
slh_dsa_sign_verify.rs

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