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/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 2234d9a52e..57617398cc 100644 --- a/crates/algorithms/ecdsa/CHANGELOG.md +++ b/crates/algorithms/ecdsa/CHANGELOG.md @@ -5,6 +5,12 @@ 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): + - Rename `PrivateKey` -> `SigningKey`, `PublicKey` -> `VerificationKey` + - Add key-centric 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/ecdsa/Cargo.toml b/crates/algorithms/ecdsa/Cargo.toml index e2cb04036d..32b6f83f24 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,6 +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 = "../../utils/secrets" } +libcrux-traits = { version = "=0.0.4", path = "../../../traits" } rand = { version = "0.9", optional = true, default-features = false } [features] @@ -22,7 +24,14 @@ default = ["rand", "std"] rand = ["dep:rand"] std = ["rand?/std"] +# 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", +] + [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" 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..a8f3016f8f --- /dev/null +++ b/crates/algorithms/ecdsa/src/key_centric_apis.rs @@ -0,0 +1,496 @@ +//! 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, [1; 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, [1; 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, +}; + +macro_rules! impl_mod { + ($ty:ident, $module:ident, + $sign_fn:ident, + $verify_fn:ident) => { + use libcrux_secrets::{Classify, 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::*; + + #[doc(inline)] + /// 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; + use super::*; + impl_key_centric_types!( + $ty, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN, + WrongLengthError, + WrongLengthError + ); + } + + /// XXX: Decide whether we need these here (or need them to be public). + pub(crate) mod slice { + //! 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(&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(); + //! ``` + + /// 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::*; + impl_sign_consts!( + $ty, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN + ); + + /// 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, + /// The length of the provided payload is invalid. + InvalidPayloadLength, + /// An unknown error occurred. + UnknownError, + } + + /// 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 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, + } + } + + impl KeyPair { + #[cfg(feature = "rand")] + /// Generate an ECDSA-P256 key pair + pub fn generate(rng: &mut impl rand::CryptoRng) -> Result { + 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)?; + + Ok(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 { + /// Sign the `payload` with the `key`. + pub(crate) 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::InvalidPayloadLength)?, + payload, + key.declassify_ref(), + &nonce.0, + ); + if !result { + return Err(slice::SigningError::UnknownError); + } + Ok(()) + } + + /// Verify the `payload` and `signature` with the `key`. + pub(crate) 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::InvalidPayloadLength)?, + payload, + key, + <&[u8; 32]>::try_from(&signature[0..32]).unwrap(), + <&[u8; 32]>::try_from(&signature[32..]).unwrap(), + ); + if !result { + return Err(slice::VerificationError::InvalidSignature); + } + Ok(()) + } + 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], + ) -> Result<(), slice::KeygenError> { + use libcrux_p256::ecdh_api::EcdhArrayref; + + 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::GenerateSecretError::Unknown => { + slice::KeygenError::UnknownError + } + })?; + + Ok(()) + } + } + + /// XXX: Decide whether we need these here (or need them to be public). + impl slice::$ty { + /// Sign the `payload` with the `key`. + pub(crate) 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) + } + /// Verify the `payload` and `signature` with the `key`. + pub(crate) 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(crate) fn keygen( + 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(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(crate) fn sign( + &self, + payload: &[u8], + signature: &mut [u8], + nonce: &Nonce, + ) -> Result<(), slice::SigningError> { + 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(crate) fn verify( + &self, + payload: &[u8], + signature: &[u8], + ) -> Result<(), slice::VerificationError> { + slice::$ty::verify(self.as_ref(), payload, signature) + } + } + + // key-centric API + impl SigningKey { + /// Sign the `payload`. + 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 { + /// Verify the `payload` and `signature`. + 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 = "expose-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, + } = KeyPair::generate(&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 = "expose-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 = [1u8; EcdsaP256::RAND_KEYGEN_LEN]; + rng.fill_bytes(&mut bytes); + EcdsaP256::keygen(&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(); +} diff --git a/crates/algorithms/ecdsa/src/lib.rs b/crates/algorithms/ecdsa/src/lib.rs index f3c375424d..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, @@ -28,3 +29,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(crate) mod key_centric_apis; diff --git a/crates/algorithms/ecdsa/src/p256.rs b/crates/algorithms/ecdsa/src/p256.rs index 3121d2de88..5bdae8059f 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; @@ -18,14 +20,82 @@ 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]); +/// 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]); + +const RAND_KEYGEN_LEN: usize = 32; + +impl ECDSAKeyPair { + pub fn generate(rng: &mut impl CryptoRng) -> Result { + 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)?; + + 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 signing_key = randomness; + let mut verification_key = [0u8; 64]; + + // 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)?, + 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, + /// An ECDSA P-256 verification key + pub verification_key: VerificationKey, +} mod conversions { use super::*; @@ -50,7 +120,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 +128,7 @@ mod conversions { } } - impl TryFrom<&[u8]> for PrivateKey { + impl TryFrom<&[u8]> for SigningKey { type Error = Error; fn try_from(value: &[u8]) -> Result { @@ -66,19 +136,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 +156,7 @@ mod conversions { } } - impl TryFrom<&[u8]> for PublicKey { + impl TryFrom<&[u8]> for VerificationKey { type Error = Error; fn try_from(value: &[u8]) -> Result { @@ -94,13 +164,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 +258,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 +297,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 +308,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 +323,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 +333,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 +375,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 +393,7 @@ fn validate_pk(public_key: &[u8]) -> Result { } }; - let pk = PublicKey(pk); + let pk = VerificationKey(pk); validate_point(&pk.0).map(|_| pk) } @@ -334,7 +404,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); diff --git a/crates/algorithms/ed25519/CHANGELOG.md b/crates/algorithms/ed25519/CHANGELOG.md index a675a48c29..d877a66e2a 100644 --- a/crates/algorithms/ed25519/CHANGELOG.md +++ b/crates/algorithms/ed25519/CHANGELOG.md @@ -5,6 +5,11 @@ 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 `Ed25519KeyPair` type + - Add key-centric signature public APIs + ## [0.0.4] (2025-11-05) ## [0.0.3] (2025-06-30) diff --git a/crates/algorithms/ed25519/Cargo.toml b/crates/algorithms/ed25519/Cargo.toml index 301f0b2008..c54cde470a 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,10 @@ wycheproof = "0.6.0" [features] rand = ["dep:rand_core"] codec = ["tls_codec/std"] + +# 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/impl_hacl.rs b/crates/algorithms/ed25519/src/impl_hacl.rs index d8552d923f..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 { @@ -114,10 +123,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 +158,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 }, + }) } 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..1df718cf80 --- /dev/null +++ b/crates/algorithms/ed25519/src/key_centric_apis.rs @@ -0,0 +1,409 @@ +//! 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, +}; + +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; + +#[doc(inline)] +pub use slice::Ed25519; + +#[doc(inline)] +pub use arrayref::{SigningError, VerificationError}; + +impl_key_centric_types!( + arrayref::Ed25519, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN, + 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; + + /// An error when signing. + #[derive(Debug, PartialEq)] + pub enum SigningError { + /// The length of the provided payload is invalid. + InvalidPayloadLength, + } + + impl From for super::slice::SigningError { + fn from(e: SigningError) -> Self { + match e { + SigningError::InvalidPayloadLength => { + super::slice::SigningError::InvalidPayloadLength + } + } + } + } + + /// An error when verifying a signature. + #[derive(Debug)] + pub enum VerificationError { + /// The provided signature is invalid. + InvalidSignature, + /// The length of the provided payload is invalid. + InvalidPayloadLength, + } + + impl From for super::slice::VerificationError { + fn from(e: VerificationError) -> Self { + match e { + VerificationError::InvalidSignature => { + super::slice::VerificationError::InvalidSignature + } + VerificationError::InvalidPayloadLength => { + super::slice::VerificationError::InvalidPayloadLength + } + } + } + } +} +/// XXX: Decide whether we need these here (or need them to be public). +pub(crate) 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(&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(); + //! ``` + + /// 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!( + Ed25519, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN + ); + + /// 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, + /// The length of the provided payload is invalid. + InvalidPayloadLength, + } + + /// 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 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, + } +} + +impl crate::Ed25519KeyPair { + #[cfg(feature = "rand")] + /// Generate an Ed25519 key pair + 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]) -> 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); + + 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(crate) fn sign( + key: &[U8; Self::SIGNING_KEY_LEN], + payload: &[u8], + signature: &mut [u8; Self::SIGNATURE_LEN], + ) -> Result<(), arrayref::SigningError> { + let payload_len: u32 = payload + .len() + .try_into() + .map_err(|_| arrayref::SigningError::InvalidPayloadLength)?; + + crate::hacl::ed25519::sign(signature, key.declassify_ref(), payload_len, payload); + + Ok(()) + } + + #[inline(always)] + pub(crate) fn verify( + key: &[u8; Self::VERIFICATION_KEY_LEN], + payload: &[u8], + signature: &[u8; Self::SIGNATURE_LEN], + ) -> Result<(), arrayref::VerificationError> { + let payload_len: u32 = payload + .len() + .try_into() + .map_err(|_| arrayref::VerificationError::InvalidPayloadLength)?; + + if crate::hacl::ed25519::verify(key, payload_len, payload, signature) { + Ok(()) + } else { + Err(arrayref::VerificationError::InvalidSignature) + } + } + 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], + ) { + *signing_key = randomness; + 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(crate) 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(crate) 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(crate) fn keygen( + 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(signing_key, verification_key, randomness); + + Ok(()) + } +} + +/// XXX: Decide whether we need these here (or need them to be public). +impl<'a> SigningKeyRef<'a> { + 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(crate) fn verify( + &self, + payload: &[u8], + signature: &[u8], + ) -> Result<(), slice::VerificationError> { + slice::Ed25519::verify(self.as_ref(), payload, signature) + } +} + +// key-centric API +impl crate::SigningKey { + pub fn sign(&self, message: &[u8]) -> Result { + crate::sign(message, &self.value).map(|sig_bytes| sig_bytes.into()) + } +} + +impl crate::VerificationKey { + pub fn verify(&self, message: &[u8], signature: &crate::Signature) -> Result<(), crate::Error> { + crate::verify(message, &self.value, &signature.0) + } +} + +#[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, + } = KeyPair::generate(&mut rng); + + let signature = signing_key.sign(b"payload").unwrap(); + verification_key.verify(b"payload", &signature).unwrap(); +} + +#[test] +#[cfg(all(feature = "rand", not(feature = "expose-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(&mut signing_key, &mut verification_key, [0; 32]).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; Ed25519::SIGNATURE_LEN]; + signing_key.sign(b"payload", &mut signature).unwrap(); + verification_key.verify(b"payload", &signature).unwrap(); +} + +#[test] +#[cfg(not(feature = "expose-secret-independence"))] +fn arrayref_apis() { + use libcrux_traits::signature::SignConsts; + + 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; arrayref::Ed25519::SIGNATURE_LEN]; + arrayref::Ed25519::sign(&signing_key, b"payload", &mut signature).unwrap(); + arrayref::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..727e76f4a0 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(crate) mod key_centric_apis; pub use impl_hacl::*; 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/crates/primitives/signature/Cargo.toml b/crates/primitives/signature/Cargo.toml new file mode 100644 index 0000000000..16c6cdce17 --- /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 = "../../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 = "../../utils/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/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`. diff --git a/crates/primitives/signature/src/lib.rs b/crates/primitives/signature/src/lib.rs new file mode 100644 index 0000000000..fdfc152f17 --- /dev/null +++ b/crates/primitives/signature/src/lib.rs @@ -0,0 +1,293 @@ +//! This crate provides signature functionality. +//! +//! We currently support three signature algorithms: +//! +//! - ECDSA-P256 +//! - Ed25519 +//! - ML-DSA + +#[doc(inline)] +#[cfg(any(feature = "ecdsa", feature = "ed25519", feature = "mldsa"))] +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} + //! }; + //! + //! // 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(); + //! + //! // generate a new signature keypair from random bytes + //! let KeyPair { signing_key, verification_key } = + //! KeyPair::generate(&mut rng.unwrap_mut()).unwrap(); + //! + //! // sign + //! let signature = signing_key.sign(b"payload", &nonce).unwrap(); + //! + //! // 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}, + p256::Nonce, + }; + } +} + +#[cfg(feature = "ed25519")] +pub mod ed25519 { + //! APIs for Ed25519 + //! + //! ### Key-centric APIs + //! + //! #### Key-centric (owned) + //! ```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; + //! + //! // generate a new signature keypair from random bytes + //! // requires `rand` feature + //! let KeyPair { signing_key, verification_key } + //! = KeyPair::generate(&mut rng.unwrap_mut()); + //! + //! // sign + //! let signature = signing_key.sign(b"payload").unwrap(); + //! + //! // 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, + VerificationKey, VerificationKeyRef, + }; +} + +#[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}; + //! + //! // 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(); + //! + //! // generate a new signature keypair from random bytes + //! // requires `rand` feature + //! let KeyPair { signing_key, verification_key } + //! = KeyPair::generate(&mut rng); + //! + //! // sign + //! 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::{ + //! # 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]; + //! + //! // 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(); + //! + //! // 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]; + //! + //! // 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}; +} 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/libcrux-ml-dsa/Cargo.toml b/libcrux-ml-dsa/Cargo.toml index de4cb04d4c..0586312d29 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,21 @@ 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", + "libcrux-traits/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..86022694cf --- /dev/null +++ b/libcrux-ml-dsa/src/key_centric_apis.rs @@ -0,0 +1,735 @@ +//! 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, +}; + +macro_rules! impl_mod { + ($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; + 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::*; + + /// XXX: Decide whether we need these here (or need them to be public). + #[doc(inline)] + 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; + use super::*; + impl_key_centric_types!( + $ty, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN, + WrongLengthError, + WrongLengthError + ); + } + + /// 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: + //! ```rust + //! use libcrux_traits::signature::SignConsts; + //! use libcrux_ml_dsa::key_centric_apis::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(&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(); + //! ``` + + /// 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::*; + impl_sign_consts!( + $ty, + SIGNING_KEY_LEN, + VERIFICATION_KEY_LEN, + SIGNATURE_LEN, + RAND_KEYGEN_LEN + ); + + /// 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, + } + + impl From for SigningError { + fn from(e: crate::SigningError) -> Self { + match e { + crate::SigningError::RejectionSamplingError => { + SigningError::RejectionSamplingError + } + crate::SigningError::ContextTooLongError => { + SigningError::ContextTooLongError + } + } + } + } + + /// 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, + } + + 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 + } + } + } + } + + /// 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, + } + } + + /// 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(crate) 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, + ) + } + /// 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(crate) 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; 32]; + 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(()) + } + + /// 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(crate) 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, + ) + } + + /// 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(crate) 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; 32]; + crate::ml_dsa_generic::multiplexing::$module::verify_pre_hashed_shake128( + key, + payload, + context, + &mut pre_hash_buffer, + signature, + ) + } + /// Generate an ML-DSA Key Pair + 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], + ) { + crate::ml_dsa_generic::multiplexing::$module::generate_key_pair( + randomness.declassify(), + signing_key.declassify_ref_mut(), + verification_key, + ); + } + } + + /// 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(crate) 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) + } + + /// 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(crate) 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) + } + + /// 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(crate) 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) + } + + /// 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(crate) 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) + } + + /// Generate an ML-DSA Key Pair + #[cfg(not(eurydice))] + pub(crate) fn keygen( + 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(signing_key, verification_key, randomness); + + 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(crate) 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) + } + + /// 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(crate) 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, + ) + } + } + + /// 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(crate) fn verify( + &self, + payload: &[u8], + signature: &[u8], + context: &[u8], + ) -> 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(crate) 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 $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 + /// and is a byte string of length at most 255 bytes. It + /// may also be empty. + pub fn sign( + &self, + message: &[u8], + context: &[u8], + 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 + /// + /// 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, + message: &[u8], + context: &[u8], + randomness: [U8; crate::SIGNING_RANDOMNESS_SIZE], + ) -> Result<$signature, crate::SigningError> { + crate::$module::sign_pre_hashed_shake128(self, message, context, randomness) + } + } + + impl $verkey { + /// 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, + message: &[u8], + signature: &$signature, + context: &[u8], + ) -> Result<(), crate::VerificationError> { + crate::$module::verify(self, message, 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( + &self, + message: &[u8], + signature: &$signature, + context: &[u8], + ) -> Result<(), crate::VerificationError> { + crate::$module::verify_pre_hashed_shake128(self, message, context, signature) + } + } + }; +} + +#[cfg(feature = "mldsa44")] +pub mod 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, + 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, + crate::ml_dsa_87::MLDSA87KeyPair, + crate::ml_dsa_87::MLDSA87SigningKey, + crate::ml_dsa_87::MLDSA87VerificationKey, + crate::ml_dsa_87::MLDSA87Signature + ); +} + +#[test] +#[cfg(all( + feature = "mldsa44", + 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, + } = KeyPair::generate(&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 = "mldsa44", + 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(&mut signing_key, &mut verification_key, [0; 32]).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; 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"payload", &mut signature, context, [0u8; 32]) + .unwrap(); + verification_key + .verify_pre_hashed_shake128(b"payload", &signature, context) + .unwrap(); +} + +#[test] +#[cfg(all(feature = "mldsa44", not(feature = "expose-secret-independence")))] +fn arrayref_apis() { + use libcrux_traits::signature::SignConsts; + use ml_dsa_44::arrayref::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(&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"payload", &mut signature, context, [0u8; 32]) + .unwrap(); + MlDsa44::verify_pre_hashed_shake128(&verification_key, b"payload", &signature, context) + .unwrap(); +} diff --git a/libcrux-ml-dsa/src/lib.rs b/libcrux-ml-dsa/src/lib.rs index 4c54c6d3d0..6959f227e0 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(crate) mod key_centric_apis; 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], 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 diff --git a/traits/Cargo.toml b/traits/Cargo.toml index 94c8bcafcf..8bf3838346 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -23,3 +23,8 @@ 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" } +rand = { version = "0.9", features = ["os_rng"] } 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..4d109ab8be --- /dev/null +++ b/traits/src/signature.rs @@ -0,0 +1,253 @@ +//! 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 a trait 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); +//! ``` +//! +//! ### 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, respectively. +//! - `VerificationKey`, `VerificationKeyRef`: owned and borrowed verification keys, respectively. +//! - `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}; +//! 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(); +//! ``` + +#[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. + 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; +} + +/// Helper macro for implementing the [`SignConsts`] trait. +#[macro_export] +macro_rules! impl_sign_consts { + ($algorithm:ty, $signing_key_len:expr, $verification_key_len:expr, $signature_len:expr, $rand_keygen_len:expr) => { + 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; + } + }; +} + +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, respectively. +/// - `VerificationKey`, `VerificationKeyRef`: owned and borrowed verification keys, respectively. +/// - `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) => { + $crate::signature::impl_sign_consts!( + $algorithm, + $signing_key_len, + $verification_key_len, + $signature_len, + $rand_keygen_len + ); + + // internal types + 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 [$crate::libcrux_secrets::U8], + } + impl<'a> AsRef<[$crate::libcrux_secrets::U8]> for SigningKeyRef<'a> { + fn as_ref(&self) -> &[$crate::libcrux_secrets::U8] { + self.key.as_ref() + } + } + impl<'a> AsRef<[u8]> for VerificationKeyRef<'a> { + fn as_ref(&self) -> &[u8] { + 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 [$crate::libcrux_secrets::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<[$crate::libcrux_secrets::U8]> for SigningKey { + fn as_ref(&self) -> &[$crate::libcrux_secrets::U8] { + self.key.as_ref() + } + } + impl AsRef for SigningKey { + fn as_ref(&self) -> &SigningKeyArray { + &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); + } 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;