From dbb3197dc1b83826dbe3448d18bd12ab4f458fab Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 11 Nov 2025 10:08:28 +0100 Subject: [PATCH 01/61] add macros and traits for implementing signature APIs --- traits/src/lib.rs | 2 + traits/src/signature.rs | 168 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 traits/src/signature.rs diff --git a/traits/src/lib.rs b/traits/src/lib.rs index 4be2d5489c..81596e3461 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -26,5 +26,7 @@ pub mod aead; pub mod digest; pub mod ecdh; pub mod kem; +pub mod signature; pub use libcrux_secrets; +pub use rand; diff --git a/traits/src/signature.rs b/traits/src/signature.rs new file mode 100644 index 0000000000..72ab856bf0 --- /dev/null +++ b/traits/src/signature.rs @@ -0,0 +1,168 @@ +//! This module provides common interface traits for signature scheme implementations. + +/// Constants defining the sizes of cryptographic elements for a signature algorithm. +pub trait SignConsts { + /// Length of verification (public) keys in bytes. + const VERIFICATION_KEY_LEN: usize; + /// Length of signing (private) keys in bytes. + const SIGNING_KEY_LEN: usize; + /// Length of signatures in bytes. + const SIGNATURE_LEN: usize; + /// Length of randomness required for key generation in bytes. + const RAND_KEYGEN_LEN: usize; +} + +/// Associated types for signature algorithm components. +/// +/// This trait defines the concrete types used by a signature algorithm for keys, +/// signatures, randomness, and auxiliary signing data. +pub trait SignTypes { + /// The type representing signing (private) keys. + type SigningKey; + /// The type representing verification (public) keys. + type VerificationKey; + /// The type representing signatures. + type Signature; +} + +/// Helper macro for implementing signing-related traits +#[macro_export] +macro_rules! impl_sign_traits { + ($algorithm:ty, $signing_key_len:expr, $verification_key_len:expr, $signature_len:expr, $rand_keygen_len:expr) => { + use $crate::libcrux_secrets::U8; + impl $crate::signature::SignConsts for $algorithm { + const SIGNING_KEY_LEN: usize = $signing_key_len; + const VERIFICATION_KEY_LEN: usize = $verification_key_len; + const SIGNATURE_LEN: usize = $signature_len; + const RAND_KEYGEN_LEN: usize = $rand_keygen_len; + } + + // internal types + type SigningKeyArray = [U8; $signing_key_len]; + type VerificationKeyArray = [u8; $verification_key_len]; + type SignatureArray = [u8; $signature_len]; + + impl $crate::signature::SignTypes for $algorithm { + type SigningKey = SigningKeyArray; + type VerificationKey = VerificationKeyArray; + type Signature = SignatureArray; + } + }; +} + +pub use impl_sign_traits; + +/// Helper macro for implementing types for key-centric APIs +#[macro_export] +macro_rules! impl_key_centric_types { + ($algorithm:ty, $signing_key_len:expr, $verification_key_len:expr, $signature_len:expr, $rand_keygen_len:expr, $from_slice_error:ty, $from_slice_error_variant:expr) => { + impl_sign_traits!( + $algorithm, + $signing_key_len, + $verification_key_len, + $signature_len, + $rand_keygen_len + ); + + pub struct SigningKeyRef<'a> { + key: &'a [U8], + } + impl<'a> AsRef<[U8]> for SigningKeyRef<'a> { + fn as_ref(&self) -> &[U8] { + self.key.as_ref() + } + } + impl<'a> AsRef<[u8]> for VerificationKeyRef<'a> { + fn as_ref(&self) -> &[u8] { + self.key.as_ref() + } + } + pub struct VerificationKeyRef<'a> { + key: &'a [u8], + } + + pub struct SigningKey { + key: SigningKeyArray, + } + pub struct VerificationKey { + key: VerificationKeyArray, + } + #[derive(Debug, PartialEq)] + pub struct Signature { + signature: SignatureArray, + } + + pub struct KeyPair { + pub signing_key: SigningKey, + pub verification_key: VerificationKey, + } + + impl<'a> SigningKeyRef<'a> { + pub fn from_slice(key: &'a [U8]) -> Result { + if key.len() != $signing_key_len { + return Err($from_slice_error_variant); + } else { + return Ok(Self { key }); + } + } + } + + impl From for SigningKey { + fn from(key: SigningKeyArray) -> Self { + Self { key } + } + } + + impl AsRef<[U8]> for SigningKey { + fn as_ref(&self) -> &[U8] { + self.key.as_ref() + } + } + impl AsRef for SigningKey { + fn as_ref(&self) -> &SigningKeyArray { + &self.key + } + } + impl<'a> VerificationKeyRef<'a> { + pub fn from_slice(key: &'a [u8]) -> Result { + if key.len() != $verification_key_len { + return Err($from_slice_error_variant); + } else { + return Ok(Self { key }); + } + } + } + impl From for VerificationKey { + fn from(key: VerificationKeyArray) -> Self { + Self { key } + } + } + impl AsRef<[u8]> for VerificationKey { + fn as_ref(&self) -> &[u8] { + self.key.as_ref() + } + } + impl AsRef for VerificationKey { + fn as_ref(&self) -> &VerificationKeyArray { + &self.key + } + } + impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + self.signature.as_ref() + } + } + impl AsRef for Signature { + fn as_ref(&self) -> &SignatureArray { + &self.signature + } + } + impl From for Signature { + fn from(signature: SignatureArray) -> Self { + Self { signature } + } + } + }; +} + +pub use impl_key_centric_types; From 2dab1d5c91b531e2f64320de56530679d1e708ae Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 11 Nov 2025 10:13:52 +0100 Subject: [PATCH 02/61] ed25519 public API implementation --- crates/algorithms/ed25519/Cargo.toml | 7 + .../ed25519/src/key_centric_apis.rs | 273 ++++++++++++++++++ crates/algorithms/ed25519/src/lib.rs | 1 + 3 files changed, 281 insertions(+) create mode 100644 crates/algorithms/ed25519/src/key_centric_apis.rs diff --git a/crates/algorithms/ed25519/Cargo.toml b/crates/algorithms/ed25519/Cargo.toml index 301f0b2008..ff96b9e079 100644 --- a/crates/algorithms/ed25519/Cargo.toml +++ b/crates/algorithms/ed25519/Cargo.toml @@ -16,6 +16,9 @@ libcrux-sha2 = { version = "=0.0.4", path = "../sha2", features = [ "expose-hacl", ] } libcrux-macros = { version = "=0.0.3", path = "../../utils/macros" } +libcrux-traits = { version = "=0.0.4", path = "../../../traits" } +libcrux-secrets = { version = "=0.0.4", path = "../../utils/secrets" } + rand_core = { version = "0.9", optional = true } tls_codec = { version = "0.4.2", features = ["derive"], optional = true } @@ -26,3 +29,7 @@ wycheproof = "0.6.0" [features] rand = ["dep:rand_core"] codec = ["tls_codec/std"] +check-secret-independence = [ + "libcrux-traits/check-secret-independence", + "libcrux-secrets/check-secret-independence", +] diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs new file mode 100644 index 0000000000..32562b2b5b --- /dev/null +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -0,0 +1,273 @@ +use libcrux_traits::signature::{impl_key_centric_types, impl_sign_traits, SignConsts}; + +use libcrux_secrets::{Classify, DeclassifyRef, U8}; +const VERIFICATION_KEY_LEN: usize = 32; +const SIGNING_KEY_LEN: usize = 32; +const SIGNATURE_LEN: usize = 64; +const RAND_KEYGEN_LEN: usize = SIGNING_KEY_LEN; + +// arrayref API +#[doc(inline)] +pub use arrayref::*; + +// TODO: different error type? +#[derive(Debug)] +/// An incorrect length when converting from slice. +pub struct WrongLengthError; + +pub mod arrayref { + #[derive(Debug, PartialEq)] + pub struct Ed25519; + use super::*; + impl_key_centric_types!( + Ed25519, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN, + WrongLengthError, + WrongLengthError + ); +} +pub mod slice { + #[derive(Debug, PartialEq)] + pub struct Ed25519; + use super::*; + impl_sign_traits!( + Ed25519, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN + ); + + // error type including wrong length + #[derive(Debug)] + pub enum SigningError { + WrongSigningKeyLength, + WrongSignatureLength, + WrongPayloadLength, + } + + // error type including wrong length + #[derive(Debug)] + pub enum VerificationError { + InvalidSignature, + WrongVerificationKeyLength, + WrongSignatureLength, + WrongPayloadLength, + } + + #[derive(Debug)] + pub enum KeygenError { + WrongSigningKeyLength, + WrongVerificationKeyLength, + } +} + +impl arrayref::Ed25519 { + #[cfg(feature = "rand")] + pub fn generate_key_pair(rng: &mut impl rand_core::CryptoRng) -> KeyPair { + let mut bytes = [0u8; Self::RAND_KEYGEN_LEN]; + rng.fill_bytes(&mut bytes); + let mut signing_key = [0u8; Self::SIGNING_KEY_LEN].classify(); + let mut verification_key = [0u8; Self::VERIFICATION_KEY_LEN]; + arrayref::Ed25519::keygen_derand(&mut signing_key, &mut verification_key, bytes.classify()); + + KeyPair { + signing_key: SigningKey::from(signing_key), + verification_key: VerificationKey::from(verification_key), + } + } +} +impl arrayref::Ed25519 { + pub fn sign( + key: &[U8; Self::SIGNING_KEY_LEN], + payload: &[u8], + signature: &mut [u8; Self::SIGNATURE_LEN], + // TODO: arrayref::SigningError? + ) -> Result<(), slice::SigningError> { + crate::hacl::ed25519::sign( + signature, + key.declassify_ref(), + payload + .len() + .try_into() + .map_err(|_| slice::SigningError::WrongPayloadLength)?, + payload, + ); + + Ok(()) + } + + pub fn verify( + key: &[u8; Self::VERIFICATION_KEY_LEN], + payload: &[u8], + signature: &[u8; Self::SIGNATURE_LEN], + ) -> Result<(), slice::VerificationError> { + if crate::hacl::ed25519::verify( + key, + payload + .len() + .try_into() + .map_err(|_| slice::VerificationError::WrongPayloadLength)?, + payload, + signature, + ) { + Ok(()) + } else { + Err(slice::VerificationError::InvalidSignature) + } + } + pub fn keygen_derand( + signing_key: &mut [U8; Self::SIGNING_KEY_LEN], + verification_key: &mut [u8; Self::VERIFICATION_KEY_LEN], + randomness: [U8; Self::RAND_KEYGEN_LEN], + ) { + *signing_key = randomness; + crate::secret_to_public(verification_key, signing_key.declassify_ref()); + } +} +impl slice::Ed25519 { + pub fn sign( + key: &[U8], + payload: &[u8], + signature: &mut [u8], + ) -> Result<(), slice::SigningError> { + let key = key + .try_into() + .map_err(|_| slice::SigningError::WrongSigningKeyLength)?; + let signature = signature + .try_into() + .map_err(|_| slice::SigningError::WrongSignatureLength)?; + + arrayref::Ed25519::sign(&key, payload, signature).map_err(slice::SigningError::from) + } + pub fn verify( + key: &[u8], + payload: &[u8], + signature: &[u8], + ) -> Result<(), slice::VerificationError> { + let key = key + .try_into() + .map_err(|_| slice::VerificationError::WrongVerificationKeyLength)?; + let signature = signature + .try_into() + .map_err(|_| slice::VerificationError::WrongSignatureLength)?; + + arrayref::Ed25519::verify(key, payload, signature).map_err(slice::VerificationError::from) + } + pub fn keygen_derand( + signing_key: &mut [U8], + verification_key: &mut [u8], + randomness: [U8; Self::RAND_KEYGEN_LEN], + ) -> Result<(), slice::KeygenError> { + let signing_key = signing_key + .try_into() + .map_err(|_| slice::KeygenError::WrongSigningKeyLength)?; + let verification_key = verification_key + .try_into() + .map_err(|_| slice::KeygenError::WrongVerificationKeyLength)?; + + arrayref::Ed25519::keygen_derand(signing_key, verification_key, randomness); + + Ok(()) + } +} +impl<'a> SigningKeyRef<'a> { + pub fn sign(&self, payload: &[u8], signature: &mut [u8]) -> Result<(), slice::SigningError> { + slice::Ed25519::sign(self.as_ref(), payload, signature) + } +} +impl<'a> VerificationKeyRef<'a> { + pub fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<(), slice::VerificationError> { + slice::Ed25519::verify(self.as_ref(), payload, signature) + } +} + +// key-centric API +impl SigningKey { + pub fn sign(&self, payload: &[u8]) -> Result { + let mut signature = [0u8; SIGNATURE_LEN]; + arrayref::Ed25519::sign(self.as_ref(), payload, &mut signature) + .map(|_| Signature::from(signature)) + } +} +impl VerificationKey { + pub fn verify( + &self, + payload: &[u8], + signature: &Signature, + ) -> Result<(), slice::VerificationError> { + arrayref::Ed25519::verify(self.as_ref(), payload, signature.as_ref()) + } +} + +#[test] +#[cfg(feature = "rand")] +fn key_centric_owned() { + use rand::TryRngCore; + let mut rng = rand::rngs::OsRng; + let mut rng = rng.unwrap_mut(); + use libcrux_traits::signature::SignConsts; + + // keys can be created from arrays + let _signing_key = SigningKey::from([0u8; Ed25519::SIGNING_KEY_LEN].classify()); + let _verification_key = VerificationKey::from([0u8; Ed25519::VERIFICATION_KEY_LEN]); + + // key-centric API + let KeyPair { + signing_key, + verification_key, + } = Ed25519::generate_key_pair(&mut rng); + + let signature = signing_key.sign(b"payload").unwrap(); + verification_key.verify(b"payload", &signature).unwrap(); +} + +#[test] +#[cfg(all(feature = "rand", not(feature = "check-secret-independence")))] +fn key_centric_refs() { + use libcrux_traits::signature::SignConsts; + + let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; + let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; + Ed25519::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]); + + // create references from slice + let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); + let verification_key = VerificationKeyRef::from_slice(&verification_key).unwrap(); + + let mut signature = [0u8; Ed25519::SIGNATURE_LEN]; + signing_key.sign(b"payload", &mut signature).unwrap(); + verification_key.verify(b"payload", &signature).unwrap(); +} + +#[test] +#[cfg(not(feature = "check-secret-independence"))] +fn arrayref_apis() { + use libcrux_traits::signature::SignConsts; + + let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; + let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; + Ed25519::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]); + + // arrayref API + let mut signature = [0u8; Ed25519::SIGNATURE_LEN]; + Ed25519::sign(&signing_key, b"payload", &mut signature).unwrap(); + Ed25519::verify(&verification_key, b"payload", &signature).unwrap(); +} +#[test] +#[cfg(not(feature = "check-secret-independence"))] +fn slice_apis() { + use libcrux_traits::signature::SignConsts; + + let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; + let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; + Ed25519::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]); + + // slice API + let mut signature = [0u8; Ed25519::SIGNATURE_LEN]; + Ed25519::sign(&signing_key, b"payload", &mut signature).unwrap(); + Ed25519::verify(&verification_key, b"payload", &signature).unwrap(); +} diff --git a/crates/algorithms/ed25519/src/lib.rs b/crates/algorithms/ed25519/src/lib.rs index bcdc79d8aa..78c941cd7c 100644 --- a/crates/algorithms/ed25519/src/lib.rs +++ b/crates/algorithms/ed25519/src/lib.rs @@ -8,5 +8,6 @@ mod hacl { } mod impl_hacl; +pub mod key_centric_apis; pub use impl_hacl::*; From 1ae48d30a66490195f161ad23d612992ebae3942 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 11 Nov 2025 10:15:34 +0100 Subject: [PATCH 03/61] update `Cargo.toml` --- crates/algorithms/ecdsa/Cargo.toml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/algorithms/ecdsa/Cargo.toml b/crates/algorithms/ecdsa/Cargo.toml index e2cb04036d..36f46be6b4 100644 --- a/crates/algorithms/ecdsa/Cargo.toml +++ b/crates/algorithms/ecdsa/Cargo.toml @@ -1,13 +1,13 @@ [package] -name = "libcrux-ecdsa" description = "Formally verified ECDSA signature library" +name = "libcrux-ecdsa" readme = "Readme.md" version = "0.0.4" authors.workspace = true -license.workspace = true -homepage.workspace = true edition.workspace = true +homepage.workspace = true +license.workspace = true repository.workspace = true [dependencies] @@ -15,14 +15,20 @@ libcrux-p256 = { version = "=0.0.4", path = "../p256", features = [ "expose-hacl", ] } libcrux-sha2 = { version = "=0.0.4", path = "../sha2" } +libcrux-secrets = { version = "=0.0.4", path = "../secrets" } +libcrux-traits = { version = "=0.0.4", path = "../traits" } rand = { version = "0.9", optional = true, default-features = false } [features] default = ["rand", "std"] rand = ["dep:rand"] std = ["rand?/std"] +check-secret-independence = [ + "libcrux-secrets/check-secret-independence", + "libcrux-traits/check-secret-independence", +] [dev-dependencies] -rand_core = { version = "0.9" , features = ["os_rng"] } +rand_core = { version = "0.9", features = ["os_rng"] } serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.138" From b96d79a901f7153cfef116f323790bb9a962c02a Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 11 Nov 2025 10:16:02 +0100 Subject: [PATCH 04/61] update visibility --- crates/algorithms/ecdsa/src/p256.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/algorithms/ecdsa/src/p256.rs b/crates/algorithms/ecdsa/src/p256.rs index 3121d2de88..a6f03a7270 100644 --- a/crates/algorithms/ecdsa/src/p256.rs +++ b/crates/algorithms/ecdsa/src/p256.rs @@ -18,7 +18,7 @@ pub struct Signature { } /// An ECDSA P-256 nonce -pub struct Nonce([u8; 32]); +pub struct Nonce(pub(super) [u8; 32]); /// An ECDSA P-256 private key pub struct PrivateKey([u8; 32]); From bbbd86951206ff5a47caccc50120a9d38f74551d Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 11 Nov 2025 10:16:38 +0100 Subject: [PATCH 05/61] ecdsa public API implementation --- crates/algorithms/ecdsa/Cargo.toml | 4 +- .../algorithms/ecdsa/src/key_centric_apis.rs | 399 ++++++++++++++++++ crates/algorithms/ecdsa/src/lib.rs | 2 + 3 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 crates/algorithms/ecdsa/src/key_centric_apis.rs diff --git a/crates/algorithms/ecdsa/Cargo.toml b/crates/algorithms/ecdsa/Cargo.toml index 36f46be6b4..29545bc6f8 100644 --- a/crates/algorithms/ecdsa/Cargo.toml +++ b/crates/algorithms/ecdsa/Cargo.toml @@ -15,8 +15,8 @@ libcrux-p256 = { version = "=0.0.4", path = "../p256", features = [ "expose-hacl", ] } libcrux-sha2 = { version = "=0.0.4", path = "../sha2" } -libcrux-secrets = { version = "=0.0.4", path = "../secrets" } -libcrux-traits = { version = "=0.0.4", path = "../traits" } +libcrux-secrets = { version = "=0.0.4", path = "../../utils/secrets" } +libcrux-traits = { version = "=0.0.4", path = "../../../traits" } rand = { version = "0.9", optional = true, default-features = false } [features] diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs new file mode 100644 index 0000000000..c36be910e2 --- /dev/null +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -0,0 +1,399 @@ +use crate::p256::Nonce; +use libcrux_traits::signature::{impl_key_centric_types, impl_sign_traits, SignConsts}; + +macro_rules! impl_mod { + ($ty:ident, $module:ident, + $sign_fn:ident, + $verify_fn:ident) => { + use libcrux_secrets::{DeclassifyRef, U8}; + + const SIGNING_KEY_LEN: usize = 32; + const VERIFICATION_KEY_LEN: usize = 64; + const SIGNATURE_LEN: usize = 64; + const RAND_KEYGEN_LEN: usize = 32; + + use super::*; + // arrayref API + #[doc(inline)] + pub use arrayref::*; + + // TODO: different error type? + #[derive(Debug)] + /// An incorrect length when converting from slice. + pub struct WrongLengthError; + + pub mod arrayref { + #[derive(Debug, PartialEq)] + pub struct $ty; + use super::*; + impl_key_centric_types!( + $ty, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN, + WrongLengthError, + WrongLengthError + ); + } + pub mod slice { + #[derive(Debug, PartialEq)] + pub struct $ty; + use super::*; + impl_sign_traits!( + $ty, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN + ); + + // error type including wrong length + #[derive(Debug)] + pub enum SigningError { + WrongSigningKeyLength, + WrongSignatureLength, + InvalidArgument, + UnknownError, + } + + // error type including wrong length + #[derive(Debug)] + pub enum VerificationError { + WrongVerificationKeyLength, + WrongSignatureLength, + WrongPayloadLength, + UnknownError, + } + + #[derive(Debug)] + pub enum KeygenError { + InvalidRandomness, + WrongSigningKeyLength, + WrongVerificationKeyLength, + UnknownError, + } + } + + impl arrayref::$ty { + #[cfg(feature = "rand")] + pub fn generate_key_pair( + rng: &mut impl rand::CryptoRng, + ) -> Result { + use libcrux_secrets::Classify; + + let mut bytes = [0u8; Self::RAND_KEYGEN_LEN]; + rng.fill_bytes(&mut bytes); + let mut signing_key = [0u8; Self::SIGNING_KEY_LEN].classify(); + let mut verification_key = [0u8; Self::VERIFICATION_KEY_LEN]; + arrayref::$ty::keygen_derand( + &mut signing_key, + &mut verification_key, + bytes.classify(), + )?; + + Ok(KeyPair { + signing_key: SigningKey::from(signing_key), + verification_key: VerificationKey::from(verification_key), + }) + } + } + impl arrayref::$ty { + pub fn sign( + key: &[U8; Self::SIGNING_KEY_LEN], + payload: &[u8], + signature: &mut [u8; Self::SIGNATURE_LEN], + nonce: &Nonce, + ) -> Result<(), slice::SigningError> { + let result = libcrux_p256::$sign_fn( + signature, + payload + .len() + .try_into() + .map_err(|_| slice::SigningError::InvalidArgument)?, + payload, + key.declassify_ref(), + &nonce.0, + ); + if !result { + return Err(slice::SigningError::UnknownError); + } + Ok(()) + } + pub fn verify( + key: &[u8; Self::VERIFICATION_KEY_LEN], + payload: &[u8], + signature: &[u8; Self::SIGNATURE_LEN], + ) -> Result<(), slice::VerificationError> { + let result = libcrux_p256::$verify_fn( + payload + .len() + .try_into() + .map_err(|_| slice::VerificationError::WrongPayloadLength)?, + payload, + key, + <&[u8; 32]>::try_from(&signature[0..32]).unwrap(), + <&[u8; 32]>::try_from(&signature[32..]).unwrap(), + ); + if !result { + return Err(slice::VerificationError::UnknownError); + } + Ok(()) + } + pub fn keygen_derand( + signing_key: &mut [U8; Self::SIGNING_KEY_LEN], + verification_key: &mut [u8; Self::VERIFICATION_KEY_LEN], + randomness: [U8; Self::RAND_KEYGEN_LEN], + ) -> Result<(), slice::KeygenError> { + use libcrux_traits::ecdh::arrayref::*; + + libcrux_p256::P256::secret_to_public(verification_key, &randomness).map_err( + |err| match err { + libcrux_traits::ecdh::arrayref::SecretToPublicError::InvalidSecret => { + slice::KeygenError::InvalidRandomness + } + libcrux_traits::ecdh::arrayref::SecretToPublicError::Unknown => { + slice::KeygenError::UnknownError + } + }, + )?; + *signing_key = randomness; + + Ok(()) + } + } + impl slice::$ty { + pub fn sign( + key: &[U8], + payload: &[u8], + signature: &mut [u8], + nonce: &Nonce, + ) -> Result<(), slice::SigningError> { + let key = key + .try_into() + .map_err(|_| slice::SigningError::WrongSigningKeyLength)?; + let signature = signature + .try_into() + .map_err(|_| slice::SigningError::WrongSignatureLength)?; + + arrayref::$ty::sign(&key, payload, signature, nonce) + .map_err(slice::SigningError::from) + } + pub fn verify( + key: &[u8], + payload: &[u8], + signature: &[u8], + ) -> Result<(), slice::VerificationError> { + let key = key + .try_into() + .map_err(|_| slice::VerificationError::WrongVerificationKeyLength)?; + let signature = signature + .try_into() + .map_err(|_| slice::VerificationError::WrongSignatureLength)?; + + arrayref::$ty::verify(key, payload, signature) + .map_err(slice::VerificationError::from) + } + pub fn keygen_derand( + signing_key: &mut [U8], + verification_key: &mut [u8], + randomness: [U8; Self::RAND_KEYGEN_LEN], + ) -> Result<(), slice::KeygenError> { + let signing_key = signing_key + .try_into() + .map_err(|_| slice::KeygenError::WrongSigningKeyLength)?; + let verification_key = verification_key + .try_into() + .map_err(|_| slice::KeygenError::WrongVerificationKeyLength)?; + + arrayref::$ty::keygen_derand(signing_key, verification_key, randomness) + } + } + impl<'a> SigningKeyRef<'a> { + pub fn sign( + &self, + payload: &[u8], + signature: &mut [u8], + nonce: &Nonce, + ) -> Result<(), slice::SigningError> { + slice::$ty::sign(self.as_ref(), payload, signature, nonce) + } + } + impl<'a> VerificationKeyRef<'a> { + pub fn verify( + &self, + payload: &[u8], + signature: &[u8], + ) -> Result<(), slice::VerificationError> { + slice::$ty::verify(self.as_ref(), payload, signature) + } + } + + // key-centric API + impl SigningKey { + pub fn sign( + &self, + payload: &[u8], + nonce: &Nonce, + ) -> Result { + let mut signature = [0u8; SIGNATURE_LEN]; + arrayref::$ty::sign(self.as_ref(), payload, &mut signature, nonce) + .map(|_| Signature::from(signature)) + } + } + impl VerificationKey { + pub fn verify( + &self, + payload: &[u8], + signature: &Signature, + ) -> Result<(), slice::VerificationError> { + arrayref::$ty::verify(self.as_ref(), payload, signature.as_ref()) + } + } + }; +} + +pub mod sha2_256 { + + impl_mod!( + EcdsaP256, + Sha2_256, + ecdsa_sign_p256_sha2, + ecdsa_verif_p256_sha2 + ); +} + +pub mod sha2_384 { + + impl_mod!( + EcdsaP256, + Sha2_384, + ecdsa_sign_p256_sha384, + ecdsa_verif_p256_sha384 + ); +} + +pub mod sha2_512 { + + impl_mod!( + EcdsaP256, + Sha2_512, + ecdsa_sign_p256_sha512, + ecdsa_verif_p256_sha512 + ); +} + +#[test] +#[cfg(all(feature = "rand", not(feature = "check-secret-independence")))] +fn key_centric_owned() { + use rand::TryRngCore; + let mut rng = rand_core::OsRng; + let mut rng = rng.unwrap_mut(); + use libcrux_traits::signature::SignConsts; + + use sha2_256::{EcdsaP256, KeyPair, SigningKey, VerificationKey}; + + // keys can be created from arrays + let _signing_key = SigningKey::from([0u8; EcdsaP256::SIGNING_KEY_LEN]); + let _verification_key = VerificationKey::from([0u8; EcdsaP256::VERIFICATION_KEY_LEN]); + + // key-centric API + let KeyPair { + signing_key, + verification_key, + } = EcdsaP256::generate_key_pair(&mut rng).unwrap(); + + let signature = signing_key + .sign(b"payload", &Nonce::random(&mut rng).unwrap()) + .unwrap(); + verification_key.verify(b"payload", &signature).unwrap(); +} + +#[test] +#[cfg(all(feature = "rand", not(feature = "check-secret-independence")))] +fn key_centric_refs() { + use libcrux_traits::signature::SignConsts; + use sha2_256::{EcdsaP256, SigningKeyRef, VerificationKeyRef}; + + use rand::{RngCore, TryRngCore}; + let mut rng = rand_core::OsRng; + let mut rng = rng.unwrap_mut(); + + let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; + let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; + + let mut bytes = [0u8; EcdsaP256::RAND_KEYGEN_LEN]; + rng.fill_bytes(&mut bytes); + EcdsaP256::keygen_derand(&mut signing_key, &mut verification_key, bytes).unwrap(); + + // create references from slice + let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); + let verification_key = VerificationKeyRef::from_slice(&verification_key).unwrap(); + + let mut signature = [0u8; EcdsaP256::SIGNATURE_LEN]; + signing_key + .sign( + b"payload", + &mut signature, + &Nonce::random(&mut rng).unwrap(), + ) + .unwrap(); + verification_key.verify(b"payload", &signature).unwrap(); +} + +#[test] +#[cfg(not(feature = "check-secret-independence"))] +fn arrayref_apis() { + use libcrux_traits::signature::SignConsts; + use sha2_256::EcdsaP256; + + use rand::{RngCore, TryRngCore}; + let mut rng = rand_core::OsRng; + let mut rng = rng.unwrap_mut(); + + let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; + let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; + + let mut bytes = [0u8; EcdsaP256::RAND_KEYGEN_LEN]; + rng.fill_bytes(&mut bytes); + EcdsaP256::keygen_derand(&mut signing_key, &mut verification_key, bytes).unwrap(); + + // arrayref API + let mut signature = [0u8; EcdsaP256::SIGNATURE_LEN]; + EcdsaP256::sign( + &signing_key, + b"payload", + &mut signature, + &Nonce::random(&mut rng).unwrap(), + ) + .unwrap(); + EcdsaP256::verify(&verification_key, b"payload", &signature).unwrap(); +} +#[test] +#[cfg(not(feature = "check-secret-independence"))] +fn slice_apis() { + use libcrux_traits::signature::SignConsts; + use sha2_256::slice::EcdsaP256; + + use rand::{RngCore, TryRngCore}; + let mut rng = rand_core::OsRng; + let mut rng = rng.unwrap_mut(); + + let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; + let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; + + let mut bytes = [0u8; EcdsaP256::RAND_KEYGEN_LEN]; + rng.fill_bytes(&mut bytes); + EcdsaP256::keygen_derand(&mut signing_key, &mut verification_key, bytes).unwrap(); + + // slice API + let mut signature = [0u8; EcdsaP256::SIGNATURE_LEN]; + EcdsaP256::sign( + &signing_key, + b"payload", + &mut signature, + &Nonce::random(&mut rng).unwrap(), + ) + .unwrap(); + EcdsaP256::verify(&verification_key, b"payload", &signature).unwrap(); +} diff --git a/crates/algorithms/ecdsa/src/lib.rs b/crates/algorithms/ecdsa/src/lib.rs index f3c375424d..7ea51501ba 100644 --- a/crates/algorithms/ecdsa/src/lib.rs +++ b/crates/algorithms/ecdsa/src/lib.rs @@ -28,3 +28,5 @@ pub type DigestAlgorithm = libcrux_sha2::Algorithm; /// The number of iteration for rejection sampling. #[cfg(feature = "rand")] pub(crate) const RAND_LIMIT: usize = 100; + +pub mod key_centric_apis; From 0500b44be0da0fd8be286b472fff0230a50c16a9 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 11 Nov 2025 10:17:31 +0100 Subject: [PATCH 06/61] implement `sign_mut()` --- .../src/ml_dsa_generic/multiplexing.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/libcrux-ml-dsa/src/ml_dsa_generic/multiplexing.rs b/libcrux-ml-dsa/src/ml_dsa_generic/multiplexing.rs index d297e00959..2b3fabef8a 100644 --- a/libcrux-ml-dsa/src/ml_dsa_generic/multiplexing.rs +++ b/libcrux-ml-dsa/src/ml_dsa_generic/multiplexing.rs @@ -12,6 +12,7 @@ macro_rules! parameter_set { #[cfg(all(feature = "simd256", feature = $feature))] use instantiations::avx2::$parameter_module::{ generate_key_pair as generate_key_pair_avx2, sign as sign_avx2, + sign_mut as sign_mut_avx2, sign_pre_hashed_shake128 as sign_pre_hashed_shake128_avx2, verify as verify_avx2, verify_pre_hashed_shake128 as verify_pre_hashed_shake128_avx2, }; @@ -24,6 +25,7 @@ macro_rules! parameter_set { #[cfg(all(feature = "simd128", feature = $feature))] use instantiations::neon::$parameter_module::{ generate_key_pair as generate_key_pair_neon, sign as sign_neon, + sign_mut as sign_mut_neon, sign_pre_hashed_shake128 as sign_pre_hashed_shake128_neon, verify as verify_neon, verify_pre_hashed_shake128 as verify_pre_hashed_shake128_neon, }; @@ -39,6 +41,7 @@ macro_rules! parameter_set { #[cfg(all(not(feature = "simd256"), feature = $feature))] use instantiations::portable::$parameter_module::{ generate_key_pair as generate_key_pair_avx2, sign as sign_avx2, + sign_mut as sign_mut_avx2, sign_pre_hashed_shake128 as sign_pre_hashed_shake128_avx2, verify as verify_avx2, verify_pre_hashed_shake128 as verify_pre_hashed_shake128_avx2, }; @@ -51,6 +54,7 @@ macro_rules! parameter_set { #[cfg(all(not(feature = "simd128"), feature = $feature))] use instantiations::portable::$parameter_module::{ generate_key_pair as generate_key_pair_neon, sign as sign_neon, + sign_mut as sign_mut_neon, sign_pre_hashed_shake128 as sign_pre_hashed_shake128_neon, verify as verify_neon, verify_pre_hashed_shake128 as verify_pre_hashed_shake128_neon, }; @@ -117,6 +121,28 @@ macro_rules! parameter_set { } } + pub(crate) fn sign_mut( + signing_key: &[u8; SIGNING_KEY_SIZE], + message: &[u8], + context: &[u8], + randomness: [u8; SIGNING_RANDOMNESS_SIZE], + signature: &mut [u8; SIGNATURE_SIZE], + ) -> Result<(), SigningError> { + if libcrux_platform::simd256_support() { + sign_mut_avx2(signing_key, message, context, randomness, signature) + } else if libcrux_platform::simd128_support() { + sign_mut_neon(signing_key, message, context, randomness, signature) + } else { + instantiations::portable::$parameter_module::sign_mut( + signing_key, + message, + context, + randomness, + signature, + ) + } + } + pub(crate) fn sign_pre_hashed_shake128( signing_key: &[u8; SIGNING_KEY_SIZE], message: &[u8], From 33d604d0a02f300e29d3bd6a25577cacf85317bc Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 11 Nov 2025 10:22:33 +0100 Subject: [PATCH 07/61] libcrux-ml-dsa public API implementation --- libcrux-ml-dsa/Cargo.toml | 9 + libcrux-ml-dsa/src/key_centric_apis.rs | 540 +++++++++++++++++++++++++ libcrux-ml-dsa/src/lib.rs | 3 + 3 files changed, 552 insertions(+) create mode 100644 libcrux-ml-dsa/src/key_centric_apis.rs diff --git a/libcrux-ml-dsa/Cargo.toml b/libcrux-ml-dsa/Cargo.toml index de4cb04d4c..b60773a928 100644 --- a/libcrux-ml-dsa/Cargo.toml +++ b/libcrux-ml-dsa/Cargo.toml @@ -22,6 +22,9 @@ libcrux-sha3 = { version = "0.0.4", path = "../crates/algorithms/sha3" } libcrux-intrinsics = { version = "0.0.4", path = "../crates/utils/intrinsics" } libcrux-platform = { version = "0.0.2", path = "../crates/sys/platform" } libcrux-macros = { version = "=0.0.3", path = "../crates/utils/macros" } +libcrux-traits = { version = "=0.0.4", path = "../traits/" } +libcrux-secrets = { version = "=0.0.4", path = "../crates/utils/secrets" } +rand = { version = "0.9", default-features = false, optional = true } hax-lib.workspace = true [dev-dependencies] @@ -42,12 +45,18 @@ pqcrypto-mldsa = { version = "0.1.0" } #, default-features = false core-models = { path = "../crates/utils/core-models", version = "0.0.4" } [features] +rand = ["dep:rand"] default = ["std", "mldsa44", "mldsa65", "mldsa87"] simd128 = ["libcrux-sha3/simd128", "libcrux-intrinsics/simd128"] simd256 = ["libcrux-sha3/simd256", "libcrux-intrinsics/simd256"] acvp = [] # expose internal API for ACVP testing test-utils = [] # exposing internal functions for testing + +# This doesn't check the secret independence of the implementation, but sets the +# exposed API into secret independence checking mode. +expose-secret-independence = ["libcrux-secrets/check-secret-independence"] + # Features for the different key sizes of ML-DSA mldsa44 = [] mldsa65 = [] diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs new file mode 100644 index 0000000000..8f4c064935 --- /dev/null +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -0,0 +1,540 @@ +use libcrux_traits::signature::{impl_key_centric_types, impl_sign_traits, SignConsts}; + +// placeholder error implementation +// TODO: more specific error +#[derive(Debug)] +pub struct Error; +macro_rules! impl_mod { + ($ty:ident, $module:ident) => { + use libcrux_secrets::{Declassify, DeclassifyRef, DeclassifyRefMut, U8}; + + pub(super) const VERIFICATION_KEY_LEN: usize = + crate::ml_dsa_generic::$module::VERIFICATION_KEY_SIZE; + pub(super) const SIGNING_KEY_LEN: usize = crate::ml_dsa_generic::$module::SIGNING_KEY_SIZE; + pub(super) const SIGNATURE_LEN: usize = crate::ml_dsa_generic::$module::SIGNATURE_SIZE; + pub(super) const RAND_KEYGEN_LEN: usize = 32; + + use super::*; + // arrayref API + #[doc(inline)] + pub use arrayref::*; + + // TODO: different error type? + #[derive(Debug)] + /// An incorrect length when converting from slice. + pub struct WrongLengthError; + + pub mod arrayref { + #[derive(Debug, PartialEq)] + pub struct $ty; + use super::*; + impl_key_centric_types!( + $ty, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN, + WrongLengthError, + WrongLengthError + ); + } + pub mod slice { + #[derive(Debug, PartialEq)] + pub struct $ty; + use super::*; + impl_sign_traits!( + $ty, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN + ); + + // error type including wrong length + #[derive(Debug)] + pub enum SigningError { + RejectionSamplingError, + ContextTooLongError, + WrongSigningKeyLength, + WrongSignatureLength, + } + + impl From for SigningError { + fn from(e: crate::SigningError) -> Self { + match e { + crate::SigningError::RejectionSamplingError => { + SigningError::RejectionSamplingError + } + crate::SigningError::ContextTooLongError => { + SigningError::ContextTooLongError + } + } + } + } + + // error type including wrong length + #[derive(Debug)] + pub enum VerificationError { + MalformedHintError, + SignerResponseExceedsBoundError, + CommitmentHashesDontMatchError, + VerificationContextTooLongError, + WrongVerificationKeyLength, + WrongSignatureLength, + } + + impl From for VerificationError { + fn from(e: crate::VerificationError) -> Self { + match e { + crate::VerificationError::MalformedHintError => { + VerificationError::MalformedHintError + } + crate::VerificationError::SignerResponseExceedsBoundError => { + VerificationError::SignerResponseExceedsBoundError + } + crate::VerificationError::CommitmentHashesDontMatchError => { + VerificationError::CommitmentHashesDontMatchError + } + crate::VerificationError::VerificationContextTooLongError => { + VerificationError::VerificationContextTooLongError + } + } + } + } + + #[derive(Debug)] + pub enum KeygenError { + WrongSigningKeyLength, + WrongVerificationKeyLength, + } + } + + impl arrayref::$ty { + #[cfg(feature = "rand")] + pub fn generate_key_pair(rng: &mut impl rand::CryptoRng) -> KeyPair { + use libcrux_secrets::Classify; + + let mut bytes = [0u8; Self::RAND_KEYGEN_LEN]; + rng.fill_bytes(&mut bytes); + let mut signing_key = [0u8; Self::SIGNING_KEY_LEN].classify(); + let mut verification_key = [0u8; Self::VERIFICATION_KEY_LEN]; + arrayref::$ty::keygen_derand(&mut signing_key, &mut verification_key, bytes.classify()); + + KeyPair { + signing_key: SigningKey::from(signing_key), + verification_key: VerificationKey::from(verification_key), + } + } + } + impl arrayref::$ty { + pub fn sign( + key: &[U8; Self::SIGNING_KEY_LEN], + payload: &[u8], + signature: &mut [u8; Self::SIGNATURE_LEN], + context: &[u8], + randomness: [U8; 32], + ) -> Result<(), crate::SigningError> { + crate::ml_dsa_generic::multiplexing::$module::sign_mut( + // length is already validated + key.declassify_ref().try_into().unwrap(), + payload, + context, + randomness.declassify(), + signature, + ) + } + pub fn sign_pre_hashed_shake128( + key: &[U8; Self::SIGNING_KEY_LEN], + payload: &[u8], + signature: &mut [u8; Self::SIGNATURE_LEN], + context: &[u8], + randomness: [U8; 32], + ) -> Result<(), crate::SigningError> { + // TODO: use mut version + let mut pre_hash_buffer = [0; 256]; + let signature_out = + crate::ml_dsa_generic::multiplexing::$module::sign_pre_hashed_shake128( + key.declassify_ref().try_into().unwrap(), + payload, + context, + &mut pre_hash_buffer, + randomness.declassify(), + )?; + + signature.copy_from_slice(signature_out.as_slice()); + + Ok(()) + } + + pub fn verify( + key: &[u8; Self::VERIFICATION_KEY_LEN], + payload: &[u8], + signature: &[u8; Self::SIGNATURE_LEN], + context: &[u8], + ) -> Result<(), crate::VerificationError> { + crate::ml_dsa_generic::multiplexing::$module::verify( + key, payload, context, signature, + ) + } + pub fn verify_pre_hashed_shake128( + key: &[u8; Self::VERIFICATION_KEY_LEN], + payload: &[u8], + signature: &[u8; Self::SIGNATURE_LEN], + context: &[u8], + ) -> Result<(), crate::VerificationError> { + let mut pre_hash_buffer = [0; 256]; + crate::ml_dsa_generic::multiplexing::$module::verify_pre_hashed_shake128( + key, + payload, + context, + &mut pre_hash_buffer, + signature, + ) + } + pub fn keygen_derand( + signing_key: &mut [U8; Self::SIGNING_KEY_LEN], + verification_key: &mut [u8; Self::VERIFICATION_KEY_LEN], + randomness: [U8; Self::RAND_KEYGEN_LEN], + ) { + crate::ml_dsa_generic::multiplexing::$module::generate_key_pair( + randomness.declassify(), + signing_key.declassify_ref_mut(), + verification_key, + ); + } + } + impl slice::$ty { + pub fn sign( + key: &[U8], + payload: &[u8], + signature: &mut [u8], + context: &[u8], + randomness: [U8; 32], + ) -> Result<(), slice::SigningError> { + let key = key + .try_into() + .map_err(|_| slice::SigningError::WrongSigningKeyLength)?; + let signature = signature + .try_into() + .map_err(|_| slice::SigningError::WrongSignatureLength)?; + + arrayref::$ty::sign(&key, payload, signature, context, randomness) + .map_err(slice::SigningError::from) + } + pub fn sign_pre_hashed_shake128( + key: &[U8], + payload: &[u8], + signature: &mut [u8], + context: &[u8], + randomness: [U8; 32], + ) -> Result<(), slice::SigningError> { + let key = key + .try_into() + .map_err(|_| slice::SigningError::WrongSigningKeyLength)?; + let signature = signature + .try_into() + .map_err(|_| slice::SigningError::WrongSignatureLength)?; + + arrayref::$ty::sign_pre_hashed_shake128( + &key, payload, signature, context, randomness, + ) + .map_err(slice::SigningError::from) + } + pub fn verify( + key: &[u8], + payload: &[u8], + signature: &[u8], + context: &[u8], + ) -> Result<(), slice::VerificationError> { + let key = key + .try_into() + .map_err(|_| slice::VerificationError::WrongVerificationKeyLength)?; + let signature = signature + .try_into() + .map_err(|_| slice::VerificationError::WrongSignatureLength)?; + + arrayref::$ty::verify(key, payload, signature, context) + .map_err(slice::VerificationError::from) + } + pub fn verify_pre_hashed_shake128( + key: &[u8], + payload: &[u8], + signature: &[u8], + context: &[u8], + ) -> Result<(), slice::VerificationError> { + let key = key + .try_into() + .map_err(|_| slice::VerificationError::WrongVerificationKeyLength)?; + let signature = signature + .try_into() + .map_err(|_| slice::VerificationError::WrongSignatureLength)?; + + arrayref::$ty::verify_pre_hashed_shake128(key, payload, signature, context) + .map_err(slice::VerificationError::from) + } + pub fn keygen_derand( + signing_key: &mut [U8], + verification_key: &mut [u8], + randomness: [U8; Self::RAND_KEYGEN_LEN], + ) -> Result<(), slice::KeygenError> { + let signing_key = signing_key + .try_into() + .map_err(|_| slice::KeygenError::WrongSigningKeyLength)?; + let verification_key = verification_key + .try_into() + .map_err(|_| slice::KeygenError::WrongVerificationKeyLength)?; + + arrayref::$ty::keygen_derand(signing_key, verification_key, randomness); + + Ok(()) + } + } + impl<'a> SigningKeyRef<'a> { + pub fn sign( + &self, + payload: &[u8], + signature: &mut [u8], + context: &[u8], + randomness: [U8; 32], + ) -> Result<(), slice::SigningError> { + slice::$ty::sign(self.as_ref(), payload, signature, context, randomness) + } + pub fn sign_pre_hashed_shake128( + &self, + payload: &[u8], + signature: &mut [u8], + context: &[u8], + randomness: [U8; 32], + ) -> Result<(), slice::SigningError> { + slice::$ty::sign_pre_hashed_shake128( + self.as_ref(), + payload, + signature, + context, + randomness, + ) + } + } + impl<'a> VerificationKeyRef<'a> { + pub fn verify( + &self, + payload: &[u8], + signature: &[u8], + context: &[u8], + ) -> Result<(), slice::VerificationError> { + slice::$ty::verify(self.as_ref(), payload, signature, context) + } + pub fn verify_pre_hashed_shake128( + &self, + payload: &[u8], + signature: &[u8], + context: &[u8], + ) -> Result<(), slice::VerificationError> { + slice::$ty::verify_pre_hashed_shake128(self.as_ref(), payload, signature, context) + } + } + + // key-centric API + impl SigningKey { + pub fn sign( + &self, + payload: &[u8], + context: &[u8], + randomness: [U8; 32], + ) -> Result { + let mut signature = [0u8; SIGNATURE_LEN]; + arrayref::$ty::sign(self.as_ref(), payload, &mut signature, context, randomness) + .map(|_| Signature::from(signature)) + } + pub fn sign_pre_hashed_shake128( + &self, + payload: &[u8], + context: &[u8], + randomness: [U8; 32], + ) -> Result { + let mut signature = [0u8; SIGNATURE_LEN]; + arrayref::$ty::sign_pre_hashed_shake128( + self.as_ref(), + payload, + &mut signature, + context, + randomness, + ) + .map(|_| Signature::from(signature)) + } + } + impl VerificationKey { + pub fn verify( + &self, + payload: &[u8], + signature: &Signature, + context: &[u8], + ) -> Result<(), crate::VerificationError> { + arrayref::$ty::verify(self.as_ref(), payload, signature.as_ref(), context) + } + pub fn verify_pre_hashed_shake128( + &self, + payload: &[u8], + signature: &Signature, + context: &[u8], + ) -> Result<(), crate::VerificationError> { + arrayref::$ty::verify_pre_hashed_shake128( + self.as_ref(), + payload, + signature.as_ref(), + context, + ) + } + } + }; +} + +#[cfg(feature = "mldsa44")] +pub mod ml_dsa_44 { + + impl_mod!(MlDsa44, ml_dsa_44); +} + +#[cfg(feature = "mldsa65")] +pub mod ml_dsa_65 { + + impl_mod!(MlDsa65, ml_dsa_65); +} + +#[cfg(feature = "mldsa87")] +pub mod ml_dsa_87 { + + impl_mod!(MlDsa87, ml_dsa_87); +} + +#[test] +#[cfg(all(feature = "rand", not(feature = "expose-secret-independence")))] +fn key_centric_owned() { + use rand::TryRngCore; + let mut rng = rand::rngs::OsRng; + let mut rng = rng.unwrap_mut(); + use libcrux_traits::signature::SignConsts; + + use ml_dsa_44::{KeyPair, MlDsa44, SigningKey, VerificationKey}; + + let context = b"context"; + + // keys can be created from arrays + let _signing_key = SigningKey::from([0u8; MlDsa44::SIGNING_KEY_LEN]); + let _verification_key = VerificationKey::from([0u8; MlDsa44::VERIFICATION_KEY_LEN]); + + // key-centric API + let KeyPair { + signing_key, + verification_key, + } = MlDsa44::generate_key_pair(&mut rng); + + let signature = signing_key.sign(b"payload", context, [0u8; 32]).unwrap(); + verification_key + .verify(b"payload", &signature, context) + .unwrap(); + + let pre_hashed = b"pre-hashed"; + + let signature_from_pre_hashed = signing_key + .sign_pre_hashed_shake128(pre_hashed, context, [0u8; 32]) + .unwrap(); + verification_key + .verify_pre_hashed_shake128(pre_hashed, &signature_from_pre_hashed, context) + .unwrap(); +} + +#[test] +#[cfg(all(feature = "rand", not(feature = "expose-secret-independence")))] +fn key_centric_refs() { + use libcrux_traits::signature::SignConsts; + use ml_dsa_44::*; + + let context = b"context"; + + let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; + let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; + MlDsa44::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]); + + // create references from slice + let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); + let verification_key = VerificationKeyRef::from_slice(&verification_key).unwrap(); + + let mut signature = [0u8; MlDsa44::SIGNATURE_LEN]; + signing_key + .sign(b"payload", &mut signature, context, [0u8; 32]) + .unwrap(); + verification_key + .verify(b"payload", &signature, context) + .unwrap(); + + signing_key + .sign_pre_hashed_shake128(b"pre-hashed", &mut signature, context, [0u8; 32]) + .unwrap(); + verification_key + .verify_pre_hashed_shake128(b"pre-hashed", &signature, context) + .unwrap(); +} + +#[test] +#[cfg(not(feature = "expose-secret-independence"))] +fn arrayref_apis() { + use libcrux_traits::signature::SignConsts; + use ml_dsa_44::MlDsa44; + + let context = b"context"; + + let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; + let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; + MlDsa44::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]); + + // arrayref API + let mut signature = [0u8; MlDsa44::SIGNATURE_LEN]; + MlDsa44::sign(&signing_key, b"payload", &mut signature, context, [0u8; 32]).unwrap(); + MlDsa44::verify(&verification_key, b"payload", &signature, context).unwrap(); + + // pre-hashed + MlDsa44::sign_pre_hashed_shake128( + &signing_key, + b"pre-hashed", + &mut signature, + context, + [0u8; 32], + ) + .unwrap(); + MlDsa44::verify_pre_hashed_shake128(&verification_key, b"pre-hashed", &signature, context) + .unwrap(); +} +#[test] +#[cfg(not(feature = "expose-secret-independence"))] +fn slice_apis() { + use libcrux_traits::signature::SignConsts; + use ml_dsa_44::slice::MlDsa44; + + let context = b"context"; + + let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; + let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; + MlDsa44::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]).unwrap(); + + // slice API + let mut signature = [0u8; MlDsa44::SIGNATURE_LEN]; + MlDsa44::sign(&signing_key, b"payload", &mut signature, context, [0u8; 32]).unwrap(); + MlDsa44::verify(&verification_key, b"payload", &signature, context).unwrap(); + + MlDsa44::sign_pre_hashed_shake128( + signing_key.as_ref(), + b"pre-hashed", + &mut signature, + context, + [0u8; 32], + ) + .unwrap(); + MlDsa44::verify_pre_hashed_shake128( + verification_key.as_ref(), + b"pre-hashed", + &signature, + context, + ) + .unwrap(); +} diff --git a/libcrux-ml-dsa/src/lib.rs b/libcrux-ml-dsa/src/lib.rs index 4c54c6d3d0..b75d87046f 100644 --- a/libcrux-ml-dsa/src/lib.rs +++ b/libcrux-ml-dsa/src/lib.rs @@ -39,3 +39,6 @@ pub mod ml_dsa_65; #[cfg(feature = "mldsa87")] pub mod ml_dsa_87; + +#[cfg(not(hax))] +pub mod key_centric_apis; From a82a2f8c6ebd7ea71f88153aa07c437866a121ae Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 11 Nov 2025 10:25:40 +0100 Subject: [PATCH 08/61] unify feature names --- crates/algorithms/ecdsa/Cargo.toml | 5 ++++- crates/algorithms/ecdsa/src/key_centric_apis.rs | 8 ++++---- crates/algorithms/ed25519/Cargo.toml | 5 ++++- crates/algorithms/ed25519/src/key_centric_apis.rs | 6 +++--- libcrux-ml-dsa/Cargo.toml | 5 ++++- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/crates/algorithms/ecdsa/Cargo.toml b/crates/algorithms/ecdsa/Cargo.toml index 29545bc6f8..32b6f83f24 100644 --- a/crates/algorithms/ecdsa/Cargo.toml +++ b/crates/algorithms/ecdsa/Cargo.toml @@ -23,7 +23,10 @@ rand = { version = "0.9", optional = true, default-features = false } default = ["rand", "std"] rand = ["dep:rand"] std = ["rand?/std"] -check-secret-independence = [ + +# This doesn't check the secret independence of the implementation, but sets the +# exposed API into secret independence checking mode. +expose-secret-independence = [ "libcrux-secrets/check-secret-independence", "libcrux-traits/check-secret-independence", ] diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index c36be910e2..c733a5deee 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -284,7 +284,7 @@ pub mod sha2_512 { } #[test] -#[cfg(all(feature = "rand", not(feature = "check-secret-independence")))] +#[cfg(all(feature = "rand", not(feature = "expose-secret-independence")))] fn key_centric_owned() { use rand::TryRngCore; let mut rng = rand_core::OsRng; @@ -310,7 +310,7 @@ fn key_centric_owned() { } #[test] -#[cfg(all(feature = "rand", not(feature = "check-secret-independence")))] +#[cfg(all(feature = "rand", not(feature = "expose-secret-independence")))] fn key_centric_refs() { use libcrux_traits::signature::SignConsts; use sha2_256::{EcdsaP256, SigningKeyRef, VerificationKeyRef}; @@ -342,7 +342,7 @@ fn key_centric_refs() { } #[test] -#[cfg(not(feature = "check-secret-independence"))] +#[cfg(not(feature = "expose-secret-independence"))] fn arrayref_apis() { use libcrux_traits::signature::SignConsts; use sha2_256::EcdsaP256; @@ -370,7 +370,7 @@ fn arrayref_apis() { EcdsaP256::verify(&verification_key, b"payload", &signature).unwrap(); } #[test] -#[cfg(not(feature = "check-secret-independence"))] +#[cfg(not(feature = "expose-secret-independence"))] fn slice_apis() { use libcrux_traits::signature::SignConsts; use sha2_256::slice::EcdsaP256; diff --git a/crates/algorithms/ed25519/Cargo.toml b/crates/algorithms/ed25519/Cargo.toml index ff96b9e079..c54cde470a 100644 --- a/crates/algorithms/ed25519/Cargo.toml +++ b/crates/algorithms/ed25519/Cargo.toml @@ -29,7 +29,10 @@ wycheproof = "0.6.0" [features] rand = ["dep:rand_core"] codec = ["tls_codec/std"] -check-secret-independence = [ + +# This doesn't check the secret independence of the implementation, but sets the +# exposed API into secret independence checking mode. +expose-secret-independence = [ "libcrux-traits/check-secret-independence", "libcrux-secrets/check-secret-independence", ] diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index 32562b2b5b..541fdcdd0d 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -226,7 +226,7 @@ fn key_centric_owned() { } #[test] -#[cfg(all(feature = "rand", not(feature = "check-secret-independence")))] +#[cfg(all(feature = "rand", not(feature = "expose-secret-independence")))] fn key_centric_refs() { use libcrux_traits::signature::SignConsts; @@ -244,7 +244,7 @@ fn key_centric_refs() { } #[test] -#[cfg(not(feature = "check-secret-independence"))] +#[cfg(not(feature = "expose-secret-independence"))] fn arrayref_apis() { use libcrux_traits::signature::SignConsts; @@ -258,7 +258,7 @@ fn arrayref_apis() { Ed25519::verify(&verification_key, b"payload", &signature).unwrap(); } #[test] -#[cfg(not(feature = "check-secret-independence"))] +#[cfg(not(feature = "expose-secret-independence"))] fn slice_apis() { use libcrux_traits::signature::SignConsts; diff --git a/libcrux-ml-dsa/Cargo.toml b/libcrux-ml-dsa/Cargo.toml index b60773a928..0586312d29 100644 --- a/libcrux-ml-dsa/Cargo.toml +++ b/libcrux-ml-dsa/Cargo.toml @@ -55,7 +55,10 @@ test-utils = [] # exposing inte # This doesn't check the secret independence of the implementation, but sets the # exposed API into secret independence checking mode. -expose-secret-independence = ["libcrux-secrets/check-secret-independence"] +expose-secret-independence = [ + "libcrux-secrets/check-secret-independence", + "libcrux-traits/check-secret-independence", +] # Features for the different key sizes of ML-DSA mldsa44 = [] From e9864f3f0680e1f9d856f3c18e195ec4ad00a7fe Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 11 Nov 2025 10:27:00 +0100 Subject: [PATCH 09/61] add `libcrux-signature` crate --- Cargo.toml | 1 + crates/primitives/signature/Cargo.toml | 37 +++++++++++ crates/primitives/signature/src/lib.rs | 92 ++++++++++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 crates/primitives/signature/Cargo.toml create mode 100644 crates/primitives/signature/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 97a6c04638..6277133d7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ members = [ "crates/algorithms/aesgcm", "crates/algorithms/aesgcm/fuzz", "crates/utils/hacl-rs", + "crates/primitives/signature", ] [workspace.package] diff --git a/crates/primitives/signature/Cargo.toml b/crates/primitives/signature/Cargo.toml new file mode 100644 index 0000000000..c88e937043 --- /dev/null +++ b/crates/primitives/signature/Cargo.toml @@ -0,0 +1,37 @@ +[package] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +name = "libcrux-signature" +readme.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +libcrux-ecdsa = { version = "0.0.4", path = "../../../ecdsa", optional = true } +libcrux-ed25519 = { version = "0.0.4", path = "../../../ed25519", optional = true } +libcrux-ml-dsa = { version = "0.0.4", path = "../../../libcrux-ml-dsa", optional = true } +libcrux-traits = { version = "0.0.4", path = "../../../traits", optional = true } +libcrux-secrets = { version = "0.0.4", path = "../../../secrets", optional = true } + +[features] +default = ["ed25519", "ecdsa", "mldsa", "rand"] +rand = ["libcrux-ed25519?/rand", "libcrux-ecdsa?/rand", "libcrux-ml-dsa?/rand"] + +check-secret-independence = [ + "libcrux-secrets/check-secret-independence", + "libcrux-traits/check-secret-independence", + "libcrux-ml-dsa?/expose-secret-independence", + "libcrux-ed25519?/expose-secret-independence", + "libcrux-ecdsa?/expose-secret-independence", +] + +ecdsa = ["dep:libcrux-ecdsa", "any"] +ed25519 = ["dep:libcrux-ed25519", "any"] +mldsa = ["dep:libcrux-ml-dsa", "any"] + +any = ["dep:libcrux-traits", "dep:libcrux-secrets"] + +[dev-dependencies] +rand = "0.9" diff --git a/crates/primitives/signature/src/lib.rs b/crates/primitives/signature/src/lib.rs new file mode 100644 index 0000000000..de920b7e80 --- /dev/null +++ b/crates/primitives/signature/src/lib.rs @@ -0,0 +1,92 @@ +#[cfg(any(feature = "ecdsa", feature = "ed25519", feature = "mldsa"))] +pub use libcrux_traits::signature::{SignConsts, SignTypes}; + +#[cfg(feature = "ecdsa")] +pub mod ecdsa { + pub mod p256 { + //! ```rust + //! use libcrux_signature::ecdsa::p256::{ + //! Nonce, sha2_256::{EcdsaP256, KeyPair, SigningKey, VerificationKey} + //! }; + //! + //! // generate a new nonce + //! use rand::TryRngCore; + //! let mut rng = rand::rngs::OsRng; + //! let nonce = Nonce::random(&mut rng.unwrap_mut()).unwrap(); + //! + //! // generate a new signature keypair from random bytes + //! let KeyPair { signing_key, verification_key } = + //! EcdsaP256::generate_key_pair(&mut rng.unwrap_mut()).unwrap(); + //! + //! // sign + //! let signature = signing_key.sign(b"payload", &nonce).unwrap(); + //! + //! // verify + //! verification_key.verify(b"payload", &signature).unwrap(); + //! ``` + pub use libcrux_ecdsa::key_centric_apis::{sha2_256, sha2_384, sha2_512}; + pub use libcrux_ecdsa::p256::Nonce; + } +} + +#[cfg(feature = "ed25519")] +pub mod ed25519 { + //! ```rust + //! use libcrux_signature::ed25519::{Ed25519, KeyPair}; + //! + //! use rand::TryRngCore; + //! let mut rng = rand::rngs::OsRng; + //! // generate a new signature keypair from random bytes + //! // requires `rand` feature + //! let KeyPair { signing_key, verification_key } + //! = Ed25519::generate_key_pair(&mut rng.unwrap_mut()); + //! + //! // sign + //! let signature = signing_key.sign(b"payload").unwrap(); + //! + //! // verify + //! verification_key.verify(b"payload", &signature).unwrap(); + //! ``` + pub use libcrux_ed25519::key_centric_apis::{ + Ed25519, KeyPair, SigningKey, SigningKeyRef, VerificationKey, VerificationKeyRef, + }; +} + +#[cfg(feature = "mldsa")] +pub mod mldsa { + //! ```rust + //! use libcrux_signature::mldsa::{MlDsa44, SigningKey, KeyPair, VerificationKey}; + //! + + //! use rand::TryRngCore; + //! let mut rng = rand::rngs::OsRng; + //! + //! // generate a new signature keypair from random bytes + //! // requires `rand` feature + //! let KeyPair { signing_key, verification_key } + //! = MlDsa44::generate_key_pair(&mut rng.unwrap_mut()); + //! + //! // sign + //! let signature = signing_key.sign(b"payload", b"context", [2; 32]).unwrap(); + //! + //! // verify + //! verification_key.verify(b"payload", &signature, b"context").unwrap(); + //! ``` + //! + //! ```rust + //! use libcrux_signature::mldsa::{MlDsa44, SigningKeyRef}; + //! use libcrux_traits::signature::SignConsts; + //! + //! + //! // signing key from bytes + //! let signing_key = SigningKeyRef::from_slice(&[1; 2560]).unwrap(); + //! + //! // sign with randomness + //! let mut signature = [0u8; MlDsa44::SIGNATURE_LEN]; + //! signing_key.sign(b"payload", &mut signature, b"context", [2; 32]).unwrap(); + //! + //! ``` + pub use libcrux_ml_dsa::key_centric_apis::ml_dsa_44::{ + KeyPair, MlDsa44, Signature, SigningKey, SigningKeyRef, VerificationKey, VerificationKeyRef, + }; +} From 5bf977b7bea0b6bc205172fdf001ccc5c92eb2dd Mon Sep 17 00:00:00 2001 From: wysiwys Date: Mon, 17 Nov 2025 10:20:39 +0100 Subject: [PATCH 10/61] add documentation --- crates/primitives/signature/src/lib.rs | 8 ++++++++ traits/src/signature.rs | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/crates/primitives/signature/src/lib.rs b/crates/primitives/signature/src/lib.rs index de920b7e80..0ead5e9c62 100644 --- a/crates/primitives/signature/src/lib.rs +++ b/crates/primitives/signature/src/lib.rs @@ -1,3 +1,11 @@ +//! This crate provides signature functionality. +//! +//! We currently support three signature algorithms: +//! +//! - ecdsa-p256 +//! - ed25519 +//! - ML-DSA + #[cfg(any(feature = "ecdsa", feature = "ed25519", feature = "mldsa"))] pub use libcrux_traits::signature::{SignConsts, SignTypes}; diff --git a/traits/src/signature.rs b/traits/src/signature.rs index 72ab856bf0..d897faed72 100644 --- a/traits/src/signature.rs +++ b/traits/src/signature.rs @@ -64,6 +64,7 @@ macro_rules! impl_key_centric_types { $rand_keygen_len ); + /// A signing key. The bytes are borrowed. pub struct SigningKeyRef<'a> { key: &'a [U8], } @@ -77,27 +78,36 @@ macro_rules! impl_key_centric_types { self.key.as_ref() } } + + /// A verification key. The bytes are borrowed. pub struct VerificationKeyRef<'a> { key: &'a [u8], } + /// A signing key. The bytes are owned. pub struct SigningKey { key: SigningKeyArray, } + + /// A verification key. The bytes are owned. pub struct VerificationKey { key: VerificationKeyArray, } + + /// A signature. #[derive(Debug, PartialEq)] pub struct Signature { signature: SignatureArray, } + /// A key pair, containing a [`SigningKey`] and a [`VerificationKey`]. pub struct KeyPair { pub signing_key: SigningKey, pub verification_key: VerificationKey, } impl<'a> SigningKeyRef<'a> { + /// Create a signing key from a byte slice. pub fn from_slice(key: &'a [U8]) -> Result { if key.len() != $signing_key_len { return Err($from_slice_error_variant); @@ -123,7 +133,9 @@ macro_rules! impl_key_centric_types { &self.key } } + impl<'a> VerificationKeyRef<'a> { + /// Create a verification key from a byte slice. pub fn from_slice(key: &'a [u8]) -> Result { if key.len() != $verification_key_len { return Err($from_slice_error_variant); From 104bc1543f93a08fc16aef0139e81a2df4703fca Mon Sep 17 00:00:00 2001 From: wysiwys Date: Mon, 17 Nov 2025 10:28:36 +0100 Subject: [PATCH 11/61] documentation --- libcrux-ml-dsa/src/key_centric_apis.rs | 97 +++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 8f4c064935..5ca23ef255 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -118,7 +118,11 @@ macro_rules! impl_mod { rng.fill_bytes(&mut bytes); let mut signing_key = [0u8; Self::SIGNING_KEY_LEN].classify(); let mut verification_key = [0u8; Self::VERIFICATION_KEY_LEN]; - arrayref::$ty::keygen_derand(&mut signing_key, &mut verification_key, bytes.classify()); + arrayref::$ty::keygen_derand( + &mut signing_key, + &mut verification_key, + bytes.classify(), + ); KeyPair { signing_key: SigningKey::from(signing_key), @@ -127,6 +131,11 @@ macro_rules! impl_mod { } } impl arrayref::$ty { + /// Generate an ML-DSA signature + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn sign( key: &[U8; Self::SIGNING_KEY_LEN], payload: &[u8], @@ -143,6 +152,11 @@ macro_rules! impl_mod { signature, ) } + /// Generate a HashML-DSA Signature, with a SHAKE128 pre-hashing + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn sign_pre_hashed_shake128( key: &[U8; Self::SIGNING_KEY_LEN], payload: &[u8], @@ -166,6 +180,11 @@ macro_rules! impl_mod { Ok(()) } + /// Verify an ML-DSA Signature + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn verify( key: &[u8; Self::VERIFICATION_KEY_LEN], payload: &[u8], @@ -176,6 +195,12 @@ macro_rules! impl_mod { key, payload, context, signature, ) } + + /// Verify an ML-DSA Signature, with a SHAKE128 pre-hashing + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn verify_pre_hashed_shake128( key: &[u8; Self::VERIFICATION_KEY_LEN], payload: &[u8], @@ -204,6 +229,11 @@ macro_rules! impl_mod { } } impl slice::$ty { + /// Generate an ML-DSA signature + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn sign( key: &[U8], payload: &[u8], @@ -221,6 +251,12 @@ macro_rules! impl_mod { arrayref::$ty::sign(&key, payload, signature, context, randomness) .map_err(slice::SigningError::from) } + + /// Generate a HashML-DSA Signature, with a SHAKE128 pre-hashing + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn sign_pre_hashed_shake128( key: &[U8], payload: &[u8], @@ -240,6 +276,12 @@ macro_rules! impl_mod { ) .map_err(slice::SigningError::from) } + + /// Verify an ML-DSA Signature + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn verify( key: &[u8], payload: &[u8], @@ -256,6 +298,12 @@ macro_rules! impl_mod { arrayref::$ty::verify(key, payload, signature, context) .map_err(slice::VerificationError::from) } + + /// Verify an ML-DSA Signature, with a SHAKE128 pre-hashing + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn verify_pre_hashed_shake128( key: &[u8], payload: &[u8], @@ -272,6 +320,9 @@ macro_rules! impl_mod { arrayref::$ty::verify_pre_hashed_shake128(key, payload, signature, context) .map_err(slice::VerificationError::from) } + + /// Generate an ML-DSA Key Pair + #[cfg(not(eurydice))] pub fn keygen_derand( signing_key: &mut [U8], verification_key: &mut [u8], @@ -290,6 +341,11 @@ macro_rules! impl_mod { } } impl<'a> SigningKeyRef<'a> { + /// Generate an ML-DSA signature + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn sign( &self, payload: &[u8], @@ -299,6 +355,12 @@ macro_rules! impl_mod { ) -> Result<(), slice::SigningError> { slice::$ty::sign(self.as_ref(), payload, signature, context, randomness) } + + /// Generate a HashML-DSA Signature, with a SHAKE128 pre-hashing + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn sign_pre_hashed_shake128( &self, payload: &[u8], @@ -316,6 +378,11 @@ macro_rules! impl_mod { } } impl<'a> VerificationKeyRef<'a> { + /// Verify an ML-DSA Signature + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn verify( &self, payload: &[u8], @@ -324,6 +391,12 @@ macro_rules! impl_mod { ) -> Result<(), slice::VerificationError> { slice::$ty::verify(self.as_ref(), payload, signature, context) } + + /// Verify an ML-DSA Signature, with a SHAKE128 pre-hashing + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn verify_pre_hashed_shake128( &self, payload: &[u8], @@ -336,6 +409,11 @@ macro_rules! impl_mod { // key-centric API impl SigningKey { + /// Generate an ML-DSA signature + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn sign( &self, payload: &[u8], @@ -346,6 +424,12 @@ macro_rules! impl_mod { arrayref::$ty::sign(self.as_ref(), payload, &mut signature, context, randomness) .map(|_| Signature::from(signature)) } + + /// Generate a HashML-DSA Signature, with a SHAKE128 pre-hashing + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn sign_pre_hashed_shake128( &self, payload: &[u8], @@ -364,6 +448,11 @@ macro_rules! impl_mod { } } impl VerificationKey { + /// Verify an ML-DSA Signature + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn verify( &self, payload: &[u8], @@ -372,6 +461,12 @@ macro_rules! impl_mod { ) -> Result<(), crate::VerificationError> { arrayref::$ty::verify(self.as_ref(), payload, signature.as_ref(), context) } + + /// Verify an ML-DSA Signature, with a SHAKE128 pre-hashing + /// + /// The parameter `context` is used for domain separation + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. pub fn verify_pre_hashed_shake128( &self, payload: &[u8], From 279bc7bf740d8e69f966bb258af741684dd07f45 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Mon, 17 Nov 2025 10:53:46 +0100 Subject: [PATCH 12/61] documentation and visibility --- .../ed25519/src/key_centric_apis.rs | 134 ++++++++++++++---- libcrux-ml-dsa/src/key_centric_apis.rs | 87 ++++++------ 2 files changed, 146 insertions(+), 75 deletions(-) diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index 541fdcdd0d..8c4e432061 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -1,6 +1,6 @@ use libcrux_traits::signature::{impl_key_centric_types, impl_sign_traits, SignConsts}; -use libcrux_secrets::{Classify, DeclassifyRef, U8}; +use libcrux_secrets::{Classify, DeclassifyRef}; const VERIFICATION_KEY_LEN: usize = 32; const SIGNING_KEY_LEN: usize = 32; const SIGNATURE_LEN: usize = 64; @@ -8,28 +8,49 @@ const RAND_KEYGEN_LEN: usize = SIGNING_KEY_LEN; // arrayref API #[doc(inline)] -pub use arrayref::*; +use arrayref::*; // TODO: different error type? #[derive(Debug)] /// An incorrect length when converting from slice. pub struct WrongLengthError; -pub mod arrayref { +impl_key_centric_types!( + Ed25519, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN, + WrongLengthError, + WrongLengthError +); + +pub(crate) mod arrayref { + // Private [`Ed25519`] struct used for internal implementations #[derive(Debug, PartialEq)] - pub struct Ed25519; - use super::*; - impl_key_centric_types!( - Ed25519, - SIGNING_KEY_LEN, - VERIFICATION_KEY_LEN, - SIGNATURE_LEN, - RAND_KEYGEN_LEN, - WrongLengthError, - WrongLengthError - ); + pub(crate) struct Ed25519; } pub mod slice { + //! Slice-based APIs for Ed25519. + //! + //! ```rust + //! use libcrux_traits::signature::SignConsts; + //! use libcrux_ed25519::key_centric_apis::slice::Ed25519; + //! + //! // generate keypair + //! let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; + //! let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; + //! Ed25519::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]); + //! + //! // create signature buffer + //! let mut signature = [0u8; Ed25519::SIGNATURE_LEN]; + //! + //! // sign + //! Ed25519::sign(&signing_key, b"payload", &mut signature).unwrap(); + //! + //! // verify + //! Ed25519::verify(&verification_key, b"payload", &signature).unwrap(); + //! ``` #[derive(Debug, PartialEq)] pub struct Ed25519; use super::*; @@ -65,7 +86,7 @@ pub mod slice { } } -impl arrayref::Ed25519 { +impl slice::Ed25519 { #[cfg(feature = "rand")] pub fn generate_key_pair(rng: &mut impl rand_core::CryptoRng) -> KeyPair { let mut bytes = [0u8; Self::RAND_KEYGEN_LEN]; @@ -81,6 +102,14 @@ impl arrayref::Ed25519 { } } impl arrayref::Ed25519 { + /// The hacl implementation requires that + /// - the private key is a 32 byte buffer + /// - the signature is a 64 byte buffer, + /// - the payload buffer is not shorter than payload_len. + /// + /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. + /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is + /// not the case. pub fn sign( key: &[U8; Self::SIGNING_KEY_LEN], payload: &[u8], @@ -100,6 +129,15 @@ impl arrayref::Ed25519 { Ok(()) } + /// The hacl implementation requires that + /// - the public key is a 32 byte buffer + /// - the signature is a 64 byte buffer, + /// - the payload buffer is not shorter than payload_len. + /// + /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. + /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is + /// not the case. + #[inline(always)] pub fn verify( key: &[u8; Self::VERIFICATION_KEY_LEN], payload: &[u8], @@ -129,6 +167,14 @@ impl arrayref::Ed25519 { } } impl slice::Ed25519 { + /// The hacl implementation requires that + /// - the private key is a 32 byte buffer + /// - the signature is a 64 byte buffer, + /// - the payload buffer is not shorter than payload_len. + /// + /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. + /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is + /// not the case. pub fn sign( key: &[U8], payload: &[u8], @@ -143,6 +189,15 @@ impl slice::Ed25519 { arrayref::Ed25519::sign(&key, payload, signature).map_err(slice::SigningError::from) } + + /// The hacl implementation requires that + /// - the public key is a 32 byte buffer + /// - the signature is a 64 byte buffer, + /// - the payload buffer is not shorter than payload_len. + /// + /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. + /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is + /// not the case. pub fn verify( key: &[u8], payload: &[u8], @@ -157,6 +212,7 @@ impl slice::Ed25519 { arrayref::Ed25519::verify(key, payload, signature).map_err(slice::VerificationError::from) } + pub fn keygen_derand( signing_key: &mut [U8], verification_key: &mut [u8], @@ -175,11 +231,27 @@ impl slice::Ed25519 { } } impl<'a> SigningKeyRef<'a> { + /// The hacl implementation requires that + /// - the private key is a 32 byte buffer + /// - the signature is a 64 byte buffer, + /// - the payload buffer is not shorter than payload_len. + /// + /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. + /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is + /// not the case. pub fn sign(&self, payload: &[u8], signature: &mut [u8]) -> Result<(), slice::SigningError> { slice::Ed25519::sign(self.as_ref(), payload, signature) } } impl<'a> VerificationKeyRef<'a> { + /// The hacl implementation requires that + /// - the public key is a 32 byte buffer + /// - the signature is a 64 byte buffer, + /// - the payload buffer is not shorter than payload_len. + /// + /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. + /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is + /// not the case. pub fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<(), slice::VerificationError> { slice::Ed25519::verify(self.as_ref(), payload, signature) } @@ -187,6 +259,14 @@ impl<'a> VerificationKeyRef<'a> { // key-centric API impl SigningKey { + /// The hacl implementation requires that + /// - the private key is a 32 byte buffer + /// - the signature is a 64 byte buffer, + /// - the payload buffer is not shorter than payload_len. + /// + /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. + /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is + /// not the case. pub fn sign(&self, payload: &[u8]) -> Result { let mut signature = [0u8; SIGNATURE_LEN]; arrayref::Ed25519::sign(self.as_ref(), payload, &mut signature) @@ -194,6 +274,14 @@ impl SigningKey { } } impl VerificationKey { + /// The hacl implementation requires that + /// - the public key is a 32 byte buffer + /// - the signature is a 64 byte buffer, + /// - the payload buffer is not shorter than payload_len. + /// + /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. + /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is + /// not the case. pub fn verify( &self, payload: &[u8], @@ -219,7 +307,7 @@ fn key_centric_owned() { let KeyPair { signing_key, verification_key, - } = Ed25519::generate_key_pair(&mut rng); + } = slice::Ed25519::generate_key_pair(&mut rng); let signature = signing_key.sign(b"payload").unwrap(); verification_key.verify(b"payload", &signature).unwrap(); @@ -257,17 +345,3 @@ fn arrayref_apis() { Ed25519::sign(&signing_key, b"payload", &mut signature).unwrap(); Ed25519::verify(&verification_key, b"payload", &signature).unwrap(); } -#[test] -#[cfg(not(feature = "expose-secret-independence"))] -fn slice_apis() { - use libcrux_traits::signature::SignConsts; - - let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; - let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; - Ed25519::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]); - - // slice API - let mut signature = [0u8; Ed25519::SIGNATURE_LEN]; - Ed25519::sign(&signing_key, b"payload", &mut signature).unwrap(); - Ed25519::verify(&verification_key, b"payload", &signature).unwrap(); -} diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 5ca23ef255..35966e8087 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -24,7 +24,7 @@ macro_rules! impl_mod { /// An incorrect length when converting from slice. pub struct WrongLengthError; - pub mod arrayref { + pub(crate) mod arrayref { #[derive(Debug, PartialEq)] pub struct $ty; use super::*; @@ -39,6 +39,41 @@ macro_rules! impl_mod { ); } pub mod slice { + //! Slice-based APIs for ML-DSA. + //! + //! Usage example: + //! ```rust + //! use libcrux_traits::signature::SignConsts; + //! use ml_dsa_44::slice::MlDsa44; + //! + //! let context = b"context"; + //! + //! let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; + //! let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; + //! MlDsa44::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]).unwrap(); + //! + //! // slice API + //! let mut signature = [0u8; MlDsa44::SIGNATURE_LEN]; + //! MlDsa44::sign(&signing_key, b"payload", &mut signature, context, [0u8; 32]).unwrap(); + //! MlDsa44::verify(&verification_key, b"payload", &signature, context).unwrap(); + //! + //! MlDsa44::sign_pre_hashed_shake128( + //! signing_key.as_ref(), + //! b"payload", + //! &mut signature, + //! context, + //! [0u8; 32], + //! ) + //! .unwrap(); + //! MlDsa44::verify_pre_hashed_shake128( + //! verification_key.as_ref(), + //! b"payload", + //! &signature, + //! context, + //! ) + //! .unwrap(); + //! ``` + #[derive(Debug, PartialEq)] pub struct $ty; use super::*; @@ -216,6 +251,7 @@ macro_rules! impl_mod { signature, ) } + /// Generate an ML-DSA Key Pair pub fn keygen_derand( signing_key: &mut [U8; Self::SIGNING_KEY_LEN], verification_key: &mut [u8; Self::VERIFICATION_KEY_LEN], @@ -564,10 +600,10 @@ fn key_centric_refs() { .unwrap(); signing_key - .sign_pre_hashed_shake128(b"pre-hashed", &mut signature, context, [0u8; 32]) + .sign_pre_hashed_shake128(b"payload", &mut signature, context, [0u8; 32]) .unwrap(); verification_key - .verify_pre_hashed_shake128(b"pre-hashed", &signature, context) + .verify_pre_hashed_shake128(b"payload", &signature, context) .unwrap(); } @@ -589,47 +625,8 @@ fn arrayref_apis() { MlDsa44::verify(&verification_key, b"payload", &signature, context).unwrap(); // pre-hashed - MlDsa44::sign_pre_hashed_shake128( - &signing_key, - b"pre-hashed", - &mut signature, - context, - [0u8; 32], - ) - .unwrap(); - MlDsa44::verify_pre_hashed_shake128(&verification_key, b"pre-hashed", &signature, context) + MlDsa44::sign_pre_hashed_shake128(&signing_key, b"payload", &mut signature, context, [0u8; 32]) + .unwrap(); + MlDsa44::verify_pre_hashed_shake128(&verification_key, b"payload", &signature, context) .unwrap(); -} -#[test] -#[cfg(not(feature = "expose-secret-independence"))] -fn slice_apis() { - use libcrux_traits::signature::SignConsts; - use ml_dsa_44::slice::MlDsa44; - - let context = b"context"; - - let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; - let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; - MlDsa44::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]).unwrap(); - - // slice API - let mut signature = [0u8; MlDsa44::SIGNATURE_LEN]; - MlDsa44::sign(&signing_key, b"payload", &mut signature, context, [0u8; 32]).unwrap(); - MlDsa44::verify(&verification_key, b"payload", &signature, context).unwrap(); - - MlDsa44::sign_pre_hashed_shake128( - signing_key.as_ref(), - b"pre-hashed", - &mut signature, - context, - [0u8; 32], - ) - .unwrap(); - MlDsa44::verify_pre_hashed_shake128( - verification_key.as_ref(), - b"pre-hashed", - &signature, - context, - ) - .unwrap(); } From 189540a2059246971ce953741bd157dfc05262be Mon Sep 17 00:00:00 2001 From: wysiwys Date: Mon, 17 Nov 2025 11:12:59 +0100 Subject: [PATCH 13/61] docs and visibility --- .../algorithms/ecdsa/src/key_centric_apis.rs | 112 +++++++++--------- 1 file changed, 53 insertions(+), 59 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index c733a5deee..6b2fb6507e 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -22,9 +22,9 @@ macro_rules! impl_mod { /// An incorrect length when converting from slice. pub struct WrongLengthError; - pub mod arrayref { + pub(crate) mod arrayref { #[derive(Debug, PartialEq)] - pub struct $ty; + pub(crate) struct $ty; use super::*; impl_key_centric_types!( $ty, @@ -37,6 +37,39 @@ macro_rules! impl_mod { ); } pub mod slice { + // TODO: different keygen in example? + //! Slice-based APIs for ECDSA-P256. + //! + //! ```rust + //! use libcrux_traits::signature::SignConsts; + //! use libcrux_ecdsa::key_centric_apis::sha2_256::slice::EcdsaP256; + //! use libcrux_ecdsa::p256::Nonce; + //! + //! use rand::{RngCore, TryRngCore}; + //! let mut rng = rand_core::OsRng; + //! let mut rng = rng.unwrap_mut(); + //! + //! let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; + //! let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; + //! + //! // keygen + //! let mut bytes = [0u8; EcdsaP256::RAND_KEYGEN_LEN]; + //! rng.fill_bytes(&mut bytes); + //! EcdsaP256::keygen_derand(&mut signing_key, &mut verification_key, bytes).unwrap(); + //! + //! // sign + //! let mut signature = [0u8; EcdsaP256::SIGNATURE_LEN]; + //! EcdsaP256::sign( + //! &signing_key, + //! b"payload", + //! &mut signature, + //! &Nonce::random(&mut rng).unwrap(), + //! ) + //! .unwrap(); + //! + //! // verify + //! EcdsaP256::verify(&verification_key, b"payload", &signature).unwrap(); + //! ``` #[derive(Debug, PartialEq)] pub struct $ty; use super::*; @@ -75,6 +108,15 @@ macro_rules! impl_mod { } } + impl slice::$ty { + #[cfg(feature = "rand")] + pub fn generate_key_pair( + rng: &mut impl rand::CryptoRng, + ) -> Result { + arrayref::$ty::generate_key_pair(rng) + } + } + impl arrayref::$ty { #[cfg(feature = "rand")] pub fn generate_key_pair( @@ -99,6 +141,7 @@ macro_rules! impl_mod { } } impl arrayref::$ty { + /// Sign the `payload` with the `key`. pub fn sign( key: &[U8; Self::SIGNING_KEY_LEN], payload: &[u8], @@ -120,6 +163,8 @@ macro_rules! impl_mod { } Ok(()) } + + /// Verify the `payload` and `signature` with the `key`. pub fn verify( key: &[u8; Self::VERIFICATION_KEY_LEN], payload: &[u8], @@ -163,6 +208,7 @@ macro_rules! impl_mod { } } impl slice::$ty { + /// Sign the `payload` with the `key`. pub fn sign( key: &[U8], payload: &[u8], @@ -179,6 +225,7 @@ macro_rules! impl_mod { arrayref::$ty::sign(&key, payload, signature, nonce) .map_err(slice::SigningError::from) } + /// Verify the `payload` and `signature` with the `key`. pub fn verify( key: &[u8], payload: &[u8], @@ -210,6 +257,7 @@ macro_rules! impl_mod { } } impl<'a> SigningKeyRef<'a> { + /// Sign the `payload`. pub fn sign( &self, payload: &[u8], @@ -220,6 +268,7 @@ macro_rules! impl_mod { } } impl<'a> VerificationKeyRef<'a> { + /// Verify the `payload` and `signature`. pub fn verify( &self, payload: &[u8], @@ -231,6 +280,7 @@ macro_rules! impl_mod { // key-centric API impl SigningKey { + /// Sign the `payload`. pub fn sign( &self, payload: &[u8], @@ -242,6 +292,7 @@ macro_rules! impl_mod { } } impl VerificationKey { + /// Verify the `payload` and `signature`. pub fn verify( &self, payload: &[u8], @@ -340,60 +391,3 @@ fn key_centric_refs() { .unwrap(); verification_key.verify(b"payload", &signature).unwrap(); } - -#[test] -#[cfg(not(feature = "expose-secret-independence"))] -fn arrayref_apis() { - use libcrux_traits::signature::SignConsts; - use sha2_256::EcdsaP256; - - use rand::{RngCore, TryRngCore}; - let mut rng = rand_core::OsRng; - let mut rng = rng.unwrap_mut(); - - let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; - let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; - - let mut bytes = [0u8; EcdsaP256::RAND_KEYGEN_LEN]; - rng.fill_bytes(&mut bytes); - EcdsaP256::keygen_derand(&mut signing_key, &mut verification_key, bytes).unwrap(); - - // arrayref API - let mut signature = [0u8; EcdsaP256::SIGNATURE_LEN]; - EcdsaP256::sign( - &signing_key, - b"payload", - &mut signature, - &Nonce::random(&mut rng).unwrap(), - ) - .unwrap(); - EcdsaP256::verify(&verification_key, b"payload", &signature).unwrap(); -} -#[test] -#[cfg(not(feature = "expose-secret-independence"))] -fn slice_apis() { - use libcrux_traits::signature::SignConsts; - use sha2_256::slice::EcdsaP256; - - use rand::{RngCore, TryRngCore}; - let mut rng = rand_core::OsRng; - let mut rng = rng.unwrap_mut(); - - let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; - let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; - - let mut bytes = [0u8; EcdsaP256::RAND_KEYGEN_LEN]; - rng.fill_bytes(&mut bytes); - EcdsaP256::keygen_derand(&mut signing_key, &mut verification_key, bytes).unwrap(); - - // slice API - let mut signature = [0u8; EcdsaP256::SIGNATURE_LEN]; - EcdsaP256::sign( - &signing_key, - b"payload", - &mut signature, - &Nonce::random(&mut rng).unwrap(), - ) - .unwrap(); - EcdsaP256::verify(&verification_key, b"payload", &signature).unwrap(); -} From 8953adfd21bdae963d6579817597e338e84bbbbc Mon Sep 17 00:00:00 2001 From: wysiwys Date: Mon, 17 Nov 2025 11:25:05 +0100 Subject: [PATCH 14/61] simplify `KeyPair` generation --- .../algorithms/ecdsa/src/key_centric_apis.rs | 23 +++++-------------- .../ed25519/src/key_centric_apis.rs | 10 ++++---- crates/primitives/signature/src/lib.rs | 12 +++++----- libcrux-ml-dsa/src/key_centric_apis.rs | 12 +++++----- 4 files changed, 23 insertions(+), 34 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index 6b2fb6507e..647413413f 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -108,26 +108,15 @@ macro_rules! impl_mod { } } - impl slice::$ty { - #[cfg(feature = "rand")] - pub fn generate_key_pair( - rng: &mut impl rand::CryptoRng, - ) -> Result { - arrayref::$ty::generate_key_pair(rng) - } - } - - impl arrayref::$ty { + impl KeyPair { #[cfg(feature = "rand")] - pub fn generate_key_pair( - rng: &mut impl rand::CryptoRng, - ) -> Result { + pub fn generate(rng: &mut impl rand::CryptoRng) -> Result { use libcrux_secrets::Classify; - let mut bytes = [0u8; Self::RAND_KEYGEN_LEN]; + let mut bytes = [0u8; arrayref::$ty::RAND_KEYGEN_LEN]; rng.fill_bytes(&mut bytes); - let mut signing_key = [0u8; Self::SIGNING_KEY_LEN].classify(); - let mut verification_key = [0u8; Self::VERIFICATION_KEY_LEN]; + let mut signing_key = [0u8; arrayref::$ty::SIGNING_KEY_LEN].classify(); + let mut verification_key = [0u8; arrayref::$ty::VERIFICATION_KEY_LEN]; arrayref::$ty::keygen_derand( &mut signing_key, &mut verification_key, @@ -352,7 +341,7 @@ fn key_centric_owned() { let KeyPair { signing_key, verification_key, - } = EcdsaP256::generate_key_pair(&mut rng).unwrap(); + } = KeyPair::generate(&mut rng).unwrap(); let signature = signing_key .sign(b"payload", &Nonce::random(&mut rng).unwrap()) diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index 8c4e432061..a650af1cad 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -86,13 +86,13 @@ pub mod slice { } } -impl slice::Ed25519 { +impl KeyPair { #[cfg(feature = "rand")] - pub fn generate_key_pair(rng: &mut impl rand_core::CryptoRng) -> KeyPair { - let mut bytes = [0u8; Self::RAND_KEYGEN_LEN]; + pub fn generate(rng: &mut impl rand_core::CryptoRng) -> KeyPair { + let mut bytes = [0u8; arrayref::Ed25519::RAND_KEYGEN_LEN]; rng.fill_bytes(&mut bytes); - let mut signing_key = [0u8; Self::SIGNING_KEY_LEN].classify(); - let mut verification_key = [0u8; Self::VERIFICATION_KEY_LEN]; + let mut signing_key = [0u8; arrayref::Ed25519::SIGNING_KEY_LEN].classify(); + let mut verification_key = [0u8; arrayref::Ed25519::VERIFICATION_KEY_LEN]; arrayref::Ed25519::keygen_derand(&mut signing_key, &mut verification_key, bytes.classify()); KeyPair { diff --git a/crates/primitives/signature/src/lib.rs b/crates/primitives/signature/src/lib.rs index 0ead5e9c62..b6e910af2d 100644 --- a/crates/primitives/signature/src/lib.rs +++ b/crates/primitives/signature/src/lib.rs @@ -14,7 +14,7 @@ pub mod ecdsa { pub mod p256 { //! ```rust //! use libcrux_signature::ecdsa::p256::{ - //! Nonce, sha2_256::{EcdsaP256, KeyPair, SigningKey, VerificationKey} + //! Nonce, sha2_256::{KeyPair, SigningKey, VerificationKey} //! }; //! //! // generate a new nonce @@ -24,7 +24,7 @@ pub mod ecdsa { //! //! // generate a new signature keypair from random bytes //! let KeyPair { signing_key, verification_key } = - //! EcdsaP256::generate_key_pair(&mut rng.unwrap_mut()).unwrap(); + //! KeyPair::generate(&mut rng.unwrap_mut()).unwrap(); //! //! // sign //! let signature = signing_key.sign(b"payload", &nonce).unwrap(); @@ -40,14 +40,14 @@ pub mod ecdsa { #[cfg(feature = "ed25519")] pub mod ed25519 { //! ```rust - //! use libcrux_signature::ed25519::{Ed25519, KeyPair}; + //! use libcrux_signature::ed25519::KeyPair; //! //! use rand::TryRngCore; //! let mut rng = rand::rngs::OsRng; //! // generate a new signature keypair from random bytes //! // requires `rand` feature //! let KeyPair { signing_key, verification_key } - //! = Ed25519::generate_key_pair(&mut rng.unwrap_mut()); + //! = KeyPair::generate(&mut rng.unwrap_mut()); //! //! // sign //! let signature = signing_key.sign(b"payload").unwrap(); @@ -56,7 +56,7 @@ pub mod ed25519 { //! verification_key.verify(b"payload", &signature).unwrap(); //! ``` pub use libcrux_ed25519::key_centric_apis::{ - Ed25519, KeyPair, SigningKey, SigningKeyRef, VerificationKey, VerificationKeyRef, + slice, KeyPair, SigningKey, SigningKeyRef, VerificationKey, VerificationKeyRef, }; } @@ -72,7 +72,7 @@ pub mod mldsa { //! // generate a new signature keypair from random bytes //! // requires `rand` feature //! let KeyPair { signing_key, verification_key } - //! = MlDsa44::generate_key_pair(&mut rng.unwrap_mut()); + //! = KeyPair::generate(&mut rng.unwrap_mut()); //! //! // sign //! let signature = signing_key.sign(b"payload", b"context", [2; 32]).unwrap(); diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 35966e8087..8debaf2135 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -144,15 +144,15 @@ macro_rules! impl_mod { } } - impl arrayref::$ty { + impl KeyPair { #[cfg(feature = "rand")] - pub fn generate_key_pair(rng: &mut impl rand::CryptoRng) -> KeyPair { + pub fn generate(rng: &mut impl rand::CryptoRng) -> KeyPair { use libcrux_secrets::Classify; - let mut bytes = [0u8; Self::RAND_KEYGEN_LEN]; + let mut bytes = [0u8; arrayref::$ty::RAND_KEYGEN_LEN]; rng.fill_bytes(&mut bytes); - let mut signing_key = [0u8; Self::SIGNING_KEY_LEN].classify(); - let mut verification_key = [0u8; Self::VERIFICATION_KEY_LEN]; + let mut signing_key = [0u8; arrayref::$ty::SIGNING_KEY_LEN].classify(); + let mut verification_key = [0u8; arrayref::$ty::VERIFICATION_KEY_LEN]; arrayref::$ty::keygen_derand( &mut signing_key, &mut verification_key, @@ -558,7 +558,7 @@ fn key_centric_owned() { let KeyPair { signing_key, verification_key, - } = MlDsa44::generate_key_pair(&mut rng); + } = KeyPair::generate(&mut rng); let signature = signing_key.sign(b"payload", context, [0u8; 32]).unwrap(); verification_key From 46ac6da758c7cf39a7ba9ef54d04e67b0572b749 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Mon, 17 Nov 2025 11:33:29 +0100 Subject: [PATCH 15/61] update pre-hash buffer length --- libcrux-ml-dsa/src/key_centric_apis.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 8debaf2135..200ee42eba 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -200,7 +200,7 @@ macro_rules! impl_mod { randomness: [U8; 32], ) -> Result<(), crate::SigningError> { // TODO: use mut version - let mut pre_hash_buffer = [0; 256]; + let mut pre_hash_buffer = [0; 32]; let signature_out = crate::ml_dsa_generic::multiplexing::$module::sign_pre_hashed_shake128( key.declassify_ref().try_into().unwrap(), @@ -242,7 +242,7 @@ macro_rules! impl_mod { signature: &[u8; Self::SIGNATURE_LEN], context: &[u8], ) -> Result<(), crate::VerificationError> { - let mut pre_hash_buffer = [0; 256]; + let mut pre_hash_buffer = [0; 32]; crate::ml_dsa_generic::multiplexing::$module::verify_pre_hashed_shake128( key, payload, From 8879a01b14dfb2e694c8a9dd102a458a1893838c Mon Sep 17 00:00:00 2001 From: wysiwys Date: Mon, 17 Nov 2025 11:34:33 +0100 Subject: [PATCH 16/61] improve feature flags and imports --- libcrux-ml-dsa/src/key_centric_apis.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 200ee42eba..9db008a9c2 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -44,7 +44,7 @@ macro_rules! impl_mod { //! Usage example: //! ```rust //! use libcrux_traits::signature::SignConsts; - //! use ml_dsa_44::slice::MlDsa44; + //! use libcrux_ml_dsa::key_centric_apis::ml_dsa_44::slice::MlDsa44; //! //! let context = b"context"; //! @@ -539,7 +539,11 @@ pub mod ml_dsa_87 { } #[test] -#[cfg(all(feature = "rand", not(feature = "expose-secret-independence")))] +#[cfg(all( + feature = "mldsa44", + feature = "rand", + not(feature = "expose-secret-independence") +))] fn key_centric_owned() { use rand::TryRngCore; let mut rng = rand::rngs::OsRng; @@ -576,7 +580,11 @@ fn key_centric_owned() { } #[test] -#[cfg(all(feature = "rand", not(feature = "expose-secret-independence")))] +#[cfg(all( + feature = "mldsa44", + feature = "rand", + not(feature = "expose-secret-independence") +))] fn key_centric_refs() { use libcrux_traits::signature::SignConsts; use ml_dsa_44::*; @@ -608,7 +616,7 @@ fn key_centric_refs() { } #[test] -#[cfg(not(feature = "expose-secret-independence"))] +#[cfg(all(feature = "mldsa44", not(feature = "expose-secret-independence")))] fn arrayref_apis() { use libcrux_traits::signature::SignConsts; use ml_dsa_44::MlDsa44; From d16a8260d5a4b9a10cd573ba3bd4c41089a01484 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Mon, 17 Nov 2025 16:09:43 +0100 Subject: [PATCH 17/61] spacing --- crates/algorithms/ecdsa/src/key_centric_apis.rs | 3 --- libcrux-ml-dsa/src/key_centric_apis.rs | 3 --- 2 files changed, 6 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index 647413413f..ab09abe111 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -294,7 +294,6 @@ macro_rules! impl_mod { } pub mod sha2_256 { - impl_mod!( EcdsaP256, Sha2_256, @@ -304,7 +303,6 @@ pub mod sha2_256 { } pub mod sha2_384 { - impl_mod!( EcdsaP256, Sha2_384, @@ -314,7 +312,6 @@ pub mod sha2_384 { } pub mod sha2_512 { - impl_mod!( EcdsaP256, Sha2_512, diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 9db008a9c2..ea183f18c6 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -522,19 +522,16 @@ macro_rules! impl_mod { #[cfg(feature = "mldsa44")] pub mod ml_dsa_44 { - impl_mod!(MlDsa44, ml_dsa_44); } #[cfg(feature = "mldsa65")] pub mod ml_dsa_65 { - impl_mod!(MlDsa65, ml_dsa_65); } #[cfg(feature = "mldsa87")] pub mod ml_dsa_87 { - impl_mod!(MlDsa87, ml_dsa_87); } From 95f7a1489e5f06bd6f671ab2a6c4229b9aeb4e75 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Mon, 17 Nov 2025 16:16:59 +0100 Subject: [PATCH 18/61] re-export all parameter sets --- crates/primitives/signature/src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/primitives/signature/src/lib.rs b/crates/primitives/signature/src/lib.rs index b6e910af2d..5fa1527a5a 100644 --- a/crates/primitives/signature/src/lib.rs +++ b/crates/primitives/signature/src/lib.rs @@ -63,7 +63,7 @@ pub mod ed25519 { #[cfg(feature = "mldsa")] pub mod mldsa { //! ```rust - //! use libcrux_signature::mldsa::{MlDsa44, SigningKey, KeyPair, VerificationKey}; + //! use libcrux_signature::mldsa::ml_dsa_44::{SigningKey, KeyPair, VerificationKey}; //! //! use rand::TryRngCore; @@ -82,7 +82,7 @@ pub mod mldsa { //! ``` //! //! ```rust - //! use libcrux_signature::mldsa::{MlDsa44, SigningKeyRef}; + //! use libcrux_signature::mldsa::ml_dsa_44::{MlDsa44, SigningKeyRef}; //! use libcrux_traits::signature::SignConsts; //! //! @@ -94,7 +94,5 @@ pub mod mldsa { //! signing_key.sign(b"payload", &mut signature, b"context", [2; 32]).unwrap(); //! //! ``` - pub use libcrux_ml_dsa::key_centric_apis::ml_dsa_44::{ - KeyPair, MlDsa44, Signature, SigningKey, SigningKeyRef, VerificationKey, VerificationKeyRef, - }; + pub use libcrux_ml_dsa::key_centric_apis::{ml_dsa_44, ml_dsa_65, ml_dsa_87}; } From f919f4d9e78271623b1ecf091489c8435edec34e Mon Sep 17 00:00:00 2001 From: wysiwys Date: Mon, 17 Nov 2025 16:17:35 +0100 Subject: [PATCH 19/61] clean up comments --- crates/algorithms/ecdsa/src/key_centric_apis.rs | 2 +- libcrux-ml-dsa/src/key_centric_apis.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index ab09abe111..41514b5afb 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -13,7 +13,7 @@ macro_rules! impl_mod { const RAND_KEYGEN_LEN: usize = 32; use super::*; - // arrayref API + #[doc(inline)] pub use arrayref::*; diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index ea183f18c6..aa7b17e617 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -15,7 +15,7 @@ macro_rules! impl_mod { pub(super) const RAND_KEYGEN_LEN: usize = 32; use super::*; - // arrayref API + #[doc(inline)] pub use arrayref::*; From 2bc0a6b7e5ce7f4bf6f59fab934011334c148eca Mon Sep 17 00:00:00 2001 From: wysiwys Date: Mon, 17 Nov 2025 16:21:45 +0100 Subject: [PATCH 20/61] update key generation in test --- crates/algorithms/ed25519/src/key_centric_apis.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index a650af1cad..683895a0eb 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -307,7 +307,7 @@ fn key_centric_owned() { let KeyPair { signing_key, verification_key, - } = slice::Ed25519::generate_key_pair(&mut rng); + } = KeyPair::generate(&mut rng); let signature = signing_key.sign(b"payload").unwrap(); verification_key.verify(b"payload", &signature).unwrap(); From 2e82addcf65901aac2851530ccdd9f5654bb7bda Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 18 Nov 2025 12:21:30 +0100 Subject: [PATCH 21/61] update method names for derand keygen --- .../algorithms/ecdsa/src/key_centric_apis.rs | 16 ++++++---------- .../algorithms/ed25519/src/key_centric_apis.rs | 14 +++++++------- libcrux-ml-dsa/src/key_centric_apis.rs | 18 +++++++----------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index 41514b5afb..c42b6818a5 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -55,7 +55,7 @@ macro_rules! impl_mod { //! // keygen //! let mut bytes = [0u8; EcdsaP256::RAND_KEYGEN_LEN]; //! rng.fill_bytes(&mut bytes); - //! EcdsaP256::keygen_derand(&mut signing_key, &mut verification_key, bytes).unwrap(); + //! EcdsaP256::keygen(&mut signing_key, &mut verification_key, bytes).unwrap(); //! //! // sign //! let mut signature = [0u8; EcdsaP256::SIGNATURE_LEN]; @@ -117,11 +117,7 @@ macro_rules! impl_mod { rng.fill_bytes(&mut bytes); let mut signing_key = [0u8; arrayref::$ty::SIGNING_KEY_LEN].classify(); let mut verification_key = [0u8; arrayref::$ty::VERIFICATION_KEY_LEN]; - arrayref::$ty::keygen_derand( - &mut signing_key, - &mut verification_key, - bytes.classify(), - )?; + arrayref::$ty::keygen(&mut signing_key, &mut verification_key, bytes.classify())?; Ok(KeyPair { signing_key: SigningKey::from(signing_key), @@ -174,7 +170,7 @@ macro_rules! impl_mod { } Ok(()) } - pub fn keygen_derand( + pub fn keygen( signing_key: &mut [U8; Self::SIGNING_KEY_LEN], verification_key: &mut [u8; Self::VERIFICATION_KEY_LEN], randomness: [U8; Self::RAND_KEYGEN_LEN], @@ -230,7 +226,7 @@ macro_rules! impl_mod { arrayref::$ty::verify(key, payload, signature) .map_err(slice::VerificationError::from) } - pub fn keygen_derand( + pub fn keygen( signing_key: &mut [U8], verification_key: &mut [u8], randomness: [U8; Self::RAND_KEYGEN_LEN], @@ -242,7 +238,7 @@ macro_rules! impl_mod { .try_into() .map_err(|_| slice::KeygenError::WrongVerificationKeyLength)?; - arrayref::$ty::keygen_derand(signing_key, verification_key, randomness) + arrayref::$ty::keygen(signing_key, verification_key, randomness) } } impl<'a> SigningKeyRef<'a> { @@ -361,7 +357,7 @@ fn key_centric_refs() { let mut bytes = [0u8; EcdsaP256::RAND_KEYGEN_LEN]; rng.fill_bytes(&mut bytes); - EcdsaP256::keygen_derand(&mut signing_key, &mut verification_key, bytes).unwrap(); + EcdsaP256::keygen(&mut signing_key, &mut verification_key, bytes).unwrap(); // create references from slice let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index 683895a0eb..7ea14d1dc7 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -40,7 +40,7 @@ pub mod slice { //! // generate keypair //! let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; //! let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; - //! Ed25519::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]); + //! Ed25519::keygen(&mut signing_key, &mut verification_key, [0; 32]); //! //! // create signature buffer //! let mut signature = [0u8; Ed25519::SIGNATURE_LEN]; @@ -93,7 +93,7 @@ impl KeyPair { rng.fill_bytes(&mut bytes); let mut signing_key = [0u8; arrayref::Ed25519::SIGNING_KEY_LEN].classify(); let mut verification_key = [0u8; arrayref::Ed25519::VERIFICATION_KEY_LEN]; - arrayref::Ed25519::keygen_derand(&mut signing_key, &mut verification_key, bytes.classify()); + arrayref::Ed25519::keygen(&mut signing_key, &mut verification_key, bytes.classify()); KeyPair { signing_key: SigningKey::from(signing_key), @@ -157,7 +157,7 @@ impl arrayref::Ed25519 { Err(slice::VerificationError::InvalidSignature) } } - pub fn keygen_derand( + pub fn keygen( signing_key: &mut [U8; Self::SIGNING_KEY_LEN], verification_key: &mut [u8; Self::VERIFICATION_KEY_LEN], randomness: [U8; Self::RAND_KEYGEN_LEN], @@ -213,7 +213,7 @@ impl slice::Ed25519 { arrayref::Ed25519::verify(key, payload, signature).map_err(slice::VerificationError::from) } - pub fn keygen_derand( + pub fn keygen( signing_key: &mut [U8], verification_key: &mut [u8], randomness: [U8; Self::RAND_KEYGEN_LEN], @@ -225,7 +225,7 @@ impl slice::Ed25519 { .try_into() .map_err(|_| slice::KeygenError::WrongVerificationKeyLength)?; - arrayref::Ed25519::keygen_derand(signing_key, verification_key, randomness); + arrayref::Ed25519::keygen(signing_key, verification_key, randomness); Ok(()) } @@ -320,7 +320,7 @@ fn key_centric_refs() { let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; - Ed25519::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]); + Ed25519::keygen(&mut signing_key, &mut verification_key, [0; 32]); // create references from slice let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); @@ -338,7 +338,7 @@ fn arrayref_apis() { let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; - Ed25519::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]); + Ed25519::keygen(&mut signing_key, &mut verification_key, [0; 32]); // arrayref API let mut signature = [0u8; Ed25519::SIGNATURE_LEN]; diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index aa7b17e617..562f465b26 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -50,7 +50,7 @@ macro_rules! impl_mod { //! //! let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; //! let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; - //! MlDsa44::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]).unwrap(); + //! MlDsa44::keygen(&mut signing_key, &mut verification_key, [0; 32]).unwrap(); //! //! // slice API //! let mut signature = [0u8; MlDsa44::SIGNATURE_LEN]; @@ -153,11 +153,7 @@ macro_rules! impl_mod { rng.fill_bytes(&mut bytes); let mut signing_key = [0u8; arrayref::$ty::SIGNING_KEY_LEN].classify(); let mut verification_key = [0u8; arrayref::$ty::VERIFICATION_KEY_LEN]; - arrayref::$ty::keygen_derand( - &mut signing_key, - &mut verification_key, - bytes.classify(), - ); + arrayref::$ty::keygen(&mut signing_key, &mut verification_key, bytes.classify()); KeyPair { signing_key: SigningKey::from(signing_key), @@ -252,7 +248,7 @@ macro_rules! impl_mod { ) } /// Generate an ML-DSA Key Pair - pub fn keygen_derand( + pub fn keygen( signing_key: &mut [U8; Self::SIGNING_KEY_LEN], verification_key: &mut [u8; Self::VERIFICATION_KEY_LEN], randomness: [U8; Self::RAND_KEYGEN_LEN], @@ -359,7 +355,7 @@ macro_rules! impl_mod { /// Generate an ML-DSA Key Pair #[cfg(not(eurydice))] - pub fn keygen_derand( + pub fn keygen( signing_key: &mut [U8], verification_key: &mut [u8], randomness: [U8; Self::RAND_KEYGEN_LEN], @@ -371,7 +367,7 @@ macro_rules! impl_mod { .try_into() .map_err(|_| slice::KeygenError::WrongVerificationKeyLength)?; - arrayref::$ty::keygen_derand(signing_key, verification_key, randomness); + arrayref::$ty::keygen(signing_key, verification_key, randomness); Ok(()) } @@ -590,7 +586,7 @@ fn key_centric_refs() { let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; - MlDsa44::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]); + MlDsa44::keygen(&mut signing_key, &mut verification_key, [0; 32]); // create references from slice let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); @@ -622,7 +618,7 @@ fn arrayref_apis() { let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; - MlDsa44::keygen_derand(&mut signing_key, &mut verification_key, [0; 32]); + MlDsa44::keygen(&mut signing_key, &mut verification_key, [0; 32]); // arrayref API let mut signature = [0u8; MlDsa44::SIGNATURE_LEN]; From 9820828f4850f925c498376ee3bf4def8e32038a Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 18 Nov 2025 12:33:34 +0100 Subject: [PATCH 22/61] separate error types for `arrayref` and `slice` variants --- .../ed25519/src/key_centric_apis.rs | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index 7ea14d1dc7..db3ef676b6 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -29,6 +29,38 @@ pub(crate) mod arrayref { // Private [`Ed25519`] struct used for internal implementations #[derive(Debug, PartialEq)] pub(crate) struct Ed25519; + + #[derive(Debug, PartialEq)] + pub enum SigningError { + WrongPayloadLength, + } + + impl From for super::slice::SigningError { + fn from(e: SigningError) -> Self { + match e { + SigningError::WrongPayloadLength => super::slice::SigningError::WrongPayloadLength, + } + } + } + + #[derive(Debug)] + pub enum VerificationError { + InvalidSignature, + WrongPayloadLength, + } + + impl From for super::slice::VerificationError { + fn from(e: VerificationError) -> Self { + match e { + VerificationError::InvalidSignature => { + super::slice::VerificationError::InvalidSignature + } + VerificationError::WrongPayloadLength => { + super::slice::VerificationError::WrongPayloadLength + } + } + } + } } pub mod slice { //! Slice-based APIs for Ed25519. @@ -114,15 +146,14 @@ impl arrayref::Ed25519 { key: &[U8; Self::SIGNING_KEY_LEN], payload: &[u8], signature: &mut [u8; Self::SIGNATURE_LEN], - // TODO: arrayref::SigningError? - ) -> Result<(), slice::SigningError> { + ) -> Result<(), arrayref::SigningError> { crate::hacl::ed25519::sign( signature, key.declassify_ref(), payload .len() .try_into() - .map_err(|_| slice::SigningError::WrongPayloadLength)?, + .map_err(|_| arrayref::SigningError::WrongPayloadLength)?, payload, ); @@ -142,19 +173,19 @@ impl arrayref::Ed25519 { key: &[u8; Self::VERIFICATION_KEY_LEN], payload: &[u8], signature: &[u8; Self::SIGNATURE_LEN], - ) -> Result<(), slice::VerificationError> { + ) -> Result<(), arrayref::VerificationError> { if crate::hacl::ed25519::verify( key, payload .len() .try_into() - .map_err(|_| slice::VerificationError::WrongPayloadLength)?, + .map_err(|_| arrayref::VerificationError::WrongPayloadLength)?, payload, signature, ) { Ok(()) } else { - Err(slice::VerificationError::InvalidSignature) + Err(arrayref::VerificationError::InvalidSignature) } } pub fn keygen( @@ -267,7 +298,7 @@ impl SigningKey { /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is /// not the case. - pub fn sign(&self, payload: &[u8]) -> Result { + pub fn sign(&self, payload: &[u8]) -> Result { let mut signature = [0u8; SIGNATURE_LEN]; arrayref::Ed25519::sign(self.as_ref(), payload, &mut signature) .map(|_| Signature::from(signature)) @@ -286,7 +317,7 @@ impl VerificationKey { &self, payload: &[u8], signature: &Signature, - ) -> Result<(), slice::VerificationError> { + ) -> Result<(), arrayref::VerificationError> { arrayref::Ed25519::verify(self.as_ref(), payload, signature.as_ref()) } } From e1dbc7e6750f762d7d762f893d53d3649e0367fc Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 18 Nov 2025 12:35:43 +0100 Subject: [PATCH 23/61] remove placeholder error implementation --- libcrux-ml-dsa/src/key_centric_apis.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 562f465b26..d17b2d89d1 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -1,9 +1,5 @@ use libcrux_traits::signature::{impl_key_centric_types, impl_sign_traits, SignConsts}; -// placeholder error implementation -// TODO: more specific error -#[derive(Debug)] -pub struct Error; macro_rules! impl_mod { ($ty:ident, $module:ident) => { use libcrux_secrets::{Declassify, DeclassifyRef, DeclassifyRefMut, U8}; From c35566c3132426189c3d5ec98f934c9b433eb048 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 11 Dec 2025 12:11:08 +0100 Subject: [PATCH 24/61] use the correct dependency paths within the workspace --- crates/primitives/signature/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/primitives/signature/Cargo.toml b/crates/primitives/signature/Cargo.toml index c88e937043..16c6cdce17 100644 --- a/crates/primitives/signature/Cargo.toml +++ b/crates/primitives/signature/Cargo.toml @@ -9,11 +9,11 @@ repository.workspace = true version.workspace = true [dependencies] -libcrux-ecdsa = { version = "0.0.4", path = "../../../ecdsa", optional = true } -libcrux-ed25519 = { version = "0.0.4", path = "../../../ed25519", optional = true } +libcrux-ecdsa = { version = "0.0.4", path = "../../algorithms/ecdsa", optional = true } +libcrux-ed25519 = { version = "0.0.4", path = "../../algorithms/ed25519", optional = true } libcrux-ml-dsa = { version = "0.0.4", path = "../../../libcrux-ml-dsa", optional = true } libcrux-traits = { version = "0.0.4", path = "../../../traits", optional = true } -libcrux-secrets = { version = "0.0.4", path = "../../../secrets", optional = true } +libcrux-secrets = { version = "0.0.4", path = "../../utils/secrets", optional = true } [features] default = ["ed25519", "ecdsa", "mldsa", "rand"] From e3adf854488a3e633dd716435d99fec88afbe249 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 11 Dec 2025 17:19:39 +0100 Subject: [PATCH 25/61] update changelogs --- crates/algorithms/ecdsa/CHANGELOG.md | 4 ++++ crates/algorithms/ed25519/CHANGELOG.md | 4 ++++ crates/primitives/signature/CHANGELOG.md | 8 ++++++++ libcrux-ml-dsa/CHANGELOG.md | 1 + traits/CHANGELOG.md | 4 ++++ 5 files changed, 21 insertions(+) create mode 100644 crates/primitives/signature/CHANGELOG.md diff --git a/crates/algorithms/ecdsa/CHANGELOG.md b/crates/algorithms/ecdsa/CHANGELOG.md index 2234d9a52e..09ad332b92 100644 --- a/crates/algorithms/ecdsa/CHANGELOG.md +++ b/crates/algorithms/ecdsa/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +- [#1241](https://github.com/cryspen/libcrux/pull/1241): Add key-centric and slice-based signature public APIs + ## [0.0.4] (2025-11-05) - [#1061](https://github.com/cryspen/libcrux/pull/1061): Add `std` feature gate for `libcrux-ecdsa` diff --git a/crates/algorithms/ed25519/CHANGELOG.md b/crates/algorithms/ed25519/CHANGELOG.md index a675a48c29..45b6be69fa 100644 --- a/crates/algorithms/ed25519/CHANGELOG.md +++ b/crates/algorithms/ed25519/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +- [#1241](https://github.com/cryspen/libcrux/pull/1241): Add key-centric and slice-based signature public APIs + ## [0.0.4] (2025-11-05) ## [0.0.3] (2025-06-30) diff --git a/crates/primitives/signature/CHANGELOG.md b/crates/primitives/signature/CHANGELOG.md new file mode 100644 index 0000000000..15a61afa62 --- /dev/null +++ b/crates/primitives/signature/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] diff --git a/libcrux-ml-dsa/CHANGELOG.md b/libcrux-ml-dsa/CHANGELOG.md index 1a9fbd8f68..3d9e011678 100644 --- a/libcrux-ml-dsa/CHANGELOG.md +++ b/libcrux-ml-dsa/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - [#1225](https://github.com/cryspen/libcrux/pull/1225): In tests, use the latest versions of ACVP KATs exported by `libcrux-kats`. +- [#1241](https://github.com/cryspen/libcrux/pull/1241): Add key-centric and slice-based signature public APIs ## [0.0.4] (2025-11-05) diff --git a/traits/CHANGELOG.md b/traits/CHANGELOG.md index 1282bf3740..7184d1ab22 100644 --- a/traits/CHANGELOG.md +++ b/traits/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +- [#1241](https://github.com/cryspen/libcrux/pull/1241): Add helper traits and macros for signatures + ## [0.0.4] (2025-11-05) - [#1190](https://github.com/cryspen/libcrux/pull/1190): Use secret types in kem traits From 467813bda4e6a7895cfaad998b56376eec665775 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 13:09:17 +0100 Subject: [PATCH 26/61] begin adding more documentation --- traits/Cargo.toml | 4 ++++ traits/src/signature.rs | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/traits/Cargo.toml b/traits/Cargo.toml index 94c8bcafcf..560612c741 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -23,3 +23,7 @@ alloc = [] [dependencies] rand = { version = "0.9", default-features = false } libcrux-secrets = { version = "=0.0.4", path = "../crates/utils/secrets" } + +[dev-dependencies] +# used for rustdocs +libcrux-signature = { path = "../crates/primitives/signature" } diff --git a/traits/src/signature.rs b/traits/src/signature.rs index d897faed72..2a658897ec 100644 --- a/traits/src/signature.rs +++ b/traits/src/signature.rs @@ -1,4 +1,35 @@ -//! This module provides common interface traits for signature scheme implementations. +//! This module provides common interface traits and helper macros for signature scheme implementations. +//! +//! Instead of a fully trait-based API for signature operations, this crate provides traits and +//! macros that can be used to implement signature APIs with a given shape. +//! +//! ### Defining useful constants with the [`SignConsts`] trait +//! +//! Structs that implement the [`SignConsts`] trait allow retrieving useful constants for that +//! signature algorithm. These can be used as the lengths of new buffers representing the +//! signing key, verification key, signature, or the randomness input to keygen functions. +//! +//! Example: +//! ```rust +//! use libcrux_traits::signature::SignConsts; +//! use libcrux_signature::mldsa::ml_dsa_44::MlDsa44; +//! +//! // the length of the signing key in bytes. +//! assert_eq!(MlDsa44::SIGNING_KEY_LEN, 2560); +//! +//! // the length of the verification key in bytes. +//! assert_eq!(MlDsa44::VERIFICATION_KEY_LEN, 1312); +//! +//! // the length of the signature in bytes. +//! assert_eq!(MlDsa44::SIGNATURE_LEN, 2420); +//! +//! // the length of the rand_keygen buffer in bytes. +//! assert_eq!(MlDsa44::RAND_KEYGEN_LEN, 32); +//! ``` +//! +//! ### Defining associated types with the [`SignTypes`] trait +//! +//! The [`SignTypes`] trait can be used to define associated types for signature algorithms. /// Constants defining the sizes of cryptographic elements for a signature algorithm. pub trait SignConsts { From 9e6cd82fa6901f102ce7d1129f41eb63449aeae6 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 13:14:03 +0100 Subject: [PATCH 27/61] remove the `SignTypes` trait --- .../algorithms/ecdsa/src/key_centric_apis.rs | 4 +- .../ed25519/src/key_centric_apis.rs | 4 +- crates/primitives/signature/src/lib.rs | 2 +- libcrux-ml-dsa/src/key_centric_apis.rs | 4 +- traits/src/signature.rs | 40 +++++-------------- 5 files changed, 16 insertions(+), 38 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index c42b6818a5..c0ea9ed7e9 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -1,5 +1,5 @@ use crate::p256::Nonce; -use libcrux_traits::signature::{impl_key_centric_types, impl_sign_traits, SignConsts}; +use libcrux_traits::signature::{impl_key_centric_types, impl_sign_consts, SignConsts}; macro_rules! impl_mod { ($ty:ident, $module:ident, @@ -73,7 +73,7 @@ macro_rules! impl_mod { #[derive(Debug, PartialEq)] pub struct $ty; use super::*; - impl_sign_traits!( + impl_sign_consts!( $ty, SIGNING_KEY_LEN, VERIFICATION_KEY_LEN, diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index db3ef676b6..2bee8877c6 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -1,4 +1,4 @@ -use libcrux_traits::signature::{impl_key_centric_types, impl_sign_traits, SignConsts}; +use libcrux_traits::signature::{impl_key_centric_types, impl_sign_consts, SignConsts}; use libcrux_secrets::{Classify, DeclassifyRef}; const VERIFICATION_KEY_LEN: usize = 32; @@ -86,7 +86,7 @@ pub mod slice { #[derive(Debug, PartialEq)] pub struct Ed25519; use super::*; - impl_sign_traits!( + impl_sign_consts!( Ed25519, SIGNING_KEY_LEN, VERIFICATION_KEY_LEN, diff --git a/crates/primitives/signature/src/lib.rs b/crates/primitives/signature/src/lib.rs index 5fa1527a5a..a486814596 100644 --- a/crates/primitives/signature/src/lib.rs +++ b/crates/primitives/signature/src/lib.rs @@ -7,7 +7,7 @@ //! - ML-DSA #[cfg(any(feature = "ecdsa", feature = "ed25519", feature = "mldsa"))] -pub use libcrux_traits::signature::{SignConsts, SignTypes}; +pub use libcrux_traits::signature::SignConsts; #[cfg(feature = "ecdsa")] pub mod ecdsa { diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index d17b2d89d1..c44b2fc73c 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -1,4 +1,4 @@ -use libcrux_traits::signature::{impl_key_centric_types, impl_sign_traits, SignConsts}; +use libcrux_traits::signature::{impl_key_centric_types, impl_sign_consts, SignConsts}; macro_rules! impl_mod { ($ty:ident, $module:ident) => { @@ -73,7 +73,7 @@ macro_rules! impl_mod { #[derive(Debug, PartialEq)] pub struct $ty; use super::*; - impl_sign_traits!( + impl_sign_consts!( $ty, SIGNING_KEY_LEN, VERIFICATION_KEY_LEN, diff --git a/traits/src/signature.rs b/traits/src/signature.rs index 2a658897ec..ef20859a6c 100644 --- a/traits/src/signature.rs +++ b/traits/src/signature.rs @@ -27,9 +27,6 @@ //! assert_eq!(MlDsa44::RAND_KEYGEN_LEN, 32); //! ``` //! -//! ### Defining associated types with the [`SignTypes`] trait -//! -//! The [`SignTypes`] trait can be used to define associated types for signature algorithms. /// Constants defining the sizes of cryptographic elements for a signature algorithm. pub trait SignConsts { @@ -43,22 +40,9 @@ pub trait SignConsts { const RAND_KEYGEN_LEN: usize; } -/// Associated types for signature algorithm components. -/// -/// This trait defines the concrete types used by a signature algorithm for keys, -/// signatures, randomness, and auxiliary signing data. -pub trait SignTypes { - /// The type representing signing (private) keys. - type SigningKey; - /// The type representing verification (public) keys. - type VerificationKey; - /// The type representing signatures. - type Signature; -} - -/// Helper macro for implementing signing-related traits +/// Helper macro for implementing the [`SignConsts`] trait. #[macro_export] -macro_rules! impl_sign_traits { +macro_rules! impl_sign_consts { ($algorithm:ty, $signing_key_len:expr, $verification_key_len:expr, $signature_len:expr, $rand_keygen_len:expr) => { use $crate::libcrux_secrets::U8; impl $crate::signature::SignConsts for $algorithm { @@ -67,27 +51,16 @@ macro_rules! impl_sign_traits { const SIGNATURE_LEN: usize = $signature_len; const RAND_KEYGEN_LEN: usize = $rand_keygen_len; } - - // internal types - type SigningKeyArray = [U8; $signing_key_len]; - type VerificationKeyArray = [u8; $verification_key_len]; - type SignatureArray = [u8; $signature_len]; - - impl $crate::signature::SignTypes for $algorithm { - type SigningKey = SigningKeyArray; - type VerificationKey = VerificationKeyArray; - type Signature = SignatureArray; - } }; } -pub use impl_sign_traits; +pub use impl_sign_consts; /// Helper macro for implementing types for key-centric APIs #[macro_export] macro_rules! impl_key_centric_types { ($algorithm:ty, $signing_key_len:expr, $verification_key_len:expr, $signature_len:expr, $rand_keygen_len:expr, $from_slice_error:ty, $from_slice_error_variant:expr) => { - impl_sign_traits!( + $crate::signature::impl_sign_consts!( $algorithm, $signing_key_len, $verification_key_len, @@ -95,6 +68,11 @@ macro_rules! impl_key_centric_types { $rand_keygen_len ); + // internal types + type SigningKeyArray = [U8; $signing_key_len]; + type VerificationKeyArray = [u8; $verification_key_len]; + type SignatureArray = [u8; $signature_len]; + /// A signing key. The bytes are borrowed. pub struct SigningKeyRef<'a> { key: &'a [U8], From f68a07b9bf25f41d43112f48b5d9a0592dba225d Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 13:15:46 +0100 Subject: [PATCH 28/61] import `U8` in the main implementation macro --- traits/src/signature.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traits/src/signature.rs b/traits/src/signature.rs index ef20859a6c..865d84d98a 100644 --- a/traits/src/signature.rs +++ b/traits/src/signature.rs @@ -44,7 +44,6 @@ pub trait SignConsts { #[macro_export] macro_rules! impl_sign_consts { ($algorithm:ty, $signing_key_len:expr, $verification_key_len:expr, $signature_len:expr, $rand_keygen_len:expr) => { - use $crate::libcrux_secrets::U8; impl $crate::signature::SignConsts for $algorithm { const SIGNING_KEY_LEN: usize = $signing_key_len; const VERIFICATION_KEY_LEN: usize = $verification_key_len; @@ -60,6 +59,7 @@ pub use impl_sign_consts; #[macro_export] macro_rules! impl_key_centric_types { ($algorithm:ty, $signing_key_len:expr, $verification_key_len:expr, $signature_len:expr, $rand_keygen_len:expr, $from_slice_error:ty, $from_slice_error_variant:expr) => { + use $crate::libcrux_secrets::U8; $crate::signature::impl_sign_consts!( $algorithm, $signing_key_len, From 3edccdc104b90cb94e2fc946b4320ed5e56eee4e Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 13:17:20 +0100 Subject: [PATCH 29/61] simplify importing the `U8` type --- .../algorithms/ed25519/src/key_centric_apis.rs | 2 +- traits/src/signature.rs | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index 2bee8877c6..f6e6fb5df7 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -1,6 +1,6 @@ use libcrux_traits::signature::{impl_key_centric_types, impl_sign_consts, SignConsts}; -use libcrux_secrets::{Classify, DeclassifyRef}; +use libcrux_secrets::{Classify, DeclassifyRef, U8}; const VERIFICATION_KEY_LEN: usize = 32; const SIGNING_KEY_LEN: usize = 32; const SIGNATURE_LEN: usize = 64; diff --git a/traits/src/signature.rs b/traits/src/signature.rs index 865d84d98a..6cb66b61fb 100644 --- a/traits/src/signature.rs +++ b/traits/src/signature.rs @@ -59,7 +59,6 @@ pub use impl_sign_consts; #[macro_export] macro_rules! impl_key_centric_types { ($algorithm:ty, $signing_key_len:expr, $verification_key_len:expr, $signature_len:expr, $rand_keygen_len:expr, $from_slice_error:ty, $from_slice_error_variant:expr) => { - use $crate::libcrux_secrets::U8; $crate::signature::impl_sign_consts!( $algorithm, $signing_key_len, @@ -69,16 +68,16 @@ macro_rules! impl_key_centric_types { ); // internal types - type SigningKeyArray = [U8; $signing_key_len]; + type SigningKeyArray = [$crate::libcrux_secrets::U8; $signing_key_len]; type VerificationKeyArray = [u8; $verification_key_len]; type SignatureArray = [u8; $signature_len]; /// A signing key. The bytes are borrowed. pub struct SigningKeyRef<'a> { - key: &'a [U8], + key: &'a [$crate::libcrux_secrets::U8], } - impl<'a> AsRef<[U8]> for SigningKeyRef<'a> { - fn as_ref(&self) -> &[U8] { + impl<'a> AsRef<[$crate::libcrux_secrets::U8]> for SigningKeyRef<'a> { + fn as_ref(&self) -> &[$crate::libcrux_secrets::U8] { self.key.as_ref() } } @@ -117,7 +116,9 @@ macro_rules! impl_key_centric_types { impl<'a> SigningKeyRef<'a> { /// Create a signing key from a byte slice. - pub fn from_slice(key: &'a [U8]) -> Result { + pub fn from_slice( + key: &'a [$crate::libcrux_secrets::U8], + ) -> Result { if key.len() != $signing_key_len { return Err($from_slice_error_variant); } else { @@ -132,8 +133,8 @@ macro_rules! impl_key_centric_types { } } - impl AsRef<[U8]> for SigningKey { - fn as_ref(&self) -> &[U8] { + impl AsRef<[$crate::libcrux_secrets::U8]> for SigningKey { + fn as_ref(&self) -> &[$crate::libcrux_secrets::U8] { self.key.as_ref() } } From 28fa7ccaa527af2d8b80fca96795478326f5b1b5 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 14:05:50 +0100 Subject: [PATCH 30/61] reorganize module exports and docs --- crates/algorithms/ecdsa/src/key_centric_apis.rs | 2 ++ crates/algorithms/ed25519/src/key_centric_apis.rs | 11 +++++++---- crates/primitives/signature/src/lib.rs | 12 +++++++++--- libcrux-ml-dsa/src/key_centric_apis.rs | 5 ++++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index c0ea9ed7e9..39cfff1926 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -70,6 +70,8 @@ macro_rules! impl_mod { //! // verify //! EcdsaP256::verify(&verification_key, b"payload", &signature).unwrap(); //! ``` + + /// Slice-based APIs for ECDSA-P256. #[derive(Debug, PartialEq)] pub struct $ty; use super::*; diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index f6e6fb5df7..bc70000f73 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -6,9 +6,11 @@ const SIGNING_KEY_LEN: usize = 32; const SIGNATURE_LEN: usize = 64; const RAND_KEYGEN_LEN: usize = SIGNING_KEY_LEN; -// arrayref API #[doc(inline)] -use arrayref::*; +pub use slice::Ed25519; + +#[doc(inline)] +pub use arrayref::{SigningError, VerificationError}; // TODO: different error type? #[derive(Debug)] @@ -16,7 +18,7 @@ use arrayref::*; pub struct WrongLengthError; impl_key_centric_types!( - Ed25519, + arrayref::Ed25519, SIGNING_KEY_LEN, VERIFICATION_KEY_LEN, SIGNATURE_LEN, @@ -26,7 +28,6 @@ impl_key_centric_types!( ); pub(crate) mod arrayref { - // Private [`Ed25519`] struct used for internal implementations #[derive(Debug, PartialEq)] pub(crate) struct Ed25519; @@ -83,7 +84,9 @@ pub mod slice { //! // verify //! Ed25519::verify(&verification_key, b"payload", &signature).unwrap(); //! ``` + #[derive(Debug, PartialEq)] + /// Slice-based APIs for Ed25519. pub struct Ed25519; use super::*; impl_sign_consts!( diff --git a/crates/primitives/signature/src/lib.rs b/crates/primitives/signature/src/lib.rs index a486814596..b5d28f935b 100644 --- a/crates/primitives/signature/src/lib.rs +++ b/crates/primitives/signature/src/lib.rs @@ -32,8 +32,11 @@ pub mod ecdsa { //! // verify //! verification_key.verify(b"payload", &signature).unwrap(); //! ``` - pub use libcrux_ecdsa::key_centric_apis::{sha2_256, sha2_384, sha2_512}; - pub use libcrux_ecdsa::p256::Nonce; + #[doc(inline)] + pub use libcrux_ecdsa::{ + key_centric_apis::{sha2_256, sha2_384, sha2_512}, + p256::Nonce, + }; } } @@ -55,8 +58,10 @@ pub mod ed25519 { //! // verify //! verification_key.verify(b"payload", &signature).unwrap(); //! ``` + #[doc(inline)] pub use libcrux_ed25519::key_centric_apis::{ - slice, KeyPair, SigningKey, SigningKeyRef, VerificationKey, VerificationKeyRef, + slice, Ed25519, KeyPair, SigningError, SigningKey, SigningKeyRef, VerificationError, + VerificationKey, VerificationKeyRef, }; } @@ -94,5 +99,6 @@ pub mod mldsa { //! signing_key.sign(b"payload", &mut signature, b"context", [2; 32]).unwrap(); //! //! ``` + #[doc(inline)] pub use libcrux_ml_dsa::key_centric_apis::{ml_dsa_44, ml_dsa_65, ml_dsa_87}; } diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index c44b2fc73c..ffe81ae174 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -14,6 +14,8 @@ macro_rules! impl_mod { #[doc(inline)] pub use arrayref::*; + #[doc(inline)] + pub use slice::$ty; // TODO: different error type? #[derive(Debug)] @@ -22,7 +24,7 @@ macro_rules! impl_mod { pub(crate) mod arrayref { #[derive(Debug, PartialEq)] - pub struct $ty; + pub(crate) struct $ty; use super::*; impl_key_centric_types!( $ty, @@ -70,6 +72,7 @@ macro_rules! impl_mod { //! .unwrap(); //! ``` + /// Slice-based APIs for ML-DSA. #[derive(Debug, PartialEq)] pub struct $ty; use super::*; From b741d3d901ebcf2f07ca1c678ba000454155d4ef Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 14:48:14 +0100 Subject: [PATCH 31/61] document API shape with examples --- traits/Cargo.toml | 1 + traits/src/signature.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/traits/Cargo.toml b/traits/Cargo.toml index 560612c741..8bf3838346 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -27,3 +27,4 @@ libcrux-secrets = { version = "=0.0.4", path = "../crates/utils/secrets" } [dev-dependencies] # used for rustdocs libcrux-signature = { path = "../crates/primitives/signature" } +rand = { version = "0.9", features = ["os_rng"] } diff --git a/traits/src/signature.rs b/traits/src/signature.rs index 6cb66b61fb..ff0f227ef9 100644 --- a/traits/src/signature.rs +++ b/traits/src/signature.rs @@ -27,6 +27,40 @@ //! assert_eq!(MlDsa44::RAND_KEYGEN_LEN, 32); //! ``` //! +//! ### Implementing key-centric APIs +//! +//! The [`impl_key_centric_types!()`] macro can be used to conveniently define types +//! used by key-centric signature APIs. +//! - `SigningKey`, `SigningKeyRef`: owned and borrowed signing keys. +//! - `VerificationKey`, `VerificationKeyRef`: owned and borrowed verification keys. +//! - `Signature`: an owned signature. +//! - `KeyPair`: an owned signature keypair (contains a `SigningKey` and a `VerificationKey`) +//! +//! Key-centric APIs can be defined on the above structs. +//! - `SigningKey::sign()`: The first argument should be the `payload`, followed by other +//! arguments. This API should return an owned `Signature`. +//! - `SigningKeyRef::sign()`: The first argument should be the `payload`, followed by a +//! `signature: &mut [u8]` representing the signature buffer to write into. +//! - `VerificationKey::verify()`: The first argument should be a slice representing the payload, +//! followed by a reference to a `Signature`. +//! - `VerificationKeyRef::verify()`: The first argument should be a slice representing the +//! payload, followed by a `signature: &[u8]` representing the signature. +//! +//! ```rust +//! use libcrux_signature::ed25519::{ +//! Ed25519, KeyPair, SigningKey, VerificationKey, SigningKeyRef, VerificationKeyRef, +//! }; +//! use libcrux_traits::signature::SignConsts; +//! +//! // generate a new signature keypair +//! use rand::TryRngCore; +//! let mut rng = rand::rngs::OsRng; +//! let KeyPair { signing_key, verification_key } = KeyPair::generate(&mut rng.unwrap_mut()); +//! +//! // sign and verify +//! let signature = signing_key.sign(b"payload").unwrap(); +//! verification_key.verify(b"payload", &signature).unwrap(); +//! ``` /// Constants defining the sizes of cryptographic elements for a signature algorithm. pub trait SignConsts { From e7ecd3d6c77816fcaf5c7758fca45434b3aa03d2 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 14:53:10 +0100 Subject: [PATCH 32/61] improve imports --- traits/src/signature.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/traits/src/signature.rs b/traits/src/signature.rs index ff0f227ef9..1e81b09ea0 100644 --- a/traits/src/signature.rs +++ b/traits/src/signature.rs @@ -47,9 +47,7 @@ //! payload, followed by a `signature: &[u8]` representing the signature. //! //! ```rust -//! use libcrux_signature::ed25519::{ -//! Ed25519, KeyPair, SigningKey, VerificationKey, SigningKeyRef, VerificationKeyRef, -//! }; +//! use libcrux_signature::ed25519::{Ed25519, KeyPair, SigningKey, VerificationKey}; //! use libcrux_traits::signature::SignConsts; //! //! // generate a new signature keypair From 5fa672a3be8b5902f81513e29ad43f3285c0cd8d Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 14:58:25 +0100 Subject: [PATCH 33/61] re-use the `WrongLengthError` --- crates/algorithms/ecdsa/src/key_centric_apis.rs | 11 ++++------- crates/algorithms/ed25519/src/key_centric_apis.rs | 9 +++------ crates/primitives/signature/src/lib.rs | 3 ++- libcrux-ml-dsa/src/key_centric_apis.rs | 9 +++------ traits/src/signature.rs | 4 ++++ 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index 39cfff1926..7641848705 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -1,5 +1,7 @@ use crate::p256::Nonce; -use libcrux_traits::signature::{impl_key_centric_types, impl_sign_consts, SignConsts}; +use libcrux_traits::signature::{ + impl_key_centric_types, impl_sign_consts, SignConsts, WrongLengthError, +}; macro_rules! impl_mod { ($ty:ident, $module:ident, @@ -17,11 +19,6 @@ macro_rules! impl_mod { #[doc(inline)] pub use arrayref::*; - // TODO: different error type? - #[derive(Debug)] - /// An incorrect length when converting from slice. - pub struct WrongLengthError; - pub(crate) mod arrayref { #[derive(Debug, PartialEq)] pub(crate) struct $ty; @@ -70,7 +67,7 @@ macro_rules! impl_mod { //! // verify //! EcdsaP256::verify(&verification_key, b"payload", &signature).unwrap(); //! ``` - + /// Slice-based APIs for ECDSA-P256. #[derive(Debug, PartialEq)] pub struct $ty; diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index bc70000f73..c683481d09 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -1,4 +1,6 @@ -use libcrux_traits::signature::{impl_key_centric_types, impl_sign_consts, SignConsts}; +use libcrux_traits::signature::{ + impl_key_centric_types, impl_sign_consts, SignConsts, WrongLengthError, +}; use libcrux_secrets::{Classify, DeclassifyRef, U8}; const VERIFICATION_KEY_LEN: usize = 32; @@ -12,11 +14,6 @@ pub use slice::Ed25519; #[doc(inline)] pub use arrayref::{SigningError, VerificationError}; -// TODO: different error type? -#[derive(Debug)] -/// An incorrect length when converting from slice. -pub struct WrongLengthError; - impl_key_centric_types!( arrayref::Ed25519, SIGNING_KEY_LEN, diff --git a/crates/primitives/signature/src/lib.rs b/crates/primitives/signature/src/lib.rs index b5d28f935b..cb294625e7 100644 --- a/crates/primitives/signature/src/lib.rs +++ b/crates/primitives/signature/src/lib.rs @@ -6,8 +6,9 @@ //! - ed25519 //! - ML-DSA +#[doc(inline)] #[cfg(any(feature = "ecdsa", feature = "ed25519", feature = "mldsa"))] -pub use libcrux_traits::signature::SignConsts; +pub use libcrux_traits::signature::{SignConsts, WrongLengthError}; #[cfg(feature = "ecdsa")] pub mod ecdsa { diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index ffe81ae174..8762df04d5 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -1,4 +1,6 @@ -use libcrux_traits::signature::{impl_key_centric_types, impl_sign_consts, SignConsts}; +use libcrux_traits::signature::{ + impl_key_centric_types, impl_sign_consts, SignConsts, WrongLengthError, +}; macro_rules! impl_mod { ($ty:ident, $module:ident) => { @@ -17,11 +19,6 @@ macro_rules! impl_mod { #[doc(inline)] pub use slice::$ty; - // TODO: different error type? - #[derive(Debug)] - /// An incorrect length when converting from slice. - pub struct WrongLengthError; - pub(crate) mod arrayref { #[derive(Debug, PartialEq)] pub(crate) struct $ty; diff --git a/traits/src/signature.rs b/traits/src/signature.rs index 1e81b09ea0..eaa48985d9 100644 --- a/traits/src/signature.rs +++ b/traits/src/signature.rs @@ -60,6 +60,10 @@ //! verification_key.verify(b"payload", &signature).unwrap(); //! ``` +#[derive(Debug)] +/// An incorrect length when converting from slice. +pub struct WrongLengthError; + /// Constants defining the sizes of cryptographic elements for a signature algorithm. pub trait SignConsts { /// Length of verification (public) keys in bytes. From 1c70c670aa75919167aa0b0044ea9ada214cb923 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 15:12:44 +0100 Subject: [PATCH 34/61] additional macro documentation --- traits/src/signature.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/traits/src/signature.rs b/traits/src/signature.rs index eaa48985d9..591001d2b2 100644 --- a/traits/src/signature.rs +++ b/traits/src/signature.rs @@ -92,6 +92,33 @@ macro_rules! impl_sign_consts { pub use impl_sign_consts; /// Helper macro for implementing types for key-centric APIs +/// +/// The [`impl_key_centric_types!()`] macro can be used to conveniently define types +/// used by key-centric signature APIs. +/// - `SigningKey`, `SigningKeyRef`: owned and borrowed signing keys. +/// - `VerificationKey`, `VerificationKeyRef`: owned and borrowed verification keys. +/// - `Signature`: an owned signature. +/// - `KeyPair`: an owned signature keypair (contains a `SigningKey` and a `VerificationKey`) +/// +/// It also implements some traits and methods on the structs: +/// - SigningKey: +/// - `AsRef<[libcrux_secrets::U8]>` +/// - `AsRef<[libcrux_secrets::U8; $signing_key_len]>` +/// - `From<[libcrux_secrets::U8; $signing_key_len]` +/// - SigningKeyRef: +/// - `AsRef<[libcrux_secrets::U8]>` +/// - `from_slice(&[libcrux_secrets::U8])` +/// - VerificationKey: +/// - `AsRef<[u8]>` +/// - `AsRef<[u8; $verification_key_len]>` +/// - `From<[u8; $verification_key_len]` +/// - VerificationKeyRef: +/// - `AsRef<[u8]>` +/// - `from_slice(&[u8])` +/// - Signature: +/// - `AsRef<[u8]>` +/// - `AsRef<[u8; $signature_len]>` +/// - `From<[u8; $signature_len]` #[macro_export] macro_rules! impl_key_centric_types { ($algorithm:ty, $signing_key_len:expr, $verification_key_len:expr, $signature_len:expr, $rand_keygen_len:expr, $from_slice_error:ty, $from_slice_error_variant:expr) => { From bde7645720de81389c3761b9b649f6faa258c545 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 15:24:51 +0100 Subject: [PATCH 35/61] clarify payload length computation and remove hacl-related docs that are not relevant for these APIs --- .../ed25519/src/key_centric_apis.rs | 94 +++---------------- 1 file changed, 12 insertions(+), 82 deletions(-) diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index c683481d09..d633cbaceb 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -134,55 +134,33 @@ impl KeyPair { } } impl arrayref::Ed25519 { - /// The hacl implementation requires that - /// - the private key is a 32 byte buffer - /// - the signature is a 64 byte buffer, - /// - the payload buffer is not shorter than payload_len. - /// - /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. - /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is - /// not the case. pub fn sign( key: &[U8; Self::SIGNING_KEY_LEN], payload: &[u8], signature: &mut [u8; Self::SIGNATURE_LEN], ) -> Result<(), arrayref::SigningError> { - crate::hacl::ed25519::sign( - signature, - key.declassify_ref(), - payload - .len() - .try_into() - .map_err(|_| arrayref::SigningError::WrongPayloadLength)?, - payload, - ); + let payload_len: u32 = payload + .len() + .try_into() + .map_err(|_| arrayref::SigningError::WrongPayloadLength)?; + + crate::hacl::ed25519::sign(signature, key.declassify_ref(), payload_len, payload); Ok(()) } - /// The hacl implementation requires that - /// - the public key is a 32 byte buffer - /// - the signature is a 64 byte buffer, - /// - the payload buffer is not shorter than payload_len. - /// - /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. - /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is - /// not the case. #[inline(always)] pub fn verify( key: &[u8; Self::VERIFICATION_KEY_LEN], payload: &[u8], signature: &[u8; Self::SIGNATURE_LEN], ) -> Result<(), arrayref::VerificationError> { - if crate::hacl::ed25519::verify( - key, - payload - .len() - .try_into() - .map_err(|_| arrayref::VerificationError::WrongPayloadLength)?, - payload, - signature, - ) { + let payload_len: u32 = payload + .len() + .try_into() + .map_err(|_| arrayref::VerificationError::WrongPayloadLength)?; + + if crate::hacl::ed25519::verify(key, payload_len, payload, signature) { Ok(()) } else { Err(arrayref::VerificationError::InvalidSignature) @@ -198,14 +176,6 @@ impl arrayref::Ed25519 { } } impl slice::Ed25519 { - /// The hacl implementation requires that - /// - the private key is a 32 byte buffer - /// - the signature is a 64 byte buffer, - /// - the payload buffer is not shorter than payload_len. - /// - /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. - /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is - /// not the case. pub fn sign( key: &[U8], payload: &[u8], @@ -221,14 +191,6 @@ impl slice::Ed25519 { arrayref::Ed25519::sign(&key, payload, signature).map_err(slice::SigningError::from) } - /// The hacl implementation requires that - /// - the public key is a 32 byte buffer - /// - the signature is a 64 byte buffer, - /// - the payload buffer is not shorter than payload_len. - /// - /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. - /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is - /// not the case. pub fn verify( key: &[u8], payload: &[u8], @@ -262,27 +224,11 @@ impl slice::Ed25519 { } } impl<'a> SigningKeyRef<'a> { - /// The hacl implementation requires that - /// - the private key is a 32 byte buffer - /// - the signature is a 64 byte buffer, - /// - the payload buffer is not shorter than payload_len. - /// - /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. - /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is - /// not the case. pub fn sign(&self, payload: &[u8], signature: &mut [u8]) -> Result<(), slice::SigningError> { slice::Ed25519::sign(self.as_ref(), payload, signature) } } impl<'a> VerificationKeyRef<'a> { - /// The hacl implementation requires that - /// - the public key is a 32 byte buffer - /// - the signature is a 64 byte buffer, - /// - the payload buffer is not shorter than payload_len. - /// - /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. - /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is - /// not the case. pub fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<(), slice::VerificationError> { slice::Ed25519::verify(self.as_ref(), payload, signature) } @@ -290,14 +236,6 @@ impl<'a> VerificationKeyRef<'a> { // key-centric API impl SigningKey { - /// The hacl implementation requires that - /// - the private key is a 32 byte buffer - /// - the signature is a 64 byte buffer, - /// - the payload buffer is not shorter than payload_len. - /// - /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. - /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is - /// not the case. pub fn sign(&self, payload: &[u8]) -> Result { let mut signature = [0u8; SIGNATURE_LEN]; arrayref::Ed25519::sign(self.as_ref(), payload, &mut signature) @@ -305,14 +243,6 @@ impl SigningKey { } } impl VerificationKey { - /// The hacl implementation requires that - /// - the public key is a 32 byte buffer - /// - the signature is a 64 byte buffer, - /// - the payload buffer is not shorter than payload_len. - /// - /// We enforce the first two using types, and the latter by using `payload.len()` and `payload_len`. - /// This has the caveat that `payload_len` must be <= u32::MAX, so we return an error if that is - /// not the case. pub fn verify( &self, payload: &[u8], From 298200b3eb45e6b9580cb915a53776cbdb9d9d68 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 15:26:32 +0100 Subject: [PATCH 36/61] documentation --- libcrux-ml-dsa/src/key_centric_apis.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 8762df04d5..50887833c0 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -142,6 +142,7 @@ macro_rules! impl_mod { impl KeyPair { #[cfg(feature = "rand")] + /// Generate an ML-DSA key pair pub fn generate(rng: &mut impl rand::CryptoRng) -> KeyPair { use libcrux_secrets::Classify; From 1c28705f23edbe25069b16ca233a72144af4e9bf Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 15:28:06 +0100 Subject: [PATCH 37/61] update documentation to reflect that only one trait is provided --- traits/src/signature.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/traits/src/signature.rs b/traits/src/signature.rs index 591001d2b2..6c968a4bd9 100644 --- a/traits/src/signature.rs +++ b/traits/src/signature.rs @@ -1,6 +1,6 @@ -//! This module provides common interface traits and helper macros for signature scheme implementations. +//! This module provides a common [`SignConsts`] trait and helper macros for signature scheme implementations. //! -//! Instead of a fully trait-based API for signature operations, this crate provides traits and +//! Instead of a fully trait-based API for signature operations, this crate provides a trait and //! macros that can be used to implement signature APIs with a given shape. //! //! ### Defining useful constants with the [`SignConsts`] trait From 5ad64509716f44a5ed63593139bb97bf6fb4865b Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 15:28:57 +0100 Subject: [PATCH 38/61] clarify descriptions of generated structs --- traits/src/signature.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/traits/src/signature.rs b/traits/src/signature.rs index 6c968a4bd9..4d109ab8be 100644 --- a/traits/src/signature.rs +++ b/traits/src/signature.rs @@ -31,8 +31,8 @@ //! //! The [`impl_key_centric_types!()`] macro can be used to conveniently define types //! used by key-centric signature APIs. -//! - `SigningKey`, `SigningKeyRef`: owned and borrowed signing keys. -//! - `VerificationKey`, `VerificationKeyRef`: owned and borrowed verification keys. +//! - `SigningKey`, `SigningKeyRef`: owned and borrowed signing keys, respectively. +//! - `VerificationKey`, `VerificationKeyRef`: owned and borrowed verification keys, respectively. //! - `Signature`: an owned signature. //! - `KeyPair`: an owned signature keypair (contains a `SigningKey` and a `VerificationKey`) //! @@ -95,8 +95,8 @@ pub use impl_sign_consts; /// /// The [`impl_key_centric_types!()`] macro can be used to conveniently define types /// used by key-centric signature APIs. -/// - `SigningKey`, `SigningKeyRef`: owned and borrowed signing keys. -/// - `VerificationKey`, `VerificationKeyRef`: owned and borrowed verification keys. +/// - `SigningKey`, `SigningKeyRef`: owned and borrowed signing keys, respectively. +/// - `VerificationKey`, `VerificationKeyRef`: owned and borrowed verification keys, respectively. /// - `Signature`: an owned signature. /// - `KeyPair`: an owned signature keypair (contains a `SigningKey` and a `VerificationKey`) /// From 2ad5489a57336a6413afa9692fea407b4d115f96 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 17:54:13 +0100 Subject: [PATCH 39/61] add derand keygen for `KeyPair` --- libcrux-ml-dsa/src/key_centric_apis.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 50887833c0..32ac328814 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -1,3 +1,4 @@ +use libcrux_secrets::Classify; use libcrux_traits::signature::{ impl_key_centric_types, impl_sign_consts, SignConsts, WrongLengthError, }; @@ -144,10 +145,14 @@ macro_rules! impl_mod { #[cfg(feature = "rand")] /// Generate an ML-DSA key pair pub fn generate(rng: &mut impl rand::CryptoRng) -> KeyPair { - use libcrux_secrets::Classify; - let mut bytes = [0u8; arrayref::$ty::RAND_KEYGEN_LEN]; rng.fill_bytes(&mut bytes); + + Self::generate_derand(bytes.classify()) + } + + /// Generate an ML-DSA key pair (derand) + pub fn generate_derand(bytes: [U8; RAND_KEYGEN_LEN]) -> KeyPair { let mut signing_key = [0u8; arrayref::$ty::SIGNING_KEY_LEN].classify(); let mut verification_key = [0u8; arrayref::$ty::VERIFICATION_KEY_LEN]; arrayref::$ty::keygen(&mut signing_key, &mut verification_key, bytes.classify()); From f765759b41b272043b36b10d669790ea6eb502e2 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 17:54:39 +0100 Subject: [PATCH 40/61] update tests --- libcrux-ml-dsa/src/key_centric_apis.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 32ac328814..2cc8037d75 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -1,11 +1,10 @@ -use libcrux_secrets::Classify; use libcrux_traits::signature::{ impl_key_centric_types, impl_sign_consts, SignConsts, WrongLengthError, }; macro_rules! impl_mod { ($ty:ident, $module:ident) => { - use libcrux_secrets::{Declassify, DeclassifyRef, DeclassifyRefMut, U8}; + use libcrux_secrets::{Classify, Declassify, DeclassifyRef, DeclassifyRefMut, U8}; pub(super) const VERIFICATION_KEY_LEN: usize = crate::ml_dsa_generic::$module::VERIFICATION_KEY_SIZE; @@ -588,7 +587,7 @@ fn key_centric_refs() { let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; - MlDsa44::keygen(&mut signing_key, &mut verification_key, [0; 32]); + MlDsa44::keygen(&mut signing_key, &mut verification_key, [0; 32]).unwrap(); // create references from slice let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); @@ -614,7 +613,7 @@ fn key_centric_refs() { #[cfg(all(feature = "mldsa44", not(feature = "expose-secret-independence")))] fn arrayref_apis() { use libcrux_traits::signature::SignConsts; - use ml_dsa_44::MlDsa44; + use ml_dsa_44::arrayref::MlDsa44; let context = b"context"; From 14d294842ab0dcfcd7b5c87a532c9d7afa8db4a7 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 17:59:50 +0100 Subject: [PATCH 41/61] add derand keygen methods for `KeyPair` --- crates/algorithms/ecdsa/src/key_centric_apis.rs | 13 ++++++++++--- crates/algorithms/ed25519/src/key_centric_apis.rs | 7 +++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index 7641848705..e19c2b31eb 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -7,7 +7,7 @@ macro_rules! impl_mod { ($ty:ident, $module:ident, $sign_fn:ident, $verify_fn:ident) => { - use libcrux_secrets::{DeclassifyRef, U8}; + use libcrux_secrets::{Classify, DeclassifyRef, U8}; const SIGNING_KEY_LEN: usize = 32; const VERIFICATION_KEY_LEN: usize = 64; @@ -109,11 +109,18 @@ macro_rules! impl_mod { impl KeyPair { #[cfg(feature = "rand")] + /// Generate an ECDSA-P256 key pair pub fn generate(rng: &mut impl rand::CryptoRng) -> Result { - use libcrux_secrets::Classify; - let mut bytes = [0u8; arrayref::$ty::RAND_KEYGEN_LEN]; rng.fill_bytes(&mut bytes); + + Self::generate_derand(bytes.classify()) + } + + /// Generate an ECDSA-P256 key pair (derand) + pub fn generate_derand( + bytes: [U8; RAND_KEYGEN_LEN], + ) -> Result { let mut signing_key = [0u8; arrayref::$ty::SIGNING_KEY_LEN].classify(); let mut verification_key = [0u8; arrayref::$ty::VERIFICATION_KEY_LEN]; arrayref::$ty::keygen(&mut signing_key, &mut verification_key, bytes.classify())?; diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index d633cbaceb..cef548fae2 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -120,9 +120,16 @@ pub mod slice { impl KeyPair { #[cfg(feature = "rand")] + /// Generate an Ed25519 key pair pub fn generate(rng: &mut impl rand_core::CryptoRng) -> KeyPair { let mut bytes = [0u8; arrayref::Ed25519::RAND_KEYGEN_LEN]; rng.fill_bytes(&mut bytes); + + Self::generate_derand(bytes.classify()) + } + + /// Generate an Ed25519 key pair (derand) + pub fn generate_derand(bytes: [U8; RAND_KEYGEN_LEN]) -> KeyPair { let mut signing_key = [0u8; arrayref::Ed25519::SIGNING_KEY_LEN].classify(); let mut verification_key = [0u8; arrayref::Ed25519::VERIFICATION_KEY_LEN]; arrayref::Ed25519::keygen(&mut signing_key, &mut verification_key, bytes.classify()); From cbf036a9cd8040ec0c52b4a53334687334682361 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 18:02:04 +0100 Subject: [PATCH 42/61] update tests --- crates/algorithms/ed25519/src/key_centric_apis.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index cef548fae2..8670889156 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -288,7 +288,7 @@ fn key_centric_refs() { let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; - Ed25519::keygen(&mut signing_key, &mut verification_key, [0; 32]); + Ed25519::keygen(&mut signing_key, &mut verification_key, [0; 32]).unwrap(); // create references from slice let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); @@ -304,12 +304,12 @@ fn key_centric_refs() { fn arrayref_apis() { use libcrux_traits::signature::SignConsts; - let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; - let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; - Ed25519::keygen(&mut signing_key, &mut verification_key, [0; 32]); + let mut signing_key = [0u8; arrayref::Ed25519::SIGNING_KEY_LEN]; + let mut verification_key = [0u8; arrayref::Ed25519::VERIFICATION_KEY_LEN]; + arrayref::Ed25519::keygen(&mut signing_key, &mut verification_key, [0; 32]); // arrayref API - let mut signature = [0u8; Ed25519::SIGNATURE_LEN]; - Ed25519::sign(&signing_key, b"payload", &mut signature).unwrap(); - Ed25519::verify(&verification_key, b"payload", &signature).unwrap(); + let mut signature = [0u8; arrayref::Ed25519::SIGNATURE_LEN]; + arrayref::Ed25519::sign(&signing_key, b"payload", &mut signature).unwrap(); + arrayref::Ed25519::verify(&verification_key, b"payload", &signature).unwrap(); } From 40160d615fc46a372522e5274ec69eccc8e15300 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 18:40:52 +0100 Subject: [PATCH 43/61] module-level documentation --- libcrux-ml-dsa/src/key_centric_apis.rs | 69 ++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 2cc8037d75..826fca1f23 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -1,3 +1,72 @@ +//! This module includes key-centric and slice-based APIs for ML-DSA. +//! +//! ### Key-centric APIs +//! This module provides key-centric APIs for ML-DSA. +//! +//! #### Key-centric (owned) +//! ```rust +//! use libcrux_ml_dsa::key_centric_apis::ml_dsa_44::{SigningKey, KeyPair, VerificationKey}; +//! +//! // generate key pair +//! let KeyPair { signing_key, verification_key } = KeyPair::generate_derand([0u8; 32]); +//! +//! // sign and verify +//! let signature = signing_key.sign(b"payload", b"context", [2; 32]).unwrap(); +//! verification_key.verify(b"payload", &signature, b"context").unwrap(); +//! ``` +//! +//! #### Key-centric (reference) +//! ```rust +//! # use libcrux_ml_dsa::key_centric_apis::ml_dsa_44::{ +//! # MlDsa44, SigningKeyRef, VerificationKeyRef, +//! # }; +//! # use libcrux_traits::signature::SignConsts; +//! # +//! # // key generation +//! # let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; +//! # let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; +//! # MlDsa44::keygen(&mut signing_key, &mut verification_key, [0; 32]).unwrap(); +//! # +//! // create key structs from references +//! let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); +//! let verification_key = VerificationKeyRef::from_slice(&verification_key).unwrap(); +//! +//! // signature buffer +//! let mut signature = [0u8; MlDsa44::SIGNATURE_LEN]; +//! +//! // sign and verify +//! signing_key +//! .sign(b"payload", &mut signature, b"context", [0u8; 32]) +//! .unwrap(); +//! verification_key +//! .verify(b"payload", &signature, b"context") +//! .unwrap(); +//! ``` +//! +//! ### Slice-based APIs +//! This module also provides slice-based APIs via the structs [`MlDsa44`], [`MlDsa65`] and +//! [`MlDsa87`]. +//! +//! ```rust +//! use libcrux_ml_dsa::key_centric_apis::ml_dsa_44::MlDsa44; +//! use libcrux_traits::signature::SignConsts; +//! +//! // keygen +//! let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; +//! let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; +//! MlDsa44::keygen(&mut signing_key, &mut verification_key, [0; 32]); +//! +//! // signature buffer +//! let mut signature = [0u8; MlDsa44::SIGNATURE_LEN]; +//! +//! // sign and verify +//! MlDsa44::sign(&signing_key, b"payload", &mut signature, b"context", [0u8; 32]).unwrap(); +//! MlDsa44::verify(&verification_key, b"payload", &signature, b"context").unwrap(); +//! ``` + +#[cfg(doc)] +use self::{ml_dsa_44::MlDsa44, ml_dsa_65::MlDsa65, ml_dsa_87::MlDsa87}; + use libcrux_traits::signature::{ impl_key_centric_types, impl_sign_consts, SignConsts, WrongLengthError, }; From 58e5327f78db6168c981a59466d11bc50bc90e54 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 19:01:51 +0100 Subject: [PATCH 44/61] documentation --- .../algorithms/ecdsa/src/key_centric_apis.rs | 91 ++++++++++++++++++- .../ed25519/src/key_centric_apis.rs | 64 +++++++++++++ 2 files changed, 153 insertions(+), 2 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index e19c2b31eb..af7973845b 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -1,3 +1,86 @@ +//! This module includes key-centric and slice-based APIs for ECDSA-P256. +//! +//! ### Key-centric APIs +//! This module provides key-centric APIs for ECDSA-P256. +//! +//! #### Key-centric (owned) +//! ```rust +//! # use libcrux_ecdsa::key_centric_apis::sha2_256::{SigningKey, KeyPair, VerificationKey}; +//! # use rand::TryRngCore; +//! # use libcrux_ecdsa::p256::Nonce; +//! # let mut rng = rand_core::OsRng; +//! # let mut rng = rng.unwrap_mut(); +//! +//! // generate key pair +//! let KeyPair { signing_key, verification_key } = KeyPair::generate(&mut rng).unwrap(); +//! +//! // sign and verify +//! let nonce = Nonce::random(&mut rng).unwrap(); +//! let signature = signing_key.sign(b"payload", &nonce).unwrap(); +//! verification_key.verify(b"payload", &signature).unwrap(); +//! ``` +//! +//! #### Key-centric (reference) +//! ```rust +//! # use libcrux_ecdsa::key_centric_apis::sha2_256::{ +//! # EcdsaP256, SigningKeyRef, VerificationKeyRef, +//! # }; +//! # use libcrux_traits::signature::SignConsts; +//! # +//! # // key generation +//! # let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; +//! # let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; +//! # EcdsaP256::keygen(&mut signing_key, &mut verification_key, [0; 32]).unwrap(); +//! # +//! # use rand::TryRngCore; +//! # use libcrux_ecdsa::p256::Nonce; +//! # let mut rng = rand_core::OsRng; +//! # let mut rng = rng.unwrap_mut(); +//! +//! // create key structs from references +//! let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); +//! let verification_key = VerificationKeyRef::from_slice(&verification_key).unwrap(); +//! +//! // signature buffer +//! let mut signature = [0u8; EcdsaP256::SIGNATURE_LEN]; +//! +//! // sign and verify +//! let nonce = Nonce::random(&mut rng).unwrap(); +//! signing_key +//! .sign(b"payload", &mut signature, &nonce) +//! .unwrap(); +//! verification_key +//! .verify(b"payload", &signature) +//! .unwrap(); +//! ``` +//! +//! ### Slice-based APIs +//! This module also provides slice-based APIs via the structs [`sha2_256::EcdsaP256`], [`sha2_384::EcdsaP256`] and +//! [`sha2_512::EcdsaP256`]. +//! +//! ```rust +//! # use libcrux_ecdsa::key_centric_apis::sha2_256::EcdsaP256; +//! # use libcrux_traits::signature::SignConsts; +//! # +//! # use rand::TryRngCore; +//! # use libcrux_ecdsa::p256::Nonce; +//! # let mut rng = rand_core::OsRng; +//! # let mut rng = rng.unwrap_mut(); +//! +//! // keygen +//! let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; +//! let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; +//! EcdsaP256::keygen(&mut signing_key, &mut verification_key, [0; 32]); +//! +//! // signature buffer +//! let mut signature = [0u8; EcdsaP256::SIGNATURE_LEN]; +//! +//! // sign and verify +//! let nonce = Nonce::random(&mut rng).unwrap(); +//! EcdsaP256::sign(&signing_key, b"payload", &mut signature, &nonce).unwrap(); +//! EcdsaP256::verify(&verification_key, b"payload", &signature).unwrap(); +//! ``` + use crate::p256::Nonce; use libcrux_traits::signature::{ impl_key_centric_types, impl_sign_consts, SignConsts, WrongLengthError, @@ -17,7 +100,12 @@ macro_rules! impl_mod { use super::*; #[doc(inline)] - pub use arrayref::*; + pub use self::{ + arrayref::{ + KeyPair, Signature, SigningKey, SigningKeyRef, VerificationKey, VerificationKeyRef, + }, + slice::*, + }; pub(crate) mod arrayref { #[derive(Debug, PartialEq)] @@ -34,7 +122,6 @@ macro_rules! impl_mod { ); } pub mod slice { - // TODO: different keygen in example? //! Slice-based APIs for ECDSA-P256. //! //! ```rust diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index 8670889156..9c95d33783 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -1,3 +1,67 @@ +//! This module includes key-centric and slice-based APIs for Ed25519. +//! +//! ### Key-centric APIs +//! This module provides key-centric APIs for Ed25519. +//! +//! #### Key-centric (owned) +//! ```rust +//! use libcrux_ed25519::key_centric_apis::{SigningKey, KeyPair, VerificationKey}; +//! +//! // generate key pair +//! let KeyPair { signing_key, verification_key } = KeyPair::generate_derand([0u8; 32]); +//! +//! // sign and verify +//! let signature = signing_key.sign(b"payload").unwrap(); +//! verification_key.verify(b"payload", &signature).unwrap(); +//! ``` +//! +//! #### Key-centric (reference) +//! ```rust +//! # use libcrux_ed25519::key_centric_apis::{ +//! # Ed25519, SigningKeyRef, VerificationKeyRef, +//! # }; +//! # use libcrux_traits::signature::SignConsts; +//! # +//! # // key generation +//! # let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; +//! # let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; +//! # Ed25519::keygen(&mut signing_key, &mut verification_key, [0; 32]).unwrap(); +//! # +//! // create key structs from references +//! let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); +//! let verification_key = VerificationKeyRef::from_slice(&verification_key).unwrap(); +//! +//! // signature buffer +//! let mut signature = [0u8; Ed25519::SIGNATURE_LEN]; +//! +//! // sign and verify +//! signing_key +//! .sign(b"payload", &mut signature) +//! .unwrap(); +//! verification_key +//! .verify(b"payload", &signature) +//! .unwrap(); +//! ``` +//! +//! ### Slice-based APIs +//! This module also provides slice-based APIs via the struct [`Ed25519`]. +//! +//! ```rust +//! use libcrux_ed25519::key_centric_apis::Ed25519; +//! use libcrux_traits::signature::SignConsts; +//! +//! // keygen +//! let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; +//! let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; +//! Ed25519::keygen(&mut signing_key, &mut verification_key, [0; 32]); +//! +//! // signature buffer +//! let mut signature = [0u8; Ed25519::SIGNATURE_LEN]; +//! +//! // sign and verify +//! Ed25519::sign(&signing_key, b"payload", &mut signature).unwrap(); +//! Ed25519::verify(&verification_key, b"payload", &signature).unwrap(); +//! ``` use libcrux_traits::signature::{ impl_key_centric_types, impl_sign_consts, SignConsts, WrongLengthError, }; From 57681f72ff7d8c94a4adca4a0c85d02d1fd46045 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 19:39:26 +0100 Subject: [PATCH 45/61] use `generate_pair()` --- crates/algorithms/ecdsa/src/key_centric_apis.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index af7973845b..8a75282426 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -268,19 +268,17 @@ macro_rules! impl_mod { verification_key: &mut [u8; Self::VERIFICATION_KEY_LEN], randomness: [U8; Self::RAND_KEYGEN_LEN], ) -> Result<(), slice::KeygenError> { - use libcrux_traits::ecdh::arrayref::*; + use libcrux_p256::ecdh_api::EcdhArrayref; - libcrux_p256::P256::secret_to_public(verification_key, &randomness).map_err( - |err| match err { - libcrux_traits::ecdh::arrayref::SecretToPublicError::InvalidSecret => { + libcrux_p256::P256::generate_pair(verification_key, signing_key, &randomness) + .map_err(|err| match err { + libcrux_traits::ecdh::arrayref::GenerateSecretError::InvalidRandomness => { slice::KeygenError::InvalidRandomness } - libcrux_traits::ecdh::arrayref::SecretToPublicError::Unknown => { + libcrux_traits::ecdh::arrayref::GenerateSecretError::Unknown => { slice::KeygenError::UnknownError } - }, - )?; - *signing_key = randomness; + })?; Ok(()) } From 43814085ca0218b9f7684e37ea6e96d9ecf92d83 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 17 Dec 2025 19:51:51 +0100 Subject: [PATCH 46/61] update test randomness arguments --- crates/algorithms/ecdsa/src/key_centric_apis.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index 8a75282426..f18e38091d 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -30,7 +30,7 @@ //! # // key generation //! # let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; //! # let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; -//! # EcdsaP256::keygen(&mut signing_key, &mut verification_key, [0; 32]).unwrap(); +//! # EcdsaP256::keygen(&mut signing_key, &mut verification_key, [1; 32]).unwrap(); //! # //! # use rand::TryRngCore; //! # use libcrux_ecdsa::p256::Nonce; @@ -70,7 +70,7 @@ //! // keygen //! let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; //! let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; -//! EcdsaP256::keygen(&mut signing_key, &mut verification_key, [0; 32]); +//! EcdsaP256::keygen(&mut signing_key, &mut verification_key, [1; 32]); //! //! // signature buffer //! let mut signature = [0u8; EcdsaP256::SIGNATURE_LEN]; @@ -446,7 +446,7 @@ fn key_centric_refs() { let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; - let mut bytes = [0u8; EcdsaP256::RAND_KEYGEN_LEN]; + let mut bytes = [1u8; EcdsaP256::RAND_KEYGEN_LEN]; rng.fill_bytes(&mut bytes); EcdsaP256::keygen(&mut signing_key, &mut verification_key, bytes).unwrap(); From fc3036ca1903a70e32de4cc3b8f5d75a0feafc31 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 18 Dec 2025 08:44:40 +0100 Subject: [PATCH 47/61] more documentation for algorithm structs --- crates/algorithms/ecdsa/src/key_centric_apis.rs | 5 +++++ crates/algorithms/ed25519/src/key_centric_apis.rs | 7 ++++++- libcrux-ml-dsa/src/key_centric_apis.rs | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index f18e38091d..5390ecd493 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -156,6 +156,11 @@ macro_rules! impl_mod { //! ``` /// Slice-based APIs for ECDSA-P256. + /// + /// This struct provides slice-based APIs for ECDSA-P256, as well as an implementation + /// of the [`SignConsts`] trait, which can be used to retrieve constants defining + /// the verification key length, signing key length, signature length, and the + /// length of the randomness required for key generation for the signature scheme. #[derive(Debug, PartialEq)] pub struct $ty; use super::*; diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index 9c95d33783..f6816fdbe8 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -146,8 +146,13 @@ pub mod slice { //! Ed25519::verify(&verification_key, b"payload", &signature).unwrap(); //! ``` - #[derive(Debug, PartialEq)] /// Slice-based APIs for Ed25519. + /// + /// This struct provides slice-based APIs for Ed25519, as well as an implementation + /// of the [`SignConsts`] trait, which can be used to retrieve constants defining + /// the verification key length, signing key length, signature length, and the + /// length of the randomness required for key generation for the signature scheme. + #[derive(Debug, PartialEq)] pub struct Ed25519; use super::*; impl_sign_consts!( diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 826fca1f23..5367222f9f 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -139,6 +139,11 @@ macro_rules! impl_mod { //! ``` /// Slice-based APIs for ML-DSA. + /// + /// This struct provides slice-based APIs for ML-DSA, as well as an implementation + /// of the [`SignConsts`] trait, which can be used to retrieve constants defining + /// the verification key length, signing key length, signature length, and the + /// length of the randomness required for key generation for the signature scheme. #[derive(Debug, PartialEq)] pub struct $ty; use super::*; From 576f634a8612d991dd583dfd74dbdc34db237b18 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 18 Dec 2025 16:28:35 +0100 Subject: [PATCH 48/61] add basic Readme --- crates/primitives/signature/Readme.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 crates/primitives/signature/Readme.md diff --git a/crates/primitives/signature/Readme.md b/crates/primitives/signature/Readme.md new file mode 100644 index 0000000000..b3769ced8e --- /dev/null +++ b/crates/primitives/signature/Readme.md @@ -0,0 +1,3 @@ +# Signature + +This crate provides a usable interface to `libcrux-ecdsa` (P256), `libcrux-ed25519` and `libcrux-ml-dsa`. From 953897b576a89b93f71915cea173919be21ec13b Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 18 Dec 2025 16:43:17 +0100 Subject: [PATCH 49/61] more documentation --- crates/primitives/signature/src/lib.rs | 202 +++++++++++++++++++++++-- 1 file changed, 189 insertions(+), 13 deletions(-) diff --git a/crates/primitives/signature/src/lib.rs b/crates/primitives/signature/src/lib.rs index cb294625e7..56bd677ac0 100644 --- a/crates/primitives/signature/src/lib.rs +++ b/crates/primitives/signature/src/lib.rs @@ -2,8 +2,8 @@ //! //! We currently support three signature algorithms: //! -//! - ecdsa-p256 -//! - ed25519 +//! - ECDSA-P256 +//! - Ed25519 //! - ML-DSA #[doc(inline)] @@ -12,7 +12,13 @@ pub use libcrux_traits::signature::{SignConsts, WrongLengthError}; #[cfg(feature = "ecdsa")] pub mod ecdsa { + //! APIs for ECDSA pub mod p256 { + //! APIs for ECDSA-P256 + //! + //! ### Key-centric APIs + //! + //! #### Key-centric (owned) //! ```rust //! use libcrux_signature::ecdsa::p256::{ //! Nonce, sha2_256::{KeyPair, SigningKey, VerificationKey} @@ -33,6 +39,66 @@ pub mod ecdsa { //! // verify //! verification_key.verify(b"payload", &signature).unwrap(); //! ``` + //! + //! #### Key-centric (reference) + //! ```rust + //! # use libcrux_signature::ecdsa::p256::{ + //! # Nonce, sha2_256::{SigningKeyRef, VerificationKeyRef, EcdsaP256} + //! # }; + //! # use libcrux_signature::SignConsts; + //! # + //! # use rand::{RngCore, TryRngCore}; + //! # let mut rng = rand::rngs::OsRng; + //! # let mut rng = rng.unwrap_mut(); + //! # // generate a new nonce + //! # let nonce = Nonce::random(&mut rng).unwrap(); + //! # + //! # let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; + //! # let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; + //! # let mut bytes = [1u8; EcdsaP256::RAND_KEYGEN_LEN]; + //! # rng.fill_bytes(&mut bytes); + //! # EcdsaP256::keygen(&mut signing_key, &mut verification_key, bytes).unwrap(); + //! + //! // create the key structs from byte slices + //! let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); + //! let verification_key = VerificationKeyRef::from_slice(&verification_key).unwrap(); + //! + //! // signature buffer + //! let mut signature = [0u8; EcdsaP256::SIGNATURE_LEN]; + //! + //! // sign + //! signing_key.sign(b"payload", &mut signature, &nonce).unwrap(); + //! + //! // verify + //! verification_key.verify(b"payload", &signature).unwrap(); + //! ``` + //! ### Slice-based + //! ```rust + //! # use libcrux_signature::ecdsa::p256::{sha2_256::EcdsaP256, Nonce}; + //! # use libcrux_signature::SignConsts; + //! # + //! # use rand::{RngCore, TryRngCore}; + //! # let mut rng = rand::rngs::OsRng; + //! # let mut rng = rng.unwrap_mut(); + + //! # // generate a new nonce + //! # let nonce = Nonce::random(&mut rng).unwrap(); + //! # + //! # let mut signing_key = [0u8; EcdsaP256::SIGNING_KEY_LEN]; + //! # let mut verification_key = [0u8; EcdsaP256::VERIFICATION_KEY_LEN]; + //! # let mut bytes = [1u8; EcdsaP256::RAND_KEYGEN_LEN]; + //! # rng.fill_bytes(&mut bytes); + //! # EcdsaP256::keygen(&mut signing_key, &mut verification_key, bytes).unwrap(); + //! # + //! // signature buffer + //! let mut signature = [0u8; EcdsaP256::SIGNATURE_LEN]; + //! + //! // sign + //! EcdsaP256::sign(&signing_key, b"payload", &mut signature, &nonce).unwrap(); + //! + //! // verify + //! EcdsaP256::verify(&verification_key, b"payload", &signature).unwrap(); + //! ``` #[doc(inline)] pub use libcrux_ecdsa::{ key_centric_apis::{sha2_256, sha2_384, sha2_512}, @@ -43,11 +109,17 @@ pub mod ecdsa { #[cfg(feature = "ed25519")] pub mod ed25519 { + //! APIs for Ed25519 + //! + //! ### Key-centric APIs + //! + //! #### Key-centric (owned) //! ```rust //! use libcrux_signature::ed25519::KeyPair; //! //! use rand::TryRngCore; //! let mut rng = rand::rngs::OsRng; + //! //! // generate a new signature keypair from random bytes //! // requires `rand` feature //! let KeyPair { signing_key, verification_key } @@ -59,6 +131,59 @@ pub mod ed25519 { //! // verify //! verification_key.verify(b"payload", &signature).unwrap(); //! ``` + //! #### Key-centric (reference) + //! ```rust + //! # use libcrux_signature::ed25519::{ + //! # SigningKeyRef, VerificationKeyRef, Ed25519, + //! # }; + //! # use libcrux_signature::SignConsts; + //! # + //! # use rand::{RngCore, TryRngCore}; + //! # let mut rng = rand::rngs::OsRng; + //! # let mut rng = rng.unwrap_mut(); + //! # + //! # let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; + //! # let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; + //! # let mut bytes = [1u8; Ed25519::RAND_KEYGEN_LEN]; + //! # rng.fill_bytes(&mut bytes); + //! # Ed25519::keygen(&mut signing_key, &mut verification_key, bytes).unwrap(); + //! // create the key structs from byte slices + //! let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); + //! let verification_key = VerificationKeyRef::from_slice(&verification_key).unwrap(); + //! + //! // signature buffer + //! let mut signature = [0u8; Ed25519::SIGNATURE_LEN]; + //! + //! // sign + //! signing_key.sign(b"payload", &mut signature).unwrap(); + //! + //! // verify + //! verification_key.verify(b"payload", &signature).unwrap(); + //! ``` + //! ### Slice-based + //! ```rust + //! # use libcrux_signature::ed25519::Ed25519; + //! # use libcrux_signature::SignConsts; + //! # + //! # use rand::{RngCore, TryRngCore}; + //! # let mut rng = rand::rngs::OsRng; + //! # let mut rng = rng.unwrap_mut(); + //! # + //! # let mut signing_key = [0u8; Ed25519::SIGNING_KEY_LEN]; + //! # let mut verification_key = [0u8; Ed25519::VERIFICATION_KEY_LEN]; + //! # let mut bytes = [1u8; Ed25519::RAND_KEYGEN_LEN]; + //! # rng.fill_bytes(&mut bytes); + //! # Ed25519::keygen(&mut signing_key, &mut verification_key, bytes).unwrap(); + //! # + //! // signature buffer + //! let mut signature = [0u8; Ed25519::SIGNATURE_LEN]; + //! + //! // sign + //! Ed25519::sign(&signing_key, b"payload", &mut signature).unwrap(); + //! + //! // verify + //! Ed25519::verify(&verification_key, b"payload", &signature).unwrap(); + //! ``` #[doc(inline)] pub use libcrux_ed25519::key_centric_apis::{ slice, Ed25519, KeyPair, SigningError, SigningKey, SigningKeyRef, VerificationError, @@ -68,37 +193,88 @@ pub mod ed25519 { #[cfg(feature = "mldsa")] pub mod mldsa { + //! APIs for ML-DSA + //! + //! ### Key-centric APIs + //! + //! #### Key-centric (owned) //! ```rust //! use libcrux_signature::mldsa::ml_dsa_44::{SigningKey, KeyPair, VerificationKey}; //! - - //! use rand::TryRngCore; + //! use rand::{RngCore, TryRngCore}; //! let mut rng = rand::rngs::OsRng; + //! let mut rng = rng.unwrap_mut(); //! //! // generate a new signature keypair from random bytes //! // requires `rand` feature //! let KeyPair { signing_key, verification_key } - //! = KeyPair::generate(&mut rng.unwrap_mut()); + //! = KeyPair::generate(&mut rng); //! //! // sign - //! let signature = signing_key.sign(b"payload", b"context", [2; 32]).unwrap(); + //! let mut signing_randomness = [0; 32]; + //! rng.fill_bytes(&mut signing_randomness); + //! let signature = signing_key.sign(b"payload", b"context", signing_randomness).unwrap(); //! //! // verify //! verification_key.verify(b"payload", &signature, b"context").unwrap(); //! ``` - //! + //! #### Key-centric (reference) //! ```rust - //! use libcrux_signature::mldsa::ml_dsa_44::{MlDsa44, SigningKeyRef}; - //! use libcrux_traits::signature::SignConsts; + //! # use libcrux_signature::mldsa::ml_dsa_44::{ + //! # SigningKeyRef, VerificationKeyRef, MlDsa44, + //! # }; + //! # use libcrux_signature::SignConsts; + //! # + //! # use rand::{RngCore, TryRngCore}; + //! # let mut rng = rand::rngs::OsRng; + //! # let mut rng = rng.unwrap_mut(); + //! # + //! # let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; + //! # let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; + //! # let mut bytes = [1u8; MlDsa44::RAND_KEYGEN_LEN]; + //! # rng.fill_bytes(&mut bytes); + //! # MlDsa44::keygen(&mut signing_key, &mut verification_key, bytes).unwrap(); + //! # + //! // create the key structs from byte slices + //! let signing_key = SigningKeyRef::from_slice(&signing_key).unwrap(); + //! let verification_key = VerificationKeyRef::from_slice(&verification_key).unwrap(); //! + //! // signature buffer + //! let mut signature = [0u8; MlDsa44::SIGNATURE_LEN]; //! - //! // signing key from bytes - //! let signing_key = SigningKeyRef::from_slice(&[1; 2560]).unwrap(); + //! // sign + //! let mut signing_randomness = [0; 32]; + //! rng.fill_bytes(&mut signing_randomness); + //! signing_key.sign(b"payload", &mut signature, b"context", signing_randomness).unwrap(); //! - //! // sign with randomness + //! // verify + //! verification_key.verify(b"payload", &signature, b"context").unwrap(); + //! ``` + //! ### Slice-based + //! ```rust + //! # use libcrux_signature::mldsa::ml_dsa_44::MlDsa44; + //! # use libcrux_signature::SignConsts; + //! # + //! # use rand::{RngCore, TryRngCore}; + //! # let mut rng = rand::rngs::OsRng; + //! # let mut rng = rng.unwrap_mut(); + //! # + //! # let mut signing_key = [0u8; MlDsa44::SIGNING_KEY_LEN]; + //! # let mut verification_key = [0u8; MlDsa44::VERIFICATION_KEY_LEN]; + //! # let mut bytes = [1u8; MlDsa44::RAND_KEYGEN_LEN]; + //! # rng.fill_bytes(&mut bytes); + //! # MlDsa44::keygen(&mut signing_key, &mut verification_key, bytes).unwrap(); + //! # + //! // signature buffer //! let mut signature = [0u8; MlDsa44::SIGNATURE_LEN]; - //! signing_key.sign(b"payload", &mut signature, b"context", [2; 32]).unwrap(); //! + //! // sign + //! let mut signing_randomness = [0; 32]; + //! rng.fill_bytes(&mut signing_randomness); + //! MlDsa44::sign(&signing_key, b"payload", &mut signature, b"context", signing_randomness).unwrap(); + //! + //! // verify + //! MlDsa44::verify(&verification_key, b"payload", &signature, b"context").unwrap(); //! ``` #[doc(inline)] pub use libcrux_ml_dsa::key_centric_apis::{ml_dsa_44, ml_dsa_65, ml_dsa_87}; From 8cdf860074aaec5611f898d97cd570932e8b4970 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 18 Dec 2025 16:54:29 +0100 Subject: [PATCH 50/61] add doc comments to error types --- libcrux-ml-dsa/src/key_centric_apis.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index 5367222f9f..a2cedc808c 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -155,12 +155,16 @@ macro_rules! impl_mod { RAND_KEYGEN_LEN ); - // error type including wrong length + /// An error when signing. #[derive(Debug)] pub enum SigningError { + // TODO: add doc comment. RejectionSamplingError, + /// The provided context is too long. ContextTooLongError, + /// The length of the provided signing key is incorrect. WrongSigningKeyLength, + /// The length of the provided signature buffer is incorrect. WrongSignatureLength, } @@ -177,14 +181,20 @@ macro_rules! impl_mod { } } - // error type including wrong length + /// An error when verifying a signature. #[derive(Debug)] pub enum VerificationError { + // TODO: add doc comment. MalformedHintError, + // TODO: add doc comment. SignerResponseExceedsBoundError, + // TODO: add doc comment. CommitmentHashesDontMatchError, + /// The verification context is too long. VerificationContextTooLongError, + /// The provided verification key too long. WrongVerificationKeyLength, + /// The length of the provided signature is incorrect. WrongSignatureLength, } From 5b8389fedf3d49f06f3a9160e8cffdfd3727f22e Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 18 Dec 2025 17:08:59 +0100 Subject: [PATCH 51/61] improve error naming and documentation --- .../algorithms/ecdsa/src/key_centric_apis.rs | 25 +++++++++--- .../ed25519/src/key_centric_apis.rs | 39 +++++++++++++------ libcrux-ml-dsa/src/key_centric_apis.rs | 3 ++ 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index 5390ecd493..273ec58bdd 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -172,29 +172,42 @@ macro_rules! impl_mod { RAND_KEYGEN_LEN ); - // error type including wrong length + /// An error when signing. #[derive(Debug)] pub enum SigningError { + /// The length of the provided signing key is incorrect. WrongSigningKeyLength, + /// The length of the provided signature buffer is incorrect. WrongSignatureLength, - InvalidArgument, + /// The length of the provided payload is invalid. + InvalidPayloadLength, + /// An unknown error occurred. UnknownError, } - // error type including wrong length + /// An error when verifying a signature. #[derive(Debug)] pub enum VerificationError { + /// The length of the provided verification key is incorrect. WrongVerificationKeyLength, + /// The length of the provided signature is incorrect. WrongSignatureLength, - WrongPayloadLength, + /// The length of the provided payload is invalid. + InvalidPayloadLength, + /// An unknown error occurred. UnknownError, } + /// An error when generating a signature key pair. #[derive(Debug)] pub enum KeygenError { + /// The provided randomness is invalid. InvalidRandomness, + /// The length of the provided signing key buffer is incorrect. WrongSigningKeyLength, + /// The length of the provided verification key buffer is incorrecct. WrongVerificationKeyLength, + /// An unknown error occurred. UnknownError, } } @@ -236,7 +249,7 @@ macro_rules! impl_mod { payload .len() .try_into() - .map_err(|_| slice::SigningError::InvalidArgument)?, + .map_err(|_| slice::SigningError::InvalidPayloadLength)?, payload, key.declassify_ref(), &nonce.0, @@ -257,7 +270,7 @@ macro_rules! impl_mod { payload .len() .try_into() - .map_err(|_| slice::VerificationError::WrongPayloadLength)?, + .map_err(|_| slice::VerificationError::InvalidPayloadLength)?, payload, key, <&[u8; 32]>::try_from(&signature[0..32]).unwrap(), diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index f6816fdbe8..5cd4fea4fb 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -92,23 +92,30 @@ pub(crate) mod arrayref { #[derive(Debug, PartialEq)] pub(crate) struct Ed25519; + /// An error when signing. #[derive(Debug, PartialEq)] pub enum SigningError { - WrongPayloadLength, + /// The length of the provided payload is invalid. + InvalidPayloadLength, } impl From for super::slice::SigningError { fn from(e: SigningError) -> Self { match e { - SigningError::WrongPayloadLength => super::slice::SigningError::WrongPayloadLength, + SigningError::InvalidPayloadLength => { + super::slice::SigningError::InvalidPayloadLength + } } } } + /// An error when verifying a signature. #[derive(Debug)] pub enum VerificationError { + /// The provided signature is invalid. InvalidSignature, - WrongPayloadLength, + /// The length of the provided payload is invalid. + InvalidPayloadLength, } impl From for super::slice::VerificationError { @@ -117,8 +124,8 @@ pub(crate) mod arrayref { VerificationError::InvalidSignature => { super::slice::VerificationError::InvalidSignature } - VerificationError::WrongPayloadLength => { - super::slice::VerificationError::WrongPayloadLength + VerificationError::InvalidPayloadLength => { + super::slice::VerificationError::InvalidPayloadLength } } } @@ -163,26 +170,36 @@ pub mod slice { RAND_KEYGEN_LEN ); - // error type including wrong length + /// An error when signing. #[derive(Debug)] pub enum SigningError { + /// The length of the provided signing key is incorrect. WrongSigningKeyLength, + /// The length of the provided signature buffer is incorrect. WrongSignatureLength, - WrongPayloadLength, + /// The length of the provided payload is invalid. + InvalidPayloadLength, } - // error type including wrong length + /// An error when verifying a signature. #[derive(Debug)] pub enum VerificationError { + /// The provided signature is invalid. InvalidSignature, + /// The length of the provided verification key is incorrect. WrongVerificationKeyLength, + /// The length of the provided signature is incorrect. WrongSignatureLength, - WrongPayloadLength, + /// The length of the provided payload is invalid. + InvalidPayloadLength, } + /// An error when generating a signature key pair. #[derive(Debug)] pub enum KeygenError { + /// The length of the provided signing key buffer is incorrect. WrongSigningKeyLength, + /// The length of the provided verification key buffer is incorrect. WrongVerificationKeyLength, } } @@ -218,7 +235,7 @@ impl arrayref::Ed25519 { let payload_len: u32 = payload .len() .try_into() - .map_err(|_| arrayref::SigningError::WrongPayloadLength)?; + .map_err(|_| arrayref::SigningError::InvalidPayloadLength)?; crate::hacl::ed25519::sign(signature, key.declassify_ref(), payload_len, payload); @@ -234,7 +251,7 @@ impl arrayref::Ed25519 { let payload_len: u32 = payload .len() .try_into() - .map_err(|_| arrayref::VerificationError::WrongPayloadLength)?; + .map_err(|_| arrayref::VerificationError::InvalidPayloadLength)?; if crate::hacl::ed25519::verify(key, payload_len, payload, signature) { Ok(()) diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index a2cedc808c..e7495547a5 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -217,9 +217,12 @@ macro_rules! impl_mod { } } + /// An error when generating a signature key pair. #[derive(Debug)] pub enum KeygenError { + /// The length of the provided signing key buffer is incorrect. WrongSigningKeyLength, + /// The length of the provided verification key buffer is incorrecct. WrongVerificationKeyLength, } } From 4516545fbbdb5f5679634614110277a9e2232027 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 18 Dec 2025 17:25:05 +0100 Subject: [PATCH 52/61] update the error variant returned when verification fails --- crates/algorithms/ecdsa/src/key_centric_apis.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index 273ec58bdd..268ee7c68d 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -188,14 +188,14 @@ macro_rules! impl_mod { /// An error when verifying a signature. #[derive(Debug)] pub enum VerificationError { + /// The provided signature is invalid. + InvalidSignature, /// The length of the provided verification key is incorrect. WrongVerificationKeyLength, /// The length of the provided signature is incorrect. WrongSignatureLength, /// The length of the provided payload is invalid. InvalidPayloadLength, - /// An unknown error occurred. - UnknownError, } /// An error when generating a signature key pair. @@ -277,7 +277,7 @@ macro_rules! impl_mod { <&[u8; 32]>::try_from(&signature[32..]).unwrap(), ); if !result { - return Err(slice::VerificationError::UnknownError); + return Err(slice::VerificationError::InvalidSignature); } Ok(()) } From 0a96eee0d0ec438dc0ec50a04d5dc156f66efe79 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 18 Dec 2025 17:32:37 +0100 Subject: [PATCH 53/61] don't call `classify()` on already-classified `bytes` array in keygen --- crates/algorithms/ecdsa/src/key_centric_apis.rs | 2 +- crates/algorithms/ed25519/src/key_centric_apis.rs | 2 +- libcrux-ml-dsa/src/key_centric_apis.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index 268ee7c68d..2a0f1d4a94 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -228,7 +228,7 @@ macro_rules! impl_mod { ) -> Result { let mut signing_key = [0u8; arrayref::$ty::SIGNING_KEY_LEN].classify(); let mut verification_key = [0u8; arrayref::$ty::VERIFICATION_KEY_LEN]; - arrayref::$ty::keygen(&mut signing_key, &mut verification_key, bytes.classify())?; + arrayref::$ty::keygen(&mut signing_key, &mut verification_key, bytes)?; Ok(KeyPair { signing_key: SigningKey::from(signing_key), diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index 5cd4fea4fb..6c3145ff99 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -218,7 +218,7 @@ impl KeyPair { pub fn generate_derand(bytes: [U8; RAND_KEYGEN_LEN]) -> KeyPair { let mut signing_key = [0u8; arrayref::Ed25519::SIGNING_KEY_LEN].classify(); let mut verification_key = [0u8; arrayref::Ed25519::VERIFICATION_KEY_LEN]; - arrayref::Ed25519::keygen(&mut signing_key, &mut verification_key, bytes.classify()); + arrayref::Ed25519::keygen(&mut signing_key, &mut verification_key, bytes); KeyPair { signing_key: SigningKey::from(signing_key), diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index e7495547a5..f7ea7fdbe5 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -241,7 +241,7 @@ macro_rules! impl_mod { pub fn generate_derand(bytes: [U8; RAND_KEYGEN_LEN]) -> KeyPair { let mut signing_key = [0u8; arrayref::$ty::SIGNING_KEY_LEN].classify(); let mut verification_key = [0u8; arrayref::$ty::VERIFICATION_KEY_LEN]; - arrayref::$ty::keygen(&mut signing_key, &mut verification_key, bytes.classify()); + arrayref::$ty::keygen(&mut signing_key, &mut verification_key, bytes); KeyPair { signing_key: SigningKey::from(signing_key), From 7a942d26235652f9ab4a866620f10ba381d1724b Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Fri, 19 Dec 2025 14:41:28 +0100 Subject: [PATCH 54/61] [Signature] Add comment about ensuring good randomness to documentation --- crates/primitives/signature/src/lib.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/primitives/signature/src/lib.rs b/crates/primitives/signature/src/lib.rs index 56bd677ac0..fdfc152f17 100644 --- a/crates/primitives/signature/src/lib.rs +++ b/crates/primitives/signature/src/lib.rs @@ -24,7 +24,11 @@ pub mod ecdsa { //! Nonce, sha2_256::{KeyPair, SigningKey, VerificationKey} //! }; //! - //! // generate a new nonce + //! // Generate a new nonce. + //! // Ensure you use good randomness. + //! // It is not recommended to use OsRng directly! + //! // Instead it is highly encouraged to use RNGs like NISTs DRBG to account for + //! // bad system entropy. //! use rand::TryRngCore; //! let mut rng = rand::rngs::OsRng; //! let nonce = Nonce::random(&mut rng.unwrap_mut()).unwrap(); @@ -117,6 +121,10 @@ pub mod ed25519 { //! ```rust //! use libcrux_signature::ed25519::KeyPair; //! + //! // Ensure you use good randomness. + //! // It is not recommended to use OsRng directly! + //! // Instead it is highly encouraged to use RNGs like NISTs DRBG to account for + //! // bad system entropy. //! use rand::TryRngCore; //! let mut rng = rand::rngs::OsRng; //! @@ -201,6 +209,10 @@ pub mod mldsa { //! ```rust //! use libcrux_signature::mldsa::ml_dsa_44::{SigningKey, KeyPair, VerificationKey}; //! + //! // Ensure you use good randomness. + //! // It is not recommended to use OsRng directly! + //! // Instead it is highly encouraged to use RNGs like NISTs DRBG to account for + //! // bad system entropy. //! use rand::{RngCore, TryRngCore}; //! let mut rng = rand::rngs::OsRng; //! let mut rng = rng.unwrap_mut(); From f9183ecfda02cd45106f5b90826281a57cb0b08d Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Fri, 19 Dec 2025 15:42:41 +0100 Subject: [PATCH 55/61] [ML-DSA] Implement key centric API on existing key types --- libcrux-ml-dsa/src/key_centric_apis.rs | 170 +++++++++++++------------ libcrux-ml-dsa/src/lib.rs | 2 +- 2 files changed, 93 insertions(+), 79 deletions(-) diff --git a/libcrux-ml-dsa/src/key_centric_apis.rs b/libcrux-ml-dsa/src/key_centric_apis.rs index f7ea7fdbe5..86022694cf 100644 --- a/libcrux-ml-dsa/src/key_centric_apis.rs +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -64,16 +64,16 @@ //! MlDsa44::verify(&verification_key, b"payload", &signature, b"context").unwrap(); //! ``` -#[cfg(doc)] -use self::{ml_dsa_44::MlDsa44, ml_dsa_65::MlDsa65, ml_dsa_87::MlDsa87}; +// #[cfg(doc)] +// use self::{ml_dsa_44::MlDsa44, ml_dsa_65::MlDsa65, ml_dsa_87::MlDsa87}; use libcrux_traits::signature::{ impl_key_centric_types, impl_sign_consts, SignConsts, WrongLengthError, }; macro_rules! impl_mod { - ($ty:ident, $module:ident) => { - use libcrux_secrets::{Classify, Declassify, DeclassifyRef, DeclassifyRefMut, U8}; + ($ty:ident, $module:ident, $keypair:ty, $sigkey:ty, $verkey:ty, $signature:ty) => { + use libcrux_secrets::{Declassify, DeclassifyRef, DeclassifyRefMut, U8}; pub(super) const VERIFICATION_KEY_LEN: usize = crate::ml_dsa_generic::$module::VERIFICATION_KEY_SIZE; @@ -83,11 +83,13 @@ macro_rules! impl_mod { use super::*; + /// XXX: Decide whether we need these here (or need them to be public). #[doc(inline)] - pub use arrayref::*; - #[doc(inline)] - pub use slice::$ty; + use arrayref::*; + // #[doc(inline)] + // use slice::$ty; + /// XXX: Decide whether we need these here (or need them to be public). pub(crate) mod arrayref { #[derive(Debug, PartialEq)] pub(crate) struct $ty; @@ -102,7 +104,9 @@ macro_rules! impl_mod { WrongLengthError ); } - pub mod slice { + + /// XXX: Decide whether we need these here (or need them to be public). + pub(crate) mod slice { //! Slice-based APIs for ML-DSA. //! //! Usage example: @@ -227,35 +231,14 @@ macro_rules! impl_mod { } } - impl KeyPair { - #[cfg(feature = "rand")] - /// Generate an ML-DSA key pair - pub fn generate(rng: &mut impl rand::CryptoRng) -> KeyPair { - let mut bytes = [0u8; arrayref::$ty::RAND_KEYGEN_LEN]; - rng.fill_bytes(&mut bytes); - - Self::generate_derand(bytes.classify()) - } - - /// Generate an ML-DSA key pair (derand) - pub fn generate_derand(bytes: [U8; RAND_KEYGEN_LEN]) -> KeyPair { - let mut signing_key = [0u8; arrayref::$ty::SIGNING_KEY_LEN].classify(); - let mut verification_key = [0u8; arrayref::$ty::VERIFICATION_KEY_LEN]; - arrayref::$ty::keygen(&mut signing_key, &mut verification_key, bytes); - - KeyPair { - signing_key: SigningKey::from(signing_key), - verification_key: VerificationKey::from(verification_key), - } - } - } + /// XXX: Decide whether we need these here (or need them to be public). impl arrayref::$ty { /// Generate an ML-DSA signature /// /// The parameter `context` is used for domain separation /// and is a byte string of length at most 255 bytes. It /// may also be empty. - pub fn sign( + pub(crate) fn sign( key: &[U8; Self::SIGNING_KEY_LEN], payload: &[u8], signature: &mut [u8; Self::SIGNATURE_LEN], @@ -276,7 +259,7 @@ macro_rules! impl_mod { /// The parameter `context` is used for domain separation /// and is a byte string of length at most 255 bytes. It /// may also be empty. - pub fn sign_pre_hashed_shake128( + pub(crate) fn sign_pre_hashed_shake128( key: &[U8; Self::SIGNING_KEY_LEN], payload: &[u8], signature: &mut [u8; Self::SIGNATURE_LEN], @@ -304,7 +287,7 @@ macro_rules! impl_mod { /// The parameter `context` is used for domain separation /// and is a byte string of length at most 255 bytes. It /// may also be empty. - pub fn verify( + pub(crate) fn verify( key: &[u8; Self::VERIFICATION_KEY_LEN], payload: &[u8], signature: &[u8; Self::SIGNATURE_LEN], @@ -320,7 +303,7 @@ macro_rules! impl_mod { /// The parameter `context` is used for domain separation /// and is a byte string of length at most 255 bytes. It /// may also be empty. - pub fn verify_pre_hashed_shake128( + pub(crate) fn verify_pre_hashed_shake128( key: &[u8; Self::VERIFICATION_KEY_LEN], payload: &[u8], signature: &[u8; Self::SIGNATURE_LEN], @@ -336,7 +319,7 @@ macro_rules! impl_mod { ) } /// Generate an ML-DSA Key Pair - pub fn keygen( + pub(crate) fn keygen( signing_key: &mut [U8; Self::SIGNING_KEY_LEN], verification_key: &mut [u8; Self::VERIFICATION_KEY_LEN], randomness: [U8; Self::RAND_KEYGEN_LEN], @@ -348,13 +331,15 @@ macro_rules! impl_mod { ); } } + + /// XXX: Decide whether we need these here (or need them to be public). impl slice::$ty { /// Generate an ML-DSA signature /// /// The parameter `context` is used for domain separation /// and is a byte string of length at most 255 bytes. It /// may also be empty. - pub fn sign( + pub(crate) fn sign( key: &[U8], payload: &[u8], signature: &mut [u8], @@ -377,7 +362,7 @@ macro_rules! impl_mod { /// The parameter `context` is used for domain separation /// and is a byte string of length at most 255 bytes. It /// may also be empty. - pub fn sign_pre_hashed_shake128( + pub(crate) fn sign_pre_hashed_shake128( key: &[U8], payload: &[u8], signature: &mut [u8], @@ -402,7 +387,7 @@ macro_rules! impl_mod { /// The parameter `context` is used for domain separation /// and is a byte string of length at most 255 bytes. It /// may also be empty. - pub fn verify( + pub(crate) fn verify( key: &[u8], payload: &[u8], signature: &[u8], @@ -424,7 +409,7 @@ macro_rules! impl_mod { /// The parameter `context` is used for domain separation /// and is a byte string of length at most 255 bytes. It /// may also be empty. - pub fn verify_pre_hashed_shake128( + pub(crate) fn verify_pre_hashed_shake128( key: &[u8], payload: &[u8], signature: &[u8], @@ -443,7 +428,7 @@ macro_rules! impl_mod { /// Generate an ML-DSA Key Pair #[cfg(not(eurydice))] - pub fn keygen( + pub(crate) fn keygen( signing_key: &mut [U8], verification_key: &mut [u8], randomness: [U8; Self::RAND_KEYGEN_LEN], @@ -460,13 +445,15 @@ macro_rules! impl_mod { Ok(()) } } + + /// XXX: Decide whether we need these here (or need them to be public). impl<'a> SigningKeyRef<'a> { /// Generate an ML-DSA signature /// /// The parameter `context` is used for domain separation /// and is a byte string of length at most 255 bytes. It /// may also be empty. - pub fn sign( + pub(crate) fn sign( &self, payload: &[u8], signature: &mut [u8], @@ -481,7 +468,7 @@ macro_rules! impl_mod { /// The parameter `context` is used for domain separation /// and is a byte string of length at most 255 bytes. It /// may also be empty. - pub fn sign_pre_hashed_shake128( + pub(crate) fn sign_pre_hashed_shake128( &self, payload: &[u8], signature: &mut [u8], @@ -497,13 +484,15 @@ macro_rules! impl_mod { ) } } + + /// XXX: Decide whether we need these here (or need them to be public). impl<'a> VerificationKeyRef<'a> { /// Verify an ML-DSA Signature /// /// The parameter `context` is used for domain separation /// and is a byte string of length at most 255 bytes. It /// may also be empty. - pub fn verify( + pub(crate) fn verify( &self, payload: &[u8], signature: &[u8], @@ -517,7 +506,7 @@ macro_rules! impl_mod { /// The parameter `context` is used for domain separation /// and is a byte string of length at most 255 bytes. It /// may also be empty. - pub fn verify_pre_hashed_shake128( + pub(crate) fn verify_pre_hashed_shake128( &self, payload: &[u8], signature: &[u8], @@ -528,7 +517,25 @@ macro_rules! impl_mod { } // key-centric API - impl SigningKey { + impl $keypair { + #[cfg(feature = "rand")] + /// Generate an ML-DSA key pair + pub fn generate(rng: &mut impl rand::CryptoRng) -> Self { + let mut bytes = [0u8; crate::KEY_GENERATION_RANDOMNESS_SIZE]; + rng.fill_bytes(&mut bytes); + + Self::generate_derand(bytes.classify()) + } + + /// Generate an ML-DSA key pair (derand) + pub fn generate_derand( + randomness: [U8; crate::KEY_GENERATION_RANDOMNESS_SIZE], + ) -> Self { + crate::$module::generate_key_pair(randomness) + } + } + + impl $sigkey { /// Generate an ML-DSA signature /// /// The parameter `context` is used for domain separation @@ -536,13 +543,11 @@ macro_rules! impl_mod { /// may also be empty. pub fn sign( &self, - payload: &[u8], + message: &[u8], context: &[u8], - randomness: [U8; 32], - ) -> Result { - let mut signature = [0u8; SIGNATURE_LEN]; - arrayref::$ty::sign(self.as_ref(), payload, &mut signature, context, randomness) - .map(|_| Signature::from(signature)) + randomness: [U8; crate::SIGNING_RANDOMNESS_SIZE], + ) -> Result<$signature, crate::SigningError> { + crate::$module::sign(self, message, context, randomness) } /// Generate a HashML-DSA Signature, with a SHAKE128 pre-hashing @@ -552,22 +557,15 @@ macro_rules! impl_mod { /// may also be empty. pub fn sign_pre_hashed_shake128( &self, - payload: &[u8], + message: &[u8], context: &[u8], - randomness: [U8; 32], - ) -> Result { - let mut signature = [0u8; SIGNATURE_LEN]; - arrayref::$ty::sign_pre_hashed_shake128( - self.as_ref(), - payload, - &mut signature, - context, - randomness, - ) - .map(|_| Signature::from(signature)) + randomness: [U8; crate::SIGNING_RANDOMNESS_SIZE], + ) -> Result<$signature, crate::SigningError> { + crate::$module::sign_pre_hashed_shake128(self, message, context, randomness) } } - impl VerificationKey { + + impl $verkey { /// Verify an ML-DSA Signature /// /// The parameter `context` is used for domain separation @@ -575,11 +573,11 @@ macro_rules! impl_mod { /// may also be empty. pub fn verify( &self, - payload: &[u8], - signature: &Signature, + message: &[u8], + signature: &$signature, context: &[u8], ) -> Result<(), crate::VerificationError> { - arrayref::$ty::verify(self.as_ref(), payload, signature.as_ref(), context) + crate::$module::verify(self, message, context, signature) } /// Verify an ML-DSA Signature, with a SHAKE128 pre-hashing @@ -589,16 +587,11 @@ macro_rules! impl_mod { /// may also be empty. pub fn verify_pre_hashed_shake128( &self, - payload: &[u8], - signature: &Signature, + message: &[u8], + signature: &$signature, context: &[u8], ) -> Result<(), crate::VerificationError> { - arrayref::$ty::verify_pre_hashed_shake128( - self.as_ref(), - payload, - signature.as_ref(), - context, - ) + crate::$module::verify_pre_hashed_shake128(self, message, context, signature) } } }; @@ -606,17 +599,38 @@ macro_rules! impl_mod { #[cfg(feature = "mldsa44")] pub mod ml_dsa_44 { - impl_mod!(MlDsa44, ml_dsa_44); + impl_mod!( + MlDsa44, + ml_dsa_44, + crate::ml_dsa_44::MLDSA44KeyPair, + crate::ml_dsa_44::MLDSA44SigningKey, + crate::ml_dsa_44::MLDSA44VerificationKey, + crate::ml_dsa_44::MLDSA44Signature + ); } #[cfg(feature = "mldsa65")] pub mod ml_dsa_65 { - impl_mod!(MlDsa65, ml_dsa_65); + impl_mod!( + MlDsa65, + ml_dsa_65, + crate::ml_dsa_65::MLDSA65KeyPair, + crate::ml_dsa_65::MLDSA65SigningKey, + crate::ml_dsa_65::MLDSA65VerificationKey, + crate::ml_dsa_65::MLDSA65Signature + ); } #[cfg(feature = "mldsa87")] pub mod ml_dsa_87 { - impl_mod!(MlDsa87, ml_dsa_87); + impl_mod!( + MlDsa87, + ml_dsa_87, + crate::ml_dsa_87::MLDSA87KeyPair, + crate::ml_dsa_87::MLDSA87SigningKey, + crate::ml_dsa_87::MLDSA87VerificationKey, + crate::ml_dsa_87::MLDSA87Signature + ); } #[test] diff --git a/libcrux-ml-dsa/src/lib.rs b/libcrux-ml-dsa/src/lib.rs index b75d87046f..6959f227e0 100644 --- a/libcrux-ml-dsa/src/lib.rs +++ b/libcrux-ml-dsa/src/lib.rs @@ -41,4 +41,4 @@ pub mod ml_dsa_65; pub mod ml_dsa_87; #[cfg(not(hax))] -pub mod key_centric_apis; +pub(crate) mod key_centric_apis; From f4a32135969e3ce14c690ffaf463118e1f59728c Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Fri, 19 Dec 2025 15:47:22 +0100 Subject: [PATCH 56/61] [Ed25519] Add `Ed25519KeyPair` type --- crates/algorithms/ed25519/CHANGELOG.md | 5 +++-- crates/algorithms/ed25519/src/impl_hacl.rs | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/crates/algorithms/ed25519/CHANGELOG.md b/crates/algorithms/ed25519/CHANGELOG.md index 45b6be69fa..85b0813aba 100644 --- a/crates/algorithms/ed25519/CHANGELOG.md +++ b/crates/algorithms/ed25519/CHANGELOG.md @@ -6,8 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - -- [#1241](https://github.com/cryspen/libcrux/pull/1241): Add key-centric and slice-based signature public APIs +- [#1241](https://github.com/cryspen/libcrux/pull/1241): + - Add `Ed25519KeyPair` type + - Add key-centric and slice-based signature public APIs ## [0.0.4] (2025-11-05) diff --git a/crates/algorithms/ed25519/src/impl_hacl.rs b/crates/algorithms/ed25519/src/impl_hacl.rs index d8552d923f..afef19de43 100644 --- a/crates/algorithms/ed25519/src/impl_hacl.rs +++ b/crates/algorithms/ed25519/src/impl_hacl.rs @@ -114,10 +114,16 @@ pub fn secret_to_public(pk: &mut [u8; 32], sk: &[u8; 32]) { crate::hacl::ed25519::secret_to_public(pk, sk) } +/// An Ed25519 key pair. +pub struct Ed25519KeyPair { + /// An Ed25519 signing key + pub signing_key: SigningKey, + /// An Ed25519 verification key + pub verification_key: VerificationKey, +} + #[cfg(feature = "rand")] -pub fn generate_key_pair( - rng: &mut impl rand_core::CryptoRng, -) -> Result<(SigningKey, VerificationKey), Error> { +pub fn generate_key_pair(rng: &mut impl rand_core::CryptoRng) -> Result { use rand_core::TryRngCore; const LIMIT: usize = 100; @@ -143,5 +149,8 @@ pub fn generate_key_pair( secret_to_public(&mut pk, &sk); - Ok((SigningKey { value: sk }, VerificationKey { value: pk })) + Ok(Ed25519KeyPair { + signing_key: SigningKey { value: sk }, + verification_key: VerificationKey { value: pk }, + }) } From 7e6d6665a007d76dbe054a9b7d01c164cc4fc4f7 Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Fri, 19 Dec 2025 15:49:52 +0100 Subject: [PATCH 57/61] [Ed25519] Expose only key centric API --- crates/algorithms/ed25519/CHANGELOG.md | 2 +- crates/algorithms/ed25519/src/impl_hacl.rs | 13 +++- .../ed25519/src/key_centric_apis.rs | 72 ++++++++++--------- crates/algorithms/ed25519/src/lib.rs | 2 +- 4 files changed, 53 insertions(+), 36 deletions(-) diff --git a/crates/algorithms/ed25519/CHANGELOG.md b/crates/algorithms/ed25519/CHANGELOG.md index 85b0813aba..d877a66e2a 100644 --- a/crates/algorithms/ed25519/CHANGELOG.md +++ b/crates/algorithms/ed25519/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - [#1241](https://github.com/cryspen/libcrux/pull/1241): - Add `Ed25519KeyPair` type - - Add key-centric and slice-based signature public APIs + - Add key-centric signature public APIs ## [0.0.4] (2025-11-05) diff --git a/crates/algorithms/ed25519/src/impl_hacl.rs b/crates/algorithms/ed25519/src/impl_hacl.rs index afef19de43..0d06b4abff 100644 --- a/crates/algorithms/ed25519/src/impl_hacl.rs +++ b/crates/algorithms/ed25519/src/impl_hacl.rs @@ -18,7 +18,7 @@ pub enum Error { #[derive(Default, Clone, Copy)] #[cfg_attr(feature = "codec", derive(TlsSerialize, TlsDeserialize, TlsSize))] pub struct VerificationKey { - value: [u8; 32], + pub(crate) value: [u8; 32], } impl VerificationKey { @@ -42,7 +42,16 @@ impl AsRef<[u8; 32]> for VerificationKey { /// An Ed25519 private, signing key #[derive(Default)] pub struct SigningKey { - value: [u8; 32], + pub(crate) value: [u8; 32], +} + +#[repr(transparent)] +/// An Ed25519 signature +pub struct Signature(pub(crate) [u8; 64]); +impl From<[u8; 64]> for Signature { + fn from(value: [u8; 64]) -> Self { + Signature(value) + } } impl SigningKey { diff --git a/crates/algorithms/ed25519/src/key_centric_apis.rs b/crates/algorithms/ed25519/src/key_centric_apis.rs index 6c3145ff99..1df718cf80 100644 --- a/crates/algorithms/ed25519/src/key_centric_apis.rs +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -87,7 +87,7 @@ impl_key_centric_types!( WrongLengthError, WrongLengthError ); - +/// XXX: Decide whether we need these here (or need them to be public). pub(crate) mod arrayref { #[derive(Debug, PartialEq)] pub(crate) struct Ed25519; @@ -131,7 +131,8 @@ pub(crate) mod arrayref { } } } -pub mod slice { +/// XXX: Decide whether we need these here (or need them to be public). +pub(crate) mod slice { //! Slice-based APIs for Ed25519. //! //! ```rust @@ -204,30 +205,29 @@ pub mod slice { } } -impl KeyPair { +impl crate::Ed25519KeyPair { #[cfg(feature = "rand")] /// Generate an Ed25519 key pair - pub fn generate(rng: &mut impl rand_core::CryptoRng) -> KeyPair { - let mut bytes = [0u8; arrayref::Ed25519::RAND_KEYGEN_LEN]; - rng.fill_bytes(&mut bytes); - - Self::generate_derand(bytes.classify()) + pub fn generate(rng: &mut impl rand_core::CryptoRng) -> Self { + crate::generate_key_pair(rng) } /// Generate an Ed25519 key pair (derand) - pub fn generate_derand(bytes: [U8; RAND_KEYGEN_LEN]) -> KeyPair { + pub fn generate_derand(bytes: [U8; RAND_KEYGEN_LEN]) -> crate::Ed25519KeyPair { let mut signing_key = [0u8; arrayref::Ed25519::SIGNING_KEY_LEN].classify(); let mut verification_key = [0u8; arrayref::Ed25519::VERIFICATION_KEY_LEN]; arrayref::Ed25519::keygen(&mut signing_key, &mut verification_key, bytes); - KeyPair { - signing_key: SigningKey::from(signing_key), - verification_key: VerificationKey::from(verification_key), + crate::Ed25519KeyPair { + signing_key: crate::SigningKey::from_bytes(signing_key), + verification_key: crate::VerificationKey::from_bytes(verification_key), } } } + +/// XXX: Decide whether we need these here (or need them to be public). impl arrayref::Ed25519 { - pub fn sign( + pub(crate) fn sign( key: &[U8; Self::SIGNING_KEY_LEN], payload: &[u8], signature: &mut [u8; Self::SIGNATURE_LEN], @@ -243,7 +243,7 @@ impl arrayref::Ed25519 { } #[inline(always)] - pub fn verify( + pub(crate) fn verify( key: &[u8; Self::VERIFICATION_KEY_LEN], payload: &[u8], signature: &[u8; Self::SIGNATURE_LEN], @@ -259,7 +259,7 @@ impl arrayref::Ed25519 { Err(arrayref::VerificationError::InvalidSignature) } } - pub fn keygen( + pub(crate) fn keygen( signing_key: &mut [U8; Self::SIGNING_KEY_LEN], verification_key: &mut [u8; Self::VERIFICATION_KEY_LEN], randomness: [U8; Self::RAND_KEYGEN_LEN], @@ -268,8 +268,10 @@ impl arrayref::Ed25519 { crate::secret_to_public(verification_key, signing_key.declassify_ref()); } } + +/// XXX: Decide whether we need these here (or need them to be public). impl slice::Ed25519 { - pub fn sign( + pub(crate) fn sign( key: &[U8], payload: &[u8], signature: &mut [u8], @@ -284,7 +286,7 @@ impl slice::Ed25519 { arrayref::Ed25519::sign(&key, payload, signature).map_err(slice::SigningError::from) } - pub fn verify( + pub(crate) fn verify( key: &[u8], payload: &[u8], signature: &[u8], @@ -299,7 +301,7 @@ impl slice::Ed25519 { arrayref::Ed25519::verify(key, payload, signature).map_err(slice::VerificationError::from) } - pub fn keygen( + pub(crate) fn keygen( signing_key: &mut [U8], verification_key: &mut [u8], randomness: [U8; Self::RAND_KEYGEN_LEN], @@ -316,32 +318,38 @@ impl slice::Ed25519 { Ok(()) } } + +/// XXX: Decide whether we need these here (or need them to be public). impl<'a> SigningKeyRef<'a> { - pub fn sign(&self, payload: &[u8], signature: &mut [u8]) -> Result<(), slice::SigningError> { + pub(crate) fn sign( + &self, + payload: &[u8], + signature: &mut [u8], + ) -> Result<(), slice::SigningError> { slice::Ed25519::sign(self.as_ref(), payload, signature) } } +/// XXX: Decide whether we need these here (or need them to be public). impl<'a> VerificationKeyRef<'a> { - pub fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<(), slice::VerificationError> { + pub(crate) fn verify( + &self, + payload: &[u8], + signature: &[u8], + ) -> Result<(), slice::VerificationError> { slice::Ed25519::verify(self.as_ref(), payload, signature) } } // key-centric API -impl SigningKey { - pub fn sign(&self, payload: &[u8]) -> Result { - let mut signature = [0u8; SIGNATURE_LEN]; - arrayref::Ed25519::sign(self.as_ref(), payload, &mut signature) - .map(|_| Signature::from(signature)) +impl crate::SigningKey { + pub fn sign(&self, message: &[u8]) -> Result { + crate::sign(message, &self.value).map(|sig_bytes| sig_bytes.into()) } } -impl VerificationKey { - pub fn verify( - &self, - payload: &[u8], - signature: &Signature, - ) -> Result<(), arrayref::VerificationError> { - arrayref::Ed25519::verify(self.as_ref(), payload, signature.as_ref()) + +impl crate::VerificationKey { + pub fn verify(&self, message: &[u8], signature: &crate::Signature) -> Result<(), crate::Error> { + crate::verify(message, &self.value, &signature.0) } } diff --git a/crates/algorithms/ed25519/src/lib.rs b/crates/algorithms/ed25519/src/lib.rs index 78c941cd7c..727e76f4a0 100644 --- a/crates/algorithms/ed25519/src/lib.rs +++ b/crates/algorithms/ed25519/src/lib.rs @@ -8,6 +8,6 @@ mod hacl { } mod impl_hacl; -pub mod key_centric_apis; +pub(crate) mod key_centric_apis; pub use impl_hacl::*; From 2672089d8349c4c9b8462661c7f58da0d18f2643 Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Fri, 19 Dec 2025 16:14:41 +0100 Subject: [PATCH 58/61] [ECDSA] Rename key types --- benchmarks/benches/ecdsa_p256.rs | 8 ++-- crates/algorithms/ecdsa/CHANGELOG.md | 4 +- crates/algorithms/ecdsa/src/p256.rs | 42 ++++++++++----------- crates/algorithms/ecdsa/tests/self.rs | 6 +-- crates/algorithms/ecdsa/tests/wycheproof.rs | 4 +- 5 files changed, 33 insertions(+), 31 deletions(-) diff --git a/benchmarks/benches/ecdsa_p256.rs b/benchmarks/benches/ecdsa_p256.rs index 773760da8f..48fe3e788c 100644 --- a/benchmarks/benches/ecdsa_p256.rs +++ b/benchmarks/benches/ecdsa_p256.rs @@ -2,7 +2,7 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use benchmarks::util::*; use libcrux_ecdsa::{ - p256::{Nonce, PrivateKey, PublicKey}, + p256::{Nonce, SigningKey, VerificationKey}, DigestAlgorithm, }; @@ -19,7 +19,7 @@ fn sign(c: &mut Criterion) { let mut rng = rand::rng(); let sk: [u8; 32] = hex_str_to_array(SK_HEX); - let sk = PrivateKey::try_from(&sk).unwrap(); + let sk = SigningKey::try_from(&sk).unwrap(); let nonce = Nonce::random(&mut rng).unwrap(); let msg = b"sample"; @@ -93,9 +93,9 @@ fn verify(c: &mut Criterion) { let mut rng = rand::rng(); let pk = hex_str_to_bytes(PK_HEX); - let pk = PublicKey::try_from(pk.as_slice()).unwrap(); + let pk = VerificationKey::try_from(pk.as_slice()).unwrap(); let sk: [u8; 32] = hex_str_to_array(SK_HEX); - let sk = PrivateKey::try_from(&sk).unwrap(); + let sk = SigningKey::try_from(&sk).unwrap(); let nonce = Nonce::random(&mut rng).unwrap(); let msg = b"sample"; let sig = libcrux_ecdsa::p256::sign(DigestAlgorithm::Sha256, &msg[..], &sk, &nonce) diff --git a/crates/algorithms/ecdsa/CHANGELOG.md b/crates/algorithms/ecdsa/CHANGELOG.md index 09ad332b92..97f9f95828 100644 --- a/crates/algorithms/ecdsa/CHANGELOG.md +++ b/crates/algorithms/ecdsa/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- [#1241](https://github.com/cryspen/libcrux/pull/1241): Add key-centric and slice-based signature public APIs +- [#1241](https://github.com/cryspen/libcrux/pull/1241): + - Rename `PrivateKey` -> `SigningKey`, `PublicKey` -> `VerificationKey` + - Add key-centric and slice-based signature public APIs ## [0.0.4] (2025-11-05) diff --git a/crates/algorithms/ecdsa/src/p256.rs b/crates/algorithms/ecdsa/src/p256.rs index a6f03a7270..6f94c12c53 100644 --- a/crates/algorithms/ecdsa/src/p256.rs +++ b/crates/algorithms/ecdsa/src/p256.rs @@ -20,12 +20,12 @@ pub struct Signature { /// An ECDSA P-256 nonce pub struct Nonce(pub(super) [u8; 32]); -/// An ECDSA P-256 private key -pub struct PrivateKey([u8; 32]); +/// An ECDSA P-256 signing key +pub struct SigningKey([u8; 32]); -/// An ECDSA P-256 public key +/// An ECDSA P-256 verification key #[derive(Debug)] -pub struct PublicKey(pub [u8; 64]); +pub struct VerificationKey(pub [u8; 64]); mod conversions { use super::*; @@ -50,7 +50,7 @@ mod conversions { } } - impl TryFrom<&[u8; 32]> for PrivateKey { + impl TryFrom<&[u8; 32]> for SigningKey { type Error = Error; fn try_from(value: &[u8; 32]) -> Result { @@ -58,7 +58,7 @@ mod conversions { } } - impl TryFrom<&[u8]> for PrivateKey { + impl TryFrom<&[u8]> for SigningKey { type Error = Error; fn try_from(value: &[u8]) -> Result { @@ -66,19 +66,19 @@ mod conversions { } } - impl AsRef<[u8]> for PrivateKey { + impl AsRef<[u8]> for SigningKey { fn as_ref(&self) -> &[u8] { &self.0 } } - impl AsRef<[u8; 32]> for PrivateKey { + impl AsRef<[u8; 32]> for SigningKey { fn as_ref(&self) -> &[u8; 32] { &self.0 } } - impl TryFrom<&[u8; 64]> for PublicKey { + impl TryFrom<&[u8; 64]> for VerificationKey { type Error = Error; fn try_from(value: &[u8; 64]) -> Result { @@ -86,7 +86,7 @@ mod conversions { } } - impl TryFrom<&[u8]> for PublicKey { + impl TryFrom<&[u8]> for VerificationKey { type Error = Error; fn try_from(value: &[u8]) -> Result { @@ -94,13 +94,13 @@ mod conversions { } } - impl AsRef<[u8]> for PublicKey { + impl AsRef<[u8]> for VerificationKey { fn as_ref(&self) -> &[u8] { &self.0 } } - impl AsRef<[u8; 64]> for PublicKey { + impl AsRef<[u8; 64]> for VerificationKey { fn as_ref(&self) -> &[u8; 64] { &self.0 } @@ -188,8 +188,8 @@ fn validate_scalar_slice(scalar: &[u8]) -> Result<[u8; 32], Error> { validate_scalar_(&private).map(|_| private) } -fn validate_private_key_slice(scalar: &[u8]) -> Result { - validate_scalar_slice(scalar).map(|a| PrivateKey(a)) +fn validate_private_key_slice(scalar: &[u8]) -> Result { + validate_scalar_slice(scalar).map(|a| SigningKey(a)) } /// Prepare the nonce for EcDSA and validate the key @@ -227,7 +227,7 @@ pub mod rand { } } - impl PrivateKey { + impl SigningKey { /// Generate a random [`PrivateKey`] for ECDSA. pub fn random(rng: &mut (impl CryptoRng + RngCore)) -> Result { random_scalar(rng).map(|s| Self(s)) @@ -238,7 +238,7 @@ pub mod rand { pub fn sign( hash: DigestAlgorithm, payload: &[u8], - private_key: &PrivateKey, + private_key: &SigningKey, rng: &mut (impl CryptoRng + RngCore), ) -> Result { let nonce = Nonce(random_scalar(rng)?); @@ -253,7 +253,7 @@ pub mod rand { pub fn sign( hash: DigestAlgorithm, payload: &[u8], - private_key: &PrivateKey, + private_key: &SigningKey, nonce: &Nonce, ) -> Result { _sign(hash, payload, private_key, nonce) @@ -263,7 +263,7 @@ pub fn sign( fn _sign( hash: DigestAlgorithm, payload: &[u8], - private_key: &PrivateKey, + private_key: &SigningKey, nonce: &Nonce, ) -> Result { let mut signature = [0u8; 64]; @@ -305,7 +305,7 @@ fn u32_len(bytes: &[u8]) -> Result { } /// Prepare the public key for EcDSA -fn validate_pk(public_key: &[u8]) -> Result { +fn validate_pk(public_key: &[u8]) -> Result { if public_key.is_empty() { return Err(Error::SigningError); } @@ -323,7 +323,7 @@ fn validate_pk(public_key: &[u8]) -> Result { } }; - let pk = PublicKey(pk); + let pk = VerificationKey(pk); validate_point(&pk.0).map(|_| pk) } @@ -334,7 +334,7 @@ pub fn verify( hash: DigestAlgorithm, payload: &[u8], signature: &Signature, - public_key: &PublicKey, + public_key: &VerificationKey, ) -> Result<(), Error> { let len = u32_len(payload)?; diff --git a/crates/algorithms/ecdsa/tests/self.rs b/crates/algorithms/ecdsa/tests/self.rs index 7601ee339a..73e17f8a82 100644 --- a/crates/algorithms/ecdsa/tests/self.rs +++ b/crates/algorithms/ecdsa/tests/self.rs @@ -4,7 +4,7 @@ mod util; mod rand { use crate::util::*; use libcrux_ecdsa::{ - p256::{Nonce, PrivateKey, PublicKey}, + p256::{Nonce, SigningKey, VerificationKey}, *, }; use rand_core::OsRng; @@ -20,9 +20,9 @@ mod rand { let mut rng = os_rng.unwrap_mut(); let pk = hex_str_to_bytes(PK_HEX); - let pk = PublicKey::try_from(pk.as_slice()).unwrap(); + let pk = VerificationKey::try_from(pk.as_slice()).unwrap(); let sk: [u8; 32] = hex_str_to_array(SK_HEX); - let sk = PrivateKey::try_from(&sk).unwrap(); + let sk = SigningKey::try_from(&sk).unwrap(); let nonce = Nonce::random(&mut rng).unwrap(); let msg = b"sample"; diff --git a/crates/algorithms/ecdsa/tests/wycheproof.rs b/crates/algorithms/ecdsa/tests/wycheproof.rs index eed4ad996b..dd24b442bb 100644 --- a/crates/algorithms/ecdsa/tests/wycheproof.rs +++ b/crates/algorithms/ecdsa/tests/wycheproof.rs @@ -1,6 +1,6 @@ mod util; use libcrux_ecdsa::{ - p256::{self, PublicKey}, + p256::{self, VerificationKey}, DigestAlgorithm, Error, }; use util::*; @@ -106,7 +106,7 @@ fn test_wycheproof() { assert_eq!(testGroup.sha, "SHA-256"); let pk = hex_str_to_bytes(&testGroup.key.uncompressed); - let pk = PublicKey::try_from(pk.as_slice()).unwrap(); + let pk = VerificationKey::try_from(pk.as_slice()).unwrap(); for test in testGroup.tests.iter() { println!("Test {:?}: {:?}", test.tcId, test.comment); From 5158003ce3174e733632547717bfe353f9171116 Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Fri, 19 Dec 2025 16:16:35 +0100 Subject: [PATCH 59/61] [ECDSA] Add `ECDSAKeyPair` type --- crates/algorithms/ecdsa/src/p256.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/algorithms/ecdsa/src/p256.rs b/crates/algorithms/ecdsa/src/p256.rs index 6f94c12c53..76a6ef7ac2 100644 --- a/crates/algorithms/ecdsa/src/p256.rs +++ b/crates/algorithms/ecdsa/src/p256.rs @@ -27,6 +27,14 @@ pub struct SigningKey([u8; 32]); #[derive(Debug)] pub struct VerificationKey(pub [u8; 64]); +/// An ECDSA P-256 +pub struct ECDSAKeyPair { + /// An ECDSA P-256 signing key + pub signing_key: SigningKey, + /// An ECDSA P-256 verification key + pub verification_key: VerificationKey, +} + mod conversions { use super::*; From 508785461e5658a6f9d29fe5d73751e173f61754 Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Fri, 19 Dec 2025 16:34:47 +0100 Subject: [PATCH 60/61] [ECDSA] Expose only key centric API --- crates/algorithms/ecdsa/CHANGELOG.md | 2 +- .../algorithms/ecdsa/src/key_centric_apis.rs | 32 ++++++---- crates/algorithms/ecdsa/src/lib.rs | 2 +- crates/algorithms/ecdsa/src/p256.rs | 58 +++++++++++++++++-- 4 files changed, 78 insertions(+), 16 deletions(-) diff --git a/crates/algorithms/ecdsa/CHANGELOG.md b/crates/algorithms/ecdsa/CHANGELOG.md index 97f9f95828..57617398cc 100644 --- a/crates/algorithms/ecdsa/CHANGELOG.md +++ b/crates/algorithms/ecdsa/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1241](https://github.com/cryspen/libcrux/pull/1241): - Rename `PrivateKey` -> `SigningKey`, `PublicKey` -> `VerificationKey` - - Add key-centric and slice-based signature public APIs + - Add key-centric signature public APIs ## [0.0.4] (2025-11-05) diff --git a/crates/algorithms/ecdsa/src/key_centric_apis.rs b/crates/algorithms/ecdsa/src/key_centric_apis.rs index 2a0f1d4a94..a8f3016f8f 100644 --- a/crates/algorithms/ecdsa/src/key_centric_apis.rs +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -100,13 +100,15 @@ macro_rules! impl_mod { use super::*; #[doc(inline)] - pub use self::{ + /// XXX: Decide whether we need these here (or need them to be public). + pub(crate) use self::{ arrayref::{ KeyPair, Signature, SigningKey, SigningKeyRef, VerificationKey, VerificationKeyRef, }, slice::*, }; + /// XXX: Decide whether we need these here (or need them to be public). pub(crate) mod arrayref { #[derive(Debug, PartialEq)] pub(crate) struct $ty; @@ -121,7 +123,9 @@ macro_rules! impl_mod { WrongLengthError ); } - pub mod slice { + + /// XXX: Decide whether we need these here (or need them to be public). + pub(crate) mod slice { //! Slice-based APIs for ECDSA-P256. //! //! ```rust @@ -236,9 +240,11 @@ macro_rules! impl_mod { }) } } + + /// XXX: Decide whether we need these here (or need them to be public). impl arrayref::$ty { /// Sign the `payload` with the `key`. - pub fn sign( + pub(crate) fn sign( key: &[U8; Self::SIGNING_KEY_LEN], payload: &[u8], signature: &mut [u8; Self::SIGNATURE_LEN], @@ -261,7 +267,7 @@ macro_rules! impl_mod { } /// Verify the `payload` and `signature` with the `key`. - pub fn verify( + pub(crate) fn verify( key: &[u8; Self::VERIFICATION_KEY_LEN], payload: &[u8], signature: &[u8; Self::SIGNATURE_LEN], @@ -281,7 +287,7 @@ macro_rules! impl_mod { } Ok(()) } - pub fn keygen( + pub(crate) fn keygen( signing_key: &mut [U8; Self::SIGNING_KEY_LEN], verification_key: &mut [u8; Self::VERIFICATION_KEY_LEN], randomness: [U8; Self::RAND_KEYGEN_LEN], @@ -301,9 +307,11 @@ macro_rules! impl_mod { Ok(()) } } + + /// XXX: Decide whether we need these here (or need them to be public). impl slice::$ty { /// Sign the `payload` with the `key`. - pub fn sign( + pub(crate) fn sign( key: &[U8], payload: &[u8], signature: &mut [u8], @@ -320,7 +328,7 @@ macro_rules! impl_mod { .map_err(slice::SigningError::from) } /// Verify the `payload` and `signature` with the `key`. - pub fn verify( + pub(crate) fn verify( key: &[u8], payload: &[u8], signature: &[u8], @@ -335,7 +343,7 @@ macro_rules! impl_mod { arrayref::$ty::verify(key, payload, signature) .map_err(slice::VerificationError::from) } - pub fn keygen( + pub(crate) fn keygen( signing_key: &mut [U8], verification_key: &mut [u8], randomness: [U8; Self::RAND_KEYGEN_LEN], @@ -350,9 +358,11 @@ macro_rules! impl_mod { arrayref::$ty::keygen(signing_key, verification_key, randomness) } } + + /// XXX: Decide whether we need these here (or need them to be public). impl<'a> SigningKeyRef<'a> { /// Sign the `payload`. - pub fn sign( + pub(crate) fn sign( &self, payload: &[u8], signature: &mut [u8], @@ -361,9 +371,11 @@ macro_rules! impl_mod { slice::$ty::sign(self.as_ref(), payload, signature, nonce) } } + + /// XXX: Decide whether we need these here (or need them to be public). impl<'a> VerificationKeyRef<'a> { /// Verify the `payload` and `signature`. - pub fn verify( + pub(crate) fn verify( &self, payload: &[u8], signature: &[u8], diff --git a/crates/algorithms/ecdsa/src/lib.rs b/crates/algorithms/ecdsa/src/lib.rs index 7ea51501ba..21a2e5d888 100644 --- a/crates/algorithms/ecdsa/src/lib.rs +++ b/crates/algorithms/ecdsa/src/lib.rs @@ -29,4 +29,4 @@ pub type DigestAlgorithm = libcrux_sha2::Algorithm; #[cfg(feature = "rand")] pub(crate) const RAND_LIMIT: usize = 100; -pub mod key_centric_apis; +pub(crate) mod key_centric_apis; diff --git a/crates/algorithms/ecdsa/src/p256.rs b/crates/algorithms/ecdsa/src/p256.rs index 76a6ef7ac2..7d7e4d5646 100644 --- a/crates/algorithms/ecdsa/src/p256.rs +++ b/crates/algorithms/ecdsa/src/p256.rs @@ -1,10 +1,12 @@ //! ECDSA on P-256 +use ::rand::CryptoRng; use libcrux_p256::{ - compressed_to_raw, ecdsa_sign_p256_sha2, ecdsa_sign_p256_sha384, ecdsa_sign_p256_sha512, - ecdsa_verif_p256_sha2, ecdsa_verif_p256_sha384, ecdsa_verif_p256_sha512, uncompressed_to_raw, - validate_private_key, validate_public_key, + compressed_to_raw, ecdh_api::EcdhArrayref, ecdsa_sign_p256_sha2, ecdsa_sign_p256_sha384, + ecdsa_sign_p256_sha512, ecdsa_verif_p256_sha2, ecdsa_verif_p256_sha384, + ecdsa_verif_p256_sha512, uncompressed_to_raw, validate_private_key, validate_public_key, }; +use libcrux_secrets::{Classify as _, U8}; use crate::DigestAlgorithm; @@ -27,7 +29,55 @@ pub struct SigningKey([u8; 32]); #[derive(Debug)] pub struct VerificationKey(pub [u8; 64]); -/// An ECDSA P-256 +const RAND_KEYGEN_LEN: usize = 32; + +impl ECDSAKeyPair { + pub fn generate(rng: &mut impl CryptoRng) -> Result { + let mut bytes = [0u8; RAND_KEYGEN_LEN]; + rng.fill_bytes(&mut bytes); + + Self::generate_derand(bytes.classify()) + } + + /// Generate an ECDSA-P256 key pair (derand) + pub fn generate_derand(randomness: [U8; RAND_KEYGEN_LEN]) -> Result { + let mut signing_key = [0u8; 32]; + let mut verification_key = [0u8; 64]; + + libcrux_p256::P256::generate_pair(&mut verification_key, &mut signing_key, &randomness); + + Ok(ECDSAKeyPair { + signing_key: SigningKey::try_from(&signing_key)?, + verification_key: VerificationKey::try_from(&verification_key)?, + }) + } +} + +impl VerificationKey { + /// Verify an ECDSA P-256 signature + pub fn verify( + &self, + hash: DigestAlgorithm, + message: &[u8], + signature: &Signature, + ) -> Result<(), Error> { + crate::p256::verify(hash, message, signature, self) + } +} + +impl SigningKey { + /// Generate and ECDSA P-256 signature + pub fn sign( + &self, + hash: DigestAlgorithm, + message: &[u8], + nonce: &Nonce, + ) -> Result { + crate::p256::sign(hash, message, self, nonce) + } +} + +/// An ECDSA P-256 key pair pub struct ECDSAKeyPair { /// An ECDSA P-256 signing key pub signing_key: SigningKey, From 4d93d7c600954d410d3a0e661957d78bfef0c7e4 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Tue, 17 Mar 2026 13:22:51 +0100 Subject: [PATCH 61/61] `SigningKey::random()` for ECDSA signature keygen --- crates/algorithms/ecdsa/src/lib.rs | 1 + crates/algorithms/ecdsa/src/p256.rs | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/crates/algorithms/ecdsa/src/lib.rs b/crates/algorithms/ecdsa/src/lib.rs index 21a2e5d888..305342d315 100644 --- a/crates/algorithms/ecdsa/src/lib.rs +++ b/crates/algorithms/ecdsa/src/lib.rs @@ -17,6 +17,7 @@ pub enum Error { NoCompressedPoint, NoUnCompressedPoint, SigningError, + SecretToPublicError, InvalidSignature, RandError, UnsupportedHash, diff --git a/crates/algorithms/ecdsa/src/p256.rs b/crates/algorithms/ecdsa/src/p256.rs index 7d7e4d5646..5bdae8059f 100644 --- a/crates/algorithms/ecdsa/src/p256.rs +++ b/crates/algorithms/ecdsa/src/p256.rs @@ -33,18 +33,30 @@ const RAND_KEYGEN_LEN: usize = 32; impl ECDSAKeyPair { pub fn generate(rng: &mut impl CryptoRng) -> Result { - let mut bytes = [0u8; RAND_KEYGEN_LEN]; - rng.fill_bytes(&mut bytes); + let mut verification_key = [0u8; 64]; + + let signing_key = SigningKey::random(rng)?; + // create verification key + libcrux_p256::P256::secret_to_public(&mut verification_key, signing_key.as_ref()) + .map_err(|_| Error::SecretToPublicError)?; - Self::generate_derand(bytes.classify()) + Ok(ECDSAKeyPair { + signing_key, + verification_key: VerificationKey::try_from(&verification_key)?, + }) } /// Generate an ECDSA-P256 key pair (derand) pub fn generate_derand(randomness: [U8; RAND_KEYGEN_LEN]) -> Result { - let mut signing_key = [0u8; 32]; + let signing_key = randomness; let mut verification_key = [0u8; 64]; - libcrux_p256::P256::generate_pair(&mut verification_key, &mut signing_key, &randomness); + // validate the signing key + crate::p256::validate_scalar_(&signing_key)?; + + // create verification key + libcrux_p256::P256::secret_to_public(&mut verification_key, &signing_key) + .map_err(|_| Error::SecretToPublicError)?; Ok(ECDSAKeyPair { signing_key: SigningKey::try_from(&signing_key)?,