Skip to main content

pq_msg/signatures/
keypair.rs

1use fn_dsa::{
2    DOMAIN_NONE, FN_DSA_LOGN_1024, HASH_ID_RAW, KeyPairGenerator, KeyPairGeneratorStandard,
3    SigningKey, SigningKeyStandard, VerifyingKey, VerifyingKeyStandard, sign_key_size,
4    signature_size, vrfy_key_size,
5};
6use rand_core::OsRng;
7use zeroize::Zeroizing;
8
9use crate::errors::CryptoError;
10
11/// Size of an FN-DSA-1024 public (verifying) key in bytes
12pub const PUBLIC_KEY_BYTES: usize = vrfy_key_size(FN_DSA_LOGN_1024);
13/// Size of an FN-DSA-1024 secret (signing) key in bytes
14pub const SECRET_KEY_BYTES: usize = sign_key_size(FN_DSA_LOGN_1024);
15/// Size of an FN-DSA-1024 signature in bytes
16pub const SIGNATURE_BYTES: usize = signature_size(FN_DSA_LOGN_1024);
17
18/// Type alias for a public key byte array
19pub type PublicKeyBytes = [u8; PUBLIC_KEY_BYTES];
20/// Type alias for a secret key byte array
21pub type SecretKeyBytes = [u8; SECRET_KEY_BYTES];
22/// Type alias for a signature byte array
23pub type SignatureBytes = [u8; SIGNATURE_BYTES];
24
25/// Common operations for verifying signatures
26///
27/// This trait provides methods for accessing public keys and verifying
28/// detached signatures with FN-DSA (Falcon).
29pub trait ViewOperations {
30    /// Gets a reference to the public key as a byte array
31    fn pub_key_bytes(&self) -> &PublicKeyBytes;
32
33    /// Verifies if a detached signature is valid for the provided message
34    ///
35    /// # Arguments
36    /// * `msg` - The message that was signed
37    /// * `sig` - The signature to verify
38    ///
39    /// # Returns
40    /// True if the signature is valid for this public key, false otherwise
41    fn verify(&self, msg: &[u8], sig: &[u8]) -> bool {
42        VerifyingKeyStandard::decode(self.pub_key_bytes())
43            .is_some_and(|vk| vk.verify(sig, &DOMAIN_NONE, &HASH_ID_RAW, msg))
44    }
45}
46
47/// A key pair used only for signature verification
48///
49/// This struct represents a post-quantum cryptography key pair used only
50/// for verifying signatures. It contains just the public key component.
51#[derive(Clone)]
52pub struct VerifierPair {
53    pub_key: PublicKeyBytes,
54}
55
56impl VerifierPair {
57    /// Creates a new verifier pair from public key bytes
58    ///
59    /// # Arguments
60    /// * `pub_key` - The public key bytes
61    ///
62    /// # Returns
63    /// - `Result<VerifierPair, CryptoError>`: The constructed verifier, or an error
64    ///   if the key has the wrong length or does not decode
65    pub fn new(pub_key: &[u8]) -> Result<Self, CryptoError> {
66        let pub_key: PublicKeyBytes = pub_key
67            .try_into()
68            .map_err(|_| CryptoError::IncongruentLength(PUBLIC_KEY_BYTES, pub_key.len()))?;
69        VerifyingKeyStandard::decode(&pub_key).ok_or(CryptoError::InvalidKey)?;
70        Ok(Self { pub_key })
71    }
72
73    /// Creates a new verifier pair from public key bytes (alias for new)
74    ///
75    /// # Arguments
76    /// * `pub_key` - The public key bytes
77    ///
78    /// # Returns
79    /// - `Result<VerifierPair, CryptoError>`: The constructed verifier or an error
80    pub fn from_bytes(pub_key: &[u8]) -> Result<Self, CryptoError> {
81        Self::new(pub_key)
82    }
83
84    /// Converts the public key to bytes
85    ///
86    /// # Returns
87    /// The public key bytes
88    pub fn to_bytes(&self) -> PublicKeyBytes {
89        self.pub_key
90    }
91}
92
93impl ViewOperations for VerifierPair {
94    fn pub_key_bytes(&self) -> &PublicKeyBytes {
95        &self.pub_key
96    }
97}
98
99/// A complete key pair used for both signing and verification
100///
101/// This struct represents a post-quantum cryptography key pair used for
102/// creating digital signatures and verifying them. It contains both
103/// public and secret key components using FN-DSA-1024 (Falcon).
104/// The secret key is wiped from memory when the pair (or any clone) is dropped.
105pub struct SignerPair {
106    pub_key: PublicKeyBytes,
107    sec_key: Zeroizing<SecretKeyBytes>,
108    /// The decoded secret key, kept so signing doesn't re-decode it every time
109    signing_key: Box<SigningKeyStandard>,
110}
111
112impl SignerPair {
113    /// Creates a new random signer pair
114    ///
115    /// # Returns
116    /// A new SignerPair with generated public and secret keys
117    pub fn create() -> Self {
118        let mut pub_key = [0u8; PUBLIC_KEY_BYTES];
119        let mut sec_key = Zeroizing::new([0u8; SECRET_KEY_BYTES]);
120        KeyPairGeneratorStandard::default().keygen(
121            FN_DSA_LOGN_1024,
122            &mut OsRng,
123            &mut sec_key[..],
124            &mut pub_key,
125        );
126        let signing_key = decode_signing_key(&sec_key).expect("freshly generated key decodes");
127        Self {
128            pub_key,
129            sec_key,
130            signing_key,
131        }
132    }
133
134    /// Signs a message using this pair's secret key
135    ///
136    /// # Arguments
137    /// * `msg` - The message to sign
138    ///
139    /// # Returns
140    /// - `Result<SignatureBytes, CryptoError>`: A detached signature, or an error
141    ///   if the secret key is invalid
142    pub fn sign(&mut self, msg: &[u8]) -> Result<SignatureBytes, CryptoError> {
143        let mut sig = [0u8; SIGNATURE_BYTES];
144        self.signing_key
145            .sign(&mut OsRng, &DOMAIN_NONE, &HASH_ID_RAW, msg, &mut sig)
146            .ok_or(CryptoError::InvalidKey)?;
147        Ok(sig)
148    }
149
150    /// Creates a signer pair from separate public and secret key bytes
151    ///
152    /// # Arguments
153    /// * `pub_key` - The public key bytes
154    /// * `sec_key` - The secret key bytes
155    ///
156    /// # Returns
157    /// - `Result<SignerPair, CryptoError>`: The constructed signer pair, or an error if
158    ///   the secret key does not decode or the public key does not belong to it
159    pub fn from_bytes(pub_key: &[u8], sec_key: &[u8]) -> Result<Self, CryptoError> {
160        let sec_key: Zeroizing<SecretKeyBytes> = Zeroizing::new(
161            sec_key
162                .try_into()
163                .map_err(|_| CryptoError::IncongruentLength(SECRET_KEY_BYTES, sec_key.len()))?,
164        );
165        let signing_key = decode_signing_key(&sec_key).ok_or(CryptoError::InvalidKey)?;
166        let mut derived = [0u8; PUBLIC_KEY_BYTES];
167        signing_key.to_verifying_key(&mut derived);
168        if derived[..] != *pub_key {
169            return Err(CryptoError::InvalidKey);
170        }
171        Ok(Self {
172            pub_key: derived,
173            sec_key,
174            signing_key,
175        })
176    }
177
178    /// Converts the key pair to raw byte arrays
179    ///
180    /// # Returns
181    /// A tuple containing the public key and the secret key (wiped when dropped)
182    pub fn to_bytes(&self) -> (PublicKeyBytes, Zeroizing<SecretKeyBytes>) {
183        (self.pub_key, self.sec_key.clone())
184    }
185
186    /// Converts the key pair to a single byte vector with public key followed by secret key
187    ///
188    /// # Returns
189    /// A vector containing the concatenated public and secret key bytes (wiped when dropped)
190    pub fn to_bytes_uniform(&self) -> Zeroizing<Vec<u8>> {
191        Zeroizing::new([&self.pub_key[..], &self.sec_key[..]].concat())
192    }
193
194    /// Creates a signer pair from a single byte slice containing both public and secret keys
195    ///
196    /// # Arguments
197    /// * `bytes` - The concatenated public and secret key bytes
198    ///
199    /// # Returns
200    /// - `Result<SignerPair, CryptoError>`: The constructed signer pair or an error
201    pub fn from_bytes_uniform(bytes: &[u8]) -> Result<Self, CryptoError> {
202        if bytes.len() != PUBLIC_KEY_BYTES + SECRET_KEY_BYTES {
203            return Err(CryptoError::IncongruentLength(
204                PUBLIC_KEY_BYTES + SECRET_KEY_BYTES,
205                bytes.len(),
206            ));
207        }
208        let (pub_key, sec_key) = bytes.split_at(PUBLIC_KEY_BYTES);
209        Self::from_bytes(pub_key, sec_key)
210    }
211}
212
213impl Clone for SignerPair {
214    fn clone(&self) -> Self {
215        Self {
216            pub_key: self.pub_key,
217            sec_key: self.sec_key.clone(),
218            signing_key: decode_signing_key(&self.sec_key).expect("a decoded key decodes again"),
219        }
220    }
221}
222
223impl ViewOperations for SignerPair {
224    fn pub_key_bytes(&self) -> &PublicKeyBytes {
225        &self.pub_key
226    }
227}
228
229/// Decodes a secret key into its heap-allocated signing form
230///
231/// The decoded key is a ~118 KB struct, and unoptimized builds copy it around enough to
232/// overflow a 1 MB stack (the Windows main-thread default), so decoding runs on a
233/// thread with a large stack whenever threads are available.
234// ponytail: workaround for fn-dsa 0.4's stack-hungry decode; drop the thread once it fits in 1 MB in debug
235fn decode_signing_key(sec_key: &SecretKeyBytes) -> Option<Box<SigningKeyStandard>> {
236    let decode = || SigningKeyStandard::decode(sec_key).map(Box::new);
237    std::thread::scope(|scope| {
238        match std::thread::Builder::new()
239            .stack_size(8 << 20)
240            .spawn_scoped(scope, decode)
241        {
242            Ok(handle) => handle
243                .join()
244                .unwrap_or_else(|panic| std::panic::resume_unwind(panic)),
245            // No threads on this platform (e.g. wasm): decode in place
246            Err(_) => decode(),
247        }
248    })
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn test_signer_sign() {
257        let mut signer = SignerPair::create();
258        let msg = b"Hello, World!";
259        let sig = signer.sign(msg).unwrap();
260
261        assert!(signer.verify(msg, &sig));
262        assert!(!signer.verify(b"Hello, World?", &sig));
263    }
264
265    #[test]
266    fn test_verifier_verify() {
267        let mut signer = SignerPair::create();
268        let msg = b"Hello, World!";
269        let sig = signer.sign(msg).unwrap();
270
271        let verifier = VerifierPair::new(signer.pub_key_bytes()).unwrap();
272        assert!(verifier.verify(msg, &sig));
273
274        let other = VerifierPair::new(SignerPair::create().pub_key_bytes()).unwrap();
275        assert!(!other.verify(msg, &sig));
276    }
277
278    #[test]
279    fn test_signer_bytes() {
280        let signer = SignerPair::create();
281        let (pub_key, sec_key) = signer.to_bytes();
282
283        let mut signer2 = SignerPair::from_bytes(&pub_key, &sec_key[..]).unwrap();
284        assert_eq!(signer.pub_key_bytes(), signer2.pub_key_bytes());
285        assert!(signer.verify(b"msg", &signer2.sign(b"msg").unwrap()));
286
287        let signer3 = SignerPair::from_bytes_uniform(&signer.to_bytes_uniform()).unwrap();
288        assert_eq!(signer.pub_key_bytes(), signer3.pub_key_bytes());
289
290        // A clone signs with the same key
291        assert!(signer.verify(b"msg", &signer3.clone().sign(b"msg").unwrap()));
292    }
293
294    #[test]
295    fn test_invalid_inputs() {
296        let mut signer = SignerPair::create();
297        let (pub_key, sec_key) = signer.to_bytes();
298
299        assert!(matches!(
300            SignerPair::from_bytes_uniform(&[0u8; 10]),
301            Err(CryptoError::IncongruentLength(n, 10)) if n == PUBLIC_KEY_BYTES + SECRET_KEY_BYTES
302        ));
303        assert!(matches!(
304            SignerPair::from_bytes(&pub_key, &sec_key[1..]),
305            Err(CryptoError::IncongruentLength(SECRET_KEY_BYTES, _))
306        ));
307
308        // Right length, but the keys don't decode (corrupted header byte)
309        let mut bad_sec = sec_key.clone();
310        bad_sec[0] ^= 0xFF;
311        assert!(matches!(
312            SignerPair::from_bytes(&pub_key, &bad_sec[..]),
313            Err(CryptoError::InvalidKey)
314        ));
315        let mut bad_pub = pub_key;
316        bad_pub[0] ^= 0xFF;
317        assert!(matches!(
318            VerifierPair::new(&bad_pub),
319            Err(CryptoError::InvalidKey)
320        ));
321
322        // Malformed signatures are rejected, never a panic
323        let sig = signer.sign(b"msg").unwrap();
324        assert!(!signer.verify(b"msg", &sig[..10]));
325        assert!(!signer.verify(b"msg", &[]));
326        let mut flipped = sig;
327        flipped[100] ^= 1;
328        assert!(!signer.verify(b"msg", &flipped));
329    }
330
331    #[test]
332    fn test_works_on_small_stack() {
333        // Regression: the decoded signing key is a ~118 KB struct that unoptimized builds
334        // copy several times, which overflowed the 1 MB Windows main-thread stack
335        let bytes = SignerPair::create().to_bytes_uniform();
336        std::thread::Builder::new()
337            .stack_size(512 << 10)
338            .spawn(move || {
339                let mut signer = SignerPair::from_bytes_uniform(&bytes).unwrap();
340                let sig = signer.sign(b"msg").unwrap();
341                assert!(signer.verify(b"msg", &sig));
342                assert!(signer.clone().verify(b"msg", &sig));
343            })
344            .unwrap()
345            .join()
346            .unwrap();
347    }
348
349    #[test]
350    fn test_signer_rejects_foreign_pubkey() {
351        let signer = SignerPair::create();
352        let other = SignerPair::create();
353        let result = SignerPair::from_bytes(other.pub_key_bytes(), &signer.to_bytes().1[..]);
354        assert!(matches!(result, Err(CryptoError::InvalidKey)));
355    }
356
357    #[test]
358    fn test_verifier_bytes() {
359        let signer = SignerPair::create();
360        let verifier = VerifierPair::new(signer.pub_key_bytes()).unwrap();
361        let pub_key = verifier.to_bytes();
362
363        let verifier2 = VerifierPair::from_bytes(&pub_key).unwrap();
364        assert_eq!(verifier.pub_key_bytes(), verifier2.pub_key_bytes());
365
366        assert!(matches!(
367            VerifierPair::new(&pub_key[1..]),
368            Err(CryptoError::IncongruentLength(PUBLIC_KEY_BYTES, _))
369        ));
370    }
371}