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    /// Generate a new FN-DSA key pair using OS entropy.
574    ///
575    /// * `logn` — 9 for FN-DSA-512, 10 for FN-DSA-1024.
576    pub fn generate(logn: u32) -> Result<Self, FalconError> {
577        if logn < 1 || logn > 10 {
578            return Err(FalconError::BadArgument);
579        }
580        let mut seed = Zeroizing::new([0u8; 48]);
581        if !get_seed(&mut *seed) {
582            return Err(FalconError::RandomError);
583        }
584        Self::generate_deterministic(&seed[..], logn)
585    }
586
587    /// Generate a key pair deterministically from `seed`.
588    pub fn generate_deterministic(seed: &[u8], logn: u32) -> Result<Self, FalconError> {
589        if logn < 1 || logn > 10 {
590            return Err(FalconError::BadArgument);
591        }
592        let sk_len = falcon_api::falcon_privkey_size(logn);
593        let pk_len = falcon_api::falcon_pubkey_size(logn);
594        let tmp_len = falcon_api::falcon_tmpsize_keygen(logn);
595
596        let mut rng = InnerShake256Context::new();
597        i_shake256_init(&mut rng);
598        i_shake256_inject(&mut rng, seed);
599        i_shake256_flip(&mut rng);
600
601        let mut privkey = vec![0u8; sk_len];
602        let mut pubkey = vec![0u8; pk_len];
603        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
604
605        let rc = falcon_api::falcon_keygen_make(
606            &mut rng,
607            logn,
608            &mut privkey,
609            Some(&mut pubkey),
610            &mut tmp,
611        );
612        if rc != 0 {
613            return Err(translate_error(rc));
614        }
615        Ok(FnDsaKeyPair {
616            privkey: Zeroizing::new(privkey),
617            pubkey,
618            logn,
619        })
620    }
621
622    /// Reconstruct from previously exported private + public key bytes.
623    pub fn from_keys(privkey: &[u8], pubkey: &[u8]) -> Result<Self, FalconError> {
624        if privkey.is_empty() || pubkey.is_empty() {
625            return Err(FalconError::FormatError);
626        }
627        let sk_logn = falcon_api::falcon_get_logn(privkey);
628        let pk_logn = falcon_api::falcon_get_logn(pubkey);
629        if sk_logn < 0 || pk_logn < 0 {
630            return Err(FalconError::FormatError);
631        }
632        if (privkey[0] & 0xF0) != 0x50 {
633            return Err(FalconError::FormatError);
634        }
635        if (pubkey[0] & 0xF0) != 0x00 {
636            return Err(FalconError::FormatError);
637        }
638        let logn = (sk_logn & 0x0F) as u32;
639        if logn != (pk_logn & 0x0F) as u32 {
640            return Err(FalconError::FormatError);
641        }
642        if privkey.len() != falcon_api::falcon_privkey_size(logn) {
643            return Err(FalconError::FormatError);
644        }
645        if pubkey.len() != falcon_api::falcon_pubkey_size(logn) {
646            return Err(FalconError::FormatError);
647        }
648        Ok(FnDsaKeyPair {
649            privkey: Zeroizing::new(privkey.to_vec()),
650            pubkey: pubkey.to_vec(),
651            logn,
652        })
653    }
654
655    /// Reconstruct from a private key only (public key is recomputed).
656    pub fn from_private_key(privkey: &[u8]) -> Result<Self, FalconError> {
657        if privkey.is_empty() {
658            return Err(FalconError::FormatError);
659        }
660        if (privkey[0] & 0xF0) != 0x50 {
661            return Err(FalconError::FormatError);
662        }
663        let logn_val = falcon_api::falcon_get_logn(privkey);
664        if logn_val < 0 {
665            return Err(FalconError::FormatError);
666        }
667        let logn = logn_val as u32;
668        if privkey.len() != falcon_api::falcon_privkey_size(logn) {
669            return Err(FalconError::FormatError);
670        }
671        let pk_len = falcon_api::falcon_pubkey_size(logn);
672        let tmp_len = falcon_api::falcon_tmpsize_makepub(logn);
673        let mut pubkey = vec![0u8; pk_len];
674        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
675        let rc = falcon_api::falcon_make_public(&mut pubkey, privkey, &mut tmp);
676        if rc != 0 {
677            return Err(translate_error(rc));
678        }
679        Ok(FnDsaKeyPair {
680            privkey: Zeroizing::new(privkey.to_vec()),
681            pubkey,
682            logn,
683        })
684    }
685
686    /// Compute the public key bytes from a private key without creating a key pair.
687    pub fn public_key_from_private(privkey: &[u8]) -> Result<Vec<u8>, FalconError> {
688        Ok(Self::from_private_key(privkey)?.pubkey)
689    }
690
691    /// Sign `message` using FIPS 206 domain separation.
692    ///
693    /// Supports both pure FN-DSA ([`DomainSeparation::None`] /
694    /// [`DomainSeparation::Context`]) and HashFN-DSA
695    /// ([`DomainSeparation::Prehashed`]).
696    ///
697    /// # Errors
698    ///
699    /// * [`FalconError::BadArgument`] — context string > 255 bytes.
700    /// * [`FalconError::RandomError`] — OS RNG unavailable.
701    pub fn sign(
702        &self,
703        message: &[u8],
704        domain: &DomainSeparation,
705    ) -> Result<FnDsaSignature, FalconError> {
706        domain.validate()?;
707
708        let sig_max = falcon_api::falcon_sig_ct_size(self.logn);
709        let tmp_len = falcon_api::falcon_tmpsize_signdyn(self.logn);
710
711        let mut seed = Zeroizing::new([0u8; 48]);
712        if !get_seed(&mut *seed) {
713            return Err(FalconError::RandomError);
714        }
715        let mut rng = InnerShake256Context::new();
716        i_shake256_init(&mut rng);
717        i_shake256_inject(&mut rng, &*seed);
718        i_shake256_flip(&mut rng);
719
720        let mut sig = vec![0u8; sig_max];
721        let mut sig_len = sig_max;
722        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
723
724        let mut nonce = [0u8; 40];
725        falcon_api::shake256_extract(&mut rng, &mut nonce);
726        let mut hd = InnerShake256Context::new();
727        falcon_api::shake256_init(&mut hd);
728        falcon_api::shake256_inject(&mut hd, &nonce);
729        domain.inject(&mut hd, message);
730
731        let rc = falcon_api::falcon_sign_dyn_finish(
732            &mut rng,
733            &mut sig,
734            &mut sig_len,
735            falcon_api::FALCON_SIG_CT,
736            &self.privkey,
737            &mut hd,
738            &nonce,
739            &mut tmp,
740        );
741        if rc != 0 {
742            return Err(translate_error(rc));
743        }
744        sig.truncate(sig_len);
745        Ok(FnDsaSignature { data: sig })
746    }
747
748    /// Sign with a deterministic seed (testing / reproducibility).
749    ///
750    /// The same `(key, message, seed, domain)` tuple always produces
751    /// the same signature.
752    ///
753    /// # Errors
754    ///
755    /// * [`FalconError::BadArgument`] — context string > 255 bytes.
756    pub fn sign_deterministic(
757        &self,
758        message: &[u8],
759        seed: &[u8],
760        domain: &DomainSeparation,
761    ) -> Result<FnDsaSignature, FalconError> {
762        domain.validate()?;
763
764        let sig_max = falcon_api::falcon_sig_ct_size(self.logn);
765        let tmp_len = falcon_api::falcon_tmpsize_signdyn(self.logn);
766
767        let mut rng = InnerShake256Context::new();
768        i_shake256_init(&mut rng);
769        i_shake256_inject(&mut rng, seed);
770        i_shake256_flip(&mut rng);
771
772        let mut sig = vec![0u8; sig_max];
773        let mut sig_len = sig_max;
774        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
775
776        let mut nonce = [0u8; 40];
777        falcon_api::shake256_extract(&mut rng, &mut nonce);
778        let mut hd = InnerShake256Context::new();
779        falcon_api::shake256_init(&mut hd);
780        falcon_api::shake256_inject(&mut hd, &nonce);
781        domain.inject(&mut hd, message);
782
783        let rc = falcon_api::falcon_sign_dyn_finish(
784            &mut rng,
785            &mut sig,
786            &mut sig_len,
787            falcon_api::FALCON_SIG_CT,
788            &self.privkey,
789            &mut hd,
790            &nonce,
791            &mut tmp,
792        );
793        if rc != 0 {
794            return Err(translate_error(rc));
795        }
796        sig.truncate(sig_len);
797        Ok(FnDsaSignature { data: sig })
798    }
799
800    /// Get the encoded public key bytes.
801    pub fn public_key(&self) -> &[u8] {
802        &self.pubkey
803    }
804
805    /// Get the encoded private key bytes.
806    ///
807    /// ⚠️ **Secret material** — handle with care.
808    pub fn private_key(&self) -> &[u8] {
809        &self.privkey
810    }
811
812    /// Get the FN-DSA degree parameter.
813    ///
814    /// Returns 9 for FN-DSA-512, 10 for FN-DSA-1024.
815    pub fn logn(&self) -> u32 {
816        self.logn
817    }
818
819    /// Get the security variant name.
820    pub fn variant_name(&self) -> &'static str {
821        match self.logn {
822            9 => "FN-DSA-512",
823            10 => "FN-DSA-1024",
824            n => match n {
825                1 => "FN-DSA-2",
826                2 => "FN-DSA-4",
827                3 => "FN-DSA-8",
828                4 => "FN-DSA-16",
829                5 => "FN-DSA-32",
830                6 => "FN-DSA-64",
831                7 => "FN-DSA-128",
832                8 => "FN-DSA-256",
833                _ => "FN-DSA-unknown",
834            },
835        }
836    }
837}
838
839// ======================================================================
840// Signature
841// ======================================================================
842
843/// An FN-DSA / HashFN-DSA digital signature.
844///
845/// Signature bytes are in constant-time (CT) format:
846/// 666 bytes for FN-DSA-512, 1280 bytes for FN-DSA-1024.
847#[derive(Debug, Clone)]
848#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
849pub struct FnDsaSignature {
850    data: Vec<u8>,
851}
852
853/// Type alias for backward compatibility.
854pub type FalconSignature = FnDsaSignature;
855
856impl FnDsaSignature {
857    /// Deserialize a signature from raw bytes.
858    ///
859    /// Returns `Err(FalconError::FormatError)` if the bytes are too short
860    /// (minimum 41 bytes: 1 header + 40 nonce) or the header byte is invalid.
861    pub fn from_bytes(data: Vec<u8>) -> Result<Self, FalconError> {
862        if data.len() < 41 {
863            return Err(FalconError::FormatError);
864        }
865        // Header high nibble must be 0x30 (compressed/padded) or 0x50 (CT).
866        let hdr = data[0] & 0xF0;
867        if hdr != 0x30 && hdr != 0x50 {
868            return Err(FalconError::FormatError);
869        }
870        let logn = data[0] & 0x0F;
871        if logn < 1 || logn > 10 {
872            return Err(FalconError::FormatError);
873        }
874        Ok(FnDsaSignature { data })
875    }
876
877    /// Verify a signature against `pubkey` and `message`.
878    ///
879    /// The `domain` must exactly match what was used during signing
880    /// (same variant, same context string, same pre-hash algorithm).
881    ///
882    /// Supports pure FN-DSA and HashFN-DSA transparently.
883    ///
884    /// # Errors
885    ///
886    /// * [`FalconError::BadArgument`]  — context string > 255 bytes.
887    /// * [`FalconError::BadSignature`] — signature is invalid.
888    /// * [`FalconError::FormatError`]  — malformed key or signature.
889    pub fn verify(
890        sig: &[u8],
891        pubkey: &[u8],
892        message: &[u8],
893        domain: &DomainSeparation,
894    ) -> Result<(), FalconError> {
895        domain.validate()?;
896
897        if pubkey.is_empty() || sig.is_empty() {
898            return Err(FalconError::FormatError);
899        }
900        let logn_val = falcon_api::falcon_get_logn(pubkey);
901        if logn_val < 0 {
902            return Err(FalconError::FormatError);
903        }
904        let logn = logn_val as u32;
905
906        // Validate signature length against expected sizes for this logn.
907        let sig_ct = falcon_api::falcon_sig_ct_size(logn);
908        let sig_padded = falcon_api::falcon_sig_padded_size(logn);
909        let sig_comp_max = falcon_api::falcon_sig_compressed_maxsize(logn);
910        if sig.len() < 41 || sig.len() > sig_ct.max(sig_padded).max(sig_comp_max) {
911            return Err(FalconError::FormatError);
912        }
913
914        let tmp_len = falcon_api::falcon_tmpsize_verify(logn);
915        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
916
917        let mut hd = InnerShake256Context::new();
918        let r = falcon_api::falcon_verify_start(&mut hd, sig);
919        if r < 0 {
920            return Err(translate_error(r));
921        }
922        domain.inject(&mut hd, message);
923        let rc = falcon_api::falcon_verify_finish(sig, 0, pubkey, &mut hd, &mut tmp);
924        if rc != 0 {
925            return Err(translate_error(rc));
926        }
927        Ok(())
928    }
929
930    /// Get the raw signature bytes.
931    pub fn to_bytes(&self) -> &[u8] {
932        &self.data
933    }
934
935    /// Consume and return the owned byte vector.
936    pub fn into_bytes(self) -> Vec<u8> {
937        self.data
938    }
939
940    /// Signature length in bytes.
941    pub fn len(&self) -> usize {
942        self.data.len()
943    }
944
945    /// Returns `true` if the signature byte vector is empty.
946    pub fn is_empty(&self) -> bool {
947        self.data.is_empty()
948    }
949}
950
951// ======================================================================
952// Expanded key (amortized multi-signature)
953// ======================================================================
954
955/// A precomputed Falcon signing tree for fast repeated signing.
956///
957/// Expanding a private key takes ~2.5× longer than a single sign operation,
958/// but each subsequent `sign`/`sign_deterministic` call is ~1.5× faster
959/// (no re-expansion). Use this when signing many messages with the same key.
960///
961/// The expanded key bytes are **automatically zeroized on drop**.
962///
963/// # Example
964/// ```rust
965/// use falcon::prelude::*;
966///
967/// let kp = FnDsaKeyPair::generate(9).unwrap();
968/// let ek = kp.expand().unwrap();
969///
970/// let sig = ek.sign(b"message", &DomainSeparation::None).unwrap();
971/// FnDsaSignature::verify(sig.to_bytes(), ek.public_key(), b"message",
972///     &DomainSeparation::None).unwrap();
973/// ```
974#[derive(Clone)]
975pub struct FnDsaExpandedKey {
976    /// Expanded key bytes — contains the Falcon LDL tree; zeroized on drop.
977    expanded: Zeroizing<Vec<u8>>,
978    /// Public key bytes.
979    pubkey: Vec<u8>,
980    /// log2 lattice dimension.
981    logn: u32,
982}
983
984impl fmt::Debug for FnDsaExpandedKey {
985    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
986        f.debug_struct("FnDsaExpandedKey")
987            .field("expanded", &"[REDACTED]")
988            .field("pubkey_len", &self.pubkey.len())
989            .field("logn", &self.logn)
990            .finish()
991    }
992}
993
994impl FnDsaKeyPair {
995    /// Expand the private key into a precomputed signing tree.
996    ///
997    /// The resulting [`FnDsaExpandedKey`] is ~1.5× faster per sign operation
998    /// at the cost of a one-time expansion (~2.5× a single sign).
999    pub fn expand(&self) -> Result<FnDsaExpandedKey, FalconError> {
1000        let logn = self.logn;
1001        let ek_len = falcon_api::falcon_expandedkey_size(logn);
1002        let tmp_len = falcon_api::falcon_tmpsize_expandpriv(logn);
1003        let mut expanded = vec![0u8; ek_len];
1004        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
1005        let rc = falcon_api::falcon_expand_privkey(&mut expanded, &self.privkey, &mut tmp);
1006        if rc != 0 {
1007            return Err(translate_error(rc));
1008        }
1009        Ok(FnDsaExpandedKey {
1010            expanded: Zeroizing::new(expanded),
1011            pubkey: self.pubkey.clone(),
1012            logn,
1013        })
1014    }
1015}
1016
1017impl FnDsaExpandedKey {
1018    /// Sign a message using OS entropy.
1019    pub fn sign(
1020        &self,
1021        message: &[u8],
1022        domain: &DomainSeparation<'_>,
1023    ) -> Result<FnDsaSignature, FalconError> {
1024        domain.validate()?;
1025        let logn = self.logn;
1026        let sig_max = falcon_api::falcon_sig_ct_size(logn);
1027        let tmp_len = falcon_api::falcon_tmpsize_signtree(logn);
1028        let mut sig = vec![0u8; sig_max];
1029        let mut sig_len = sig_max;
1030        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
1031
1032        let mut seed = Zeroizing::new([0u8; 48]);
1033        if !get_seed(&mut *seed) {
1034            return Err(FalconError::RandomError);
1035        }
1036        let mut rng = InnerShake256Context::new();
1037        i_shake256_init(&mut rng);
1038        i_shake256_inject(&mut rng, &*seed);
1039        i_shake256_flip(&mut rng);
1040
1041        let mut hd = InnerShake256Context::new();
1042        let mut nonce = [0u8; 40];
1043        falcon_api::falcon_sign_start(&mut rng, &mut nonce, &mut hd);
1044        domain.inject(&mut hd, message);
1045
1046        let rc = falcon_api::falcon_sign_tree_finish(
1047            &mut rng,
1048            &mut sig,
1049            &mut sig_len,
1050            falcon_api::FALCON_SIG_CT,
1051            &self.expanded,
1052            &mut hd,
1053            &nonce,
1054            &mut tmp,
1055        );
1056        if rc != 0 {
1057            return Err(translate_error(rc));
1058        }
1059        sig.truncate(sig_len);
1060        Ok(FnDsaSignature { data: sig })
1061    }
1062
1063    /// Sign a message deterministically from a seed (for testing / `no_std`).
1064    pub fn sign_deterministic(
1065        &self,
1066        message: &[u8],
1067        sign_seed: &[u8],
1068        domain: &DomainSeparation<'_>,
1069    ) -> Result<FnDsaSignature, FalconError> {
1070        let logn = self.logn;
1071        let sig_max = falcon_api::falcon_sig_ct_size(logn);
1072        let tmp_len = falcon_api::falcon_tmpsize_signtree(logn);
1073        let mut sig = vec![0u8; sig_max];
1074        let mut sig_len = sig_max;
1075        let mut tmp = Zeroizing::new(vec![0u8; tmp_len]);
1076
1077        let mut rng = InnerShake256Context::new();
1078        i_shake256_init(&mut rng);
1079        i_shake256_inject(&mut rng, sign_seed);
1080        i_shake256_flip(&mut rng);
1081
1082        let mut hd = InnerShake256Context::new();
1083        let mut nonce = [0u8; 40];
1084        falcon_api::falcon_sign_start(&mut rng, &mut nonce, &mut hd);
1085        domain.inject(&mut hd, message);
1086
1087        let rc = falcon_api::falcon_sign_tree_finish(
1088            &mut rng,
1089            &mut sig,
1090            &mut sig_len,
1091            falcon_api::FALCON_SIG_CT,
1092            &self.expanded,
1093            &mut hd,
1094            &nonce,
1095            &mut tmp,
1096        );
1097        if rc != 0 {
1098            return Err(translate_error(rc));
1099        }
1100        sig.truncate(sig_len);
1101        Ok(FnDsaSignature { data: sig })
1102    }
1103
1104    /// The public key corresponding to this expanded key.
1105    pub fn public_key(&self) -> &[u8] {
1106        &self.pubkey
1107    }
1108
1109    /// The `logn` parameter (9 = FN-DSA-512, 10 = FN-DSA-1024).
1110    pub fn logn(&self) -> u32 {
1111        self.logn
1112    }
1113}