oqs/
sig.rs

1//! Signature API
2//!
3//! See [`Sig`] for the main functionality and [`Algorithm`]
4//! for the list of supported algorithms.
5use alloc::vec::Vec;
6
7use core::ptr::{null, NonNull};
8use core::str::FromStr;
9
10#[cfg(not(feature = "std"))]
11use cstr_core::CStr;
12#[cfg(feature = "std")]
13use std::ffi::CStr;
14
15use crate::ffi::sig as ffi;
16use crate::newtype_buffer;
17use crate::*;
18
19#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
21
22newtype_buffer!(PublicKey, PublicKeyRef);
23newtype_buffer!(SecretKey, SecretKeyRef);
24newtype_buffer!(Signature, SignatureRef);
25
26/// Message type
27pub type Message = [u8];
28/// Context string type
29pub type CtxStr = [u8];
30
31macro_rules! implement_sigs {
32    { $(($feat: literal) $sig: ident: $oqs_id: ident),* $(,)? } => (
33        /// Supported algorithms by liboqs
34        ///
35        /// They may not all be enabled
36        ///
37        /// Optional support for `serde` if that feature is enabled.
38        #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
39        #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40        #[allow(missing_docs)]
41        pub enum Algorithm {
42            $(
43                $sig,
44            )*
45        }
46
47        fn algorithm_to_id(algorithm: Algorithm) -> *const libc::c_char {
48            let id: &[u8] = match algorithm {
49                $(
50                    Algorithm::$sig => &ffi::$oqs_id[..],
51                )*
52            };
53            id as *const _ as *const libc::c_char
54        }
55
56        impl FromStr for Algorithm {
57            type Err = crate::Error;
58
59            fn from_str(s: &str) -> Result<Self> {
60                $(
61                    if s == Algorithm::$sig.name() {
62                        return Ok(Algorithm::$sig);
63                    }
64                )*
65                Err(crate::Error::AlgorithmParsingError)
66            }
67        }
68
69        $(
70            #[cfg(test)]
71            #[allow(non_snake_case)]
72            mod $sig {
73                use super::*;
74
75                #[test]
76                #[cfg(feature = $feat)]
77                fn test_signing() -> Result<()> {
78                    crate::init();
79                    let message = [0u8; 100];
80                    let sig = Sig::new(Algorithm::$sig)?;
81                    let (pk, sk) = sig.keypair()?;
82                    let signature = sig.sign(&message, &sk)?;
83                    sig.verify(&message, &signature, &pk)
84                }
85
86                #[test]
87                #[cfg(feature = $feat)]
88                fn test_signing_with_empty_context_string() -> Result<()> {
89                    crate::init();
90                    let message = [0u8; 100];
91                    let ctx_str: [u8; 0] = [];
92                    let sig = Sig::new(Algorithm::$sig)?;
93                    let (pk, sk) = sig.keypair()?;
94                    let signature = sig.sign_with_ctx_str(&message, &ctx_str, &sk)?;
95                    sig.verify_with_ctx_str(&message, &signature, &ctx_str, &pk)
96                }
97
98                #[test]
99                #[cfg(feature = $feat)]
100                fn test_signing_with_nonempty_context_string() -> Result<()> {
101                    crate::init();
102                    let message = [0u8; 100];
103                    let ctx_str = [0u8; 100];
104                    let sig = Sig::new(Algorithm::$sig)?;
105                    let (pk, sk) = sig.keypair()?;
106                    if sig.has_ctx_str_support() {
107                        let signature = sig.sign_with_ctx_str(&message, &ctx_str, &sk)?;
108                        sig.verify_with_ctx_str(&message, &signature, &ctx_str, &pk)
109                    } else {
110                        let sig_result = sig.sign_with_ctx_str(&message, &ctx_str, &sk);
111                        // Expect a generic error
112                        let sig_result: Result<()> = match sig_result {
113                            Err(Error::Error) => Ok(()),
114                            Ok(_) => Err(Error::Error),
115                            Err(e) => Err(e)
116                        };
117                        if sig_result.is_ok() {
118                            // get a valid signature with which to test verify
119                            let signature = sig.sign(&message, &sk)?;
120                            // Expect a generic error
121                            match sig.verify_with_ctx_str(&message, &signature, &ctx_str, &pk) {
122                                Err(Error::Error) => Ok(()),
123                                Ok(_) => Err(Error::Error),
124                                Err(e) => Err(e)
125
126                            }
127                        } else {
128                            sig_result
129                        }
130                    }
131                }
132
133                #[test]
134                fn test_enabled() {
135                    crate::init();
136                    if cfg!(feature = $feat) {
137                        assert!(Algorithm::$sig.is_enabled());
138                    } else {
139                        assert!(!Algorithm::$sig.is_enabled())
140                    }
141                }
142
143                #[test]
144                fn test_name() {
145                    let algo = Algorithm::$sig;
146                    // Just make sure the name impl does not panic or crash.
147                    let name = algo.name();
148
149                    #[cfg(feature = "std")]
150                    assert_eq!(name, algo.to_string());
151
152                    // ... And actually contains something.
153                    assert!(!name.is_empty());
154                }
155
156                #[test]
157                fn test_get_algorithm_back() {
158                    let algorithm = Algorithm::$sig;
159                    if algorithm.is_enabled() {
160                        let sig = Sig::new(algorithm).unwrap();
161                        assert_eq!(algorithm, sig.algorithm());
162                    }
163                }
164
165                #[test]
166                fn test_version() {
167                    if let Ok(sig) = Sig::new(Algorithm::$sig) {
168                        // Just make sure the version can be called without panic
169                        let version = sig.version();
170                        // ... And actually contains something.
171                        assert!(!version.is_empty());
172                    }
173                }
174
175                #[test]
176                fn test_from_str() {
177                    let algorithm = Algorithm::$sig;
178                    let name = algorithm.name();
179                    let parsed = Algorithm::from_str(name).unwrap();
180                    assert_eq!(algorithm, parsed);}
181            }
182        )*
183    )
184}
185
186implement_sigs! {
187    ("cross") CrossRsdp128Balanced: OQS_SIG_alg_cross_rsdp_128_balanced,
188    ("cross") CrossRsdp128Fast: OQS_SIG_alg_cross_rsdp_128_fast,
189    ("cross") CrossRsdp128Small: OQS_SIG_alg_cross_rsdp_128_small,
190    ("cross") CrossRsdp192Balanced: OQS_SIG_alg_cross_rsdp_192_balanced,
191    ("cross") CrossRsdp192Fast: OQS_SIG_alg_cross_rsdp_192_fast,
192    ("cross") CrossRsdp192Small: OQS_SIG_alg_cross_rsdp_192_small,
193    ("cross") CrossRsdp256Balanced: OQS_SIG_alg_cross_rsdp_256_balanced,
194    ("cross") CrossRsdp256Fast: OQS_SIG_alg_cross_rsdp_256_fast,
195    ("cross") CrossRsdp256Small: OQS_SIG_alg_cross_rsdp_256_small,
196    ("cross") CrossRsdpg128Balanced: OQS_SIG_alg_cross_rsdpg_128_balanced,
197    ("cross") CrossRsdpg128Fast: OQS_SIG_alg_cross_rsdpg_128_fast,
198    ("cross") CrossRsdpg128Small: OQS_SIG_alg_cross_rsdpg_128_small,
199    ("cross") CrossRsdpg192Balanced: OQS_SIG_alg_cross_rsdpg_192_balanced,
200    ("cross") CrossRsdpg192Fast: OQS_SIG_alg_cross_rsdpg_192_fast,
201    ("cross") CrossRsdpg192Small: OQS_SIG_alg_cross_rsdpg_192_small,
202    ("cross") CrossRsdpg256Balanced: OQS_SIG_alg_cross_rsdpg_256_balanced,
203    ("cross") CrossRsdpg256Fast: OQS_SIG_alg_cross_rsdpg_256_fast,
204    ("cross") CrossRsdpg256Small: OQS_SIG_alg_cross_rsdpg_256_small,
205    ("dilithium") Dilithium2: OQS_SIG_alg_dilithium_2,
206    ("dilithium") Dilithium3: OQS_SIG_alg_dilithium_3,
207    ("dilithium") Dilithium5: OQS_SIG_alg_dilithium_5,
208    ("falcon") Falcon512: OQS_SIG_alg_falcon_512,
209    ("falcon") Falcon1024: OQS_SIG_alg_falcon_1024,
210    ("mayo") Mayo1: OQS_SIG_alg_mayo_1,
211    ("mayo") Mayo2: OQS_SIG_alg_mayo_2,
212    ("mayo") Mayo3: OQS_SIG_alg_mayo_3,
213    ("mayo") Mayo5: OQS_SIG_alg_mayo_5,
214    ("ml_dsa") MlDsa44: OQS_SIG_alg_ml_dsa_44,
215    ("ml_dsa") MlDsa65: OQS_SIG_alg_ml_dsa_65,
216    ("ml_dsa") MlDsa87: OQS_SIG_alg_ml_dsa_87,
217    ("sphincs") SphincsSha2128fSimple: OQS_SIG_alg_sphincs_sha2_128f_simple,
218    ("sphincs") SphincsSha2128sSimple: OQS_SIG_alg_sphincs_sha2_128s_simple,
219    ("sphincs") SphincsSha2192fSimple: OQS_SIG_alg_sphincs_sha2_192f_simple,
220    ("sphincs") SphincsSha2192sSimple: OQS_SIG_alg_sphincs_sha2_192s_simple,
221    ("sphincs") SphincsSha2256fSimple: OQS_SIG_alg_sphincs_sha2_256f_simple,
222    ("sphincs") SphincsSha2256sSimple: OQS_SIG_alg_sphincs_sha2_256s_simple,
223    ("sphincs") SphincsShake128fSimple: OQS_SIG_alg_sphincs_shake_128f_simple,
224    ("sphincs") SphincsShake128sSimple: OQS_SIG_alg_sphincs_shake_128s_simple,
225    ("sphincs") SphincsShake192fSimple: OQS_SIG_alg_sphincs_shake_192f_simple,
226    ("sphincs") SphincsShake192sSimple: OQS_SIG_alg_sphincs_shake_192s_simple,
227    ("sphincs") SphincsShake256fSimple: OQS_SIG_alg_sphincs_shake_256f_simple,
228    ("sphincs") SphincsShake256sSimple: OQS_SIG_alg_sphincs_shake_256s_simple,
229    ("uov") UovOvIs: OQS_SIG_alg_uov_ov_Is,
230    ("uov") UovOvIp: OQS_SIG_alg_uov_ov_Ip,
231    ("uov") UovOvIII: OQS_SIG_alg_uov_ov_III,
232    ("uov") UovOvV: OQS_SIG_alg_uov_ov_V,
233    ("uov") UovOvIsPkc: OQS_SIG_alg_uov_ov_Is_pkc,
234    ("uov") UovOvIpPkc: OQS_SIG_alg_uov_ov_Ip_pkc,
235    ("uov") UovOvIIIPkc: OQS_SIG_alg_uov_ov_III_pkc,
236    ("uov") UovOvVPkc: OQS_SIG_alg_uov_ov_V_pkc,
237    ("uov") UovOvIsPkcSkc: OQS_SIG_alg_uov_ov_Is_pkc_skc,
238    ("uov") UovOvIpPkcSkc: OQS_SIG_alg_uov_ov_Ip_pkc_skc,
239    ("uov") UovOvIIIPkcSkc: OQS_SIG_alg_uov_ov_III_pkc_skc,
240    ("uov") UovOvVPkcSkc: OQS_SIG_alg_uov_ov_V_pkc_skc,
241}
242
243impl Algorithm {
244    /// Returns true if this algorithm is enabled in the linked version
245    /// of liboqs
246    pub fn is_enabled(self) -> bool {
247        unsafe { ffi::OQS_SIG_alg_is_enabled(algorithm_to_id(self)) == 1 }
248    }
249
250    /// Provides a pointer to the id of the algorithm
251    ///
252    /// For use with the FFI api methods
253    pub fn to_id(self) -> *const libc::c_char {
254        algorithm_to_id(self)
255    }
256
257    /// Returns the algorithm's name as a static Rust string.
258    ///
259    /// This is the same as the `to_id`, but as a safe Rust string.
260    pub fn name(&self) -> &'static str {
261        // SAFETY: The id from ffi must be a proper null terminated C string
262        let id = unsafe { CStr::from_ptr(self.to_id()) };
263        id.to_str().expect("OQS algorithm names must be UTF-8")
264    }
265}
266
267/// Signature scheme
268///
269/// # Example
270/// ```rust
271/// # if !cfg!(feature = "ml_dsa") { return; }
272/// use oqs;
273/// oqs::init();
274/// let scheme = oqs::sig::Sig::new(oqs::sig::Algorithm::MlDsa44).unwrap();
275/// let message = [0u8; 100];
276/// let (pk, sk) = scheme.keypair().unwrap();
277/// let signature = scheme.sign(&message, &sk).unwrap();
278/// assert!(scheme.verify(&message, &signature, &pk).is_ok());
279/// ```
280pub struct Sig {
281    algorithm: Algorithm,
282    sig: NonNull<ffi::OQS_SIG>,
283}
284
285unsafe impl Sync for Sig {}
286unsafe impl Send for Sig {}
287
288impl Drop for Sig {
289    fn drop(&mut self) {
290        unsafe { ffi::OQS_SIG_free(self.sig.as_ptr()) };
291    }
292}
293
294#[cfg(feature = "std")]
295impl std::fmt::Display for Algorithm {
296    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297        self.name().fmt(f)
298    }
299}
300
301impl core::convert::TryFrom<Algorithm> for Sig {
302    type Error = crate::Error;
303    fn try_from(alg: Algorithm) -> Result<Sig> {
304        Sig::new(alg)
305    }
306}
307
308impl Sig {
309    /// Construct a new algorithm
310    ///
311    /// May fail if the algorithm is not available
312    pub fn new(algorithm: Algorithm) -> Result<Self> {
313        let sig = unsafe { ffi::OQS_SIG_new(algorithm_to_id(algorithm)) };
314        NonNull::new(sig).map_or_else(
315            || Err(Error::AlgorithmDisabled),
316            |sig| Ok(Self { algorithm, sig }),
317        )
318    }
319
320    /// Get the algorithm used by this `Sig`
321    pub fn algorithm(&self) -> Algorithm {
322        self.algorithm
323    }
324
325    /// Get the version of the implementation
326    pub fn version(&self) -> &'static str {
327        let sig = unsafe { self.sig.as_ref() };
328        // SAFETY: The alg_version from ffi must be a proper null terminated C string
329        let cstr = unsafe { CStr::from_ptr(sig.alg_version) };
330        cstr.to_str()
331            .expect("Algorithm version strings must be UTF-8")
332    }
333
334    /// Obtain the claimed nist level
335    pub fn claimed_nist_level(&self) -> u8 {
336        let sig = unsafe { self.sig.as_ref() };
337        sig.claimed_nist_level
338    }
339
340    /// Is this algorithm EUF-CMA?
341    pub fn is_euf_cma(&self) -> bool {
342        let sig = unsafe { self.sig.as_ref() };
343        sig.euf_cma
344    }
345
346    /// Does this algorithm support signing with a context string?
347    pub fn has_ctx_str_support(&self) -> bool {
348        let sig = unsafe { self.sig.as_ref() };
349        sig.sig_with_ctx_support
350    }
351
352    /// Length of the public key
353    pub fn length_public_key(&self) -> usize {
354        let sig = unsafe { self.sig.as_ref() };
355        sig.length_public_key
356    }
357
358    /// Length of the secret key
359    pub fn length_secret_key(&self) -> usize {
360        let sig = unsafe { self.sig.as_ref() };
361        sig.length_secret_key
362    }
363
364    /// Maximum length of a signature
365    pub fn length_signature(&self) -> usize {
366        let sig = unsafe { self.sig.as_ref() };
367        sig.length_signature
368    }
369
370    /// Construct a secret key object from bytes
371    pub fn secret_key_from_bytes<'a>(&self, buf: &'a [u8]) -> Option<SecretKeyRef<'a>> {
372        if buf.len() != self.length_secret_key() {
373            None
374        } else {
375            Some(SecretKeyRef::new(buf))
376        }
377    }
378
379    /// Construct a public key object from bytes
380    pub fn public_key_from_bytes<'a>(&self, buf: &'a [u8]) -> Option<PublicKeyRef<'a>> {
381        if buf.len() != self.length_public_key() {
382            None
383        } else {
384            Some(PublicKeyRef::new(buf))
385        }
386    }
387
388    /// Construct a signature object from bytes
389    pub fn signature_from_bytes<'a>(&self, buf: &'a [u8]) -> Option<SignatureRef<'a>> {
390        if buf.len() > self.length_signature() {
391            None
392        } else {
393            Some(SignatureRef::new(buf))
394        }
395    }
396
397    /// Generate a new keypair
398    pub fn keypair(&self) -> Result<(PublicKey, SecretKey)> {
399        let sig = unsafe { self.sig.as_ref() };
400        let func = sig.keypair.unwrap();
401        let mut pk = PublicKey {
402            bytes: Vec::with_capacity(sig.length_public_key),
403        };
404        let mut sk = SecretKey {
405            bytes: Vec::with_capacity(sig.length_secret_key),
406        };
407        let status = unsafe { func(pk.bytes.as_mut_ptr(), sk.bytes.as_mut_ptr()) };
408        // update the lengths of the vecs
409        unsafe {
410            pk.bytes.set_len(sig.length_public_key);
411            sk.bytes.set_len(sig.length_secret_key);
412        }
413        status_to_result(status)?;
414        Ok((pk, sk))
415    }
416
417    /// Sign a message
418    pub fn sign<'a, S: Into<SecretKeyRef<'a>>>(
419        &self,
420        message: &Message,
421        sk: S,
422    ) -> Result<Signature> {
423        let sk = sk.into();
424        let sig = unsafe { self.sig.as_ref() };
425        let func = sig.sign.unwrap();
426        let mut sig = Signature {
427            bytes: Vec::with_capacity(sig.length_signature),
428        };
429        let mut sig_len = 0;
430        let status = unsafe {
431            func(
432                sig.bytes.as_mut_ptr(),
433                &mut sig_len,
434                message.as_ptr(),
435                message.len(),
436                sk.bytes.as_ptr(),
437            )
438        };
439        status_to_result(status)?;
440        // This is safe to do as it's initialised now.
441        unsafe {
442            sig.bytes.set_len(sig_len);
443        }
444        Ok(sig)
445    }
446
447    /// Sign a message with a context string
448    pub fn sign_with_ctx_str<'a, S: Into<SecretKeyRef<'a>>>(
449        &self,
450        message: &Message,
451        ctx_str: &CtxStr,
452        sk: S,
453    ) -> Result<Signature> {
454        let sk = sk.into();
455        let sig = unsafe { self.sig.as_ref() };
456        let func = sig.sign_with_ctx_str.unwrap();
457        let mut sig = Signature {
458            bytes: Vec::with_capacity(sig.length_signature),
459        };
460        let mut sig_len = 0;
461        // For algorithms without context string support, liboqs
462        // expects the context to be NULL. Converting an empty
463        // slice to a pointer doesn't actually do this.
464        let ctx_str_ptr = if !ctx_str.is_empty() {
465            ctx_str.as_ptr()
466        } else {
467            null()
468        };
469        let status = unsafe {
470            func(
471                sig.bytes.as_mut_ptr(),
472                &mut sig_len,
473                message.as_ptr(),
474                message.len(),
475                ctx_str_ptr,
476                ctx_str.len(),
477                sk.bytes.as_ptr(),
478            )
479        };
480        status_to_result(status)?;
481        // This is safe to do as it's initialised now.
482        unsafe {
483            sig.bytes.set_len(sig_len);
484        }
485        Ok(sig)
486    }
487
488    /// Verify a message
489    pub fn verify<'a, 'b>(
490        &self,
491        message: &Message,
492        signature: impl Into<SignatureRef<'a>>,
493        pk: impl Into<PublicKeyRef<'b>>,
494    ) -> Result<()> {
495        let signature = signature.into();
496        let pk = pk.into();
497        if signature.bytes.len() > self.length_signature()
498            || pk.bytes.len() != self.length_public_key()
499        {
500            return Err(Error::InvalidLength);
501        }
502        let sig = unsafe { self.sig.as_ref() };
503        let func = sig.verify.unwrap();
504        let status = unsafe {
505            func(
506                message.as_ptr(),
507                message.len(),
508                signature.bytes.as_ptr(),
509                signature.len(),
510                pk.bytes.as_ptr(),
511            )
512        };
513        status_to_result(status)
514    }
515
516    /// Verify a message with a context string
517    pub fn verify_with_ctx_str<'a, 'b>(
518        &self,
519        message: &Message,
520        signature: impl Into<SignatureRef<'a>>,
521        ctx_str: &CtxStr,
522        pk: impl Into<PublicKeyRef<'b>>,
523    ) -> Result<()> {
524        let signature = signature.into();
525        let pk = pk.into();
526        if signature.bytes.len() > self.length_signature()
527            || pk.bytes.len() != self.length_public_key()
528        {
529            return Err(Error::InvalidLength);
530        }
531        let sig = unsafe { self.sig.as_ref() };
532        let func = sig.verify_with_ctx_str.unwrap();
533        // For algorithms without context string support, liboqs
534        // expects the context to be NULL. Converting an empty
535        // slice to a pointer doesn't actually do this.
536        let ctx_str_ptr = if !ctx_str.is_empty() {
537            ctx_str.as_ptr()
538        } else {
539            null()
540        };
541        let status = unsafe {
542            func(
543                message.as_ptr(),
544                message.len(),
545                signature.bytes.as_ptr(),
546                signature.len(),
547                ctx_str_ptr,
548                ctx_str.len(),
549                pk.bytes.as_ptr(),
550            )
551        };
552        status_to_result(status)
553    }
554}