Skip to main content

falcon/
safe_api.rs

1//! High-level safe Rust SDK for FN-DSA (FIPS 206) post-quantum digital signatures.
2//!
3//! FN-DSA (FFT over NTRU-Lattice-Based Digital Signature Algorithm) is the
4//! NIST standardization of the Falcon signature scheme as FIPS 206.
5//!
6//! # Quick Start
7//!
8//! ```rust
9//! use falcon::safe_api::{FnDsaKeyPair, FnDsaSignature, DomainSeparation};
10//!
11//! let kp = FnDsaKeyPair::generate(9).unwrap();
12//! let sig = kp.sign(b"Hello, post-quantum world!", &DomainSeparation::None).unwrap();
13//! FnDsaSignature::verify(sig.to_bytes(), kp.public_key(), b"Hello, post-quantum world!", &DomainSeparation::None).unwrap();
14//! ```
15//!
16//! # Domain Separation (FIPS 206 §6)
17//!
18//! FIPS 206 defines two signing modes:
19//!
20//! * **FN-DSA** (`ph_flag = 0x00`) — pure signing; the raw message is hashed
21//!   inside the algorithm. Use [`DomainSeparation::None`] or
22//!   [`DomainSeparation::Context`] for optional domain binding.
23//!
24//! * **HashFN-DSA** (`ph_flag = 0x01`) — hash-and-sign; the message is
25//!   pre-hashed with SHA-256 or SHA-512 *before* signing.  Use
26//!   [`DomainSeparation::Prehashed`] with a [`PreHashAlgorithm`] selector.
27//!
28//! ```rust
29//! # use falcon::safe_api::{FnDsaKeyPair, FnDsaSignature, DomainSeparation, PreHashAlgorithm};
30//! let kp = FnDsaKeyPair::generate(9).unwrap();
31//!
32//! // Pure FN-DSA with an application context string
33//! let ctx = DomainSeparation::Context(b"my-protocol-v1");
34//! let sig = kp.sign(b"msg", &ctx).unwrap();
35//! FnDsaSignature::verify(sig.to_bytes(), kp.public_key(), b"msg", &ctx).unwrap();
36//!
37//! // HashFN-DSA (pre-hash with SHA-256)
38//! let ph = DomainSeparation::Prehashed { alg: PreHashAlgorithm::Sha256, context: b"" };
39//! let sig2 = kp.sign(b"msg", &ph).unwrap();
40//! FnDsaSignature::verify(sig2.to_bytes(), kp.public_key(), b"msg", &ph).unwrap();
41//! ```
42//!
43//! # Security Levels
44//!
45//! | logn | Variant     | NIST Level | Private Key | Public Key | Signature |
46//! |------|-------------|------------|-------------|------------|-----------|
47//! | 9    | FN-DSA-512  | I          | 1281 B      | 897 B      | 666 B     |
48//! | 10   | FN-DSA-1024 | V          | 2305 B      | 1793 B     | 1280 B    |
49
50#![deny(missing_docs)]
51#![deny(unsafe_code)]
52
53use alloc::{vec, vec::Vec};
54use core::fmt;
55
56use zeroize::Zeroizing;
57
58use crate::{
59    falcon as falcon_api,
60    rng::get_seed,
61    shake::{i_shake256_flip, i_shake256_init, i_shake256_inject, InnerShake256Context},
62};
63
64// ======================================================================
65// SHA-2 pure-Rust helpers (no external dependency)
66// ======================================================================
67
68/// Compute SHA-256 of `data`. Pure Rust, no_std compatible.
69///
70/// Exposed for integration-test NIST-vector validation.
71/// Do not use directly in protocol code — use [`DomainSeparation::Prehashed`].
72#[doc(hidden)]
73pub fn sha256_public(data: &[u8]) -> [u8; 32] {
74    sha256(data)
75}
76
77/// Compute SHA-512 of `data`. Pure Rust, no_std compatible.
78///
79/// Exposed for integration-test NIST-vector validation.
80#[doc(hidden)]
81pub fn sha512_public(data: &[u8]) -> [u8; 64] {
82    sha512(data)
83}
84
85/// Compute SHA-256 of `data`. Pure Rust, no_std compatible.
86fn sha256(data: &[u8]) -> [u8; 32] {
87    // Initial hash values (first 32 bits of fractional parts of sqrt of primes 2..19)
88    let mut h: [u32; 8] = [
89        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
90        0x5be0cd19,
91    ];
92    // Round constants
93    const K: [u32; 64] = [
94        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
95        0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
96        0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
97        0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
98        0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
99        0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
100        0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
101        0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
102        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
103        0xc67178f2,
104    ];
105
106    // Pad the message: append 0x80, then zeros, then 64-bit bit-length (big-endian)
107    let bit_len = (data.len() as u64).wrapping_mul(8);
108    let mut msg: Vec<u8> = Vec::with_capacity(data.len() + 64);
109    msg.extend_from_slice(data);
110    msg.push(0x80);
111    while (msg.len() % 64) != 56 {
112        msg.push(0x00);
113    }
114    msg.extend_from_slice(&bit_len.to_be_bytes());
115
116    // Process each 512-bit (64-byte) chunk
117    for chunk in msg.chunks(64) {
118        let mut w = [0u32; 64];
119        for i in 0..16 {
120            w[i] = u32::from_be_bytes([
121                chunk[4 * i],
122                chunk[4 * i + 1],
123                chunk[4 * i + 2],
124                chunk[4 * i + 3],
125            ]);
126        }
127        for i in 16..64 {
128            let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
129            let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
130            w[i] = w[i - 16]
131                .wrapping_add(s0)
132                .wrapping_add(w[i - 7])
133                .wrapping_add(s1);
134        }
135        let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] =
136            [h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]];
137        for i in 0..64 {
138            let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
139            let ch = (e & f) ^ ((!e) & g);
140            let temp1 = hh
141                .wrapping_add(s1)
142                .wrapping_add(ch)
143                .wrapping_add(K[i])
144                .wrapping_add(w[i]);
145            let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
146            let maj = (a & b) ^ (a & c) ^ (b & c);
147            let temp2 = s0.wrapping_add(maj);
148            hh = g;
149            g = f;
150            f = e;
151            e = d.wrapping_add(temp1);
152            d = c;
153            c = b;
154            b = a;
155            a = temp1.wrapping_add(temp2);
156        }
157        h[0] = h[0].wrapping_add(a);
158        h[1] = h[1].wrapping_add(b);
159        h[2] = h[2].wrapping_add(c);
160        h[3] = h[3].wrapping_add(d);
161        h[4] = h[4].wrapping_add(e);
162        h[5] = h[5].wrapping_add(f);
163        h[6] = h[6].wrapping_add(g);
164        h[7] = h[7].wrapping_add(hh);
165    }
166
167    let mut out = [0u8; 32];
168    for (i, &v) in h.iter().enumerate() {
169        out[4 * i..4 * i + 4].copy_from_slice(&v.to_be_bytes());
170    }
171    out
172}
173
174/// Compute SHA-512 of `data`. Pure Rust, no_std compatible.
175fn sha512(data: &[u8]) -> [u8; 64] {
176    let mut h: [u64; 8] = [
177        0x6a09e667f3bcc908,
178        0xbb67ae8584caa73b,
179        0x3c6ef372fe94f82b,
180        0xa54ff53a5f1d36f1,
181        0x510e527fade682d1,
182        0x9b05688c2b3e6c1f,
183        0x1f83d9abfb41bd6b,
184        0x5be0cd19137e2179,
185    ];
186    const K: [u64; 80] = [
187        0x428a2f98d728ae22,
188        0x7137449123ef65cd,
189        0xb5c0fbcfec4d3b2f,
190        0xe9b5dba58189dbbc,
191        0x3956c25bf348b538,
192        0x59f111f1b605d019,
193        0x923f82a4af194f9b,
194        0xab1c5ed5da6d8118,
195        0xd807aa98a3030242,
196        0x12835b0145706fbe,
197        0x243185be4ee4b28c,
198        0x550c7dc3d5ffb4e2,
199        0x72be5d74f27b896f,
200        0x80deb1fe3b1696b1,
201        0x9bdc06a725c71235,
202        0xc19bf174cf692694,
203        0xe49b69c19ef14ad2,
204        0xefbe4786384f25e3,
205        0x0fc19dc68b8cd5b5,
206        0x240ca1cc77ac9c65,
207        0x2de92c6f592b0275,
208        0x4a7484aa6ea6e483,
209        0x5cb0a9dcbd41fbd4,
210        0x76f988da831153b5,
211        0x983e5152ee66dfab,
212        0xa831c66d2db43210,
213        0xb00327c898fb213f,
214        0xbf597fc7beef0ee4,
215        0xc6e00bf33da88fc2,
216        0xd5a79147930aa725,
217        0x06ca6351e003826f,
218        0x142929670a0e6e70,
219        0x27b70a8546d22ffc,
220        0x2e1b21385c26c926,
221        0x4d2c6dfc5ac42aed,
222        0x53380d139d95b3df,
223        0x650a73548baf63de,
224        0x766a0abb3c77b2a8,
225        0x81c2c92e47edaee6,
226        0x92722c851482353b,
227        0xa2bfe8a14cf10364,
228        0xa81a664bbc423001,
229        0xc24b8b70d0f89791,
230        0xc76c51a30654be30,
231        0xd192e819d6ef5218,
232        0xd69906245565a910,
233        0xf40e35855771202a,
234        0x106aa07032bbd1b8,
235        0x19a4c116b8d2d0c8,
236        0x1e376c085141ab53,
237        0x2748774cdf8eeb99,
238        0x34b0bcb5e19b48a8,
239        0x391c0cb3c5c95a63,
240        0x4ed8aa4ae3418acb,
241        0x5b9cca4f7763e373,
242        0x682e6ff3d6b2b8a3,
243        0x748f82ee5defb2fc,
244        0x78a5636f43172f60,
245        0x84c87814a1f0ab72,
246        0x8cc702081a6439ec,
247        0x90befffa23631e28,
248        0xa4506cebde82bde9,
249        0xbef9a3f7b2c67915,
250        0xc67178f2e372532b,
251        0xca273eceea26619c,
252        0xd186b8c721c0c207,
253        0xeada7dd6cde0eb1e,
254        0xf57d4f7fee6ed178,
255        0x06f067aa72176fba,
256        0x0a637dc5a2c898a6,
257        0x113f9804bef90dae,
258        0x1b710b35131c471b,
259        0x28db77f523047d84,
260        0x32caab7b40c72493,
261        0x3c9ebe0a15c9bebc,
262        0x431d67c49c100d4c,
263        0x4cc5d4becb3e42b6,
264        0x597f299cfc657e2a,
265        0x5fcb6fab3ad6faec,
266        0x6c44198c4a475817,
267    ];
268
269    let bit_len = (data.len() as u128).wrapping_mul(8);
270    let mut msg: Vec<u8> = Vec::with_capacity(data.len() + 128);
271    msg.extend_from_slice(data);
272    msg.push(0x80);
273    while (msg.len() % 128) != 112 {
274        msg.push(0x00);
275    }
276    msg.extend_from_slice(&[0u8; 8]); // high 64 bits of 128-bit length
277    msg.extend_from_slice(&(bit_len as u64).to_be_bytes());
278
279    for chunk in msg.chunks(128) {
280        let mut w = [0u64; 80];
281        for i in 0..16 {
282            let b = &chunk[8 * i..8 * i + 8];
283            w[i] = u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]);
284        }
285        for i in 16..80 {
286            let s0 = w[i - 15].rotate_right(1) ^ w[i - 15].rotate_right(8) ^ (w[i - 15] >> 7);
287            let s1 = w[i - 2].rotate_right(19) ^ w[i - 2].rotate_right(61) ^ (w[i - 2] >> 6);
288            w[i] = w[i - 16]
289                .wrapping_add(s0)
290                .wrapping_add(w[i - 7])
291                .wrapping_add(s1);
292        }
293        let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] =
294            [h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]];
295        for i in 0..80 {
296            let s1 = e.rotate_right(14) ^ e.rotate_right(18) ^ e.rotate_right(41);
297            let ch = (e & f) ^ ((!e) & g);
298            let t1 = hh
299                .wrapping_add(s1)
300                .wrapping_add(ch)
301                .wrapping_add(K[i])
302                .wrapping_add(w[i]);
303            let s0 = a.rotate_right(28) ^ a.rotate_right(34) ^ a.rotate_right(39);
304            let maj = (a & b) ^ (a & c) ^ (b & c);
305            let t2 = s0.wrapping_add(maj);
306            hh = g;
307            g = f;
308            f = e;
309            e = d.wrapping_add(t1);
310            d = c;
311            c = b;
312            b = a;
313            a = t1.wrapping_add(t2);
314        }
315        h[0] = h[0].wrapping_add(a);
316        h[1] = h[1].wrapping_add(b);
317        h[2] = h[2].wrapping_add(c);
318        h[3] = h[3].wrapping_add(d);
319        h[4] = h[4].wrapping_add(e);
320        h[5] = h[5].wrapping_add(f);
321        h[6] = h[6].wrapping_add(g);
322        h[7] = h[7].wrapping_add(hh);
323    }
324
325    let mut out = [0u8; 64];
326    for (i, &v) in h.iter().enumerate() {
327        out[8 * i..8 * i + 8].copy_from_slice(&v.to_be_bytes());
328    }
329    out
330}
331
332// ======================================================================
333// OID constants for HashFN-DSA (FIPS 206 Table 3)
334// ======================================================================
335
336/// ASN.1 DER OID for id-sha256 (2.16.840.1.101.3.4.2.1)
337const OID_SHA256: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01];
338/// ASN.1 DER OID for id-sha512 (2.16.840.1.101.3.4.2.3)
339const OID_SHA512: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03];
340
341// ======================================================================
342// Domain Separation (FIPS 206)
343// ======================================================================
344
345/// Pre-hash algorithm selector for `HashFN-DSA` (FIPS 206 §6.2).
346///
347/// The message is hashed with the chosen algorithm before signing.
348/// The algorithm OID is injected into the hash context so that sign
349/// and verify must use matching algorithms.
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
351#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
352pub enum PreHashAlgorithm {
353    /// SHA-256 (32-byte digest, OID 2.16.840.1.101.3.4.2.1)
354    Sha256,
355    /// SHA-512 (64-byte digest, OID 2.16.840.1.101.3.4.2.3)
356    Sha512,
357}
358
359impl PreHashAlgorithm {
360    fn oid(self) -> &'static [u8] {
361        match self {
362            PreHashAlgorithm::Sha256 => OID_SHA256,
363            PreHashAlgorithm::Sha512 => OID_SHA512,
364        }
365    }
366
367    fn hash(self, msg: &[u8]) -> Vec<u8> {
368        match self {
369            PreHashAlgorithm::Sha256 => sha256(msg).to_vec(),
370            PreHashAlgorithm::Sha512 => sha512(msg).to_vec(),
371        }
372    }
373}
374
375/// Domain separation context for FN-DSA / HashFN-DSA (FIPS 206 §6).
376///
377/// # Variants
378///
379/// * [`None`](DomainSeparation::None) — Pure FN-DSA, no context string
380///   (`ph_flag = 0x00`, context length = 0).
381///
382/// * [`Context`](DomainSeparation::Context) — Pure FN-DSA with an
383///   application context string (1–255 bytes, `ph_flag = 0x00`).
384///
385/// * [`Prehashed`](DomainSeparation::Prehashed) — HashFN-DSA mode
386///   (`ph_flag = 0x01`). The message is pre-hashed; the algorithm OID
387///   and optional context string are injected into the hash context.
388///
389/// # FIPS 206 Wire Format
390///
391/// For all variants the bytes injected into the hash context (after the
392/// 40-byte nonce) are:
393///
394/// * Pure:  `ph_flag(0x00) || len(ctx) || ctx`
395/// * Hashed: `ph_flag(0x01) || len(ctx) || ctx || OID || hash(msg)`
396///
397/// The context string **must not exceed 255 bytes**; passing a longer
398/// slice returns `Err(FalconError::BadArgument)`.
399#[derive(Debug, Clone, Copy, PartialEq, Eq)]
400#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
401pub enum DomainSeparation<'a> {
402    /// No context string (pure FN-DSA, empty context).
403    None,
404    /// Application context string — max 255 bytes, pure FN-DSA.
405    Context(&'a [u8]),
406    /// HashFN-DSA mode: pre-hash the message with `alg`, optionally
407    /// bind with `context` (max 255 bytes).
408    Prehashed {
409        /// Pre-hash algorithm.
410        alg: PreHashAlgorithm,
411        /// Optional context string (max 255 bytes).
412        context: &'a [u8],
413    },
414}
415
416impl<'a> DomainSeparation<'a> {
417    /// Return `Err(BadArgument)` if the context string exceeds 255 bytes.
418    fn validate_context(ctx: &[u8]) -> Result<(), FalconError> {
419        if ctx.len() > 255 {
420            return Err(FalconError::BadArgument);
421        }
422        Ok(())
423    }
424
425    /// Validate lengths. Call before sign/verify.
426    pub(crate) fn validate(&self) -> Result<(), FalconError> {
427        match self {
428            DomainSeparation::None => Ok(()),
429            DomainSeparation::Context(ctx) => Self::validate_context(ctx),
430            DomainSeparation::Prehashed { context, .. } => Self::validate_context(context),
431        }
432    }
433
434    /// `ph_flag` byte: 0x00 for pure FN-DSA, 0x01 for HashFN-DSA.
435    fn ph_flag(&self) -> u8 {
436        match self {
437            DomainSeparation::Prehashed { .. } => 0x01,
438            _ => 0x00,
439        }
440    }
441
442    /// Inject `ph_flag || len(ctx) || ctx [|| OID]` into a SHAKE256 context.
443    ///
444    /// For `Prehashed`, the caller must subsequently inject the digest
445    /// via `inject_prehash_digest`.
446    pub(crate) fn inject_header(&self, sc: &mut InnerShake256Context) {
447        let (ctx, oid) = match self {
448            DomainSeparation::None => (&b""[..], None),
449            DomainSeparation::Context(c) => (*c, None),
450            DomainSeparation::Prehashed { alg, context } => (*context, Some(alg.oid())),
451        };
452        // ph_flag || len(ctx) || ctx
453        let len = ctx.len().min(255) as u8;
454        i_shake256_inject(sc, &[self.ph_flag(), len]);
455        if len > 0 {
456            i_shake256_inject(sc, &ctx[..len as usize]);
457        }
458        // For HashFN-DSA also inject the OID
459        if let Some(o) = oid {
460            i_shake256_inject(sc, o);
461        }
462    }
463
464    /// For `Prehashed`, compute and inject `hash(message)`.
465    /// For pure modes, inject `message` directly.
466    pub(crate) fn inject_message(&self, sc: &mut InnerShake256Context, message: &[u8]) {
467        match self {
468            DomainSeparation::Prehashed { alg, .. } => {
469                let digest = alg.hash(message);
470                falcon_api::shake256_inject(sc, &digest);
471            }
472            _ => {
473                falcon_api::shake256_inject(sc, message);
474            }
475        }
476    }
477
478    /// Convenience: inject the full domain + message into `sc`.
479    pub(crate) fn inject(&self, sc: &mut InnerShake256Context, message: &[u8]) {
480        self.inject_header(sc);
481        self.inject_message(sc, message);
482    }
483}
484
485// ======================================================================
486// Error type
487// ======================================================================
488
489/// Errors returned by the FN-DSA API.
490#[derive(Debug, Clone, Copy, PartialEq, Eq)]
491#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
492pub enum FalconError {
493    /// Random number generation failed.
494    RandomError,
495    /// A buffer was too small.
496    SizeError,
497    /// Invalid key or signature format.
498    FormatError,
499    /// Signature verification failed.
500    BadSignature,
501    /// An argument was invalid (e.g., logn out of range, context too long).
502    BadArgument,
503    /// Internal error in the algorithm.
504    InternalError,
505}
506
507impl fmt::Display for FalconError {
508    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509        match self {
510            FalconError::RandomError => write!(f, "random number generation failed"),
511            FalconError::SizeError => write!(f, "buffer size error"),
512            FalconError::FormatError => write!(f, "invalid format"),
513            FalconError::BadSignature => write!(f, "invalid signature"),
514            FalconError::BadArgument => write!(f, "invalid argument"),
515            FalconError::InternalError => write!(f, "internal error"),
516        }
517    }
518}
519
520#[cfg(feature = "std")]
521impl std::error::Error for FalconError {}
522
523fn translate_error(rc: i32) -> FalconError {
524    match rc {
525        falcon_api::FALCON_ERR_RANDOM => FalconError::RandomError,
526        falcon_api::FALCON_ERR_SIZE => FalconError::SizeError,
527        falcon_api::FALCON_ERR_FORMAT => FalconError::FormatError,
528        falcon_api::FALCON_ERR_BADSIG => FalconError::BadSignature,
529        falcon_api::FALCON_ERR_BADARG => FalconError::BadArgument,
530        falcon_api::FALCON_ERR_INTERNAL => FalconError::InternalError,
531        _ => FalconError::InternalError,
532    }
533}
534
535// ======================================================================
536// Key pair
537// ======================================================================
538
539/// An FN-DSA key pair (private key + public key).
540///
541/// Use `logn = 9` for FN-DSA-512 (NIST Level I) or `logn = 10`
542/// for FN-DSA-1024 (NIST Level V).
543///
544/// The private key bytes are **automatically zeroized on drop**.
545///
546/// **Note:** `Clone` duplicates private key material in memory.
547/// Only clone when you need multiple owners; prefer references otherwise.
548#[derive(Clone)]
549#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
550pub struct FnDsaKeyPair {
551    /// Private key bytes — zeroized on drop.
552    privkey: Zeroizing<Vec<u8>>,
553    /// Public key bytes.
554    pubkey: Vec<u8>,
555    /// Parameter: log2 of the lattice dimension (9 = FN-DSA-512, 10 = FN-DSA-1024).
556    logn: u32,
557}
558
559impl fmt::Debug for FnDsaKeyPair {
560    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
561        f.debug_struct("FnDsaKeyPair")
562            .field("privkey", &"[REDACTED]")
563            .field("pubkey_len", &self.pubkey.len())
564            .field("logn", &self.logn)
565            .finish()
566    }
567}
568
569/// Type alias for backward compatibility.
570pub type FalconKeyPair = FnDsaKeyPair;
571
572impl FnDsaKeyPair {
573    #[inline]
574    const fn is_fips_logn(logn: u32) -> bool {
575        matches!(logn, 9 | 10)
576    }
577
578    /// Generate a new FN-DSA key pair using OS entropy.
579    ///
580    /// * `logn` — 9 for FN-DSA-512, 10 for FN-DSA-1024.
581    pub fn generate(logn: u32) -> Result<Self, FalconError> {
582        if !Self::is_fips_logn(logn) {
583            return Err(FalconError::BadArgument);
584        }
585        let mut seed = Zeroizing::new([0u8; 48]);
586        if !get_seed(&mut *seed) {
587            return Err(FalconError::RandomError);
588        }
589        Self::generate_deterministic(&seed[..], logn)
590    }
591
592    /// Generate a key pair deterministically from `seed`.
593    pub fn generate_deterministic(seed: &[u8], logn: u32) -> Result<Self, FalconError> {
594        if !Self::is_fips_logn(logn) {
595            return Err(FalconError::BadArgument);
596        }
597        let sk_len = falcon_api::falcon_privkey_size(logn);
598        let pk_len = falcon_api::falcon_pubkey_size(logn);
599        let tmp_len = falcon_api::falcon_tmpsize_keygen(logn);
600
601        let mut rng = InnerShake256Context::new();
602        i_shake256_init(&mut rng);
603        i_shake256_inject(&mut rng, seed);
604        i_shake256_flip(&mut rng);
605
606        let mut privkey = vec![0u8; sk_len];
607        let mut pubkey = vec![0u8; pk_len];
608        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
609
610        let rc = falcon_api::falcon_keygen_make(
611            &mut rng,
612            logn,
613            &mut privkey,
614            Some(&mut pubkey),
615            &mut tmp,
616        );
617        if rc != 0 {
618            return Err(translate_error(rc));
619        }
620        Ok(FnDsaKeyPair {
621            privkey: Zeroizing::new(privkey),
622            pubkey,
623            logn,
624        })
625    }
626
627    /// Reconstruct from previously exported private + public key bytes.
628    pub fn from_keys(privkey: &[u8], pubkey: &[u8]) -> Result<Self, FalconError> {
629        if privkey.is_empty() || pubkey.is_empty() {
630            return Err(FalconError::FormatError);
631        }
632        let sk_logn = falcon_api::falcon_get_logn(privkey);
633        let pk_logn = falcon_api::falcon_get_logn(pubkey);
634        if sk_logn < 0 || pk_logn < 0 {
635            return Err(FalconError::FormatError);
636        }
637        if (privkey[0] & 0xF0) != 0x50 {
638            return Err(FalconError::FormatError);
639        }
640        if (pubkey[0] & 0xF0) != 0x00 {
641            return Err(FalconError::FormatError);
642        }
643        let logn = (sk_logn & 0x0F) as u32;
644        if logn != (pk_logn & 0x0F) as u32 {
645            return Err(FalconError::FormatError);
646        }
647        if !Self::is_fips_logn(logn) {
648            return Err(FalconError::BadArgument);
649        }
650        if privkey.len() != falcon_api::falcon_privkey_size(logn) {
651            return Err(FalconError::FormatError);
652        }
653        if pubkey.len() != falcon_api::falcon_pubkey_size(logn) {
654            return Err(FalconError::FormatError);
655        }
656        Ok(FnDsaKeyPair {
657            privkey: Zeroizing::new(privkey.to_vec()),
658            pubkey: pubkey.to_vec(),
659            logn,
660        })
661    }
662
663    /// Reconstruct from a private key only (public key is recomputed).
664    pub fn from_private_key(privkey: &[u8]) -> Result<Self, FalconError> {
665        if privkey.is_empty() {
666            return Err(FalconError::FormatError);
667        }
668        if (privkey[0] & 0xF0) != 0x50 {
669            return Err(FalconError::FormatError);
670        }
671        let logn_val = falcon_api::falcon_get_logn(privkey);
672        if logn_val < 0 {
673            return Err(FalconError::FormatError);
674        }
675        let logn = logn_val as u32;
676        if !Self::is_fips_logn(logn) {
677            return Err(FalconError::BadArgument);
678        }
679        if privkey.len() != falcon_api::falcon_privkey_size(logn) {
680            return Err(FalconError::FormatError);
681        }
682        let pk_len = falcon_api::falcon_pubkey_size(logn);
683        let tmp_len = falcon_api::falcon_tmpsize_makepub(logn);
684        let mut pubkey = vec![0u8; pk_len];
685        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
686        let rc = falcon_api::falcon_make_public(&mut pubkey, privkey, &mut tmp);
687        if rc != 0 {
688            return Err(translate_error(rc));
689        }
690        Ok(FnDsaKeyPair {
691            privkey: Zeroizing::new(privkey.to_vec()),
692            pubkey,
693            logn,
694        })
695    }
696
697    /// Compute the public key bytes from a private key without creating a key pair.
698    pub fn public_key_from_private(privkey: &[u8]) -> Result<Vec<u8>, FalconError> {
699        Ok(Self::from_private_key(privkey)?.pubkey)
700    }
701
702    /// Sign `message` using FIPS 206 domain separation.
703    ///
704    /// Supports both pure FN-DSA ([`DomainSeparation::None`] /
705    /// [`DomainSeparation::Context`]) and HashFN-DSA
706    /// ([`DomainSeparation::Prehashed`]).
707    ///
708    /// # Errors
709    ///
710    /// * [`FalconError::BadArgument`] — context string > 255 bytes.
711    /// * [`FalconError::RandomError`] — OS RNG unavailable.
712    pub fn sign(
713        &self,
714        message: &[u8],
715        domain: &DomainSeparation,
716    ) -> Result<FnDsaSignature, FalconError> {
717        domain.validate()?;
718
719        let sig_max = falcon_api::falcon_sig_ct_size(self.logn);
720        let tmp_len = falcon_api::falcon_tmpsize_signdyn(self.logn);
721
722        let mut seed = Zeroizing::new([0u8; 48]);
723        if !get_seed(&mut *seed) {
724            return Err(FalconError::RandomError);
725        }
726        let mut rng = InnerShake256Context::new();
727        i_shake256_init(&mut rng);
728        i_shake256_inject(&mut rng, &*seed);
729        i_shake256_flip(&mut rng);
730
731        let mut sig = vec![0u8; sig_max];
732        let mut sig_len = sig_max;
733        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
734
735        let mut nonce = [0u8; 40];
736        falcon_api::shake256_extract(&mut rng, &mut nonce);
737        let mut hd = InnerShake256Context::new();
738        falcon_api::shake256_init(&mut hd);
739        falcon_api::shake256_inject(&mut hd, &nonce);
740        domain.inject(&mut hd, message);
741
742        let rc = falcon_api::falcon_sign_dyn_finish(
743            &mut rng,
744            &mut sig,
745            &mut sig_len,
746            falcon_api::FALCON_SIG_CT,
747            &self.privkey,
748            &mut hd,
749            &nonce,
750            &mut tmp,
751        );
752        if rc != 0 {
753            return Err(translate_error(rc));
754        }
755        sig.truncate(sig_len);
756        Ok(FnDsaSignature { data: sig })
757    }
758
759    /// Sign with a deterministic seed (testing / reproducibility).
760    ///
761    /// The same `(key, message, seed, domain)` tuple always produces
762    /// the same signature.
763    ///
764    /// # Errors
765    ///
766    /// * [`FalconError::BadArgument`] — context string > 255 bytes.
767    pub fn sign_deterministic(
768        &self,
769        message: &[u8],
770        seed: &[u8],
771        domain: &DomainSeparation,
772    ) -> Result<FnDsaSignature, FalconError> {
773        domain.validate()?;
774
775        let sig_max = falcon_api::falcon_sig_ct_size(self.logn);
776        let tmp_len = falcon_api::falcon_tmpsize_signdyn(self.logn);
777
778        let mut rng = InnerShake256Context::new();
779        i_shake256_init(&mut rng);
780        i_shake256_inject(&mut rng, seed);
781        i_shake256_flip(&mut rng);
782
783        let mut sig = vec![0u8; sig_max];
784        let mut sig_len = sig_max;
785        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
786
787        let mut nonce = [0u8; 40];
788        falcon_api::shake256_extract(&mut rng, &mut nonce);
789        let mut hd = InnerShake256Context::new();
790        falcon_api::shake256_init(&mut hd);
791        falcon_api::shake256_inject(&mut hd, &nonce);
792        domain.inject(&mut hd, message);
793
794        let rc = falcon_api::falcon_sign_dyn_finish(
795            &mut rng,
796            &mut sig,
797            &mut sig_len,
798            falcon_api::FALCON_SIG_CT,
799            &self.privkey,
800            &mut hd,
801            &nonce,
802            &mut tmp,
803        );
804        if rc != 0 {
805            return Err(translate_error(rc));
806        }
807        sig.truncate(sig_len);
808        Ok(FnDsaSignature { data: sig })
809    }
810
811    /// Get the encoded public key bytes.
812    pub fn public_key(&self) -> &[u8] {
813        &self.pubkey
814    }
815
816    /// Get the encoded private key bytes.
817    ///
818    /// ⚠️ **Secret material** — handle with care.
819    pub fn private_key(&self) -> &[u8] {
820        &self.privkey
821    }
822
823    /// Get the FN-DSA degree parameter.
824    ///
825    /// Returns 9 for FN-DSA-512, 10 for FN-DSA-1024.
826    pub fn logn(&self) -> u32 {
827        self.logn
828    }
829
830    /// Get the security variant name.
831    pub fn variant_name(&self) -> &'static str {
832        match self.logn {
833            9 => "FN-DSA-512",
834            10 => "FN-DSA-1024",
835            n => match n {
836                1 => "FN-DSA-2",
837                2 => "FN-DSA-4",
838                3 => "FN-DSA-8",
839                4 => "FN-DSA-16",
840                5 => "FN-DSA-32",
841                6 => "FN-DSA-64",
842                7 => "FN-DSA-128",
843                8 => "FN-DSA-256",
844                _ => "FN-DSA-unknown",
845            },
846        }
847    }
848}
849
850// ======================================================================
851// Signature
852// ======================================================================
853
854/// An FN-DSA / HashFN-DSA digital signature.
855///
856/// Signature bytes are in constant-time (CT) format:
857/// 666 bytes for FN-DSA-512, 1280 bytes for FN-DSA-1024.
858#[derive(Debug, Clone)]
859#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
860pub struct FnDsaSignature {
861    data: Vec<u8>,
862}
863
864/// Type alias for backward compatibility.
865pub type FalconSignature = FnDsaSignature;
866
867impl FnDsaSignature {
868    /// Deserialize a signature from raw bytes.
869    ///
870    /// Returns `Err(FalconError::FormatError)` if the bytes are too short
871    /// (minimum 41 bytes: 1 header + 40 nonce) or the header byte is invalid.
872    /// Returns `Err(FalconError::BadArgument)` for non-FIPS parameter sets.
873    pub fn from_bytes(data: Vec<u8>) -> Result<Self, FalconError> {
874        if data.len() < 41 {
875            return Err(FalconError::FormatError);
876        }
877        // Header high nibble must be 0x30 (compressed/padded) or 0x50 (CT).
878        let hdr = data[0] & 0xF0;
879        if hdr != 0x30 && hdr != 0x50 {
880            return Err(FalconError::FormatError);
881        }
882        let logn = data[0] & 0x0F;
883        if !FnDsaKeyPair::is_fips_logn(logn as u32) {
884            return Err(FalconError::BadArgument);
885        }
886        Ok(FnDsaSignature { data })
887    }
888
889    /// Verify a signature against `pubkey` and `message`.
890    ///
891    /// The `domain` must exactly match what was used during signing
892    /// (same variant, same context string, same pre-hash algorithm).
893    ///
894    /// Supports pure FN-DSA and HashFN-DSA transparently.
895    ///
896    /// # Errors
897    ///
898    /// * [`FalconError::BadArgument`]  — context string > 255 bytes.
899    /// * [`FalconError::BadSignature`] — signature is invalid.
900    /// * [`FalconError::FormatError`]  — malformed key or signature.
901    pub fn verify(
902        sig: &[u8],
903        pubkey: &[u8],
904        message: &[u8],
905        domain: &DomainSeparation,
906    ) -> Result<(), FalconError> {
907        domain.validate()?;
908
909        if pubkey.is_empty() || sig.is_empty() {
910            return Err(FalconError::FormatError);
911        }
912        let logn_val = falcon_api::falcon_get_logn(pubkey);
913        if logn_val < 0 {
914            return Err(FalconError::FormatError);
915        }
916        let logn = logn_val as u32;
917        if !FnDsaKeyPair::is_fips_logn(logn) {
918            return Err(FalconError::BadArgument);
919        }
920
921        // Validate signature length against expected sizes for this logn.
922        let sig_ct = falcon_api::falcon_sig_ct_size(logn);
923        let sig_padded = falcon_api::falcon_sig_padded_size(logn);
924        let sig_comp_max = falcon_api::falcon_sig_compressed_maxsize(logn);
925        if sig.len() < 41 || sig.len() > sig_ct.max(sig_padded).max(sig_comp_max) {
926            return Err(FalconError::FormatError);
927        }
928
929        let tmp_len = falcon_api::falcon_tmpsize_verify(logn);
930        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
931
932        let mut hd = InnerShake256Context::new();
933        let r = falcon_api::falcon_verify_start(&mut hd, sig);
934        if r < 0 {
935            return Err(translate_error(r));
936        }
937        domain.inject(&mut hd, message);
938        let rc = falcon_api::falcon_verify_finish(sig, 0, pubkey, &mut hd, &mut tmp);
939        if rc != 0 {
940            return Err(translate_error(rc));
941        }
942        Ok(())
943    }
944
945    /// Get the raw signature bytes.
946    pub fn to_bytes(&self) -> &[u8] {
947        &self.data
948    }
949
950    /// Consume and return the owned byte vector.
951    pub fn into_bytes(self) -> Vec<u8> {
952        self.data
953    }
954
955    /// Signature length in bytes.
956    pub fn len(&self) -> usize {
957        self.data.len()
958    }
959
960    /// Returns `true` if the signature byte vector is empty.
961    pub fn is_empty(&self) -> bool {
962        self.data.is_empty()
963    }
964}
965
966// ======================================================================
967// Expanded key (amortized multi-signature)
968// ======================================================================
969
970/// A precomputed Falcon signing tree for fast repeated signing.
971///
972/// Expanding a private key takes ~2.5× longer than a single sign operation,
973/// but each subsequent `sign`/`sign_deterministic` call is ~1.5× faster
974/// (no re-expansion). Use this when signing many messages with the same key.
975///
976/// The expanded key bytes are **automatically zeroized on drop**.
977///
978/// # Example
979/// ```rust
980/// use falcon::prelude::*;
981///
982/// let kp = FnDsaKeyPair::generate(9).unwrap();
983/// let ek = kp.expand().unwrap();
984///
985/// let sig = ek.sign(b"message", &DomainSeparation::None).unwrap();
986/// FnDsaSignature::verify(sig.to_bytes(), ek.public_key(), b"message",
987///     &DomainSeparation::None).unwrap();
988/// ```
989#[derive(Clone)]
990pub struct FnDsaExpandedKey {
991    /// Expanded key bytes — contains the Falcon LDL tree; zeroized on drop.
992    expanded: Zeroizing<Vec<u8>>,
993    /// Public key bytes.
994    pubkey: Vec<u8>,
995    /// log2 lattice dimension.
996    logn: u32,
997}
998
999impl fmt::Debug for FnDsaExpandedKey {
1000    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1001        f.debug_struct("FnDsaExpandedKey")
1002            .field("expanded", &"[REDACTED]")
1003            .field("pubkey_len", &self.pubkey.len())
1004            .field("logn", &self.logn)
1005            .finish()
1006    }
1007}
1008
1009impl FnDsaKeyPair {
1010    /// Expand the private key into a precomputed signing tree.
1011    ///
1012    /// The resulting [`FnDsaExpandedKey`] is ~1.5× faster per sign operation
1013    /// at the cost of a one-time expansion (~2.5× a single sign).
1014    pub fn expand(&self) -> Result<FnDsaExpandedKey, FalconError> {
1015        let logn = self.logn;
1016        let ek_len = falcon_api::falcon_expandedkey_size(logn);
1017        let tmp_len = falcon_api::falcon_tmpsize_expandpriv(logn);
1018        let mut expanded = vec![0u8; ek_len];
1019        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
1020        let rc = falcon_api::falcon_expand_privkey(&mut expanded, &self.privkey, &mut tmp);
1021        if rc != 0 {
1022            return Err(translate_error(rc));
1023        }
1024        Ok(FnDsaExpandedKey {
1025            expanded: Zeroizing::new(expanded),
1026            pubkey: self.pubkey.clone(),
1027            logn,
1028        })
1029    }
1030}
1031
1032impl FnDsaExpandedKey {
1033    /// Sign a message using OS entropy.
1034    pub fn sign(
1035        &self,
1036        message: &[u8],
1037        domain: &DomainSeparation<'_>,
1038    ) -> Result<FnDsaSignature, FalconError> {
1039        domain.validate()?;
1040        let logn = self.logn;
1041        let sig_max = falcon_api::falcon_sig_ct_size(logn);
1042        let tmp_len = falcon_api::falcon_tmpsize_signtree(logn);
1043        let mut sig = vec![0u8; sig_max];
1044        let mut sig_len = sig_max;
1045        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
1046
1047        let mut seed = Zeroizing::new([0u8; 48]);
1048        if !get_seed(&mut *seed) {
1049            return Err(FalconError::RandomError);
1050        }
1051        let mut rng = InnerShake256Context::new();
1052        i_shake256_init(&mut rng);
1053        i_shake256_inject(&mut rng, &*seed);
1054        i_shake256_flip(&mut rng);
1055
1056        let mut hd = InnerShake256Context::new();
1057        let mut nonce = [0u8; 40];
1058        falcon_api::falcon_sign_start(&mut rng, &mut nonce, &mut hd);
1059        domain.inject(&mut hd, message);
1060
1061        let rc = falcon_api::falcon_sign_tree_finish(
1062            &mut rng,
1063            &mut sig,
1064            &mut sig_len,
1065            falcon_api::FALCON_SIG_CT,
1066            &self.expanded,
1067            &mut hd,
1068            &nonce,
1069            &mut tmp,
1070        );
1071        if rc != 0 {
1072            return Err(translate_error(rc));
1073        }
1074        sig.truncate(sig_len);
1075        Ok(FnDsaSignature { data: sig })
1076    }
1077
1078    /// Sign a message deterministically from a seed (for testing / `no_std`).
1079    pub fn sign_deterministic(
1080        &self,
1081        message: &[u8],
1082        sign_seed: &[u8],
1083        domain: &DomainSeparation<'_>,
1084    ) -> Result<FnDsaSignature, FalconError> {
1085        domain.validate()?;
1086        let logn = self.logn;
1087        let sig_max = falcon_api::falcon_sig_ct_size(logn);
1088        let tmp_len = falcon_api::falcon_tmpsize_signtree(logn);
1089        let mut sig = vec![0u8; sig_max];
1090        let mut sig_len = sig_max;
1091        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
1092
1093        let mut rng = InnerShake256Context::new();
1094        i_shake256_init(&mut rng);
1095        i_shake256_inject(&mut rng, sign_seed);
1096        i_shake256_flip(&mut rng);
1097
1098        let mut hd = InnerShake256Context::new();
1099        let mut nonce = [0u8; 40];
1100        falcon_api::falcon_sign_start(&mut rng, &mut nonce, &mut hd);
1101        domain.inject(&mut hd, message);
1102
1103        let rc = falcon_api::falcon_sign_tree_finish(
1104            &mut rng,
1105            &mut sig,
1106            &mut sig_len,
1107            falcon_api::FALCON_SIG_CT,
1108            &self.expanded,
1109            &mut hd,
1110            &nonce,
1111            &mut tmp,
1112        );
1113        if rc != 0 {
1114            return Err(translate_error(rc));
1115        }
1116        sig.truncate(sig_len);
1117        Ok(FnDsaSignature { data: sig })
1118    }
1119
1120    /// The public key corresponding to this expanded key.
1121    pub fn public_key(&self) -> &[u8] {
1122        &self.pubkey
1123    }
1124
1125    /// The `logn` parameter (9 = FN-DSA-512, 10 = FN-DSA-1024).
1126    pub fn logn(&self) -> u32 {
1127        self.logn
1128    }
1129}