diff --git a/.github/workflows/rust-wrapper.yml b/.github/workflows/rust-wrapper.yml index 218f7d8320a..d83aaa31217 100644 --- a/.github/workflows/rust-wrapper.yml +++ b/.github/workflows/rust-wrapper.yml @@ -38,6 +38,7 @@ jobs: # Add new configs here '', '--enable-all', + '--enable-all --enable-dilithium', '--enable-cryptonly --disable-examples', '--enable-cryptonly --disable-examples --disable-aes --disable-aesgcm', '--enable-cryptonly --disable-examples --disable-aescbc', diff --git a/wrapper/rust/wolfssl-wolfcrypt/build.rs b/wrapper/rust/wolfssl-wolfcrypt/build.rs index 0f9985acbac..63e098b0ae7 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/build.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/build.rs @@ -312,6 +312,22 @@ fn scan_cfg() -> Result<()> { println!("cargo:rustc-cfg=rsa_const_api"); } + /* dilithium / ML-DSA */ + check_cfg(&binding, "wc_dilithium_init", "dilithium"); + check_cfg(&binding, "wc_dilithium_make_key", "dilithium_make_key"); + check_cfg(&binding, "wc_dilithium_make_key_from_seed", "dilithium_make_key_from_seed"); + check_cfg(&binding, "wc_dilithium_sign_msg", "dilithium_sign"); + check_cfg(&binding, "wc_dilithium_sign_msg_with_seed", "dilithium_sign_with_seed"); + check_cfg(&binding, "wc_dilithium_verify_msg", "dilithium_verify"); + check_cfg(&binding, "wc_dilithium_import_public", "dilithium_import"); + check_cfg(&binding, "wc_dilithium_export_public", "dilithium_export"); + check_cfg(&binding, "wc_dilithium_check_key", "dilithium_check_key"); + check_cfg(&binding, "DILITHIUM_LEVEL2_KEY_SIZE", "dilithium_level2"); + check_cfg(&binding, "DILITHIUM_LEVEL3_KEY_SIZE", "dilithium_level3"); + check_cfg(&binding, "DILITHIUM_LEVEL5_KEY_SIZE", "dilithium_level5"); + check_cfg(&binding, "DILITHIUM_SEED_SZ", "dilithium_make_key_seed_sz"); + check_cfg(&binding, "DILITHIUM_RND_SZ", "dilithium_rnd_sz"); + /* sha */ check_cfg(&binding, "wc_InitSha", "sha"); check_cfg(&binding, "wc_InitSha224", "sha224"); diff --git a/wrapper/rust/wolfssl-wolfcrypt/headers.h b/wrapper/rust/wolfssl-wolfcrypt/headers.h index da521fa7033..ee1038bf808 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/headers.h +++ b/wrapper/rust/wolfssl-wolfcrypt/headers.h @@ -19,3 +19,4 @@ #include "wolfssl/wolfcrypt/logging.h" #include "wolfssl/wolfcrypt/aes.h" #include "wolfssl/wolfcrypt/pwdbased.h" +#include "wolfssl/wolfcrypt/dilithium.h" diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs b/wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs new file mode 100644 index 00000000000..0dcf9200434 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs @@ -0,0 +1,1313 @@ +/* + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/*! +This module provides a Rust wrapper for the wolfCrypt library's ML-DSA +(Dilithium) post-quantum digital signature functionality. + +The primary component is the [`Dilithium`] struct, which manages the lifecycle +of a wolfSSL `dilithium_key` object. It ensures proper initialization and +deallocation. + +Three security parameter sets are supported, selected via +[`Dilithium::set_level()`]: + +| Constant | Level | NIST PQC Level | +|-----------------|-------|----------------| +| [`Dilithium::LEVEL_44`] | 2 | 2 (ML-DSA-44) | +| [`Dilithium::LEVEL_65`] | 3 | 3 (ML-DSA-65) | +| [`Dilithium::LEVEL_87`] | 5 | 5 (ML-DSA-87) | + +# Examples + +```rust +#[cfg(all(dilithium, dilithium_make_key, dilithium_sign, dilithium_verify, random))] +{ +use wolfssl_wolfcrypt::random::RNG; +use wolfssl_wolfcrypt::dilithium::Dilithium; +let mut rng = RNG::new().expect("RNG creation failed"); +let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Key generation failed"); +let message = b"Hello, ML-DSA!"; +let mut sig = vec![0u8; key.sig_size().expect("sig_size failed")]; +let sig_len = key.sign_msg(message, &mut sig, &mut rng) + .expect("Signing failed"); +let valid = key.verify_msg(&sig[..sig_len], message) + .expect("Verification failed"); +assert!(valid); +} +``` +*/ + +#![cfg(dilithium)] + +use crate::sys; +#[cfg(all(random, any(dilithium_make_key, dilithium_sign)))] +use crate::random::RNG; +use core::mem::MaybeUninit; + +/// Rust wrapper for a wolfSSL `dilithium_key` object. +/// +/// Manages the lifecycle of the underlying key, including initialization and +/// deallocation via the [`Drop`] trait. +/// +/// An instance is created with [`Dilithium::generate()`], +/// [`Dilithium::generate_from_seed()`], or [`Dilithium::new()`]. +pub struct Dilithium { + ws_key: sys::dilithium_key, +} + +impl Dilithium { + /// ML-DSA-44 security parameter set (NIST Level 2). + pub const LEVEL_44: u8 = sys::WC_ML_DSA_44 as u8; + /// ML-DSA-65 security parameter set (NIST Level 3). + pub const LEVEL_65: u8 = sys::WC_ML_DSA_65 as u8; + /// ML-DSA-87 security parameter set (NIST Level 5). + pub const LEVEL_87: u8 = sys::WC_ML_DSA_87 as u8; + + /// Required size in bytes of the seed passed to + /// [`Dilithium::generate_from_seed()`] (`DILITHIUM_SEED_SZ`). + #[cfg(dilithium_make_key_seed_sz)] + pub const DILITHIUM_SEED_SZ: usize = sys::DILITHIUM_SEED_SZ as usize; + + /// Required size in bytes of the seed passed to signing-with-seed + /// functions such as [`Dilithium::sign_msg_with_seed()`] + /// (`DILITHIUM_RND_SZ`). + #[cfg(dilithium_rnd_sz)] + pub const SIGN_SEED_SIZE: usize = sys::DILITHIUM_RND_SZ as usize; + + /// Private (secret) key size in bytes for ML-DSA-44. + #[cfg(dilithium_level2)] + pub const LEVEL2_KEY_SIZE: usize = sys::DILITHIUM_LEVEL2_KEY_SIZE as usize; + /// Signature size in bytes for ML-DSA-44. + #[cfg(dilithium_level2)] + pub const LEVEL2_SIG_SIZE: usize = sys::DILITHIUM_LEVEL2_SIG_SIZE as usize; + /// Public key size in bytes for ML-DSA-44. + #[cfg(dilithium_level2)] + pub const LEVEL2_PUB_KEY_SIZE: usize = sys::DILITHIUM_LEVEL2_PUB_KEY_SIZE as usize; + /// Combined private-plus-public key size in bytes for ML-DSA-44. + #[cfg(dilithium_level2)] + pub const LEVEL2_PRV_KEY_SIZE: usize = + sys::DILITHIUM_LEVEL2_PUB_KEY_SIZE as usize + sys::DILITHIUM_LEVEL2_KEY_SIZE as usize; + + /// Private (secret) key size in bytes for ML-DSA-65. + #[cfg(dilithium_level3)] + pub const LEVEL3_KEY_SIZE: usize = sys::DILITHIUM_LEVEL3_KEY_SIZE as usize; + /// Signature size in bytes for ML-DSA-65. + #[cfg(dilithium_level3)] + pub const LEVEL3_SIG_SIZE: usize = sys::DILITHIUM_LEVEL3_SIG_SIZE as usize; + /// Public key size in bytes for ML-DSA-65. + #[cfg(dilithium_level3)] + pub const LEVEL3_PUB_KEY_SIZE: usize = sys::DILITHIUM_LEVEL3_PUB_KEY_SIZE as usize; + /// Combined private-plus-public key size in bytes for ML-DSA-65. + #[cfg(dilithium_level3)] + pub const LEVEL3_PRV_KEY_SIZE: usize = + sys::DILITHIUM_LEVEL3_PUB_KEY_SIZE as usize + sys::DILITHIUM_LEVEL3_KEY_SIZE as usize; + + /// Private (secret) key size in bytes for ML-DSA-87. + #[cfg(dilithium_level5)] + pub const LEVEL5_KEY_SIZE: usize = sys::DILITHIUM_LEVEL5_KEY_SIZE as usize; + /// Signature size in bytes for ML-DSA-87. + #[cfg(dilithium_level5)] + pub const LEVEL5_SIG_SIZE: usize = sys::DILITHIUM_LEVEL5_SIG_SIZE as usize; + /// Public key size in bytes for ML-DSA-87. + #[cfg(dilithium_level5)] + pub const LEVEL5_PUB_KEY_SIZE: usize = sys::DILITHIUM_LEVEL5_PUB_KEY_SIZE as usize; + /// Combined private-plus-public key size in bytes for ML-DSA-87. + #[cfg(dilithium_level5)] + pub const LEVEL5_PRV_KEY_SIZE: usize = + sys::DILITHIUM_LEVEL5_PUB_KEY_SIZE as usize + sys::DILITHIUM_LEVEL5_KEY_SIZE as usize; + + /// Generate a new Dilithium key pair using a random number generator. + /// + /// # Parameters + /// + /// * `level`: Security parameter set. One of [`Dilithium::LEVEL_44`], + /// [`Dilithium::LEVEL_65`], or [`Dilithium::LEVEL_87`]. + /// * `rng`: `RNG` instance to use for random number generation. + /// + /// # Returns + /// + /// Returns either Ok(Dilithium) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// } + /// ``` + #[cfg(all(dilithium_make_key, random))] + pub fn generate(level: u8, rng: &mut RNG) -> Result { + Self::generate_ex(level, rng, None, None) + } + + /// Generate a new Dilithium key pair with optional heap hint and device ID. + /// + /// # Parameters + /// + /// * `level`: Security parameter set. One of [`Dilithium::LEVEL_44`], + /// [`Dilithium::LEVEL_65`], or [`Dilithium::LEVEL_87`]. + /// * `rng`: `RNG` instance to use for random number generation. + /// * `heap`: Optional heap hint. + /// * `dev_id`: Optional device ID for crypto callbacks or async hardware. + /// + /// # Returns + /// + /// Returns either Ok(Dilithium) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let key = Dilithium::generate_ex(Dilithium::LEVEL_44, &mut rng, None, None) + /// .expect("Error with generate_ex()"); + /// } + /// ``` + #[cfg(all(dilithium_make_key, random))] + pub fn generate_ex( + level: u8, + rng: &mut RNG, + heap: Option<*mut core::ffi::c_void>, + dev_id: Option, + ) -> Result { + let mut key = Self::new_ex(heap, dev_id)?; + let rc = unsafe { sys::wc_dilithium_set_level(&mut key.ws_key, level) }; + if rc != 0 { + return Err(rc); + } + let rc = unsafe { sys::wc_dilithium_make_key(&mut key.ws_key, &mut rng.wc_rng) }; + if rc != 0 { + return Err(rc); + } + Ok(key) + } + + /// Generate a Dilithium key pair from a fixed seed. + /// + /// Produces the same key pair for a given `(level, seed)` pair, enabling + /// deterministic key generation. + /// + /// # Parameters + /// + /// * `level`: Security parameter set. One of [`Dilithium::LEVEL_44`], + /// [`Dilithium::LEVEL_65`], or [`Dilithium::LEVEL_87`]. + /// * `seed`: Seed bytes. Must be `DILITHIUM_SEED_SZ` (32) bytes. + /// + /// # Returns + /// + /// Returns either Ok(Dilithium) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key_from_seed))] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let seed = [0x42u8; 32]; + /// let key = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &seed) + /// .expect("Error with generate_from_seed()"); + /// } + /// ``` + #[cfg(dilithium_make_key_from_seed)] + pub fn generate_from_seed(level: u8, seed: &[u8]) -> Result { + Self::generate_from_seed_ex(level, seed, None, None) + } + + /// Generate a Dilithium key pair from a fixed seed with optional heap hint + /// and device ID. + /// + /// # Parameters + /// + /// * `level`: Security parameter set. One of [`Dilithium::LEVEL_44`], + /// [`Dilithium::LEVEL_65`], or [`Dilithium::LEVEL_87`]. + /// * `seed`: Seed bytes. Must be `DILITHIUM_SEED_SZ` (32) bytes. + /// * `heap`: Optional heap hint. + /// * `dev_id`: Optional device ID for crypto callbacks or async hardware. + /// + /// # Returns + /// + /// Returns either Ok(Dilithium) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key_from_seed))] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let seed = [0x42u8; 32]; + /// let key = Dilithium::generate_from_seed_ex(Dilithium::LEVEL_44, &seed, None, None) + /// .expect("Error with generate_from_seed_ex()"); + /// } + /// ``` + #[cfg(dilithium_make_key_from_seed)] + pub fn generate_from_seed_ex( + level: u8, + seed: &[u8], + heap: Option<*mut core::ffi::c_void>, + dev_id: Option, + ) -> Result { + #[cfg(dilithium_make_key_seed_sz)] + if seed.len() != Self::DILITHIUM_SEED_SZ { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let mut key = Self::new_ex(heap, dev_id)?; + let rc = unsafe { sys::wc_dilithium_set_level(&mut key.ws_key, level) }; + if rc != 0 { + return Err(rc); + } + let rc = unsafe { + sys::wc_dilithium_make_key_from_seed(&mut key.ws_key, seed.as_ptr()) + }; + if rc != 0 { + return Err(rc); + } + Ok(key) + } + + /// Create and initialize a new Dilithium key instance without a key. + /// + /// The security level and key material can be set afterwards using + /// [`Dilithium::set_level()`] and one of the import functions. + /// + /// # Returns + /// + /// Returns either Ok(Dilithium) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(dilithium)] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let key = Dilithium::new().expect("Error with new()"); + /// } + /// ``` + pub fn new() -> Result { + Self::new_ex(None, None) + } + + /// Create and initialize a new Dilithium key instance with optional heap + /// hint and device ID. + /// + /// # Parameters + /// + /// * `heap`: Optional heap hint. + /// * `dev_id`: Optional device ID for crypto callbacks or async hardware. + /// + /// # Returns + /// + /// Returns either Ok(Dilithium) containing the key instance or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(dilithium)] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let key = Dilithium::new_ex(None, None).expect("Error with new_ex()"); + /// } + /// ``` + pub fn new_ex( + heap: Option<*mut core::ffi::c_void>, + dev_id: Option, + ) -> Result { + let mut ws_key: MaybeUninit = MaybeUninit::uninit(); + let heap = match heap { + Some(h) => h, + None => core::ptr::null_mut(), + }; + let dev_id = match dev_id { + Some(id) => id, + None => sys::INVALID_DEVID, + }; + let rc = unsafe { sys::wc_dilithium_init_ex(ws_key.as_mut_ptr(), heap, dev_id) }; + if rc != 0 { + return Err(rc); + } + let ws_key = unsafe { ws_key.assume_init() }; + Ok(Dilithium { ws_key }) + } + + /// Set the security parameter level for this key. + /// + /// Must be called before generating or importing key material. Use one of + /// the level constants: [`Dilithium::LEVEL_44`], [`Dilithium::LEVEL_65`], + /// or [`Dilithium::LEVEL_87`]. + /// + /// # Parameters + /// + /// * `level`: Security level (2 = ML-DSA-44, 3 = ML-DSA-65, 5 = ML-DSA-87). + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(dilithium)] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut key = Dilithium::new().expect("Error with new()"); + /// key.set_level(Dilithium::LEVEL_65).expect("Error with set_level()"); + /// } + /// ``` + pub fn set_level(&mut self, level: u8) -> Result<(), i32> { + let rc = unsafe { sys::wc_dilithium_set_level(&mut self.ws_key, level) }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Get the security parameter level of this key. + /// + /// # Returns + /// + /// Returns either Ok(level) containing the current security level or + /// Err(e) containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(dilithium)] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut key = Dilithium::new().expect("Error with new()"); + /// key.set_level(Dilithium::LEVEL_87).expect("Error with set_level()"); + /// let level = key.get_level().expect("Error with get_level()"); + /// assert_eq!(level, Dilithium::LEVEL_87); + /// } + /// ``` + pub fn get_level(&mut self) -> Result { + let mut level = 0u8; + let rc = unsafe { sys::wc_dilithium_get_level(&mut self.ws_key, &mut level) }; + if rc != 0 { + return Err(rc); + } + Ok(level) + } + + /// Get the private (secret) key size in bytes for the current level. + /// + /// # Returns + /// + /// Returns either Ok(size) containing the private key size or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let sz = key.size().expect("Error with size()"); + /// assert_eq!(sz, Dilithium::LEVEL2_KEY_SIZE); + /// } + /// ``` + pub fn size(&mut self) -> Result { + let rc = unsafe { sys::wc_dilithium_size(&mut self.ws_key) }; + if rc < 0 { + return Err(rc); + } + Ok(rc as usize) + } + + /// Get the combined private-plus-public key size in bytes for the current + /// level. + /// + /// # Returns + /// + /// Returns either Ok(size) or Err(e) containing the wolfSSL library error + /// code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let sz = key.priv_size().expect("Error with priv_size()"); + /// assert_eq!(sz, Dilithium::LEVEL2_PRV_KEY_SIZE); + /// } + /// ``` + pub fn priv_size(&mut self) -> Result { + let rc = unsafe { sys::wc_dilithium_priv_size(&mut self.ws_key) }; + if rc < 0 { + return Err(rc); + } + Ok(rc as usize) + } + + /// Get the public key size in bytes for the current level. + /// + /// # Returns + /// + /// Returns either Ok(size) or Err(e) containing the wolfSSL library error + /// code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let sz = key.pub_size().expect("Error with pub_size()"); + /// assert_eq!(sz, Dilithium::LEVEL2_PUB_KEY_SIZE); + /// } + /// ``` + pub fn pub_size(&mut self) -> Result { + let rc = unsafe { sys::wc_dilithium_pub_size(&mut self.ws_key) }; + if rc < 0 { + return Err(rc); + } + Ok(rc as usize) + } + + /// Get the signature size in bytes for the current level. + /// + /// # Returns + /// + /// Returns either Ok(size) or Err(e) containing the wolfSSL library error + /// code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let sz = key.sig_size().expect("Error with sig_size()"); + /// assert_eq!(sz, Dilithium::LEVEL2_SIG_SIZE); + /// } + /// ``` + pub fn sig_size(&mut self) -> Result { + let rc = unsafe { sys::wc_dilithium_sig_size(&mut self.ws_key) }; + if rc < 0 { + return Err(rc); + } + Ok(rc as usize) + } + + /// Check that the key pair is valid (public key matches private key). + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_check_key, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// key.check_key().expect("Error with check_key()"); + /// } + /// ``` + #[cfg(dilithium_check_key)] + pub fn check_key(&mut self) -> Result<(), i32> { + let rc = unsafe { sys::wc_dilithium_check_key(&mut self.ws_key) }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Import a public key from a raw byte buffer. + /// + /// # Parameters + /// + /// * `public`: Input buffer containing the raw public key bytes. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_import, dilithium_export, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let mut pub_buf = vec![0u8; key.pub_size().unwrap()]; + /// key.export_public(&mut pub_buf).expect("Error with export_public()"); + /// let mut key2 = Dilithium::new().expect("Error with new()"); + /// key2.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + /// key2.import_public(&pub_buf).expect("Error with import_public()"); + /// } + /// ``` + #[cfg(dilithium_import)] + pub fn import_public(&mut self, public: &[u8]) -> Result<(), i32> { + let public_size = public.len() as u32; + let rc = unsafe { + sys::wc_dilithium_import_public(public.as_ptr(), public_size, &mut self.ws_key) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Import a private (secret) key from a raw byte buffer. + /// + /// The buffer should contain the raw private key bytes only + /// (size = `LEVEL*_KEY_SIZE`). + /// + /// # Parameters + /// + /// * `private`: Input buffer containing the raw private key bytes. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_import, dilithium_export, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let mut priv_buf = vec![0u8; key.size().unwrap()]; + /// key.export_private(&mut priv_buf).expect("Error with export_private()"); + /// let mut key2 = Dilithium::new().expect("Error with new()"); + /// key2.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + /// key2.import_private(&priv_buf).expect("Error with import_private()"); + /// } + /// ``` + #[cfg(dilithium_import)] + pub fn import_private(&mut self, private: &[u8]) -> Result<(), i32> { + let private_size = private.len() as u32; + let rc = unsafe { + sys::wc_dilithium_import_private(private.as_ptr(), private_size, &mut self.ws_key) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Import both private and public key material from raw byte buffers. + /// + /// # Parameters + /// + /// * `private`: Input buffer containing the raw private key bytes. + /// * `public`: Input buffer containing the raw public key bytes. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_import, dilithium_export, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let mut priv_buf = vec![0u8; key.size().unwrap()]; + /// let mut pub_buf = vec![0u8; key.pub_size().unwrap()]; + /// key.export_key(&mut priv_buf, &mut pub_buf).expect("Error with export_key()"); + /// let mut key2 = Dilithium::new().expect("Error with new()"); + /// key2.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + /// key2.import_key(&priv_buf, &pub_buf).expect("Error with import_key()"); + /// } + /// ``` + #[cfg(dilithium_import)] + pub fn import_key(&mut self, private: &[u8], public: &[u8]) -> Result<(), i32> { + let private_size = private.len() as u32; + let public_size = public.len() as u32; + let rc = unsafe { + sys::wc_dilithium_import_key( + private.as_ptr(), private_size, + public.as_ptr(), public_size, + &mut self.ws_key, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Export the public key to a raw byte buffer. + /// + /// # Parameters + /// + /// * `public`: Output buffer to receive the public key. Must be at least + /// `pub_size()` bytes. + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_export, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let mut pub_buf = vec![0u8; key.pub_size().unwrap()]; + /// let written = key.export_public(&mut pub_buf).expect("Error with export_public()"); + /// assert_eq!(written, Dilithium::LEVEL2_PUB_KEY_SIZE); + /// } + /// ``` + #[cfg(dilithium_export)] + pub fn export_public(&mut self, public: &mut [u8]) -> Result { + let mut public_size = public.len() as u32; + let rc = unsafe { + sys::wc_dilithium_export_public(&mut self.ws_key, public.as_mut_ptr(), &mut public_size) + }; + if rc != 0 { + return Err(rc); + } + Ok(public_size as usize) + } + + /// Export the private (secret) key to a raw byte buffer. + /// + /// # Parameters + /// + /// * `private`: Output buffer to receive the private key. Must be at + /// least `size()` bytes. + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written or Err(e) + /// containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_export, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let mut priv_buf = vec![0u8; key.size().unwrap()]; + /// let written = key.export_private(&mut priv_buf).expect("Error with export_private()"); + /// assert_eq!(written, Dilithium::LEVEL2_KEY_SIZE); + /// } + /// ``` + #[cfg(dilithium_export)] + pub fn export_private(&mut self, private: &mut [u8]) -> Result { + let mut private_size = private.len() as u32; + let rc = unsafe { + sys::wc_dilithium_export_private( + &mut self.ws_key, private.as_mut_ptr(), &mut private_size, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(private_size as usize) + } + + /// Export both private and public key material to separate raw byte + /// buffers. + /// + /// # Parameters + /// + /// * `private`: Output buffer for the private key. Must be at least + /// `size()` bytes. + /// * `public`: Output buffer for the public key. Must be at least + /// `pub_size()` bytes. + /// + /// # Returns + /// + /// Returns either Ok(()) on success or Err(e) containing the wolfSSL + /// library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_export, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let mut priv_buf = vec![0u8; key.size().unwrap()]; + /// let mut pub_buf = vec![0u8; key.pub_size().unwrap()]; + /// key.export_key(&mut priv_buf, &mut pub_buf).expect("Error with export_key()"); + /// } + /// ``` + #[cfg(dilithium_export)] + pub fn export_key(&mut self, private: &mut [u8], public: &mut [u8]) -> Result<(), i32> { + let mut private_size = private.len() as u32; + let mut public_size = public.len() as u32; + let rc = unsafe { + sys::wc_dilithium_export_key( + &mut self.ws_key, + private.as_mut_ptr(), &mut private_size, + public.as_mut_ptr(), &mut public_size, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Sign a message and write the signature to `sig`. + /// + /// # Parameters + /// + /// * `msg`: Message to sign. + /// * `sig`: Output buffer to hold the signature. Must be at least + /// `sig_size()` bytes. + /// * `rng`: RNG instance for hedged signing. For deterministic signing, + /// use [`Dilithium::sign_msg_with_seed()`] instead. + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written to `sig` + /// on success or Err(e) containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let message = b"Hello, ML-DSA!"; + /// let mut sig = vec![0u8; key.sig_size().unwrap()]; + /// let sig_len = key.sign_msg(message, &mut sig, &mut rng) + /// .expect("Error with sign_msg()"); + /// assert_eq!(sig_len, Dilithium::LEVEL2_SIG_SIZE); + /// } + /// ``` + #[cfg(all(dilithium_sign, random))] + pub fn sign_msg( + &mut self, + msg: &[u8], + sig: &mut [u8], + rng: &mut RNG, + ) -> Result { + let msg_len = msg.len() as u32; + let mut sig_len = sig.len() as u32; + let rc = unsafe { + sys::wc_dilithium_sign_msg( + msg.as_ptr(), msg_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.ws_key, + &mut rng.wc_rng, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_len as usize) + } + + /// Sign a message with a context string and write the signature to `sig`. + /// + /// # Parameters + /// + /// * `ctx`: Context string (at most 255 bytes). + /// * `msg`: Message to sign. + /// * `sig`: Output buffer to hold the signature. Must be at least + /// `sig_size()` bytes. + /// * `rng`: RNG instance for hedged signing. For deterministic signing, + /// use [`Dilithium::sign_ctx_msg_with_seed()`] instead. + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written to `sig` + /// on success or Err(e) containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let message = b"Hello, ML-DSA!"; + /// let ctx = b"my context"; + /// let mut sig = vec![0u8; key.sig_size().unwrap()]; + /// key.sign_ctx_msg(ctx, message, &mut sig, &mut rng) + /// .expect("Error with sign_ctx_msg()"); + /// } + /// ``` + #[cfg(all(dilithium_sign, random))] + pub fn sign_ctx_msg( + &mut self, + ctx: &[u8], + msg: &[u8], + sig: &mut [u8], + rng: &mut RNG, + ) -> Result { + if ctx.len() > 255 { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let ctx_len = ctx.len() as u8; + let msg_len = msg.len() as u32; + let mut sig_len = sig.len() as u32; + let rc = unsafe { + sys::wc_dilithium_sign_ctx_msg( + ctx.as_ptr(), ctx_len, + msg.as_ptr(), msg_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.ws_key, + &mut rng.wc_rng, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_len as usize) + } + + /// Sign a pre-hashed message with a context string. + /// + /// This is the HashML-DSA variant: the message is supplied as a hash + /// digest along with the hash algorithm identifier. + /// + /// # Parameters + /// + /// * `ctx`: Context string (at most 255 bytes). + /// * `hash_alg`: Hash algorithm identifier (e.g. `WC_HASH_TYPE_SHA256`). + /// * `hash`: Hash digest of the message to sign. + /// * `sig`: Output buffer to hold the signature. Must be at least + /// `sig_size()` bytes. + /// * `rng`: RNG instance for hedged signing. For deterministic signing, + /// use [`Dilithium::sign_ctx_hash_with_seed()`] instead. + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written to `sig` + /// on success or Err(e) containing the wolfSSL library error code value. + #[cfg(all(dilithium_sign, random))] + pub fn sign_ctx_hash( + &mut self, + ctx: &[u8], + hash_alg: i32, + hash: &[u8], + sig: &mut [u8], + rng: &mut RNG, + ) -> Result { + if ctx.len() > 255 { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let ctx_len = ctx.len() as u8; + let hash_len = hash.len() as u32; + let mut sig_len = sig.len() as u32; + let rc = unsafe { + sys::wc_dilithium_sign_ctx_hash( + ctx.as_ptr(), ctx_len, + hash_alg, + hash.as_ptr(), hash_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.ws_key, + &mut rng.wc_rng, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_len as usize) + } + + /// Sign a message using a fixed random seed instead of an RNG. + /// + /// Produces a deterministic signature for a given `(key, msg, seed)` + /// triple. + /// + /// # Parameters + /// + /// * `msg`: Message to sign. + /// * `sig`: Output buffer to hold the signature. + /// * `seed`: Random seed bytes (`DILITHIUM_RND_SZ` = 32 bytes). + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written to `sig` + /// on success or Err(e) containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key_from_seed, dilithium_sign_with_seed))] + /// { + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let key_seed = [0x42u8; 32]; + /// let mut key = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &key_seed) + /// .expect("Error with generate_from_seed()"); + /// let message = b"Hello, ML-DSA!"; + /// let sign_seed = [0x55u8; 32]; + /// let mut sig = vec![0u8; key.sig_size().unwrap()]; + /// key.sign_msg_with_seed(message, &mut sig, &sign_seed) + /// .expect("Error with sign_msg_with_seed()"); + /// } + /// ``` + #[cfg(dilithium_sign_with_seed)] + pub fn sign_msg_with_seed( + &mut self, + msg: &[u8], + sig: &mut [u8], + seed: &[u8], + ) -> Result { + #[cfg(dilithium_rnd_sz)] + if seed.len() != sys::DILITHIUM_RND_SZ as usize { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let msg_len = msg.len() as u32; + let mut sig_len = sig.len() as u32; + let rc = unsafe { + sys::wc_dilithium_sign_msg_with_seed( + msg.as_ptr(), msg_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.ws_key, + seed.as_ptr(), + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_len as usize) + } + + /// Sign a message with a context string using a fixed random seed. + /// + /// # Parameters + /// + /// * `ctx`: Context string (at most 255 bytes). + /// * `msg`: Message to sign. + /// * `sig`: Output buffer to hold the signature. + /// * `seed`: Random seed bytes (`DILITHIUM_RND_SZ` = 32 bytes). + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written to `sig` + /// on success or Err(e) containing the wolfSSL library error code value. + #[cfg(dilithium_sign_with_seed)] + pub fn sign_ctx_msg_with_seed( + &mut self, + ctx: &[u8], + msg: &[u8], + sig: &mut [u8], + seed: &[u8], + ) -> Result { + if ctx.len() > 255 { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + #[cfg(dilithium_rnd_sz)] + if seed.len() != sys::DILITHIUM_RND_SZ as usize { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let ctx_len = ctx.len() as u8; + let msg_len = msg.len() as u32; + let mut sig_len = sig.len() as u32; + let rc = unsafe { + sys::wc_dilithium_sign_ctx_msg_with_seed( + ctx.as_ptr(), ctx_len, + msg.as_ptr(), msg_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.ws_key, + seed.as_ptr(), + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_len as usize) + } + + /// Sign a pre-hashed message with a context string using a fixed random + /// seed. + /// + /// # Parameters + /// + /// * `ctx`: Context string (at most 255 bytes). + /// * `hash_alg`: Hash algorithm identifier (e.g. `WC_HASH_TYPE_SHA256`). + /// * `hash`: Hash digest of the message to sign. + /// * `sig`: Output buffer to hold the signature. + /// * `seed`: Random seed bytes (`DILITHIUM_RND_SZ` = 32 bytes). + /// + /// # Returns + /// + /// Returns either Ok(size) containing the number of bytes written to `sig` + /// on success or Err(e) containing the wolfSSL library error code value. + #[cfg(dilithium_sign_with_seed)] + pub fn sign_ctx_hash_with_seed( + &mut self, + ctx: &[u8], + hash_alg: i32, + hash: &[u8], + sig: &mut [u8], + seed: &[u8], + ) -> Result { + if ctx.len() > 255 { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + #[cfg(dilithium_rnd_sz)] + if seed.len() != sys::DILITHIUM_RND_SZ as usize { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let ctx_len = ctx.len() as u8; + let hash_len = hash.len() as u32; + let mut sig_len = sig.len() as u32; + let rc = unsafe { + sys::wc_dilithium_sign_ctx_hash_with_seed( + ctx.as_ptr(), ctx_len, + hash_alg, + hash.as_ptr(), hash_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.ws_key, + seed.as_ptr(), + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(sig_len as usize) + } + + /// Verify a message signature. + /// + /// # Parameters + /// + /// * `sig`: Signature to verify. + /// * `msg`: Message the signature was created over. + /// + /// # Returns + /// + /// Returns either Ok(true) if the signature is valid, Ok(false) if it is + /// invalid, or Err(e) containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign, dilithium_verify, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let message = b"Hello, ML-DSA!"; + /// let mut sig = vec![0u8; key.sig_size().unwrap()]; + /// let sig_len = key.sign_msg(message, &mut sig, &mut rng) + /// .expect("Error with sign_msg()"); + /// let valid = key.verify_msg(&sig[..sig_len], message) + /// .expect("Error with verify_msg()"); + /// assert!(valid); + /// } + /// ``` + #[cfg(dilithium_verify)] + pub fn verify_msg(&mut self, sig: &[u8], msg: &[u8]) -> Result { + let sig_len = sig.len() as u32; + let msg_len = msg.len() as u32; + let mut res = 0i32; + let rc = unsafe { + sys::wc_dilithium_verify_msg( + sig.as_ptr(), sig_len, + msg.as_ptr(), msg_len, + &mut res, + &mut self.ws_key, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(res == 1) + } + + /// Verify a message signature with a context string. + /// + /// # Parameters + /// + /// * `sig`: Signature to verify. + /// * `ctx`: Context string used when signing. + /// * `msg`: Message the signature was created over. + /// + /// # Returns + /// + /// Returns either Ok(true) if the signature is valid, Ok(false) if it is + /// invalid, or Err(e) containing the wolfSSL library error code value. + /// + /// # Example + /// + /// ```rust + /// #[cfg(all(dilithium, dilithium_make_key, dilithium_sign, dilithium_verify, random))] + /// { + /// use wolfssl_wolfcrypt::random::RNG; + /// use wolfssl_wolfcrypt::dilithium::Dilithium; + /// let mut rng = RNG::new().expect("Error creating RNG"); + /// let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + /// .expect("Error with generate()"); + /// let message = b"Hello, ML-DSA!"; + /// let ctx = b"my context"; + /// let mut sig = vec![0u8; key.sig_size().unwrap()]; + /// let sig_len = key.sign_ctx_msg(ctx, message, &mut sig, &mut rng) + /// .expect("Error with sign_ctx_msg()"); + /// let valid = key.verify_ctx_msg(&sig[..sig_len], ctx, message) + /// .expect("Error with verify_ctx_msg()"); + /// assert!(valid); + /// } + /// ``` + #[cfg(dilithium_verify)] + pub fn verify_ctx_msg(&mut self, sig: &[u8], ctx: &[u8], msg: &[u8]) -> Result { + if ctx.len() > 255 { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let sig_len = sig.len() as u32; + let ctx_len = ctx.len() as u32; + let msg_len = msg.len() as u32; + let mut res = 0i32; + let rc = unsafe { + sys::wc_dilithium_verify_ctx_msg( + sig.as_ptr(), sig_len, + ctx.as_ptr(), ctx_len, + msg.as_ptr(), msg_len, + &mut res, + &mut self.ws_key, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(res == 1) + } + + /// Verify a pre-hashed message signature with a context string. + /// + /// This is the HashML-DSA variant: the message is supplied as a hash + /// digest along with the hash algorithm identifier. + /// + /// # Parameters + /// + /// * `sig`: Signature to verify. + /// * `ctx`: Context string used when signing. + /// * `hash_alg`: Hash algorithm identifier (e.g. `WC_HASH_TYPE_SHA256`). + /// * `hash`: Hash digest of the message to verify. + /// + /// # Returns + /// + /// Returns either Ok(true) if the signature is valid, Ok(false) if it is + /// invalid, or Err(e) containing the wolfSSL library error code value. + #[cfg(dilithium_verify)] + pub fn verify_ctx_hash( + &mut self, + sig: &[u8], + ctx: &[u8], + hash_alg: i32, + hash: &[u8], + ) -> Result { + if ctx.len() > 255 { + return Err(sys::wolfCrypt_ErrorCodes_BUFFER_E); + } + let sig_len = sig.len() as u32; + let ctx_len = ctx.len() as u32; + let hash_len = hash.len() as u32; + let mut res = 0i32; + let rc = unsafe { + sys::wc_dilithium_verify_ctx_hash( + sig.as_ptr(), sig_len, + ctx.as_ptr(), ctx_len, + hash_alg, + hash.as_ptr(), hash_len, + &mut res, + &mut self.ws_key, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(res == 1) + } +} + +impl Drop for Dilithium { + /// Safely free the underlying wolfSSL Dilithium key context. + /// + /// This calls `wc_dilithium_free()`. The Rust Drop trait guarantees this + /// is called when the `Dilithium` struct goes out of scope. + fn drop(&mut self) { + unsafe { sys::wc_dilithium_free(&mut self.ws_key); } + } +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs b/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs index a073e8289e7..9bbc7562f37 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs @@ -29,6 +29,7 @@ pub mod chacha20_poly1305; pub mod cmac; pub mod curve25519; pub mod dh; +pub mod dilithium; pub mod ecc; pub mod ed25519; pub mod ed448; diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/common/mod.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/common/mod.rs index 8d191ed97bc..38fbdd907ba 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/common/mod.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/common/mod.rs @@ -5,6 +5,7 @@ fn setup_fips() fips::set_private_key_read_enable(1).expect("Error with set_private_key_read_enable()"); } +#[allow(dead_code)] pub fn setup() { #[cfg(fips)] diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_dilithium.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_dilithium.rs new file mode 100644 index 00000000000..d8b3b452d09 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_dilithium.rs @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#![cfg(dilithium)] + +mod common; + +use wolfssl_wolfcrypt::dilithium::Dilithium; +#[cfg(all(random, any(dilithium_make_key, dilithium_sign)))] +use wolfssl_wolfcrypt::random::RNG; + +/// Verify the level constants have the correct numeric values required by +/// the wolfCrypt API. +#[test] +fn test_level_constants() { + assert_eq!(Dilithium::LEVEL_44, 2); + assert_eq!(Dilithium::LEVEL_65, 3); + assert_eq!(Dilithium::LEVEL_87, 5); +} + +/// Verify `new()` + `set_level()` + `get_level()` for all three parameter sets. +#[test] +fn test_new_and_level() { + common::setup(); + + let mut key = Dilithium::new().expect("Error with new()"); + + key.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + assert_eq!(key.get_level().expect("Error with get_level()"), Dilithium::LEVEL_44); + + key.set_level(Dilithium::LEVEL_65).expect("Error with set_level()"); + assert_eq!(key.get_level().expect("Error with get_level()"), Dilithium::LEVEL_65); + + key.set_level(Dilithium::LEVEL_87).expect("Error with set_level()"); + assert_eq!(key.get_level().expect("Error with get_level()"), Dilithium::LEVEL_87); +} + +/// Verify that `new_ex()` accepts the optional heap and device ID parameters. +#[test] +fn test_new_ex() { + common::setup(); + let mut key = Dilithium::new_ex(None, None).expect("Error with new_ex()"); + key.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + assert_eq!(key.get_level().expect("Error with get_level()"), Dilithium::LEVEL_44); +} + +/// Verify the runtime size queries match the compile-time constants for +/// ML-DSA-44. +#[test] +#[cfg(all(dilithium_make_key, dilithium_level2))] +fn test_sizes_level44() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Error with generate()"); + assert_eq!(key.size().expect("Error with size()"), Dilithium::LEVEL2_KEY_SIZE); + assert_eq!(key.priv_size().expect("Error with priv_size()"), Dilithium::LEVEL2_PRV_KEY_SIZE); + assert_eq!(key.pub_size().expect("Error with pub_size()"), Dilithium::LEVEL2_PUB_KEY_SIZE); + assert_eq!(key.sig_size().expect("Error with sig_size()"), Dilithium::LEVEL2_SIG_SIZE); +} + +/// Verify the runtime size queries match the compile-time constants for +/// ML-DSA-65. +#[test] +#[cfg(all(dilithium_make_key, dilithium_level3))] +fn test_sizes_level65() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_65, &mut rng) + .expect("Error with generate()"); + assert_eq!(key.size().expect("Error with size()"), Dilithium::LEVEL3_KEY_SIZE); + assert_eq!(key.priv_size().expect("Error with priv_size()"), Dilithium::LEVEL3_PRV_KEY_SIZE); + assert_eq!(key.pub_size().expect("Error with pub_size()"), Dilithium::LEVEL3_PUB_KEY_SIZE); + assert_eq!(key.sig_size().expect("Error with sig_size()"), Dilithium::LEVEL3_SIG_SIZE); +} + +/// Verify the runtime size queries match the compile-time constants for +/// ML-DSA-87. +#[test] +#[cfg(all(dilithium_make_key, dilithium_level5))] +fn test_sizes_level87() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_87, &mut rng) + .expect("Error with generate()"); + assert_eq!(key.size().expect("Error with size()"), Dilithium::LEVEL5_KEY_SIZE); + assert_eq!(key.priv_size().expect("Error with priv_size()"), Dilithium::LEVEL5_PRV_KEY_SIZE); + assert_eq!(key.pub_size().expect("Error with pub_size()"), Dilithium::LEVEL5_PUB_KEY_SIZE); + assert_eq!(key.sig_size().expect("Error with sig_size()"), Dilithium::LEVEL5_SIG_SIZE); +} + +/// Verify that `check_key()` accepts a freshly generated ML-DSA-44 key pair. +#[test] +#[cfg(all(dilithium_make_key, dilithium_check_key))] +fn test_check_key_level44() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Error with generate()"); + key.check_key().expect("Error with check_key()"); +} + +/// Verify that `check_key()` accepts a freshly generated ML-DSA-65 key pair. +#[test] +#[cfg(all(dilithium_make_key, dilithium_check_key))] +fn test_check_key_level65() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_65, &mut rng) + .expect("Error with generate()"); + key.check_key().expect("Error with check_key()"); +} + +/// Verify that `check_key()` accepts a freshly generated ML-DSA-87 key pair. +#[test] +#[cfg(all(dilithium_make_key, dilithium_check_key))] +fn test_check_key_level87() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_87, &mut rng) + .expect("Error with generate()"); + key.check_key().expect("Error with check_key()"); +} + +/// Sign and verify a message round-trip using ML-DSA-44. +/// +/// Also verifies that a tampered message or signature produces a +/// verification failure rather than an error. +#[test] +#[cfg(all(dilithium_make_key, dilithium_sign, dilithium_verify))] +fn test_sign_verify_level44() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Error with generate()"); + let message = b"Hello, ML-DSA-44!"; + let mut sig = vec![0u8; key.sig_size().expect("Error with sig_size()")]; + + let sig_len = key.sign_msg(message, &mut sig, &mut rng) + .expect("Error with sign_msg()"); + assert_eq!(sig_len, sig.len()); + + let valid = key.verify_msg(&sig, message).expect("Error with verify_msg()"); + assert!(valid, "Valid signature should verify"); + + // A different message must not verify with the original signature. + let valid = key.verify_msg(&sig, b"Tampered message") + .expect("Error with verify_msg() on tampered message"); + assert!(!valid, "Tampered message should not verify"); +} + +/// Sign and verify a message round-trip using ML-DSA-65. +#[test] +#[cfg(all(dilithium_make_key, dilithium_sign, dilithium_verify))] +fn test_sign_verify_level65() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_65, &mut rng) + .expect("Error with generate()"); + let message = b"Hello, ML-DSA-65!"; + let mut sig = vec![0u8; key.sig_size().expect("Error with sig_size()")]; + + let sig_len = key.sign_msg(message, &mut sig, &mut rng) + .expect("Error with sign_msg()"); + assert_eq!(sig_len, sig.len()); + + let valid = key.verify_msg(&sig, message).expect("Error with verify_msg()"); + assert!(valid, "Valid signature should verify"); +} + +/// Sign and verify a message round-trip using ML-DSA-87. +#[test] +#[cfg(all(dilithium_make_key, dilithium_sign, dilithium_verify))] +fn test_sign_verify_level87() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_87, &mut rng) + .expect("Error with generate()"); + let message = b"Hello, ML-DSA-87!"; + let mut sig = vec![0u8; key.sig_size().expect("Error with sig_size()")]; + + let sig_len = key.sign_msg(message, &mut sig, &mut rng) + .expect("Error with sign_msg()"); + assert_eq!(sig_len, sig.len()); + + let valid = key.verify_msg(&sig, message).expect("Error with verify_msg()"); + assert!(valid, "Valid signature should verify"); +} + +/// Sign with a context string and verify using ML-DSA-44. +/// +/// Also verifies that a mismatched context causes verification to fail. +#[test] +#[cfg(all(dilithium_make_key, dilithium_sign, dilithium_verify))] +fn test_sign_ctx_verify_level44() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Error with generate()"); + let message = b"Context-bound message"; + let ctx = b"my context"; + let mut sig = vec![0u8; key.sig_size().expect("Error with sig_size()")]; + + let sig_len = key.sign_ctx_msg(ctx, message, &mut sig, &mut rng) + .expect("Error with sign_ctx_msg()"); + + let valid = key.verify_ctx_msg(&sig[..sig_len], ctx, message) + .expect("Error with verify_ctx_msg()"); + assert!(valid, "Valid context signature should verify"); + + // Wrong context must not verify. + let valid = key.verify_ctx_msg(&sig[..sig_len], b"wrong context", message) + .expect("Error with verify_ctx_msg() with wrong context"); + assert!(!valid, "Wrong context should not verify"); +} + +/// Export both keys, re-import them separately, and verify that: +/// - a signature from the original key is accepted by a public-key-only +/// import, and +/// - the re-imported private key can sign messages that verify with the +/// original public key. +#[test] +#[cfg(all(dilithium_make_key, dilithium_import, dilithium_export, dilithium_sign, dilithium_verify))] +fn test_import_export_level44() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Error with generate()"); + + let priv_size = key.size().expect("Error with size()"); + let pub_size = key.pub_size().expect("Error with pub_size()"); + let sig_size = key.sig_size().expect("Error with sig_size()"); + + let mut priv_buf = vec![0u8; priv_size]; + let mut pub_buf = vec![0u8; pub_size]; + key.export_key(&mut priv_buf, &mut pub_buf).expect("Error with export_key()"); + + // Verify export_public and export_private return the same bytes. + let mut pub_buf2 = vec![0u8; pub_size]; + let pub_written = key.export_public(&mut pub_buf2).expect("Error with export_public()"); + assert_eq!(pub_written, pub_size); + assert_eq!(pub_buf2, pub_buf); + + let mut priv_buf2 = vec![0u8; priv_size]; + let priv_written = key.export_private(&mut priv_buf2).expect("Error with export_private()"); + assert_eq!(priv_written, priv_size); + assert_eq!(priv_buf2, priv_buf); + + // Sign with the original key. + let message = b"Import/export test message"; + let mut sig = vec![0u8; sig_size]; + let sig_len = key.sign_msg(message, &mut sig, &mut rng) + .expect("Error with sign_msg()"); + + // Re-import public key only and verify. + let mut pub_key = Dilithium::new().expect("Error with new()"); + pub_key.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + pub_key.import_public(&pub_buf).expect("Error with import_public()"); + let valid = pub_key.verify_msg(&sig[..sig_len], message) + .expect("Error with verify_msg() via imported public key"); + assert!(valid, "Imported public key should accept original signature"); + + // Re-import private key, sign a message, and verify with the original key. + let mut priv_key = Dilithium::new().expect("Error with new()"); + priv_key.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + priv_key.import_private(&priv_buf).expect("Error with import_private()"); + let mut sig2 = vec![0u8; sig_size]; + let sig2_len = priv_key.sign_msg(message, &mut sig2, &mut rng) + .expect("Error with sign_msg() from imported private key"); + let valid = key.verify_msg(&sig2[..sig2_len], message) + .expect("Error with verify_msg() after import_private"); + assert!(valid, "Signature from re-imported private key should verify"); +} + +/// Export both keys, import them together via `import_key()`, then sign and +/// verify using the re-imported key pair. +#[test] +#[cfg(all(dilithium_make_key, dilithium_import, dilithium_export, dilithium_sign, dilithium_verify))] +fn test_import_key_level44() { + common::setup(); + let mut rng = RNG::new().expect("Error creating RNG"); + let mut key = Dilithium::generate(Dilithium::LEVEL_44, &mut rng) + .expect("Error with generate()"); + + let priv_size = key.size().expect("Error with size()"); + let pub_size = key.pub_size().expect("Error with pub_size()"); + let sig_size = key.sig_size().expect("Error with sig_size()"); + + let mut priv_buf = vec![0u8; priv_size]; + let mut pub_buf = vec![0u8; pub_size]; + key.export_key(&mut priv_buf, &mut pub_buf).expect("Error with export_key()"); + + let mut key2 = Dilithium::new().expect("Error with new()"); + key2.set_level(Dilithium::LEVEL_44).expect("Error with set_level()"); + key2.import_key(&priv_buf, &pub_buf).expect("Error with import_key()"); + + let message = b"import_key round-trip"; + let mut sig = vec![0u8; sig_size]; + let sig_len = key2.sign_msg(message, &mut sig, &mut rng) + .expect("Error with sign_msg() from imported key pair"); + let valid = key.verify_msg(&sig[..sig_len], message) + .expect("Error with verify_msg()"); + assert!(valid, "Imported key pair should produce valid signatures"); +} + +/// Verify that `generate_from_seed()` is deterministic: the same seed +/// produces the same key pair on repeated calls. +#[test] +#[cfg(all(dilithium_make_key_from_seed, dilithium_export))] +fn test_generate_from_seed_determinism() { + common::setup(); + // DILITHIUM_SEED_SZ = 32 bytes + let seed = [0x42u8; 32]; + + let mut key1 = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &seed) + .expect("Error with generate_from_seed() first call"); + let mut key2 = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &seed) + .expect("Error with generate_from_seed() second call"); + + let pub_size = key1.pub_size().expect("Error with pub_size()"); + let mut pub1 = vec![0u8; pub_size]; + let mut pub2 = vec![0u8; pub_size]; + key1.export_public(&mut pub1).expect("Error with export_public() key1"); + key2.export_public(&mut pub2).expect("Error with export_public() key2"); + assert_eq!(pub1, pub2, "Same seed must yield same public key"); + + let priv_size = key1.size().expect("Error with size()"); + let mut priv1 = vec![0u8; priv_size]; + let mut priv2 = vec![0u8; priv_size]; + key1.export_private(&mut priv1).expect("Error with export_private() key1"); + key2.export_private(&mut priv2).expect("Error with export_private() key2"); + assert_eq!(priv1, priv2, "Same seed must yield same private key"); +} + +/// Verify that `sign_msg_with_seed()` is deterministic: the same key, +/// message, and signing seed always produce the same signature bytes, and +/// the signature verifies correctly. +#[test] +#[cfg(all(dilithium_make_key_from_seed, dilithium_sign_with_seed, dilithium_verify))] +fn test_sign_with_seed_determinism() { + common::setup(); + // DILITHIUM_SEED_SZ = 32 bytes + let key_seed = [0x42u8; 32]; + // DILITHIUM_RND_SZ = 32 bytes + let sign_seed = [0x55u8; 32]; + let message = b"Deterministic ML-DSA signing test"; + + let mut key = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &key_seed) + .expect("Error with generate_from_seed()"); + + let sig_size = key.sig_size().expect("Error with sig_size()"); + let mut sig1 = vec![0u8; sig_size]; + let mut sig2 = vec![0u8; sig_size]; + + let len1 = key.sign_msg_with_seed(message, &mut sig1, &sign_seed) + .expect("Error with sign_msg_with_seed() first call"); + let len2 = key.sign_msg_with_seed(message, &mut sig2, &sign_seed) + .expect("Error with sign_msg_with_seed() second call"); + + assert_eq!(len1, len2, "Signature lengths must match"); + assert_eq!(sig1[..len1], sig2[..len2], "Same inputs must yield same signature"); + + let valid = key.verify_msg(&sig1[..len1], message) + .expect("Error with verify_msg()"); + assert!(valid, "Deterministically signed message should verify"); +} + +/// Verify that `sign_ctx_msg_with_seed()` is deterministic and that the +/// produced signature verifies with `verify_ctx_msg()`. +#[test] +#[cfg(all(dilithium_make_key_from_seed, dilithium_sign_with_seed, dilithium_verify))] +fn test_sign_ctx_with_seed_determinism() { + common::setup(); + let key_seed = [0x11u8; 32]; + let sign_seed = [0x22u8; 32]; + let message = b"Context deterministic signing test"; + let ctx = b"test-context"; + + let mut key = Dilithium::generate_from_seed(Dilithium::LEVEL_44, &key_seed) + .expect("Error with generate_from_seed()"); + + let sig_size = key.sig_size().expect("Error with sig_size()"); + let mut sig1 = vec![0u8; sig_size]; + let mut sig2 = vec![0u8; sig_size]; + + let len1 = key.sign_ctx_msg_with_seed(ctx, message, &mut sig1, &sign_seed) + .expect("Error with sign_ctx_msg_with_seed() first call"); + let len2 = key.sign_ctx_msg_with_seed(ctx, message, &mut sig2, &sign_seed) + .expect("Error with sign_ctx_msg_with_seed() second call"); + + assert_eq!(len1, len2); + assert_eq!(sig1[..len1], sig2[..len2], "Same inputs must yield same signature"); + + let valid = key.verify_ctx_msg(&sig1[..len1], ctx, message) + .expect("Error with verify_ctx_msg()"); + assert!(valid, "Context-signed message should verify"); +} + +/// Verify that `generate_from_seed()` + `sign_msg_with_seed()` + +/// `verify_msg()` work across all three security levels. +#[test] +#[cfg(all(dilithium_make_key_from_seed, dilithium_sign_with_seed, dilithium_verify))] +fn test_seed_sign_verify_all_levels() { + common::setup(); + let key_seed = [0xABu8; 32]; + let sign_seed = [0xCDu8; 32]; + let message = b"All-levels seed sign/verify test"; + + for level in [Dilithium::LEVEL_44, Dilithium::LEVEL_65, Dilithium::LEVEL_87] { + let mut key = Dilithium::generate_from_seed(level, &key_seed) + .expect("Error with generate_from_seed()"); + let sig_size = key.sig_size().expect("Error with sig_size()"); + let mut sig = vec![0u8; sig_size]; + let sig_len = key.sign_msg_with_seed(message, &mut sig, &sign_seed) + .expect("Error with sign_msg_with_seed()"); + let valid = key.verify_msg(&sig[..sig_len], message) + .expect("Error with verify_msg()"); + assert!(valid, "Level {} seed-signed message should verify", level); + } +}