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