From d82c76e4ccdccd0da359f623afe9cf1cbb63739d Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sun, 7 Jun 2026 21:08:36 -0500 Subject: [PATCH 1/9] removed nightly feature from mldsa --- alpha_0.1.2_release_notes.md | 2 ++ crypto/core/src/lib.rs | 1 - crypto/mldsa/src/hash_mldsa.rs | 51 +++++++++++++++++++------------ crypto/mldsa/src/lib.rs | 11 +++---- crypto/rng/src/hash_drbg80090a.rs | 29 +++++++++++++++--- crypto/rng/src/lib.rs | 2 -- 6 files changed, 62 insertions(+), 34 deletions(-) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index 06dcefa..5039c13 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -23,6 +23,8 @@ appropriate. * Probably it makes sense to leave Hex and Base64 as requiring std; ... or maybe add a no_std version that uses fixed-sized blocks? +* Make this build on the stable compiler. IE Remove the rust-toolchain.toml file that builds with nightly. Will require + some refactoring. * Create a cargo feature #[cfg(feature='rng')] and put it around things like keygen that takes an rng so that the build dependency on bouncycastle_rng is optional. * Enhance the default HashDRBG instantiation to take in NIST-compatible CPU jitter entropy? Or not? Maybe this is the diff --git a/crypto/core/src/lib.rs b/crypto/core/src/lib.rs index cb0d8d8..379e54c 100644 --- a/crypto/core/src/lib.rs +++ b/crypto/core/src/lib.rs @@ -3,7 +3,6 @@ // todo -- this is the goal, but first need to remove all the Vec in favour of compile-time array sizing. // #![no_std] -#![feature(adt_const_params)] #![forbid(unsafe_code)] pub mod errors; diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 780bd76..15f1464 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -94,7 +94,7 @@ use bouncycastle_core::traits::{ Algorithm, Hash, PHSignature, RNG, SecurityStrength, Signature, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha2::{SHA256, SHA512}; +use bouncycastle_sha2::{SHA256, SHA256_NAME, SHA512, SHA512_NAME}; use core::marker::PhantomData; // Imports needed only for docs @@ -126,7 +126,6 @@ pub const HASH_ML_DSA_87_WITH_SHA512_NAME: &str = "HashML-DSA-87_with_SHA512"; pub type HashMLDSA44_with_SHA256 = HashMLDSA< SHA256, 32, - SHA256_OID, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_SIG_LEN, @@ -160,7 +159,6 @@ impl Algorithm for HashMLDSA44_with_SHA256 { pub type HashMLDSA65_with_SHA256 = HashMLDSA< SHA256, 32, - SHA256_OID, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_SIG_LEN, @@ -194,7 +192,6 @@ impl Algorithm for HashMLDSA65_with_SHA256 { pub type HashMLDSA87_with_SHA256 = HashMLDSA< SHA256, 32, - SHA256_OID, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_SIG_LEN, @@ -228,7 +225,6 @@ impl Algorithm for HashMLDSA87_with_SHA256 { pub type HashMLDSA44_with_SHA512 = HashMLDSA< SHA512, 64, - SHA512_OID, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_SIG_LEN, @@ -262,7 +258,6 @@ impl Algorithm for HashMLDSA44_with_SHA512 { pub type HashMLDSA65_with_SHA512 = HashMLDSA< SHA512, 64, - SHA512_OID, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_SIG_LEN, @@ -296,7 +291,6 @@ impl Algorithm for HashMLDSA65_with_SHA512 { pub type HashMLDSA87_with_SHA512 = HashMLDSA< SHA512, 64, - SHA512_OID, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_SIG_LEN, @@ -332,9 +326,8 @@ impl Algorithm for HashMLDSA87_with_SHA512 { /// by specifying the hash function to use (in the verifier), and specifying the bytes of the OID to /// to use as its domain separator in constructing the message representative M'. pub struct HashMLDSA< - HASH: Hash + Default, + HASH: Hash + Algorithm + Default, const HASH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -381,9 +374,8 @@ pub struct HashMLDSA< } impl< - HASH: Hash + Default, + HASH: Hash + Algorithm + Default, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -410,7 +402,6 @@ impl< HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, @@ -568,7 +559,20 @@ impl< h.absorb(&[1u8]); h.absorb(&[ctx.len() as u8]); h.absorb(ctx); - h.absorb(oid); + + // this is all statics, so the branch should compile out. + // Really, this should be a generic param of HashMLDSA, but unsized_const_params is currently + // a nightly-only feature. + match HASH::ALG_NAME { + SHA256_NAME => h.absorb(SHA256_OID), + SHA512_NAME => h.absorb(SHA512_OID), + _ => { + return Err(SignatureError::GenericError( + "Unsupported hash algorithm; you need to add it to the switch", + )); + } + }; + h.absorb(ph); let mut mu = [0u8; MLDSA_MU_LEN]; let bytes_written = h.squeeze_out(&mut mu); @@ -691,7 +695,18 @@ impl< h.absorb(&[1u8]); h.absorb(&[ctx.len() as u8]); h.absorb(ctx); - h.absorb(oid); + // this is all statics, so the branch should compile out. + // Really, this should be a generic param of HashMLDSA, but unsized_const_params is currently + // a nightly-only feature. + match HASH::ALG_NAME { + SHA256_NAME => h.absorb(SHA256_OID), + SHA512_NAME => h.absorb(SHA512_OID), + _ => { + return Err(SignatureError::GenericError( + "Unsupported hash algorithm; you need to add it to the switch", + )); + } + }; h.absorb(ph); let mut mu = [0u8; MLDSA_MU_LEN]; _ = h.squeeze_out(&mut mu); @@ -765,12 +780,11 @@ impl< } impl< - HASH: Hash + Default, + HASH: Hash + Algorithm + Default, PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -794,7 +808,6 @@ impl< for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, @@ -971,9 +984,8 @@ impl< } impl< - HASH: Hash + Default, + HASH: Hash + Algorithm + Default, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -1000,7 +1012,6 @@ impl< for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, diff --git a/crypto/mldsa/src/lib.rs b/crypto/mldsa/src/lib.rs index 06e37f9..9c3a2b8 100644 --- a/crypto/mldsa/src/lib.rs +++ b/crypto/mldsa/src/lib.rs @@ -115,9 +115,6 @@ #![no_std] #![forbid(missing_docs)] #![forbid(unsafe_code)] -#![allow(incomplete_features)] // needed because currently generic_const_exprs is experimental -#![feature(generic_const_exprs)] -#![feature(adt_const_params)] // These are because I'm matching variable names exactly against FIPS 204, for example both 'K' and 'k', // or 'A' and 'a' are used and have specific meanings. // But need to tell the rust linter to not care. @@ -127,8 +124,10 @@ // MLDSA implementation, but I don't want accessed from outside, such as FIPS-internal functions. #![allow(private_bounds)] #![allow(private_interfaces)] -// Used in HashMLDSA -#![feature(unsized_const_params)] +// Used in HashMLDSA for oid: &'static [u8] params. +// #![allow(incomplete_features)] // needed because currently unsized_const_params is experimental +// #![feature(adt_const_params)] +// #![feature(unsized_const_params)] // imports needed just for docs #[allow(unused_imports)] @@ -136,8 +135,6 @@ use bouncycastle_core::key_material::KeyMaterialTrait; #[allow(unused_imports)] use bouncycastle_core::traits::Signature; -// todo -- re-run mutants - // todo -- crucible tests mod aux_functions; diff --git a/crypto/rng/src/hash_drbg80090a.rs b/crypto/rng/src/hash_drbg80090a.rs index 4614692..2a06228 100644 --- a/crypto/rng/src/hash_drbg80090a.rs +++ b/crypto/rng/src/hash_drbg80090a.rs @@ -6,7 +6,7 @@ use crate::Sp80090ADrbg; use bouncycastle_core::errors::{KeyMaterialError, RNGError}; -use bouncycastle_core::key_material::{KeyMaterial512, KeyType, KeyMaterialTrait}; +use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{Hash, HashAlgParams, RNG, SecurityStrength}; use bouncycastle_sha2::{SHA256, SHA512}; use bouncycastle_utils::min; @@ -67,6 +67,7 @@ const LARGEST_HASHER_OUTPUT_LEN: usize = 64; #[allow(private_bounds)] /// Implementation of the Hash_DRBG algorithm as specified in NIST SP 800-90Ar1. pub struct HashDRBG80090A { + _phantom: core::marker::PhantomData, // Rust is stupid. What's the point of having a generic parameter if we can't use constants inside it? // state: WorkingState, state: WorkingState, @@ -123,6 +124,7 @@ impl HashDRBG80090A { /// and relies on you to provide a strong seed.** pub fn new_unititialized() -> Self { Self { + _phantom: core::marker::PhantomData, state: WorkingState:: { v: [0u8; LARGEST_HASHER_OUTPUT_LEN], c: [0u8; LARGEST_HASHER_OUTPUT_LEN], @@ -266,7 +268,11 @@ impl Sp80090ADrbg for HashDRBG80090A { Ok(()) } - fn reseed(&mut self, seed: &impl KeyMaterialTrait, additional_input: &[u8]) -> Result<(), RNGError> { + fn reseed( + &mut self, + seed: &impl KeyMaterialTrait, + additional_input: &[u8], + ) -> Result<(), RNGError> { // Hash_DRBG Reseed Process: // 1. seed_material = 0x01 || V || entropy_input || additional_input. // 2. seed = Hash_df (seed_material, seedlen). @@ -475,7 +481,10 @@ impl RNG for HashDRBG80090A { // todo!() // } - fn add_seed_keymaterial(&mut self, additional_seed: impl KeyMaterialTrait) -> Result<(), RNGError> { + fn add_seed_keymaterial( + &mut self, + additional_seed: impl KeyMaterialTrait, + ) -> Result<(), RNGError> { self.reseed(&additional_seed, "add_seed_keymaterial".as_bytes()) } @@ -572,7 +581,19 @@ fn test_hash_df() { assert_ne!(out, [0u8; 100]); // repeatability test // println!("out: {:?}", out); - assert_eq!(out, [150u8, 177u8, 87u8, 145u8, 138u8, 4u8, 164u8, 14u8, 162u8, 43u8, 159u8, 152u8, 121u8, 117u8, 6u8, 18u8, 253u8, 84u8, 41u8, 64u8, 40u8, 209u8, 16u8, 176u8, 106u8, 115u8, 172u8, 193u8, 246u8, 228u8, 208u8, 79u8, 37u8, 31u8, 134u8, 141u8, 200u8, 7u8, 42u8, 199u8, 229u8, 236u8, 236u8, 186u8, 28u8, 87u8, 200u8, 14u8, 127u8, 36u8, 132u8, 23u8, 36u8, 150u8, 23u8, 215u8, 247u8, 121u8, 175u8, 82u8, 99u8, 187u8, 235u8, 25u8, 213u8, 18u8, 106u8, 22u8, 4u8, 99u8, 1u8, 184u8, 211u8, 160u8, 177u8, 67u8, 78u8, 181u8, 69u8, 51u8, 117u8, 2u8, 72u8, 36u8, 134u8, 72u8, 2u8, 9u8, 105u8, 149u8, 136u8, 35u8, 81u8, 114u8, 142u8, 80u8, 94u8, 42u8, 85u8, 155]); + assert_eq!( + out, + [ + 150u8, 177u8, 87u8, 145u8, 138u8, 4u8, 164u8, 14u8, 162u8, 43u8, 159u8, 152u8, 121u8, + 117u8, 6u8, 18u8, 253u8, 84u8, 41u8, 64u8, 40u8, 209u8, 16u8, 176u8, 106u8, 115u8, + 172u8, 193u8, 246u8, 228u8, 208u8, 79u8, 37u8, 31u8, 134u8, 141u8, 200u8, 7u8, 42u8, + 199u8, 229u8, 236u8, 236u8, 186u8, 28u8, 87u8, 200u8, 14u8, 127u8, 36u8, 132u8, 23u8, + 36u8, 150u8, 23u8, 215u8, 247u8, 121u8, 175u8, 82u8, 99u8, 187u8, 235u8, 25u8, 213u8, + 18u8, 106u8, 22u8, 4u8, 99u8, 1u8, 184u8, 211u8, 160u8, 177u8, 67u8, 78u8, 181u8, 69u8, + 51u8, 117u8, 2u8, 72u8, 36u8, 134u8, 72u8, 2u8, 9u8, 105u8, 149u8, 136u8, 35u8, 81u8, + 114u8, 142u8, 80u8, 94u8, 42u8, 85u8, 155 + ] + ); // Test success with out.len() at the maximum allowed for SHA256 (255 * 32 = 8160) let mut out_max_sha256 = vec![0u8; 255 * 32]; diff --git a/crypto/rng/src/lib.rs b/crypto/rng/src/lib.rs index dbe62fb..43a65c1 100644 --- a/crypto/rng/src/lib.rs +++ b/crypto/rng/src/lib.rs @@ -28,8 +28,6 @@ //! cryptographic application. #![forbid(unsafe_code)] -#![allow(incomplete_features)] // Need this because generic_const_exprs is currently experimental. -#![feature(generic_const_exprs)] use crate::hash_drbg80090a::{ HashDRBG80090A, HashDRBG80090AParams_SHA256, HashDRBG80090AParams_SHA512, From 837b8d6aecd4255b72f8e88cb2ebb50960492ba6 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 9 Jun 2026 18:56:01 -0500 Subject: [PATCH 2/9] adjusted the todo list --- alpha_0.1.2_release_notes.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index 5039c13..ae53ed2 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -54,7 +54,11 @@ # 0.1.2 Features / Changelog -* ML-DSA -* Low-Memory ML-DSA -- runs in about 1/10th of the usual memory (~ 30 kb of stack) with only minor performance impact. +* New algorithms added to crypto/ : + * mldsa (FIPS 204) + * mldsa-lowmemory -- runs in about 1/10th of the usual memory (~ 30 kb of stack) with comparable performance impact. + * mlkem (FIPS 203) + * mlkem-lowmemory -- runs in about 1/4th of the usual memory (~ 12 kb of stack) with comparable performance impact. * Github issues resolved: - * #2, or whatever \ No newline at end of file + * #6: https://github.com/bcgit/bc-rust/issues/6, thanks to Q. T. Felix (github: @Quant-TheodoreFelix) + * #10: https://github.com/bcgit/bc-rust/issues/10, thanks to Nicola Tuveri (github: @romen) \ No newline at end of file From 8b460ac298eeae90c6ac183485bcef2102f2434b Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 10 Jun 2026 03:05:55 -0500 Subject: [PATCH 3/9] Split the Signature trait into Signer and SignatureVerifier --- cli/src/mldsa_cmd.rs | 2 +- crypto/core-test-framework/src/signature.rs | 146 +++++---- crypto/core/src/traits.rs | 164 +++++++--- crypto/mldsa/benches/mldsa_benches.rs | 2 +- crypto/mldsa/src/hash_mldsa.rs | 208 +++++++++--- crypto/mldsa/src/lib.rs | 7 +- crypto/mldsa/src/mldsa.rs | 98 ++++-- crypto/mldsa/tests/bc_test_data.rs | 2 +- crypto/mldsa/tests/hash_mldsa_tests.rs | 22 +- crypto/mldsa/tests/mldsa_key_tests.rs | 12 +- crypto/mldsa/tests/mldsa_tests.rs | 8 +- crypto/mldsa/tests/wycheproof.rs | 2 +- .../mldsa_lowmemory/benches/mldsa_benches.rs | 2 +- crypto/mldsa_lowmemory/src/hash_mldsa.rs | 302 ++++++++++++++---- crypto/mldsa_lowmemory/src/lib.rs | 7 +- crypto/mldsa_lowmemory/src/mldsa.rs | 113 ++++++- crypto/mldsa_lowmemory/tests/bc_test_data.rs | 2 +- .../mldsa_lowmemory/tests/hash_mldsa_tests.rs | 22 +- .../mldsa_lowmemory/tests/mldsa_key_tests.rs | 8 +- crypto/mldsa_lowmemory/tests/mldsa_tests.rs | 8 +- crypto/mldsa_lowmemory/tests/wycheproof.rs | 2 +- mem_usage_benches/bench_mldsa_mem_usage.rs | 2 +- 22 files changed, 828 insertions(+), 313 deletions(-) diff --git a/cli/src/mldsa_cmd.rs b/cli/src/mldsa_cmd.rs index 79ba941..abced47 100644 --- a/cli/src/mldsa_cmd.rs +++ b/cli/src/mldsa_cmd.rs @@ -2,7 +2,7 @@ //! by using generics or macros. I just, haven't ... yet. use crate::helpers::{parse_seed, read_from_file, read_from_file_or_stdin, write_bytes_or_hex}; -use bouncycastle::core::traits::{Signature, SignaturePrivateKey, SignaturePublicKey}; +use bouncycastle::core::traits::{SignatureVerifier, Signer, SignaturePrivateKey, SignaturePublicKey}; use bouncycastle::hex; use bouncycastle::mldsa::{MLDSA_SEED_LEN, MLDSA44, MLDSA44_SK_LEN, MLDSA44PrivateKey, MLDSA87_SK_LEN, MLDSAPrivateKeyTrait, MLDSATrait, MLDSA44PublicKey, MLDSA44_PK_LEN, MLDSA65_SK_LEN, MLDSA65PrivateKey, MLDSA65, MLDSA65PublicKey, MLDSA65_PK_LEN, MLDSA87PrivateKey, MLDSA87, MLDSA87PublicKey, MLDSA87_PK_LEN, HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA512, HashMLDSA87_with_SHA512}; use std::{io}; diff --git a/crypto/core-test-framework/src/signature.rs b/crypto/core-test-framework/src/signature.rs index d97dc0c..914ae44 100644 --- a/crypto/core-test-framework/src/signature.rs +++ b/crypto/core-test-framework/src/signature.rs @@ -1,7 +1,8 @@ use crate::DUMMY_SEED_1024; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::traits::{ - Hash, PHSignature, Signature, SignaturePrivateKey, SignaturePublicKey, + Hash, PHSignatureVerifier, PHSigner, SignaturePrivateKey, SignaturePublicKey, + SignatureVerifier, Signer, }; pub struct TestFrameworkSignature { @@ -18,54 +19,59 @@ impl TestFrameworkSignature { Self { alg_is_deterministic, alg_accepts_ctx } } - /// Test all the members of trait Hash against the given input-output pair. + /// Test all the members of traits [Signer] and [SignatureVerifier] against the given input-output pair. /// This gives good baseline test coverage, but is not exhaustive. + /// + /// Since key generation is not part of either signature trait, the caller supplies a + /// `keygen` function pointer (the inherent `keygen` associated function on the algorithm struct). pub fn test_signature< PK: SignaturePublicKey, SK: SignaturePrivateKey, - SigAlg: Signature, + SIGNER: Signer, + VERIFIER: SignatureVerifier, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, >( &self, + keygen: fn() -> Result<(PK, SK), SignatureError>, run_full_bitflipping_tests: bool, ) { let msg = b"The quick brown fox jumped over the lazy dog"; // Basic test - let (pk, sk) = SigAlg::keygen().unwrap(); - let sig_val = SigAlg::sign(&sk, msg, None).unwrap(); - SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); + let (pk, sk) = keygen().unwrap(); + let sig_val = SIGNER::sign(&sk, msg, None).unwrap(); + VERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); // Test non-determinism if !self.alg_is_deterministic { - let sig1 = SigAlg::sign(&sk, msg, None).unwrap(); - let sig2 = SigAlg::sign(&sk, msg, None).unwrap(); + let sig1 = SIGNER::sign(&sk, msg, None).unwrap(); + let sig2 = SIGNER::sign(&sk, msg, None).unwrap(); assert_ne!(sig1, sig2); } // uses ctx // success case - let sig = SigAlg::sign(&sk, msg, Some(b"test with ctx")).unwrap(); - SigAlg::verify(&pk, msg, Some(b"test with ctx"), &sig).unwrap(); + let sig = SIGNER::sign(&sk, msg, Some(b"test with ctx")).unwrap(); + VERIFIER::verify(&pk, msg, Some(b"test with ctx"), &sig).unwrap(); // but it had better produce something different if !self.alg_accepts_ctx { - let sig1 = SigAlg::sign(&sk, msg, None).unwrap(); - let sig2 = SigAlg::sign(&sk, msg, Some(&[0u8; 1])).unwrap(); + let sig1 = SIGNER::sign(&sk, msg, None).unwrap(); + let sig2 = SIGNER::sign(&sk, msg, Some(&[0u8; 1])).unwrap(); assert_ne!(sig1, sig2); } // Test that verification fails for broken signature value - let (pk, sk) = SigAlg::keygen().unwrap(); - let sig_val = SigAlg::sign(&sk, msg, None).unwrap(); + let (pk, sk) = keygen().unwrap(); + let sig_val = SIGNER::sign(&sk, msg, None).unwrap(); // spot-check let mut sig_val_copy = sig_val.clone(); sig_val_copy[8] ^= 0x0F; // should throw an Err - match SigAlg::verify(&pk, msg, None, &sig_val_copy) { + match VERIFIER::verify(&pk, msg, None, &sig_val_copy) { Err(SignatureError::SignatureVerificationFailed) => (), _ => panic!("This should have thrown an error but it didn't."), } @@ -78,7 +84,7 @@ impl TestFrameworkSignature { sig_val_copy[i] ^= 1 << j; // should throw an Err - match SigAlg::verify(&pk, msg, None, &sig_val_copy) { + match VERIFIER::verify(&pk, msg, None, &sig_val_copy) { Err(SignatureError::SignatureVerificationFailed) => (), _ => panic!( "This should have thrown an error but it didn't when byte {i} bit {j} of the signature was flipped" @@ -93,13 +99,13 @@ impl TestFrameworkSignature { // Success case let mut output = [0u8; SIG_LEN]; - let bytes_written = SigAlg::sign_out(&sk, msg, None, &mut output).unwrap(); + let bytes_written = SIGNER::sign_out(&sk, msg, None, &mut output).unwrap(); assert_eq!(bytes_written, SIG_LEN); - SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); + VERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); // test with a large message - let sig = SigAlg::sign(&sk, DUMMY_SEED_1024, None).unwrap(); - SigAlg::verify(&pk, DUMMY_SEED_1024, None, &sig).unwrap(); + let sig = SIGNER::sign(&sk, DUMMY_SEED_1024, None).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED_1024, None, &sig).unwrap(); // Test the streaming signing API // fn sign_init(&mut self, sk: &SK) -> Result<(), SignatureError>; @@ -108,37 +114,37 @@ impl TestFrameworkSignature { // fn sign_final_out(&mut self, msg_chunk: &[u8], ctx: &[u8], output: &mut [u8]) -> Result<(), SignatureError>; // First, test the streaming API with one call to .sign_update - let mut s = SigAlg::sign_init(&sk, Some(b"streaming API")).unwrap(); + let mut s = SIGNER::sign_init(&sk, Some(b"streaming API")).unwrap(); s.sign_update(DUMMY_SEED_1024); let sig_val = s.sign_final().unwrap(); - SigAlg::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); // Then with the message broken into chunks - let mut s = SigAlg::sign_init(&sk, Some(b"streaming API chunked")).unwrap(); + let mut s = SIGNER::sign_init(&sk, Some(b"streaming API chunked")).unwrap(); for msg_chunk in DUMMY_SEED_1024.chunks(100) { s.sign_update(msg_chunk); } let sig_val = s.sign_final().unwrap(); - SigAlg::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API chunked"), &sig_val).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API chunked"), &sig_val).unwrap(); // Test the streaming verification API // one-shot - let sig = SigAlg::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); - let mut v = SigAlg::verify_init(&pk, Some(b"streaming API")).unwrap(); + let sig = SIGNER::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); + let mut v = VERIFIER::verify_init(&pk, Some(b"streaming API")).unwrap(); v.verify_update(DUMMY_SEED_1024); v.verify_final(&sig).unwrap(); // chunked - let sig = SigAlg::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); - let mut v = SigAlg::verify_init(&pk, Some(b"streaming API")).unwrap(); + let sig = SIGNER::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); + let mut v = VERIFIER::verify_init(&pk, Some(b"streaming API")).unwrap(); for msg_chunk in DUMMY_SEED_1024.chunks(100) { v.verify_update(msg_chunk); } v.verify_final(&sig).unwrap(); // failure case for streaming verify - let sig = SigAlg::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); - let mut v = SigAlg::verify_init(&pk, Some(b"streaming API")).unwrap(); + let sig = SIGNER::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); + let mut v = VERIFIER::verify_init(&pk, Some(b"streaming API")).unwrap(); v.verify_update(b"this is the wrong message"); match v.verify_final(&sig) { Err(SignatureError::SignatureVerificationFailed) => (), @@ -146,25 +152,30 @@ impl TestFrameworkSignature { } // test sign_out version of streaming API - let mut s = SigAlg::sign_init(&sk, Some(b"streaming API")).unwrap(); + let mut s = SIGNER::sign_init(&sk, Some(b"streaming API")).unwrap(); s.sign_update(DUMMY_SEED_1024); let mut sig_val = [0u8; SIG_LEN]; let bytes_written = s.sign_final_out(&mut sig_val).unwrap(); assert_eq!(bytes_written, SIG_LEN); - SigAlg::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); // the ::verify API should accept a sig value that's too long and just ignore the extra bytes let mut sig_val_too_long = vec![1u8; SIG_LEN + 2]; sig_val_too_long[..SIG_LEN].copy_from_slice(&sig_val); - SigAlg::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); } - /// Test all the members of trait Hash against the given input-output pair. + /// Test all the members of traits [PHSigner] and [PHSignatureVerifier] against the given input-output pair. /// This gives good baseline test coverage, but is not exhaustive. + /// + /// Since key generation is not part of either signature trait, the caller supplies a + /// `keygen` function pointer (the inherent `keygen` associated function on the algorithm struct). pub fn test_ph_signature< PK: SignaturePublicKey, SK: SignaturePrivateKey, - SigAlg: PHSignature, + // todo split this into two params: SIGNER: Signer and VERIFIER: SignatureVerifier + PHSIGNER: PHSigner, + PHVERIFIER: PHSignatureVerifier, HASH: Hash + Default, const PK_LEN: usize, const SK_LEN: usize, @@ -172,43 +183,44 @@ impl TestFrameworkSignature { const PH_LEN: usize, >( &self, + keygen: fn() -> Result<(PK, SK), SignatureError>, run_full_bitflipping_tests: bool, ) { let msg = b"The quick brown fox jumped over the lazy dog"; // Basic test - let (pk, sk) = SigAlg::keygen().unwrap(); - let sig_val = SigAlg::sign(&sk, msg, None).unwrap(); - SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); + let (pk, sk) = keygen().unwrap(); + let sig_val = PHSIGNER::sign(&sk, msg, None).unwrap(); + PHVERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); // Test non-determinism if !self.alg_is_deterministic { - let sig1 = SigAlg::sign(&sk, msg, None).unwrap(); - let sig2 = SigAlg::sign(&sk, msg, None).unwrap(); + let sig1 = PHSIGNER::sign(&sk, msg, None).unwrap(); + let sig2 = PHSIGNER::sign(&sk, msg, None).unwrap(); assert_ne!(sig1, sig2); } // uses ctx // success case - let sig = SigAlg::sign(&sk, msg, Some(b"test with ctx")).unwrap(); - SigAlg::verify(&pk, msg, Some(b"test with ctx"), &sig).unwrap(); + let sig = PHSIGNER::sign(&sk, msg, Some(b"test with ctx")).unwrap(); + PHVERIFIER::verify(&pk, msg, Some(b"test with ctx"), &sig).unwrap(); // but it had better produce something different if !self.alg_accepts_ctx { - let sig1 = SigAlg::sign(&sk, msg, None).unwrap(); - let sig2 = SigAlg::sign(&sk, msg, Some(&[0u8; 1])).unwrap(); + let sig1 = PHSIGNER::sign(&sk, msg, None).unwrap(); + let sig2 = PHSIGNER::sign(&sk, msg, Some(&[0u8; 1])).unwrap(); assert_ne!(sig1, sig2); } // Test that verification fails for broken signature value - let (pk, sk) = SigAlg::keygen().unwrap(); - let sig_val = SigAlg::sign(&sk, msg, None).unwrap(); + let (pk, sk) = keygen().unwrap(); + let sig_val = PHSIGNER::sign(&sk, msg, None).unwrap(); // spot-check let mut sig_val_copy = sig_val.clone(); sig_val_copy[8] ^= 0x0F; // should throw an Err - match SigAlg::verify(&pk, msg, None, &sig_val_copy) { + match PHVERIFIER::verify(&pk, msg, None, &sig_val_copy) { Err(SignatureError::SignatureVerificationFailed) => (), _ => panic!("This should have thrown an error but it didn't."), } @@ -221,7 +233,7 @@ impl TestFrameworkSignature { sig_val_copy[i] ^= 1 << j; // should throw an Err - match SigAlg::verify(&pk, msg, None, &sig_val_copy) { + match PHVERIFIER::verify(&pk, msg, None, &sig_val_copy) { Err(SignatureError::SignatureVerificationFailed) => (), _ => panic!( "This should have thrown an error but it didn't when byte {i} bit {j} of the signature was flipped" @@ -236,37 +248,37 @@ impl TestFrameworkSignature { // Success case let mut output = [0u8; SIG_LEN]; - let bytes_written = SigAlg::sign_out(&sk, msg, None, &mut output).unwrap(); + let bytes_written = PHSIGNER::sign_out(&sk, msg, None, &mut output).unwrap(); assert_eq!(bytes_written, SIG_LEN); - SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); + PHVERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); // test with a large message - let sig = SigAlg::sign(&sk, DUMMY_SEED_1024, None).unwrap(); - SigAlg::verify(&pk, DUMMY_SEED_1024, None, &sig).unwrap(); + let sig = PHSIGNER::sign(&sk, DUMMY_SEED_1024, None).unwrap(); + PHVERIFIER::verify(&pk, DUMMY_SEED_1024, None, &sig).unwrap(); // the ::verify API should not accept a sig value that's too let mut sig_val_too_long = vec![1u8; SIG_LEN + 2]; sig_val_too_long[..SIG_LEN].copy_from_slice(&sig); - match SigAlg::verify(&pk, DUMMY_SEED_1024, None, &sig_val_too_long) { + match PHVERIFIER::verify(&pk, DUMMY_SEED_1024, None, &sig_val_too_long) { Err(SignatureError::LengthError(_)) => (), _ => panic!("Unexpected error"), } // sign_ph - let (pk, sk) = SigAlg::keygen().unwrap(); + let (pk, sk) = keygen().unwrap(); let ph: [u8; PH_LEN] = HASH::default().hash(msg)[..PH_LEN].try_into().unwrap(); - let sig_val = SigAlg::sign_ph(&sk, &ph, None).unwrap(); - SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); - SigAlg::verify_ph(&pk, &ph, None, &sig_val).unwrap(); + let sig_val = PHSIGNER::sign_ph(&sk, &ph, None).unwrap(); + PHVERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); + PHVERIFIER::verify_ph(&pk, &ph, None, &sig_val).unwrap(); // sign_ph_out - let (pk, sk) = SigAlg::keygen().unwrap(); + let (pk, sk) = keygen().unwrap(); let ph: [u8; PH_LEN] = HASH::default().hash(msg)[..PH_LEN].try_into().unwrap(); let mut sig_val = [0u8; SIG_LEN]; - let bytes_written = SigAlg::sign_ph_out(&sk, &ph, None, &mut sig_val).unwrap(); + let bytes_written = PHSIGNER::sign_ph_out(&sk, &ph, None, &mut sig_val).unwrap(); assert_eq!(bytes_written, SIG_LEN); - SigAlg::verify_ph(&pk, &ph, None, &sig_val).unwrap(); - SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); + PHVERIFIER::verify_ph(&pk, &ph, None, &sig_val).unwrap(); + PHVERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); } } @@ -277,31 +289,31 @@ impl TestFrameworkSignatureKeys { Self {} } + /// Since key generation is not part of either signature trait, the caller supplies a + /// `keygen` function pointer (the inherent `keygen` associated function on the algorithm struct). pub fn test_keys< PK: SignaturePublicKey, SK: SignaturePrivateKey, - SigAlg: Signature, const PK_LEN: usize, const SK_LEN: usize, - const SIG_LEN: usize, >( &self, + keygen: fn() -> Result<(PK, SK), SignatureError>, ) { - self.test_boundary_conditions::(); + self.test_boundary_conditions::(keygen); } /// Tests the correct behaviour on buffers too large / too small. fn test_boundary_conditions< PK: SignaturePublicKey, SK: SignaturePrivateKey, - SigAlg: Signature, const PK_LEN: usize, const SK_LEN: usize, - const SIG_LEN: usize, >( &self, + keygen: fn() -> Result<(PK, SK), SignatureError>, ) { - let (pk, sk) = SigAlg::keygen().unwrap(); + let (pk, sk) = keygen().unwrap(); let pk_bytes = pk.encode(); assert_eq!(pk_bytes.len(), PK_LEN); diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 27dd844..1a623ab 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -1,9 +1,9 @@ //! Provides simplified abstracted APIs over classes of cryptigraphic primitives, such as Hash, KDF, etc. -use core::marker::Sized; -use core::fmt::{Debug, Display}; use crate::errors::{HashError, KDFError, KEMError, MACError, RNGError, SignatureError}; use crate::key_material::KeyMaterialTrait; +use core::fmt::{Debug, Display}; +use core::marker::Sized; // Imports needed for docs #[allow(unused_imports)] @@ -17,7 +17,7 @@ pub trait Algorithm { const MAX_SECURITY_STRENGTH: SecurityStrength; } -pub trait Hash : Default { +pub trait Hash: Default { /// The size of the internal block in bits -- needed by functions such as HMAC to compute security parameters. fn block_bitlen(&self) -> usize; @@ -84,7 +84,7 @@ pub trait HashAlgParams: Algorithm { /// A Key Derivation Function (KDF) is a function that takes in one or more input key and some unstructured /// additional input, and uses them to produces a derived key. -pub trait KDF : Default { +pub trait KDF: Default { /// Implementations of this function are capable of deriving an output key from an input key, /// assuming that they have been properly initialized. /// @@ -186,11 +186,12 @@ pub trait KEM< const SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, ->: Sized { +>: Sized +{ /// Generate a keypair. /// Error condition: Basically only on RNG failures fn keygen() -> Result<(PK, SK), KEMError>; - + /// Performs an encapsulation against the given public key. /// Returns the ciphertext and derived shared secret. fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; @@ -204,7 +205,9 @@ pub trait KEM< // todo: that automatically call the encode and from_bytes() ? /// A public key for a KEM algorithm, often denoted "pk". -pub trait KEMPublicKey : PartialEq + Eq + Clone + Debug + Display + Sized { +pub trait KEMPublicKey: + PartialEq + Eq + Clone + Debug + Display + Sized +{ /// Write it out to bytes in its standard encoding. fn encode(&self) -> [u8; PK_LEN]; /// Write it out to bytes in its standard encoding. @@ -214,7 +217,7 @@ pub trait KEMPublicKey : PartialEq + Eq + Clone + Debug + D } /// A private key for a KEM algorithm, often denoted "sk" (for "secret key"). -pub trait KEMPrivateKey : PartialEq + Eq + Clone + Secret + Sized { +pub trait KEMPrivateKey: PartialEq + Eq + Clone + Secret + Sized { /// Write it out to bytes in its standard encoding. fn encode(&self) -> [u8; SK_LEN]; /// Write it out to bytes in its standard encoding. @@ -223,7 +226,6 @@ pub trait KEMPrivateKey : PartialEq + Eq + Clone + Secret + fn from_bytes(bytes: &[u8]) -> Result; } - /// A Message Authentication Code algorithm is a keyed hash function that behaves somewhat like a symmetric signature function. /// A MAC algorithm takes in a key and some data, and produces a MAC (message authentication code) that /// can be used to verify the integrity of data. @@ -293,7 +295,7 @@ pub trait MAC: Sized { /// Depending on the underlying MAC implementation, NIST may require that the library enforce /// a minimum length on the mac output value. See documentation for the underlying implementation /// to see conditions under which it throws [MACError::InvalidLength]. - fn mac_out(self, data: &[u8],out: &mut [u8]) -> Result; + fn mac_out(self, data: &[u8], out: &mut [u8]) -> Result; /// One-shot API that verifies a MAC for the provided data. /// `data` can be of any length, including zero bytes. @@ -381,11 +383,14 @@ impl SecurityStrength { /// be used by applications that intend to submit to FIPS certification as it more closely aligns with the /// requirements of SP 800-90A. /// Note: this interface produces bytes. If you want a [KeyMaterialTrait], then use [KeyMaterial::from_rng]. -pub trait RNG : Default { +pub trait RNG: Default { // TODO: add back once we figure out streaming interaction with entropy sources. // fn add_seed_bytes(&mut self, additional_seed: &[u8]) -> Result<(), RNGError>; - fn add_seed_keymaterial(&mut self, additional_seed: impl KeyMaterialTrait) -> Result<(), RNGError>; + fn add_seed_keymaterial( + &mut self, + additional_seed: impl KeyMaterialTrait, + ) -> Result<(), RNGError>; fn next_int(&mut self) -> Result; /// Returns the number of requested bytes. @@ -402,21 +407,23 @@ pub trait RNG : Default { /// A trait that forces an object to implement a zeroizing Drop() as well as Debug and Display that /// will not log the sensitive contents, even in error or crash-dump scenarios. -#[allow(drop_bounds)] // Since rust auto-implements Drop, there's a lint that explicitly bounding on Drop is useless. - // I disagree because I want to force things that are secrets to manually implement Drop that zeroizes the data. - // So I'm turning off this lint. -pub trait Secret : Drop + Debug + Display {} +// Since rust auto-implements Drop, there's a lint that explicitly bounding on Drop is useless. +// I disagree because I want to force things that are secrets to manually implement Drop that zeroizes the data. +// So I'm turning off this lint. +#[allow(drop_bounds)] +pub trait Secret: Drop + Debug + Display {} -/// Pre-Hashed Signature is an extension to [Signature] that adds functionality specific to signature +/// Pre-Hashed Signer is an extension to [Signer] that adds functionality specific to signature /// primatives that can operate on a pre-hashed message instead of the full message. -pub trait PHSignature< +pub trait PHSigner< PK: SignaturePublicKey, SK: SignaturePrivateKey, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - const PH_LEN: usize>: - Signature{ + const PH_LEN: usize, +>: Signer +{ /// Produce a signature for the provided pre-hashed message and context. /// /// `ctx` accepts a zero-length byte array. @@ -441,40 +448,92 @@ pub trait PHSignature< /// Not all signature primitives will support a context value, so you may need to consult the /// documentation for the underlying primitive for how it handles a ctx in that case, for example, it /// might throw an error, ignore the provided ctx value, or append the ctx to the msg in a non-standard way. - fn sign_ph(sk: &SK, ph: &[u8; PH_LEN], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError>; + fn sign_ph( + sk: &SK, + ph: &[u8; PH_LEN], + ctx: Option<&[u8]>, + ) -> Result<[u8; SIG_LEN], SignatureError>; /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. - fn sign_ph_out(sk: &SK, ph: &[u8; PH_LEN], ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN]) -> Result; + fn sign_ph_out( + sk: &SK, + ph: &[u8; PH_LEN], + ctx: Option<&[u8]>, + output: &mut [u8; SIG_LEN], + ) -> Result; +} + +/// Pre-Hashed Signature Verifier is an extension to [SignatureVerifier] that adds functionality specific to signature +/// primatives that can operate on a pre-hashed message instead of the full message. +pub trait PHSignatureVerifier< + PK: SignaturePublicKey, + const PK_LEN: usize, + const SIG_LEN: usize, + const PH_LEN: usize, +>: SignatureVerifier +{ /// On success, returns Ok(()) /// On failure, returns Err([SignatureError::SignatureVerificationFailed]); may also return other types of [SignatureError] as appropriate (such as for invalid-length inputs). - fn verify_ph(pk: &PK, ph: &[u8; PH_LEN], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError>; + fn verify_ph( + pk: &PK, + ph: &[u8; PH_LEN], + ctx: Option<&[u8]>, + sig: &[u8], + ) -> Result<(), SignatureError>; +} + +// todo: could the public and private key types impl Into> and From> +// todo: that automatically call the encode and from_bytes() ? + +/// A public key for a signature algorithm, often denoted "pk". +pub trait SignaturePublicKey: + PartialEq + Eq + Clone + Debug + Display + Sized +{ + /// Write it out to bytes in its standard encoding. + fn encode(&self) -> [u8; PK_LEN]; + /// Write it out to bytes in its standard encoding. + /// The entire output buffer is zeroized before the encoding is written. + fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize; + /// Read it in from bytes in its standard encoding. + fn from_bytes(bytes: &[u8]) -> Result; +} + +/// A private key for a signature algorithm, often denoted "sk" (for "secret key"). +pub trait SignaturePrivateKey: + PartialEq + Eq + Clone + Secret + Sized +{ + /// Write it out to bytes in its standard encoding. + fn encode(&self) -> [u8; SK_LEN]; + /// Write it out to bytes in its standard encoding. + /// The entire output buffer is zeroized before the encoding is written. + fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize; + /// Read it in from bytes in its standard encoding. + fn from_bytes(bytes: &[u8]) -> Result; } /// A digital signature algorithm is defined as a set of three operations: /// key generation, signing, and verification. /// -/// To avoid the use of dyn, this trait does not include key generation; you'll have to consult the -/// documentation for the underlying signature primitive for how to generate a key pair. +/// This trait represents the operations performed by the holder of the signing private key: +/// which include signing and key generation. Verification operations are performed by the corresponding +/// [SignatureVerifier] trait. +/// There are several reasons for this split: first is architectural; some complex algorithms may +/// benefit from having the signature generation and verification implementations split into separate modules. +/// Second is for compliance: sometimes a policy soft-deprecates an algorithm so that new signatures +/// can no longer be created, but existing signatures can still be verified. Splitting the traits +/// makes this policy easier to enforce. /// /// This high-level trait defines the operations over a generic signature algorithm that is assumed /// to source all its randomness from bouncycastle's default os-backed RNG. /// The underlying signature primitives will expose APIs that allow for specifying a specific RNG /// or deterministic seed values. /// -/// Here we statically-size the arrays used to encode public keys, private keys, and signature values +/// The arrays used to encode public keys, private keys, and signature values are statically-sized /// because this allows us to safely remove runtime checks for array lengths, which overall reduces /// the fallibility of the library. This design choice could make this trait complicated to apply /// to a signature algorithm that do not have fixed sizes for the encodings of these objects. -pub trait Signature< - PK: SignaturePublicKey, - SK: SignaturePrivateKey, - const PK_LEN: usize, - const SK_LEN: usize, - const SIG_LEN: usize ->: Sized { - /// Generate a keypair. - /// Error condition: Basically only on RNG failures - fn keygen() -> Result<(PK, SK), SignatureError>; - +pub trait Signer, const SK_LEN: usize, const SIG_LEN: usize>: + Sized +{ /// Produce a signature for the provided message and context. /// Both the `msg` and `ctx` accept zero-length byte arrays. /// @@ -501,7 +560,12 @@ pub trait Signature< fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError>; /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. - fn sign_out(sk: &SK, msg: &[u8], ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN]) -> Result; + fn sign_out( + sk: &SK, + msg: &[u8], + ctx: Option<&[u8]>, + output: &mut [u8; SIG_LEN], + ) -> Result; /* streaming signing API */ /// Initialize a signer for streaming mode with the provided private key. @@ -517,7 +581,29 @@ pub trait Signature< /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. fn sign_final_out(self, output: &mut [u8; SIG_LEN]) -> Result; +} +/// A digital signature algorithm is defined as a set of three operations: +/// key generation, signing, and verification. +/// +/// This trait represents the verification operations performed by the holder of the verification public key. +/// Keygen and signing operations are performed by the corresponding [Signer] trait. +/// There are several reasons for this split: first is architectural; some complex algorithms may +/// benefit from having the signature generation and verification implementations split into separate modules. +/// Second is for compliance: sometimes a policy soft-deprecates an algorithm so that new signatures +/// can no longer be created, but existing signatures can still be verified. Splitting the traits +/// makes this policy easier to enforce. +/// +/// Here we statically-size the arrays used to encode public keys, private keys, and signature values +/// because this allows us to safely remove runtime checks for array lengths, which overall reduces +/// the fallibility of the library. This design choice could make this trait complicated to apply +/// to a signature algorithm that do not have fixed sizes for the encodings of these objects. +pub trait SignatureVerifier< + PK: SignaturePublicKey, + const PK_LEN: usize, + const SIG_LEN: usize, +>: Sized +{ /// On success, returns Ok(()) /// On failure, returns Err([SignatureError::SignatureVerificationFailed]); may also return other types of [SignatureError] as appropriate (such as for invalid-length inputs). fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError>; @@ -577,7 +663,7 @@ pub trait SignaturePrivateKey : PartialEq + Eq + Clone + Se /// to break anonymity-preserving technology. /// Applications that require the arbitrary-length output of an XOF, but also care about these /// distinguishing attacks should consider adding a cryptographic salt to diversify the inputs. -pub trait XOF : Default { +pub trait XOF: Default { /// A static one-shot API that digests the input data and produces `result_len` bytes of output. fn hash_xof(self, data: &[u8], result_len: usize) -> Vec; diff --git a/crypto/mldsa/benches/mldsa_benches.rs b/crypto/mldsa/benches/mldsa_benches.rs index 9d019a4..2c44529 100644 --- a/crypto/mldsa/benches/mldsa_benches.rs +++ b/crypto/mldsa/benches/mldsa_benches.rs @@ -1,7 +1,7 @@ use criterion::{Criterion, criterion_group, criterion_main}; use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; use std::hint::black_box; -use bouncycastle_core::traits::Signature; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; use bouncycastle_mldsa::{MLDSA44PrivateKeyExpanded, MLDSA44PublicKeyExpanded, MLDSA65PrivateKeyExpanded, MLDSA65PublicKeyExpanded, MLDSA87PrivateKeyExpanded, MLDSA87PublicKeyExpanded, MLDSAPrivateKeyTrait, MLDSAPublicKeyTrait, MLDSATrait, MLDSA44, MLDSA44_SIG_LEN, MLDSA65, MLDSA65_SIG_LEN, MLDSA87, MLDSA87_SIG_LEN}; use bouncycastle_hex as hex; diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 15f1464..48442aa 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -3,12 +3,12 @@ //! mode of [MLDSA]; possibly because you have to digest the message before you know which public key //! will sign it. //! -//! HashML-DSA is a full signature algorithm implementing the [Signature] trait: +//! HashML-DSA is a full signature algorithm implementing the [Signer] and [SignatureVerifier] traits: //! //! ```rust //! use bouncycastle_core::errors::SignatureError; //! use bouncycastle_mldsa::{HashMLDSA65_with_SHA512, MLDSATrait, HashMLDSA44_with_SHA512}; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; //! @@ -24,12 +24,14 @@ //! } //! ``` //! -//! But you also have access to the pre-hashed function available from [PHSignature]: +//! But you also have access to the pre-hashed functions available from [PHSigner] and [PHVerifier]: //! //! ```rust //! use bouncycastle_core::errors::SignatureError; //! use bouncycastle_mldsa::{HashMLDSA65_with_SHA512, MLDSATrait, HashMLDSA44_with_SHA512}; -//! use bouncycastle_core::traits::{Signature, PHSignature, Hash}; +//! use bouncycastle_core::traits::{ +//! Hash, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, +//! }; //! use bouncycastle_sha2::SHA512; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -44,14 +46,14 @@ //! let sig = HashMLDSA65_with_SHA512::sign_ph(&sk, &ph, None).unwrap(); //! // This is the signature value that you can save to a file or whatever you need. //! -//! // This verifies either through the usual one-shot API of the [Signature] trait +//! // This verifies either through the usual one-shot API of the [SignatureVerifier] trait //! match HashMLDSA65_with_SHA512::verify(&pk, msg, None, &sig) { //! Ok(()) => println!("Signature is valid!"), //! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), //! Err(e) => panic!("Something else went wrong: {:?}", e), //! } //! -//! // Or though the verify_ph of the [PHSignature] trait +//! // Or though the verify_ph of the [PHSignatureVerifier] trait //! match HashMLDSA65_with_SHA512::verify_ph(&pk, &ph, None, &sig) { //! Ok(()) => println!("Signature is valid!"), //! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), @@ -61,7 +63,7 @@ //! //! Note that the [HashMLDSA] object is just a light wrapper around [MLDSA], and, for example, they share key types, //! so if you need the fancy keygen functions, just use them from [MLDSA]. -//! But a simple [HashMLDSA::keygen] is provided in order to have conformance to the [Signature] trait. +//! But a simple [HashMLDSA::keygen] is provided. use crate::mldsa::{H, MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSATrait}; use crate::mldsa::{ @@ -91,7 +93,8 @@ use crate::{ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ - Algorithm, Hash, PHSignature, RNG, SecurityStrength, Signature, XOF, + Algorithm, Hash, PHSignatureVerifier, PHSigner, RNG, SecurityStrength, SignatureVerifier, + Signer, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha2::{SHA256, SHA256_NAME, SHA512, SHA512_NAME}; @@ -425,6 +428,38 @@ impl< GAMMA1_MASK_LEN, > { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [Signer] / [SignatureVerifier] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Keygen, and keys in general, are interchangeable between MLDSA and HashMLDSA. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), SignatureError> { + MLDSA::< + PK_LEN, + SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + >::keygen() + } + /// Imports a secret key from a seed. pub fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError> { MLDSA::< @@ -451,7 +486,7 @@ impl< GAMMA1_MASK_LEN, >::keygen_internal(seed) } - /// Same as [Signature::sign], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [Signer::sign], but signs from an [MLDSAPrivateKeyExpanded]. pub fn sign_with_expanded_key( sk: &MLDSAPrivateKeyExpanded, msg: &[u8], @@ -462,7 +497,7 @@ impl< Ok(out) } - /// Same as [Signature::sign_out], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [Signer::sign_out], but signs from an [MLDSAPrivateKeyExpanded]. pub fn sign_with_expanded_key_out( sk: &MLDSAPrivateKeyExpanded, msg: &[u8], @@ -473,7 +508,7 @@ impl< _ = HASH::default().hash_out(msg, &mut ph_m); Self::sign_ph_with_expanded_key_out(sk, &ph_m, ctx, output) } - /// Same as [PHSignature::sign_ph], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [PHSigner::sign_ph], but signs from an [MLDSAPrivateKeyExpanded]. pub fn sign_ph_with_expanded_key( sk: &MLDSAPrivateKeyExpanded, ph: &[u8; PH_LEN], @@ -484,7 +519,7 @@ impl< Ok(out) } - /// Same as [PHSignature::sign_ph_out], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [PHSigner::sign_ph_out], but signs from an [MLDSAPrivateKeyExpanded]. pub fn sign_ph_with_expanded_key_out( sk: &MLDSAPrivateKeyExpanded, ph: &[u8; PH_LEN], @@ -609,9 +644,9 @@ impl< Ok(bytes_written) } - /// To be used for deterministic signing in conjunction with the [Signature::sign_init], - /// [Signature::sign_update], and [Signature::sign_final] flow. - /// Can be set anywhere after [Signature::sign_init] and before [Signature::sign_final] + /// To be used for deterministic signing in conjunction with the [Signer::sign_init], + /// [Signer::sign_update], and [Signer::sign_final] flow. + /// Can be set anywhere after [Signer::sign_init] and before [Signer::sign_final] pub fn set_signer_rnd(&mut self, rnd: [u8; 32]) { self.signer_rnd = Some(rnd); } @@ -650,7 +685,7 @@ impl< ctx_len, }) } - /// Same as [Signature::verify], but verifies from an [MLDSAPublicKeyExpanded]. + /// Same as [SignatureVerifier::verify], but verifies from an [MLDSAPublicKeyExpanded]. pub fn verify_with_expanded_key( pk: &MLDSAPublicKeyExpanded, msg: &[u8], @@ -804,7 +839,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> Signature +> Signer for HashMLDSA< HASH, PH_LEN, @@ -831,33 +866,6 @@ impl< GAMMA1_MASK_LEN, > { - /// Keygen, and keys in general, are interchangeable between MLDSA and HashMLDSA. - fn keygen() -> Result<(PK, SK), SignatureError> { - MLDSA::< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::keygen() - } - /// Algorithm 4 HashML-DSA.Sign(π‘ π‘˜, 𝑀 , 𝑐𝑑π‘₯, PH) /// Generate a β€œpre-hash” ML-DSA signature. fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { @@ -947,7 +955,62 @@ impl< unreachable!() } } +} +impl< + HASH: Hash + Default, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const PH_LEN: usize, + const oid: &'static [u8], + const PK_LEN: usize, + const SK_LEN: usize, + const SIG_LEN: usize, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, + const GAMMA1_MASK_LEN: usize, +> SignatureVerifier + for HashMLDSA< + HASH, + PH_LEN, + oid, + PK_LEN, + SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mut ph_m = [0u8; PH_LEN]; _ = HASH::default().hash_out(msg, &mut ph_m); @@ -1008,7 +1071,7 @@ impl< const GAMMA1_MASK_LEN: usize, const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, -> PHSignature +> PHSigner for HashMLDSA< HASH, PH_LEN, @@ -1060,7 +1123,62 @@ impl< HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; Self::sign_ph_deterministic_out(sk, None, ctx, ph, rnd, output) } +} +impl< + HASH: Hash + Default, + const PH_LEN: usize, + const oid: &'static [u8], + const PK_LEN: usize, + const SK_LEN: usize, + const SIG_LEN: usize, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MASK_LEN: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, +> PHSignatureVerifier + for HashMLDSA< + HASH, + PH_LEN, + oid, + PK_LEN, + SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify_ph( pk: &PK, ph: &[u8; PH_LEN], diff --git a/crypto/mldsa/src/lib.rs b/crypto/mldsa/src/lib.rs index 9c3a2b8..c304171 100644 --- a/crypto/mldsa/src/lib.rs +++ b/crypto/mldsa/src/lib.rs @@ -15,7 +15,6 @@ //! //! ```rust //! use bouncycastle_mldsa::MLDSA65; -//! use bouncycastle_core::traits::Signature; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); //! ``` @@ -39,13 +38,13 @@ //! //! See [MLDSATrait] and [MLDSATrait::sign_mu_deterministic_from_seed] for an API flow that uses a merged //! keygen-and-sign function to provide improved speed and memory performance compared with making -//! separate calls to [MLDSATrait::keygen_from_seed] followed by [Signature::sign]. +//! separate calls to [MLDSATrait::keygen_from_seed] followed by [Signer::sign]. //! //! ## Generating and Verifying Signatures //! //! ```rust //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait}; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_core::errors::SignatureError; //! //! let msg = b"The quick brown fox"; @@ -133,7 +132,7 @@ #[allow(unused_imports)] use bouncycastle_core::key_material::KeyMaterialTrait; #[allow(unused_imports)] -use bouncycastle_core::traits::Signature; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; // todo -- crucible tests diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index d207f01..6fe2001 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -9,7 +9,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -51,7 +51,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -98,7 +98,7 @@ //! ```rust //! use bouncycastle_core::errors::SignatureError; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! //! let (pk, _) = MLDSA65::keygen().unwrap(); //! @@ -116,7 +116,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, _) = MLDSA65::keygen().unwrap(); @@ -136,7 +136,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -182,7 +182,7 @@ //! ```rust //! use bouncycastle_core::errors::SignatureError; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait}; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! //! let msg = b"The quick brown fox"; //! let ctx = b"FooTextDocumentFormat"; @@ -220,7 +220,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -258,7 +258,7 @@ //! ```rust //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait}; //! use bouncycastle_mldsa::{MLDSA65PublicKeyExpanded, MLDSA65PrivateKeyExpanded}; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_core::errors::SignatureError; //! //! let msg = b"The quick brown fox"; @@ -303,7 +303,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType, KeyMaterialTrait}; //! use bouncycastle_hex as hex; //! use bouncycastle_mldsa::{MLDSA44, MLDSA44_SIG_LEN, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; @@ -375,7 +375,7 @@ //! ```rust //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait}; //! use bouncycastle_mldsa::{MLDSA65PublicKeyExpanded, MLDSA65PrivateKeyExpanded}; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_core::errors::SignatureError; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -412,7 +412,7 @@ use crate::{ }; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, Signature, XOF}; +use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, XOF}; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::{SHAKE128, SHAKE256}; use core::marker::PhantomData; @@ -421,7 +421,7 @@ use core::marker::PhantomData; #[allow(unused_imports)] use crate::hash_mldsa; #[allow(unused_imports)] -use bouncycastle_core::traits::PHSignature; +use bouncycastle_core::traits::{PHSignatureVerifier, PHSigner}; /*** Constants ***/ @@ -728,6 +728,15 @@ impl< GAMMA1_MASK_LEN, > { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [Signer] / [SignatureVerifier] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), SignatureError> { + Self::keygen_from_os_rng() + } + /// Should still be ok in FIPS mode pub fn keygen_from_os_rng() -> Result<(PK, SK), SignatureError> { let mut seed = KeyMaterial256::new(); @@ -1712,7 +1721,7 @@ pub trait MLDSATrait< msg: &[u8], ctx: Option<&[u8]>, ) -> Result<[u8; 64], SignatureError>; - /// Same as [Signature::sign], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [Signer::sign], but signs from an [MLDSAPrivateKeyExpanded]. fn sign_with_expanded_key( sk: &MLDSAPrivateKeyExpanded, msg: &[u8], @@ -1750,13 +1759,13 @@ pub trait MLDSATrait< mu: &[u8; 64], output: &mut [u8; SIG_LEN], ) -> Result; - /// Same as [Signature::sign_mu], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [MLDSATrait::sign_mu], but signs from an [MLDSAPrivateKeyExpanded]. fn sign_mu_with_expanded_key( sk: &MLDSAPrivateKeyExpanded, A_hat: Option<&Matrix>, mu: &[u8; 64], ) -> Result<[u8; SIG_LEN], SignatureError>; - /// Same as [Signature::sign_mu_out], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [MLDSATrait::sign_mu_out], but signs from an [MLDSAPrivateKeyExpanded]. fn sign_mu_with_expanded_key_out( sk: &MLDSAPrivateKeyExpanded, A_hat: Option<&Matrix>, @@ -1846,7 +1855,7 @@ pub trait MLDSATrait< seed: &KeyMaterial<32>, ctx: Option<&[u8]>, ) -> Result; - /// Same as [Signature::verify], but signs from an expanded key object. + /// Same as [SignatureVerifier::verify], but signs from an expanded key object. fn verify_with_expanded_key( pk: &MLDSAPublicKeyExpanded, msg: &[u8], @@ -1888,7 +1897,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> Signature +> Signer for MLDSA< PK_LEN, SK_LEN, @@ -1913,10 +1922,6 @@ impl< GAMMA1_MASK_LEN, > { - fn keygen() -> Result<(PK, SK), SignatureError> { - Self::keygen_from_os_rng() - } - fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { let mut out = [0u8; SIG_LEN]; Self::sign_out(sk, msg, ctx, &mut out)?; @@ -1991,7 +1996,56 @@ impl< unreachable!() } } +} +impl< + const PK_LEN: usize, + const SK_LEN: usize, + const SIG_LEN: usize, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, + const GAMMA1_MASK_LEN: usize, +> SignatureVerifier + for MLDSA< + PK_LEN, + SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)?; @@ -2048,7 +2102,7 @@ impl< /// Note: this struct is only exposed for "pure" ML-DSA and not for HashML-DSA because HashML-DSA /// does not benefit from allowing external construction of the message representative mu. /// You can get the same behaviour by computing the pre-hash `ph` with the appropriate hash function -/// and providing that to HashMLDSA via [PHSignature::sign_ph]. +/// and providing that to HashMLDSA via [PHSigner::sign_ph]. pub struct MuBuilder { h: H, } diff --git a/crypto/mldsa/tests/bc_test_data.rs b/crypto/mldsa/tests/bc_test_data.rs index 77ae858..a732489 100644 --- a/crypto/mldsa/tests/bc_test_data.rs +++ b/crypto/mldsa/tests/bc_test_data.rs @@ -14,7 +14,7 @@ mod bc_test_data { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Hash, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + Hash, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, }; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ diff --git a/crypto/mldsa/tests/hash_mldsa_tests.rs b/crypto/mldsa/tests/hash_mldsa_tests.rs index 4301d7b..ecdfb7c 100644 --- a/crypto/mldsa/tests/hash_mldsa_tests.rs +++ b/crypto/mldsa/tests/hash_mldsa_tests.rs @@ -2,7 +2,9 @@ mod hash_mldsa_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; - use bouncycastle_core::traits::{Hash, PHSignature, Signature}; + use bouncycastle_core::traits::{ + Hash, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, + }; use bouncycastle_core_test_framework::signature::TestFrameworkSignature; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ @@ -21,19 +23,19 @@ mod hash_mldsa_tests { let tf = TestFrameworkSignature::new(false, true); // Test HashML-DSA-SHA512 as a regular signature alg - tf.test_signature::(false); - tf.test_signature::(false); - tf.test_signature::(false); + tf.test_signature::(HashMLDSA44_with_SHA512::keygen, false); + tf.test_signature::(HashMLDSA65_with_SHA512::keygen, false); + tf.test_signature::(HashMLDSA87_with_SHA512::keygen, false); // Test HashML-DSA-SHA256 as a ph signature alg - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); + tf.test_ph_signature::(HashMLDSA44_with_SHA256::keygen, false); + tf.test_ph_signature::(HashMLDSA65_with_SHA256::keygen, false); + tf.test_ph_signature::(HashMLDSA87_with_SHA256::keygen, false); // Test HashML-DSA-SHA512 as a ph signature alg - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); + tf.test_ph_signature::(HashMLDSA44_with_SHA512::keygen, false); + tf.test_ph_signature::(HashMLDSA65_with_SHA512::keygen, false); + tf.test_ph_signature::(HashMLDSA87_with_SHA512::keygen, false); } #[test] diff --git a/crypto/mldsa/tests/mldsa_key_tests.rs b/crypto/mldsa/tests/mldsa_key_tests.rs index c2451a0..476a5b6 100644 --- a/crypto/mldsa/tests/mldsa_key_tests.rs +++ b/crypto/mldsa/tests/mldsa_key_tests.rs @@ -3,7 +3,7 @@ mod mldsa_key_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + SecurityStrength, SignaturePrivateKey, SignaturePublicKey, }; use bouncycastle_core_test_framework::signature::TestFrameworkSignatureKeys; use bouncycastle_hex as hex; @@ -15,17 +15,17 @@ mod mldsa_key_tests { MLDSAPrivateKeyTrait, MLDSATrait, }; use bouncycastle_mldsa::{ - MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN, MLDSA65_PK_LEN, MLDSA65_SIG_LEN, - MLDSA65_SK_LEN, MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN, + MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA87_PK_LEN, + MLDSA87_SK_LEN, }; #[test] fn core_framework_tests() { let tf = TestFrameworkSignatureKeys::new(); - tf.test_keys::(); - tf.test_keys::(); - tf.test_keys::(); + tf.test_keys::(MLDSA44::keygen); + tf.test_keys::(MLDSA65::keygen); + tf.test_keys::(MLDSA87::keygen); } #[test] diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index b5a1b5f..b87a0a8 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -5,7 +5,7 @@ mod mldsa_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - RNG, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::signature::*; @@ -44,9 +44,9 @@ mod mldsa_tests { fn test_framework_signature() { let tf = TestFrameworkSignature::new(false, true); - tf.test_signature::(false); - tf.test_signature::(false); - tf.test_signature::(false); + tf.test_signature::(MLDSA44::keygen, false); + tf.test_signature::(MLDSA65::keygen, false); + tf.test_signature::(MLDSA87::keygen, false); } /// This runs the full bitflipping tests and takes several minutes. diff --git a/crypto/mldsa/tests/wycheproof.rs b/crypto/mldsa/tests/wycheproof.rs index bc7fd34..abe6842 100644 --- a/crypto/mldsa/tests/wycheproof.rs +++ b/crypto/mldsa/tests/wycheproof.rs @@ -20,7 +20,7 @@ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, }; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ diff --git a/crypto/mldsa_lowmemory/benches/mldsa_benches.rs b/crypto/mldsa_lowmemory/benches/mldsa_benches.rs index 79ffd00..c6eb407 100644 --- a/crypto/mldsa_lowmemory/benches/mldsa_benches.rs +++ b/crypto/mldsa_lowmemory/benches/mldsa_benches.rs @@ -1,7 +1,7 @@ use criterion::{Criterion, criterion_group, criterion_main}; use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; use std::hint::black_box; -use bouncycastle_core::traits::Signature; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; use bouncycastle_mldsa_lowmemory::{MLDSATrait, MLDSA44, MLDSA44_SIG_LEN, MLDSA65, MLDSA65_SIG_LEN, MLDSA87, MLDSA87_SIG_LEN}; use bouncycastle_hex as hex; diff --git a/crypto/mldsa_lowmemory/src/hash_mldsa.rs b/crypto/mldsa_lowmemory/src/hash_mldsa.rs index 33b9176..27cbf7e 100644 --- a/crypto/mldsa_lowmemory/src/hash_mldsa.rs +++ b/crypto/mldsa_lowmemory/src/hash_mldsa.rs @@ -3,11 +3,11 @@ //! mode of [MLDSA]; possibly because you have to digest the message before you know which public key //! will sign it. //! -//! HashML-DSA is a full signature algorithm implementing the [Signature] trait: +//! HashML-DSA is a full signature algorithm implementing the [Signer] and [SignatureVerifier] traits: //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSATrait, HashMLDSA65_with_SHA512, HashMLDSA44_with_SHA512}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -24,11 +24,13 @@ //! } //! ``` //! -//! But you also have access to the pre-hashed function available from [PHSignature]: +//! But you also have access to the pre-hashed function available from [PHSigner] and [PHSignatureVerifier]: //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::{Signature, PHSignature, Hash}; +//! use bouncycastle_core::traits::{ +//! Hash, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, +//! }; //! use bouncycastle_sha2::SHA512; //! use bouncycastle_mldsa_lowmemory::{MLDSATrait, HashMLDSA65_with_SHA512, HashMLDSA44_with_SHA512}; //! @@ -44,14 +46,14 @@ //! let sig = HashMLDSA65_with_SHA512::sign_ph(&sk, &ph, None).unwrap(); //! // This is the signature value that you can save to a file or whatever you need. //! -//! // This verifies either through the usual one-shot API of the [Signature] trait +//! // This verifies either through the usual one-shot API of the [SignatureVerifier] trait //! match HashMLDSA65_with_SHA512::verify(&pk, msg, None, &sig) { //! Ok(()) => println!("Signature is valid!"), //! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), //! Err(e) => panic!("Something else went wrong: {:?}", e), //! } //! -//! // Or though the verify_ph of the [PHSignature] trait +//! // Or though the verify_ph of the [PHSignatureVerifier] trait //! match HashMLDSA65_with_SHA512::verify_ph(&pk, &ph, None, &sig) { //! Ok(()) => println!("Signature is valid!"), //! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), @@ -61,27 +63,30 @@ //! //! Note that the [HashMLDSA] object is just a light wrapper around [MLDSA], and, for example, they share key types, //! so if you need the fancy keygen functions, just use them from [MLDSA]. -//! But a simple [HashMLDSA::keygen] is provided in order to have conformance to the [Signature] trait. +//! But a simple [HashMLDSA::keygen] is provided. use crate::mldsa::{H, MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSATrait}; use crate::mldsa::{ - MLDSA44_BETA, MLDSA44_C_TILDE, MLDSA44_ETA, MLDSA44_GAMMA1, MLDSA44_GAMMA1_MINUS_BETA, MLDSA44_GAMMA2_MINUS_BETA, MLDSA44_GAMMA1_MASK_LEN, - MLDSA44_GAMMA2, MLDSA44_LAMBDA, MLDSA44_LAMBDA_over_4, MLDSA44_OMEGA, MLDSA44_PK_LEN, - MLDSA44_POLY_W1_PACKED_LEN, MLDSA44_POLY_Z_PACKED_LEN, - MLDSA44_SIG_LEN, MLDSA44_SK_LEN, MLDSA44_FULL_SK_LEN, MLDSA44_TAU, MLDSA44_S1_PACKED_LEN, MLDSA44_S2_PACKED_LEN, MLDSA44_k, MLDSA44_l, + MLDSA44_BETA, MLDSA44_C_TILDE, MLDSA44_ETA, MLDSA44_FULL_SK_LEN, MLDSA44_GAMMA1, + MLDSA44_GAMMA1_MASK_LEN, MLDSA44_GAMMA1_MINUS_BETA, MLDSA44_GAMMA2, MLDSA44_GAMMA2_MINUS_BETA, + MLDSA44_LAMBDA, MLDSA44_LAMBDA_over_4, MLDSA44_OMEGA, MLDSA44_PK_LEN, + MLDSA44_POLY_W1_PACKED_LEN, MLDSA44_POLY_Z_PACKED_LEN, MLDSA44_S1_PACKED_LEN, + MLDSA44_S2_PACKED_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN, MLDSA44_TAU, MLDSA44_k, MLDSA44_l, }; use crate::mldsa::{MLDSA44_T1_PACKED_LEN, MLDSA65_T1_PACKED_LEN, MLDSA87_T1_PACKED_LEN}; use crate::mldsa::{ - MLDSA65_BETA, MLDSA65_C_TILDE, MLDSA65_ETA, MLDSA65_GAMMA1, MLDSA65_GAMMA1_MINUS_BETA, MLDSA65_GAMMA2_MINUS_BETA, MLDSA65_GAMMA1_MASK_LEN, - MLDSA65_GAMMA2, MLDSA65_LAMBDA, MLDSA65_LAMBDA_over_4, MLDSA65_OMEGA, MLDSA65_PK_LEN, - MLDSA65_POLY_W1_PACKED_LEN, MLDSA65_POLY_Z_PACKED_LEN, - MLDSA65_SIG_LEN, MLDSA65_SK_LEN, MLDSA65_FULL_SK_LEN, MLDSA65_TAU, MLDSA65_S1_PACKED_LEN, MLDSA65_S2_PACKED_LEN, MLDSA65_k, MLDSA65_l, + MLDSA65_BETA, MLDSA65_C_TILDE, MLDSA65_ETA, MLDSA65_FULL_SK_LEN, MLDSA65_GAMMA1, + MLDSA65_GAMMA1_MASK_LEN, MLDSA65_GAMMA1_MINUS_BETA, MLDSA65_GAMMA2, MLDSA65_GAMMA2_MINUS_BETA, + MLDSA65_LAMBDA, MLDSA65_LAMBDA_over_4, MLDSA65_OMEGA, MLDSA65_PK_LEN, + MLDSA65_POLY_W1_PACKED_LEN, MLDSA65_POLY_Z_PACKED_LEN, MLDSA65_S1_PACKED_LEN, + MLDSA65_S2_PACKED_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN, MLDSA65_TAU, MLDSA65_k, MLDSA65_l, }; use crate::mldsa::{ - MLDSA87_BETA, MLDSA87_C_TILDE, MLDSA87_ETA, MLDSA87_GAMMA1, MLDSA87_GAMMA1_MINUS_BETA, MLDSA87_GAMMA2_MINUS_BETA, MLDSA87_GAMMA1_MASK_LEN, - MLDSA87_GAMMA2, MLDSA87_LAMBDA, MLDSA87_LAMBDA_over_4, MLDSA87_OMEGA, MLDSA87_PK_LEN, - MLDSA87_POLY_W1_PACKED_LEN, MLDSA87_POLY_Z_PACKED_LEN, - MLDSA87_SIG_LEN, MLDSA87_SK_LEN, MLDSA87_FULL_SK_LEN, MLDSA87_TAU, MLDSA87_S1_PACKED_LEN, MLDSA87_S2_PACKED_LEN, MLDSA87_k, MLDSA87_l, + MLDSA87_BETA, MLDSA87_C_TILDE, MLDSA87_ETA, MLDSA87_FULL_SK_LEN, MLDSA87_GAMMA1, + MLDSA87_GAMMA1_MASK_LEN, MLDSA87_GAMMA1_MINUS_BETA, MLDSA87_GAMMA2, MLDSA87_GAMMA2_MINUS_BETA, + MLDSA87_LAMBDA, MLDSA87_LAMBDA_over_4, MLDSA87_OMEGA, MLDSA87_PK_LEN, + MLDSA87_POLY_W1_PACKED_LEN, MLDSA87_POLY_Z_PACKED_LEN, MLDSA87_S1_PACKED_LEN, + MLDSA87_S2_PACKED_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN, MLDSA87_TAU, MLDSA87_k, MLDSA87_l, }; use crate::mldsa_keys::{MLDSAPrivateKeyInternalTrait, MLDSAPublicKeyInternalTrait}; use crate::{ @@ -91,7 +96,8 @@ use crate::{ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ - Algorithm, Hash, PHSignature, RNG, SecurityStrength, Signature, XOF, + Algorithm, Hash, PHSignatureVerifier, PHSigner, RNG, SecurityStrength, SignatureVerifier, + Signer, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha2::{SHA256, SHA512}; @@ -508,6 +514,42 @@ impl< GAMMA1_MASK_LEN, > { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [Signer] / [SignatureVerifier] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Keys are interchangeable between MLDSA and HashMLDSA. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), SignatureError> { + MLDSA::< + PK_LEN, + SK_LEN, + FULL_SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + >::keygen() + } + /// Imports a secret key from a seed. pub fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError> { MLDSA::< @@ -639,9 +681,9 @@ impl< Ok(bytes_written) } - /// To be used for deterministic signing in conjunction with the [Signature::sign_init], - /// [Signature::sign_update], and [Signature::sign_final] flow. - /// Can be set anywhere after [Signature::sign_init] and before [Signature::sign_final] + /// To be used for deterministic signing in conjunction with the [Signer::sign_init], + /// [Signer::sign_update], and [Signer::sign_final] flow. + /// Can be set anywhere after [Signer::sign_init] and before [Signer::sign_final] pub fn set_signer_rnd(&mut self, rnd: [u8; 32]) { self.signer_rnd = Some(rnd); } @@ -731,7 +773,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> Signature +> Signer for HashMLDSA< HASH, PH_LEN, @@ -763,37 +805,6 @@ impl< GAMMA1_MASK_LEN, > { - /// Keygen, and keys in general, are interchangeable between MLDSA and HashMLDSA. - fn keygen() -> Result<(PK, SK), SignatureError> { - MLDSA::< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::keygen() - } - /// Algorithm 4 HashML-DSA.Sign(π‘ π‘˜, 𝑀 , 𝑐𝑑π‘₯, PH) /// Generate a β€œpre-hash” ML-DSA signature. fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { @@ -849,12 +860,7 @@ impl< if self.sk.is_some() { if self.signer_rnd.is_none() { - Self::sign_ph_out( - &self.sk.unwrap(), - &ph, - Some(&self.ctx[..self.ctx_len]), - output, - ) + Self::sign_ph_out(&self.sk.unwrap(), &ph, Some(&self.ctx[..self.ctx_len]), output) } else { Self::sign_ph_deterministic_out( &self.sk.unwrap(), @@ -875,18 +881,94 @@ impl< // since at this point we need to fully reconstruct SK in order to compute tr for mu anyway // there is no savings to using the fancy MLDSA::sign_from_seed let (_pk, sk) = Self::keygen_from_seed(&self.seed.unwrap())?; - Self::sign_ph_deterministic_out( - &sk, - Some(&self.ctx[..self.ctx_len]), - &ph, - rnd, - output, - ) + Self::sign_ph_deterministic_out(&sk, Some(&self.ctx[..self.ctx_len]), &ph, rnd, output) } else { unreachable!() } } +} +impl< + HASH: Hash + Default, + PK: MLDSAPublicKeyTrait + + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait< + k, + l, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > + MLDSAPrivateKeyInternalTrait< + LAMBDA, + GAMMA2, + k, + l, + ETA, + S1_PACKED_LEN, + S2_PACKED_LEN, + PK_LEN, + SK_LEN, + >, + const PH_LEN: usize, + const oid: &'static [u8], + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const SIG_LEN: usize, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const S1_PACKED_LEN: usize, + const S2_PACKED_LEN: usize, + const T1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, + const GAMMA1_MASK_LEN: usize, +> SignatureVerifier + for HashMLDSA< + HASH, + PH_LEN, + oid, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mut ph_m = [0u8; PH_LEN]; _ = HASH::default().hash_out(msg, &mut ph_m); @@ -971,7 +1053,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> PHSignature +> PHSigner for HashMLDSA< HASH, PH_LEN, @@ -1028,7 +1110,89 @@ impl< HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; Self::sign_ph_deterministic_out(sk, ctx, ph, rnd, output) } +} +impl< + HASH: Hash + Default, + const PH_LEN: usize, + const oid: &'static [u8], + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const SIG_LEN: usize, + PK: MLDSAPublicKeyTrait + + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait< + k, + l, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > + MLDSAPrivateKeyInternalTrait< + LAMBDA, + GAMMA2, + k, + l, + ETA, + S1_PACKED_LEN, + S2_PACKED_LEN, + PK_LEN, + SK_LEN, + >, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const S1_PACKED_LEN: usize, + const S2_PACKED_LEN: usize, + const T1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, + const GAMMA1_MASK_LEN: usize, +> PHSignatureVerifier + for HashMLDSA< + HASH, + PH_LEN, + oid, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify_ph( pk: &PK, ph: &[u8; PH_LEN], diff --git a/crypto/mldsa_lowmemory/src/lib.rs b/crypto/mldsa_lowmemory/src/lib.rs index ccc5a33..3af01a2 100644 --- a/crypto/mldsa_lowmemory/src/lib.rs +++ b/crypto/mldsa_lowmemory/src/lib.rs @@ -128,7 +128,6 @@ //! ## Generating Keys //! //! ```rust -//! use bouncycastle_core::traits::Signature; //! use bouncycastle_mldsa_lowmemory::MLDSA65; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -153,13 +152,13 @@ //! //! See [MLDSATrait] and [MLDSATrait::sign_mu_deterministic_from_seed] for an API flow that uses a merged //! keygen-and-sign function to provide improved speed and memory performance compared with making -//! separate calls to [MLDSATrait::keygen_from_seed] followed by [Signature::sign]. +//! separate calls to [MLDSATrait::keygen_from_seed] followed by [Signer::sign]. //! //! ## Generating and Verifying Signatures //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait}; //! //! let msg = b"The quick brown fox"; @@ -221,7 +220,7 @@ #[allow(unused_imports)] use bouncycastle_core::key_material::KeyMaterialTrait; #[allow(unused_imports)] -use bouncycastle_core::traits::Signature; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; // todo -- re-run mutants diff --git a/crypto/mldsa_lowmemory/src/mldsa.rs b/crypto/mldsa_lowmemory/src/mldsa.rs index a6efa48..1756a8a 100644 --- a/crypto/mldsa_lowmemory/src/mldsa.rs +++ b/crypto/mldsa_lowmemory/src/mldsa.rs @@ -9,7 +9,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -51,7 +51,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -97,7 +97,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, _) = MLDSA65::keygen().unwrap(); @@ -116,7 +116,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, _) = MLDSA65::keygen().unwrap(); @@ -136,7 +136,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -181,7 +181,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait}; //! //! let msg = b"The quick brown fox"; @@ -220,7 +220,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -265,7 +265,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType, KeyMaterialTrait}; //! use bouncycastle_hex as hex; //! use bouncycastle_mldsa_lowmemory::{MLDSA44, MLDSA44_SIG_LEN, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; @@ -324,7 +324,7 @@ use crate::{ }; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, Signature, XOF}; +use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, XOF}; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::{SHAKE128, SHAKE256}; use core::marker::PhantomData; @@ -335,7 +335,7 @@ use crate::hash_mldsa; #[allow(unused_imports)] use bouncycastle_core::key_material::KeyMaterial256; #[allow(unused_imports)] -use bouncycastle_core::traits::PHSignature; +use bouncycastle_core::traits::{PHSignatureVerifier, PHSigner}; /*** Constants ***/ /// @@ -718,6 +718,15 @@ impl< GAMMA1_MASK_LEN, > { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [Signer] / [SignatureVerifier] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), SignatureError> { + Self::keygen_from_os_rng() + } + /// Should still be ok in FIPS mode pub fn keygen_from_os_rng() -> Result<(PK, SK), SignatureError> { let mut seed = KeyMaterial::<32>::new(); @@ -1540,7 +1549,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> Signature +> Signer for MLDSA< PK_LEN, SK_LEN, @@ -1569,10 +1578,6 @@ impl< GAMMA1_MASK_LEN, > { - fn keygen() -> Result<(PK, SK), SignatureError> { - Self::keygen_from_os_rng() - } - fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { let mut out = [0u8; SIG_LEN]; Self::sign_out(sk, msg, ctx, &mut out)?; @@ -1646,7 +1651,83 @@ impl< unreachable!() } } +} +impl< + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const SIG_LEN: usize, + PK: MLDSAPublicKeyTrait + + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait< + k, + l, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > + MLDSAPrivateKeyInternalTrait< + LAMBDA, + GAMMA2, + k, + l, + ETA, + S1_PACKED_LEN, + S2_PACKED_LEN, + PK_LEN, + SK_LEN, + >, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const S1_PACKED_LEN: usize, + const S2_PACKED_LEN: usize, + const T1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, + const GAMMA1_MASK_LEN: usize, +> SignatureVerifier + for MLDSA< + PK_LEN, + SK_LEN, + FULL_SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)?; @@ -1703,7 +1784,7 @@ impl< /// Note: this struct is only exposed for "pure" ML-DSA and not for HashML-DSA because HashML-DSA /// does not benefit from allowing external construction of the message representative mu. /// You can get the same behaviour by computing the pre-hash `ph` with the appropriate hash function -/// and providing that to HashMLDSA via [PHSignature::sign_ph]. +/// and providing that to HashMLDSA via [PHSigner::sign_ph]. pub struct MuBuilder { h: H, } diff --git a/crypto/mldsa_lowmemory/tests/bc_test_data.rs b/crypto/mldsa_lowmemory/tests/bc_test_data.rs index 706c82a..fdc5c56 100644 --- a/crypto/mldsa_lowmemory/tests/bc_test_data.rs +++ b/crypto/mldsa_lowmemory/tests/bc_test_data.rs @@ -18,7 +18,7 @@ mod bc_test_data { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Hash, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + Hash, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, }; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ diff --git a/crypto/mldsa_lowmemory/tests/hash_mldsa_tests.rs b/crypto/mldsa_lowmemory/tests/hash_mldsa_tests.rs index 1380296..d5b1bdf 100644 --- a/crypto/mldsa_lowmemory/tests/hash_mldsa_tests.rs +++ b/crypto/mldsa_lowmemory/tests/hash_mldsa_tests.rs @@ -1,4 +1,4 @@ -use bouncycastle_core::traits::Signature; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; use bouncycastle_hex as hex; #[cfg(test)] @@ -6,7 +6,7 @@ mod hash_mldsa_tests { use super::*; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; - use bouncycastle_core::traits::{Hash, PHSignature}; + use bouncycastle_core::traits::{Hash, PHSignatureVerifier}; use bouncycastle_core_test_framework::signature::TestFrameworkSignature; use bouncycastle_mldsa_lowmemory::{ HashMLDSA44_with_SHA256, HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA256, @@ -24,19 +24,19 @@ mod hash_mldsa_tests { let tf = TestFrameworkSignature::new(false, true); // Test HashML-DSA-SHA512 as a regular signature alg - tf.test_signature::(false); - tf.test_signature::(false); - tf.test_signature::(false); + tf.test_signature::(HashMLDSA44_with_SHA512::keygen, false); + tf.test_signature::(HashMLDSA65_with_SHA512::keygen, false); + tf.test_signature::(HashMLDSA87_with_SHA512::keygen, false); // Test HashML-DSA-SHA256 as a ph signature alg - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); + tf.test_ph_signature::(HashMLDSA44_with_SHA256::keygen, false); + tf.test_ph_signature::(HashMLDSA65_with_SHA256::keygen, false); + tf.test_ph_signature::(HashMLDSA87_with_SHA256::keygen, false); // Test HashML-DSA-SHA512 as a ph signature alg - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); + tf.test_ph_signature::(HashMLDSA44_with_SHA512::keygen, false); + tf.test_ph_signature::(HashMLDSA65_with_SHA512::keygen, false); + tf.test_ph_signature::(HashMLDSA87_with_SHA512::keygen, false); } #[test] diff --git a/crypto/mldsa_lowmemory/tests/mldsa_key_tests.rs b/crypto/mldsa_lowmemory/tests/mldsa_key_tests.rs index 4919fbb..a957079 100644 --- a/crypto/mldsa_lowmemory/tests/mldsa_key_tests.rs +++ b/crypto/mldsa_lowmemory/tests/mldsa_key_tests.rs @@ -6,7 +6,7 @@ mod mldsa_key_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + SecurityStrength, SignaturePrivateKey, SignaturePublicKey, }; use bouncycastle_core_test_framework::signature::TestFrameworkSignatureKeys; use bouncycastle_hex as hex; @@ -24,9 +24,9 @@ mod mldsa_key_tests { fn core_framework_tests() { let tf = TestFrameworkSignatureKeys::new(); - tf.test_keys::(); - tf.test_keys::(); - tf.test_keys::(); + tf.test_keys::(MLDSA44::keygen); + tf.test_keys::(MLDSA65::keygen); + tf.test_keys::(MLDSA87::keygen); } #[test] diff --git a/crypto/mldsa_lowmemory/tests/mldsa_tests.rs b/crypto/mldsa_lowmemory/tests/mldsa_tests.rs index b3eb8c3..e45ed76 100644 --- a/crypto/mldsa_lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa_lowmemory/tests/mldsa_tests.rs @@ -5,7 +5,7 @@ mod mldsa_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - RNG, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::signature::*; @@ -22,9 +22,9 @@ mod mldsa_tests { fn test_framework_signature() { let tf = TestFrameworkSignature::new(false, true); - tf.test_signature::(false); - tf.test_signature::(false); - tf.test_signature::(false); + tf.test_signature::(MLDSA44::keygen, false); + tf.test_signature::(MLDSA65::keygen, false); + tf.test_signature::(MLDSA87::keygen, false); } /// This runs the full bitflipping tests and takes several minutes. diff --git a/crypto/mldsa_lowmemory/tests/wycheproof.rs b/crypto/mldsa_lowmemory/tests/wycheproof.rs index ca9df24..080efdf 100644 --- a/crypto/mldsa_lowmemory/tests/wycheproof.rs +++ b/crypto/mldsa_lowmemory/tests/wycheproof.rs @@ -23,7 +23,7 @@ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{SecurityStrength, Signature, SignaturePublicKey}; +use bouncycastle_core::traits::{SecurityStrength, SignaturePublicKey, SignatureVerifier}; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ MLDSA44, MLDSA44PublicKey, MLDSA65, MLDSA65PublicKey, MLDSA87, MLDSA87PublicKey, diff --git a/mem_usage_benches/bench_mldsa_mem_usage.rs b/mem_usage_benches/bench_mldsa_mem_usage.rs index 6b648fe..92b2c03 100644 --- a/mem_usage_benches/bench_mldsa_mem_usage.rs +++ b/mem_usage_benches/bench_mldsa_mem_usage.rs @@ -21,7 +21,7 @@ #![allow(unused_imports)] use bouncycastle::core::key_material::{KeyMaterial256, KeyType}; -use bouncycastle::core::traits::{Signature, SignaturePrivateKey, SignaturePublicKey}; +use bouncycastle::core::traits::{SignatureVerifier, SignaturePrivateKey, SignaturePublicKey}; use bouncycastle::hex as hex; use bouncycastle::mldsa::MLDSA44_SIG_LEN; From 3cee677e24a5f56ba500cf3183c0c7e8d41d5641 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 10 Jun 2026 13:00:11 -0500 Subject: [PATCH 4/9] Refactored the KEM trait into KEMEncapsuloter and KEMDecapsulator --- cli/src/mlkem_cmd.rs | 2 +- crypto/core-test-framework/src/kem.rs | 63 ++++++++-------- crypto/core/src/traits.rs | 73 +++++++++++-------- crypto/mldsa/src/hash_mldsa.rs | 8 +- crypto/mlkem/benches/mlkem_benches.rs | 2 +- crypto/mlkem/src/lib.rs | 41 ++++++----- crypto/mlkem/src/mlkem.rs | 59 +++++++++++---- crypto/mlkem/tests/bc_test_data.rs | 2 +- crypto/mlkem/tests/mlkem_key_tests.rs | 14 ++-- crypto/mlkem/tests/mlkem_tests.rs | 8 +- crypto/mlkem/tests/wycheproof.rs | 2 +- .../mlkem_lowmemory/benches/mlkem_benches.rs | 2 +- crypto/mlkem_lowmemory/src/lib.rs | 29 ++++---- crypto/mlkem_lowmemory/src/mlkem.rs | 54 +++++++++++--- .../mlkem_lowmemory/tests/mlkem_key_tests.rs | 12 +-- crypto/mlkem_lowmemory/tests/mlkem_tests.rs | 8 +- crypto/mlkem_lowmemory/tests/wycheproof.rs | 2 +- mem_usage_benches/bench_mlkem_mem_usage.rs | 2 +- 18 files changed, 227 insertions(+), 156 deletions(-) diff --git a/cli/src/mlkem_cmd.rs b/cli/src/mlkem_cmd.rs index de56238..da69cea 100644 --- a/cli/src/mlkem_cmd.rs +++ b/cli/src/mlkem_cmd.rs @@ -4,7 +4,7 @@ use std::process::exit; use clap::ValueEnum; use bouncycastle::core::key_material::KeyMaterialTrait; -use bouncycastle::core::traits::{KEMPrivateKey, KEMPublicKey, KEM}; +use bouncycastle::core::traits::{KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey}; use bouncycastle::hex; use bouncycastle::mlkem::{MLKEM512, MLKEMTrait, MLKEM512PrivateKey, MLKEM512_SK_LEN, MLKEM512PublicKey, MLKEM512_PK_LEN, MLKEMPrivateKeyTrait, MLKEM512_CT_LEN, MLKEM768PrivateKey, MLKEM768_SK_LEN, MLKEM768, MLKEM768PublicKey, MLKEM768_PK_LEN, MLKEM1024PrivateKey, MLKEM1024_SK_LEN, MLKEM1024, MLKEM1024_PK_LEN, MLKEM1024PublicKey, MLKEM768_CT_LEN, MLKEM1024_CT_LEN}; use crate::helpers::{parse_seed, read_from_file, read_from_file_or_stdin, write_bytes_or_hex, write_bytes_or_hex_to_file}; diff --git a/crypto/core-test-framework/src/kem.rs b/crypto/core-test-framework/src/kem.rs index 785d499..82f7783 100644 --- a/crypto/core-test-framework/src/kem.rs +++ b/crypto/core-test-framework/src/kem.rs @@ -1,5 +1,5 @@ use bouncycastle_core::errors::KEMError; -use bouncycastle_core::traits::{KEMPrivateKey, KEMPublicKey, KEM}; +use bouncycastle_core::traits::{KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey}; pub struct TestFrameworkKEM { // Put any config options here @@ -16,43 +16,48 @@ impl TestFrameworkKEM { Self { alg_is_deterministic, is_implicitly_rejecting } } - /// Test all the members of trait Hash against the given input-output pair. + /// Test all the members of traits [KEMEncapsulator] and [KEMDecapsulator] against the given input-output pair. /// This gives good baseline test coverage, but is not exhaustive. + /// + /// Since key generation is not part of either KEM trait, the caller supplies a + /// `keygen` function pointer (the inherent `keygen` associated function on the algorithm struct). pub fn test_kem< PK: KEMPublicKey, SK: KEMPrivateKey, - KEMAlg: KEM, + ENCAPSULATOR: KEMEncapsulator, + DECAPSULATOR: KEMDecapsulator, const PK_LEN: usize, const SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, >( &self, + keygen: fn() -> Result<(PK, SK), KEMError>, run_full_bitflipping_tests: bool, ) { // Basic test - let (pk, sk) = KEMAlg::keygen().unwrap(); - let (ss, ct) = KEMAlg::encaps(&pk).unwrap(); - let ss1 = KEMAlg::decaps(&sk, &ct).unwrap(); + let (pk, sk) = keygen().unwrap(); + let (ss, ct) = ENCAPSULATOR::encaps(&pk).unwrap(); + let ss1 = DECAPSULATOR::decaps(&sk, &ct).unwrap(); assert_eq!(ss, ss1); // Test non-determinism if !self.alg_is_deterministic { - let (ss1, ct1) = KEMAlg::encaps(&pk).unwrap(); - let (ss2, ct2) = KEMAlg::encaps(&pk).unwrap(); + let (ss1, ct1) = ENCAPSULATOR::encaps(&pk).unwrap(); + let (ss2, ct2) = ENCAPSULATOR::encaps(&pk).unwrap(); assert_ne!(ss1, ss2); assert_ne!(ct1, ct2); } // Test that decaps fails for broken ct value - let (pk, sk) = KEMAlg::keygen().unwrap(); - let (ss, mut ct) = KEMAlg::encaps(&pk).unwrap(); + let (pk, sk) = keygen().unwrap(); + let (ss, mut ct) = ENCAPSULATOR::encaps(&pk).unwrap(); ct[17] ^= 0xFF; if self.is_implicitly_rejecting { - let ss2 = KEMAlg::decaps(&sk, &ct).unwrap(); + let ss2 = DECAPSULATOR::decaps(&sk, &ct).unwrap(); assert_ne!(ss, ss2); } else { - match KEMAlg::decaps(&sk, &ct) { + match DECAPSULATOR::decaps(&sk, &ct) { Err(KEMError::DecapsulationFailed) => /* good */ (), _ => panic!("This should have thrown an error but it didn't."), } @@ -68,10 +73,10 @@ impl TestFrameworkKEM { // should throw an Err if self.is_implicitly_rejecting { - let ss2 = KEMAlg::decaps(&sk, &ct_copy).unwrap(); + let ss2 = DECAPSULATOR::decaps(&sk, &ct_copy).unwrap(); assert_ne!(ss, ss2); } else { - match KEMAlg::decaps(&sk, &ct) { + match DECAPSULATOR::decaps(&sk, &ct) { Err(KEMError::DecapsulationFailed) => /* good */ (), _ => panic!("This should have thrown an error but it didn't."), } @@ -79,22 +84,22 @@ impl TestFrameworkKEM { } } } - + // test ct the wrong length - let (pk, sk) = KEMAlg::keygen().unwrap(); - let (_ss, ct) = KEMAlg::encaps(&pk).unwrap(); - + let (pk, sk) = keygen().unwrap(); + let (_ss, ct) = ENCAPSULATOR::encaps(&pk).unwrap(); + // too short - match KEMAlg::decaps(&sk, &ct[..CT_LEN-1]) { + match DECAPSULATOR::decaps(&sk, &ct[..CT_LEN-1]) { Err(KEMError::LengthError(_)) => { /* good */ }, _ => panic!("This should have thrown an error but it didn't."), }; - + // too long let mut long_ct = vec![1u8; CT_LEN + 2]; long_ct.as_mut_slice()[..CT_LEN].copy_from_slice(&ct); - match KEMAlg::decaps(&sk, &long_ct) { + match DECAPSULATOR::decaps(&sk, &long_ct) { Err(KEMError::LengthError(_)) => { /* good */ }, _ => panic!("This should have thrown an error but it didn't."), }; @@ -109,29 +114,25 @@ impl TestFrameworkKEMKeys { Self { } } + /// Since key generation is not part of either KEM trait, the caller supplies a + /// `keygen` function pointer (the inherent `keygen` associated function on the algorithm struct). pub fn test_keys< PK: KEMPublicKey, SK: KEMPrivateKey, - KEMAlg: KEM, const PK_LEN: usize, const SK_LEN: usize, - const CT_LEN: usize, - const SS_LEN: usize, - >(&self) { - self.test_boundary_conditions::(); + >(&self, keygen: fn() -> Result<(PK, SK), KEMError>) { + self.test_boundary_conditions::(keygen); } /// Tests the correct behaviour on buffers too large / too small. fn test_boundary_conditions< PK: KEMPublicKey, SK: KEMPrivateKey, - KEMAlg: KEM, const PK_LEN: usize, const SK_LEN: usize, - const CT_LEN: usize, - const SS_LEN: usize, - >(&self) { - let (pk, sk) = KEMAlg::keygen().unwrap(); + >(&self, keygen: fn() -> Result<(PK, SK), KEMError>) { + let (pk, sk) = keygen().unwrap(); let pk_bytes = pk.encode(); assert_eq!(pk_bytes.len(), PK_LEN); diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 1a623ab..492180e 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -178,24 +178,57 @@ pub trait KDF: Default { fn max_security_strength(&self) -> SecurityStrength; } -/// A Key Encapsulation Mechanism -pub trait KEM< +/// A Key Encapsulation Mechanism (KEM) is defined as a set of three operations: +/// key generation, encapsulation, and decapsulation. +/// +/// This trait represents the encapsulation operation performed by the holder of the public key. +/// Decapsulation operations are performed by the corresponding [KEMDecapsulator] trait, and key +/// generation is provided as an inherent associated function directly on the algorithm struct. +/// There are several reasons for this split: first is architectural; some complex algorithms may +/// benefit from having the encapsulation and decapsulation implementations split into separate modules. +/// Second is for compliance: sometimes a policy soft-deprecates an algorithm so that new ciphertexts +/// can no longer be created, but existing ciphertexts can still be decapsulated. Splitting the traits +/// makes this policy easier to enforce. +/// +/// The arrays used to encode public keys, ciphertexts, and shared secrets are statically-sized +/// because this allows us to safely remove runtime checks for array lengths, which overall reduces +/// the fallibility of the library. This design choice could make this trait complicated to apply +/// to a KEM algorithm that does not have fixed sizes for the encodings of these objects. +pub trait KEMEncapsulator< PK: KEMPublicKey, - SK: KEMPrivateKey, const PK_LEN: usize, - const SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, >: Sized { - /// Generate a keypair. - /// Error condition: Basically only on RNG failures - fn keygen() -> Result<(PK, SK), KEMError>; - /// Performs an encapsulation against the given public key. /// Returns the ciphertext and derived shared secret. fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; +} +/// A Key Encapsulation Mechanism (KEM) is defined as a set of three operations: +/// key generation, encapsulation, and decapsulation. +/// +/// This trait represents the decapsulation operation performed by the holder of the private key. +/// Encapsulation operations are performed by the corresponding [KEMEncapsulator] trait, and key +/// generation is provided as an inherent associated function directly on the algorithm struct. +/// There are several reasons for this split: first is architectural; some complex algorithms may +/// benefit from having the encapsulation and decapsulation implementations split into separate modules. +/// Second is for compliance: sometimes a policy soft-deprecates an algorithm so that new ciphertexts +/// can no longer be created, but existing ciphertexts can still be decapsulated. Splitting the traits +/// makes this policy easier to enforce. +/// +/// The arrays used to encode private keys, ciphertexts, and shared secrets are statically-sized +/// because this allows us to safely remove runtime checks for array lengths, which overall reduces +/// the fallibility of the library. This design choice could make this trait complicated to apply +/// to a KEM algorithm that does not have fixed sizes for the encodings of these objects. +pub trait KEMDecapsulator< + SK: KEMPrivateKey, + const SK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, +>: Sized +{ /// Performs a decapsulation of the given ciphertext. /// Returns the derived shared secret. fn decaps(sk: &SK, ct: &[u8]) -> Result, KEMError>; @@ -621,30 +654,6 @@ pub trait SignatureVerifier< fn verify_final(self, sig: &[u8]) -> Result<(), SignatureError>; } -// todo: could the public and private key types impl Into> and From> -// todo: that automatically call the encode and from_bytes() ? - -/// A public key for a signature algorithm, often denoted "pk". -pub trait SignaturePublicKey : PartialEq + Eq + Clone + Debug + Display + Sized { - /// Write it out to bytes in its standard encoding. - fn encode(&self) -> [u8; PK_LEN]; - /// Write it out to bytes in its standard encoding. - fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize; - /// Read it in from bytes in its standard encoding. - fn from_bytes(bytes: &[u8]) -> Result; -} - -/// A private key for a signature algorithm, often denoted "sk" (for "secret key"). -pub trait SignaturePrivateKey : PartialEq + Eq + Clone + Secret + Sized { - /// Write it out to bytes in its standard encoding. - fn encode(&self) -> [u8; SK_LEN]; - /// Write it out to bytes in its standard encoding. - fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize; - /// Read it in from bytes in its standard encoding. - fn from_bytes(bytes: &[u8]) -> Result; -} - - /// Extensible Output Functions (XOFs) are similar to hash functions, except that they can produce output of arbitrary length. /// The naming used for the functions of this trait are borrowed from the SHA3-style sponge constructions that split XOF operation /// into two phases: an absorb phase in which an arbitrary amount of input is provided to the XOF, diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 48442aa..609c4fb 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -958,12 +958,11 @@ impl< } impl< - HASH: Hash + Default, + HASH: Hash + Algorithm + Default, PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -987,7 +986,6 @@ impl< for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, @@ -1126,9 +1124,8 @@ impl< } impl< - HASH: Hash + Default, + HASH: Hash + Algorithm + Default, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -1155,7 +1152,6 @@ impl< for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, diff --git a/crypto/mlkem/benches/mlkem_benches.rs b/crypto/mlkem/benches/mlkem_benches.rs index d6a78da..c8d5a2d 100644 --- a/crypto/mlkem/benches/mlkem_benches.rs +++ b/crypto/mlkem/benches/mlkem_benches.rs @@ -1,7 +1,7 @@ use criterion::{Criterion, criterion_group, criterion_main}; use bouncycastle_core::key_material::{KeyMaterial512, KeyType}; use std::hint::black_box; -use bouncycastle_core::traits::KEM; +use bouncycastle_core::traits::KEMDecapsulator; use bouncycastle_hex as hex; use bouncycastle_mlkem::{MLKEM1024PrivateKeyExpanded, MLKEM512PrivateKeyExpanded, MLKEM768PrivateKeyExpanded, MLKEMPublicKeyTrait, MLKEMTrait, MLKEM1024, MLKEM1024_CT_LEN, MLKEM512, MLKEM512_CT_LEN, MLKEM768, MLKEM768_CT_LEN, MLKEM_RND_LEN}; diff --git a/crypto/mlkem/src/lib.rs b/crypto/mlkem/src/lib.rs index 117627d..28142b6 100644 --- a/crypto/mlkem/src/lib.rs +++ b/crypto/mlkem/src/lib.rs @@ -46,7 +46,6 @@ //! //! ```rust //! use bouncycastle_mlkem::MLKEM768; -//! use bouncycastle_core::traits::KEM; //! //! let (pk, sk) = MLKEM768::keygen().unwrap(); //! ``` @@ -76,7 +75,7 @@ //! //! ```rust //! use bouncycastle_mlkem::{MLKEM768, MLKEMTrait}; -//! use bouncycastle_core::traits::KEM; +//! use bouncycastle_core::traits::{KEMEncapsulator, KEMDecapsulator}; //! use bouncycastle_core::errors::KEMError; //! //! let (pk, sk) = MLKEM768::keygen().unwrap(); @@ -138,25 +137,20 @@ //! constant-time after compilation. #![no_std] - #![forbid(missing_docs)] - #![forbid(unsafe_code)] #![allow(incomplete_features)] // needed because currently generic_const_exprs is experimental #![feature(generic_const_exprs)] #![feature(adt_const_params)] - // These are because I'm matching variable names exactly against FIPS 204, for example both 'K' and 'k', // or 'A' and 'a' are used and have specific meanings. // But need to tell the rust linter to not care. #![allow(non_snake_case)] #![allow(non_upper_case_globals)] - // so I can use private traits to hide internal stuff that needs to be generic within the // MLKEM implementation, but I don't want accessed from outside, such as FIPS-internal functions. #![allow(private_bounds)] - // imports needed just for docs #[allow(unused_imports)] use bouncycastle_core::key_material::KeyMaterialTrait; @@ -165,33 +159,40 @@ use bouncycastle_core::key_material::KeyMaterialTrait; // todo -- crucible tests +mod aux_functions; +mod matrix; pub mod mlkem; mod mlkem_keys; mod polynomial; -mod aux_functions; -mod matrix; - /*** Exported types ***/ -pub use mlkem::{MLKEMTrait, MLKEM, MLKEM512, MLKEM768, MLKEM1024}; +pub use mlkem::{MLKEM, MLKEM512, MLKEM768, MLKEM1024, MLKEMTrait}; +pub use mlkem_keys::{ + MLKEM512PrivateKey, MLKEM768PrivateKey, MLKEM1024PrivateKey, MLKEMPrivateKey, +}; +pub use mlkem_keys::{ + MLKEM512PrivateKeyExpanded, MLKEM768PrivateKeyExpanded, MLKEM1024PrivateKeyExpanded, + MLKEMPrivateKeyExpanded, +}; +pub use mlkem_keys::{MLKEM512PublicKey, MLKEM768PublicKey, MLKEM1024PublicKey, MLKEMPublicKey}; +pub use mlkem_keys::{ + MLKEM512PublicKeyExpanded, MLKEM768PublicKeyExpanded, MLKEM1024PublicKeyExpanded, + MLKEMPublicKeyExpanded, +}; pub use mlkem_keys::{MLKEMPrivateKeyTrait, MLKEMPublicKeyTrait}; -pub use mlkem_keys::{MLKEMPublicKey, MLKEM512PublicKey, MLKEM768PublicKey, MLKEM1024PublicKey}; -pub use mlkem_keys::{MLKEMPublicKeyExpanded, MLKEM512PublicKeyExpanded, MLKEM768PublicKeyExpanded, MLKEM1024PublicKeyExpanded}; -pub use mlkem_keys::{MLKEMPrivateKey, MLKEM512PrivateKey, MLKEM768PrivateKey, MLKEM1024PrivateKey}; -pub use mlkem_keys::{MLKEMPrivateKeyExpanded, MLKEM512PrivateKeyExpanded, MLKEM768PrivateKeyExpanded, MLKEM1024PrivateKeyExpanded}; /*** Exported constants ***/ pub use mlkem::ML_KEM_512_NAME; pub use mlkem::ML_KEM_768_NAME; pub use mlkem::ML_KEM_1024_NAME; -pub use mlkem::{MLKEM_SEED_LEN, MLKEM_SS_LEN, MLKEM_RND_LEN}; +pub use mlkem::{MLKEM_RND_LEN, MLKEM_SEED_LEN, MLKEM_SS_LEN}; -pub use mlkem::{MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512_CT_LEN}; -pub use mlkem::{MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM768_CT_LEN}; -pub use mlkem::{MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM1024_CT_LEN}; +pub use mlkem::{MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM512_SK_LEN}; +pub use mlkem::{MLKEM768_CT_LEN, MLKEM768_PK_LEN, MLKEM768_SK_LEN}; +pub use mlkem::{MLKEM1024_CT_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN}; pub use matrix::Matrix; // re-export just so it's visible to unit tests -pub use polynomial::Polynomial; \ No newline at end of file +pub use polynomial::Polynomial; diff --git a/crypto/mlkem/src/mlkem.rs b/crypto/mlkem/src/mlkem.rs index 70f4352..9e974b9 100644 --- a/crypto/mlkem/src/mlkem.rs +++ b/crypto/mlkem/src/mlkem.rs @@ -30,7 +30,6 @@ //! ```rust //! use bouncycastle_mlkem::{MLKEM768, MLKEMTrait}; //! use bouncycastle_mlkem::{MLKEM768PublicKeyExpanded, MLKEM768PrivateKeyExpanded}; -//! use bouncycastle_core::traits::KEM; //! use bouncycastle_core::errors::KEMError; //! //! let (pk, sk) = MLKEM768::keygen().unwrap(); @@ -60,7 +59,7 @@ //! //! ```rust //! use bouncycastle_mlkem::{MLKEM768, MLKEMTrait}; -//! use bouncycastle_core::traits::KEM; +//! use bouncycastle_core::traits::KEMEncapsulator; //! use bouncycastle_core::errors::KEMError; //! use bouncycastle_core::key_material::{KeyMaterial512, KeyType}; //! use bouncycastle_hex as hex; @@ -104,7 +103,7 @@ //! //! ```rust //! use bouncycastle_mlkem::{MLKEM768, MLKEMTrait}; -//! use bouncycastle_core::traits::{KEM}; +//! use bouncycastle_core::traits::KEMDecapsulator; //! use bouncycastle_core::errors::KEMError; //! use bouncycastle_core::key_material::KeyMaterialTrait; //! @@ -129,7 +128,9 @@ use core::marker::PhantomData; use bouncycastle_core::errors::{KEMError}; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial, KeyType}; -use bouncycastle_core::traits::{RNG, SecurityStrength, XOF, Algorithm, Hash, KEM}; +use bouncycastle_core::traits::{ + Algorithm, Hash, KEMDecapsulator, KEMEncapsulator, RNG, SecurityStrength, XOF, +}; use bouncycastle_rng::{HashDRBG_SHA512}; use bouncycastle_sha3::{SHA3_256, SHA3_512, SHAKE256}; use bouncycastle_utils::ct::{conditional_copy_bytes, ct_eq_bytes}; @@ -329,6 +330,15 @@ impl< LAMBDA, > { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [KEMEncapsulator] / [KEMDecapsulator] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), KEMError> { + Self::keygen_from_os_rng() + } + /// Should still be ok in FIPS mode pub fn keygen_from_os_rng() -> Result< (PK, SK), @@ -544,13 +554,13 @@ impl< /// Alternatively, you can use a [MLKEMPublicKeyExpanded] with [MLKEM::encaps_for_expanded_key]. /// If you specify None, the function will compute A_hat internally and everything will work fine. /// - /// Unlike the more public function exposed by [KEM::encaps], this returns the shared secret as raw bytes + /// Unlike the more public function exposed by [KEMEncapsulator::encaps], this returns the shared secret as raw bytes /// instead of wrapped in an appropriately-set [KeyMaterialTrait], so you're on your own for handling it properly. /// /// Note: this is an internal function that allows the caller to specify the encapsulation /// randomness (which is the message `m` to be encrypted by the underlying PKE scheme). /// This function should not be used directly unless you really have a - /// good reason. [KEM::encaps] should be used in 99.9% of cases. + /// good reason. [KEMEncapsulator::encaps] should be used in 99.9% of cases. /// The reason this is exposed publicly is: A) for unit testing that requires access /// to the deterministically reproducible function, and B) for operational environments /// that wish to provide randomness from their own source instead of the built-in RNG in bc-rust. @@ -876,12 +886,12 @@ pub trait MLKEMTrait< sk: &SK, ) -> Result<(), KEMError>; - /// Same as [KEM::encaps], but acts on an [MLKEMPublicKeyExpanded]. + /// Same as [KEMEncapsulator::encaps], but acts on an [MLKEMPublicKeyExpanded]. fn encaps_for_expanded_key( pk: &MLKEMPublicKeyExpanded ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; - /// Same as [KEM::decaps], but acts on an [MLKEMPrivateKeyExpanded]. + /// Same as [KEMDecapsulator::decaps], but acts on an [MLKEMPrivateKeyExpanded]. fn decaps_with_expanded_key( sk: &MLKEMPrivateKeyExpanded, ct: &[u8], @@ -900,7 +910,7 @@ impl< const du: i16, const dv: i16, const LAMBDA: i16, -> KEM for MLKEM< +> KEMEncapsulator for MLKEM< PK_LEN, SK_LEN, CT_LEN, @@ -913,11 +923,6 @@ impl< dv, LAMBDA, > { - /// Generates a fresh key pair. - fn keygen() -> Result<(PK, SK), KEMError> { - Self::keygen_from_os_rng() - } - /// Performs an encapsulation against the given public key, using the library's default internal RNG. /// Returns (shared_secret_key, ciphertext) /// The derived shared secret key is returned as a KeyMaterial with the SecurityStrength set to @@ -931,7 +936,33 @@ impl< fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { Self::encaps_for_expanded_key(&MLKEMPublicKeyExpanded::::from(pk)) } +} +impl< + const PK_LEN: usize, + const SK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + const k: usize, + const eta: i16, + const du: i16, + const dv: i16, + const LAMBDA: i16, +> KEMDecapsulator for MLKEM< + PK_LEN, + SK_LEN, + CT_LEN, + SS_LEN, + PK, + SK, + k, + eta, + du, + dv, + LAMBDA, +> { /// Performs a decapsulation of the given ciphertext. /// Returns the shared secret key. /// The derived shared secret key is returned as a KeyMaterial with the SecurityStrength set to diff --git a/crypto/mlkem/tests/bc_test_data.rs b/crypto/mlkem/tests/bc_test_data.rs index 5c14a28..036c85c 100644 --- a/crypto/mlkem/tests/bc_test_data.rs +++ b/crypto/mlkem/tests/bc_test_data.rs @@ -5,7 +5,7 @@ #[cfg(test)] mod bc_test_data { use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; - use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength}; + use bouncycastle_core::traits::{KEMDecapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; use bouncycastle_mlkem::{ MLKEM512, MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512PrivateKey, MLKEM512PublicKey, diff --git a/crypto/mlkem/tests/mlkem_key_tests.rs b/crypto/mlkem/tests/mlkem_key_tests.rs index 7317d50..4a4f3de 100644 --- a/crypto/mlkem/tests/mlkem_key_tests.rs +++ b/crypto/mlkem/tests/mlkem_key_tests.rs @@ -2,12 +2,12 @@ mod mlkem_key_tests { use bouncycastle_core::errors::KEMError; use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; - use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength}; + use bouncycastle_core::traits::{KEMPrivateKey, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; use bouncycastle_mlkem::{ - MLKEM_SS_LEN, MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM512_SK_LEN, - MLKEM512PrivateKeyExpanded, MLKEM512PublicKeyExpanded, MLKEM768_CT_LEN, MLKEM768_PK_LEN, - MLKEM768_SK_LEN, MLKEM1024_CT_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, + MLKEM512_PK_LEN, MLKEM512_SK_LEN, + MLKEM512PrivateKeyExpanded, MLKEM512PublicKeyExpanded, MLKEM768_PK_LEN, + MLKEM768_SK_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEMPrivateKeyTrait, MLKEMPublicKeyTrait, MLKEMTrait, }; use bouncycastle_mlkem::{MLKEM512, MLKEM768, MLKEM1024}; @@ -22,9 +22,9 @@ mod mlkem_key_tests { let tf = TestFrameworkKEMKeys::new(); - tf.test_keys::(); - tf.test_keys::(); - tf.test_keys::(); + tf.test_keys::(MLKEM512::keygen); + tf.test_keys::(MLKEM768::keygen); + tf.test_keys::(MLKEM1024::keygen); } #[test] diff --git a/crypto/mlkem/tests/mlkem_tests.rs b/crypto/mlkem/tests/mlkem_tests.rs index 71137fe..fb087f7 100644 --- a/crypto/mlkem/tests/mlkem_tests.rs +++ b/crypto/mlkem/tests/mlkem_tests.rs @@ -3,7 +3,7 @@ mod mlkem_tests { use bouncycastle_core::errors::KEMError; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial512, KeyType}; - use bouncycastle_core::traits::{KEMPrivateKey, KEMPublicKey, SecurityStrength, KEM, XOF}; + use bouncycastle_core::traits::{KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength, XOF}; use bouncycastle_mlkem::{MLKEM512, MLKEM768, MLKEM1024, MLKEM_RND_LEN, Polynomial}; use bouncycastle_mlkem::{MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey, MLKEM1024PrivateKey, MLKEM1024PublicKey}; use bouncycastle_mlkem::{MLKEMPrivateKeyTrait, MLKEMTrait}; @@ -35,9 +35,9 @@ mod mlkem_tests { let tf = TestFrameworkKEM::new(false, true); - tf.test_kem::(false); - tf.test_kem::(false); - tf.test_kem::(false); + tf.test_kem::(MLKEM512::keygen, false); + tf.test_kem::(MLKEM768::keygen, false); + tf.test_kem::(MLKEM1024::keygen, false); } /// This runs the full bitflipping tests and takes about 30s.. diff --git a/crypto/mlkem/tests/wycheproof.rs b/crypto/mlkem/tests/wycheproof.rs index ee3ddc9..a141e63 100644 --- a/crypto/mlkem/tests/wycheproof.rs +++ b/crypto/mlkem/tests/wycheproof.rs @@ -21,7 +21,7 @@ #![allow(dead_code)] use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength}; +use bouncycastle_core::traits::{KEMDecapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; use bouncycastle_mlkem::{ MLKEM512, MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768, MLKEM768PrivateKey, diff --git a/crypto/mlkem_lowmemory/benches/mlkem_benches.rs b/crypto/mlkem_lowmemory/benches/mlkem_benches.rs index a708f3e..277a28d 100644 --- a/crypto/mlkem_lowmemory/benches/mlkem_benches.rs +++ b/crypto/mlkem_lowmemory/benches/mlkem_benches.rs @@ -1,7 +1,7 @@ use criterion::{Criterion, criterion_group, criterion_main}; use bouncycastle_core::key_material::{KeyMaterial512, KeyType}; use std::hint::black_box; -use bouncycastle_core::traits::KEM; +use bouncycastle_core::traits::KEMDecapsulator; use bouncycastle_hex as hex; use bouncycastle_mlkem_lowmemory::{MLKEMTrait, MLKEM1024, MLKEM1024_CT_LEN, MLKEM512, MLKEM512_CT_LEN, MLKEM768, MLKEM768_CT_LEN, MLKEM_RND_LEN}; diff --git a/crypto/mlkem_lowmemory/src/lib.rs b/crypto/mlkem_lowmemory/src/lib.rs index 32b3254..0f70fc5 100644 --- a/crypto/mlkem_lowmemory/src/lib.rs +++ b/crypto/mlkem_lowmemory/src/lib.rs @@ -155,7 +155,6 @@ //! //! ```rust //! use bouncycastle_mlkem_lowmemory::MLKEM768; -//! use bouncycastle_core::traits::KEM; //! //! let (pk, sk) = MLKEM768::keygen().unwrap(); //! ``` @@ -185,7 +184,7 @@ //! //! ```rust //! use bouncycastle_mlkem_lowmemory::{MLKEM768, MLKEMTrait}; -//! use bouncycastle_core::traits::KEM; +//! use bouncycastle_core::traits::{KEMEncapsulator, KEMDecapsulator}; //! use bouncycastle_core::errors::KEMError; //! //! let (pk, sk) = MLKEM768::keygen().unwrap(); @@ -224,20 +223,16 @@ //! constant-time after compilation. #![no_std] - #![forbid(missing_docs)] - #![forbid(unsafe_code)] #![allow(incomplete_features)] // needed because currently generic_const_exprs is experimental #![feature(generic_const_exprs)] #![feature(adt_const_params)] - // These are because I'm matching variable names exactly against FIPS 204, for example both 'K' and 'k', // or 'A' and 'a' are used and have specific meanings. // But need to tell the rust linter to not care. #![allow(non_snake_case)] #![allow(non_upper_case_globals)] - // so I can use private traits to hide internal stuff that needs to be generic within the // MLKEM implementation, but I don't want accessed from outside, such as FIPS-internal functions. #![allow(private_bounds)] @@ -250,28 +245,30 @@ use bouncycastle_core::key_material::KeyMaterialTrait; // todo -- crucible tests +mod aux_functions; +mod low_memory_helpers; pub mod mlkem; mod mlkem_keys; mod polynomial; -mod aux_functions; -mod low_memory_helpers; /*** Exported types ***/ -pub use mlkem::{MLKEMTrait, MLKEM, MLKEM512, MLKEM768, MLKEM1024}; +pub use mlkem::{MLKEM, MLKEM512, MLKEM768, MLKEM1024, MLKEMTrait}; +pub use mlkem_keys::{ + MLKEM512PrivateKey, MLKEM768PrivateKey, MLKEM1024PrivateKey, MLKEMSeedPrivateKey, +}; +pub use mlkem_keys::{MLKEM512PublicKey, MLKEM768PublicKey, MLKEM1024PublicKey, MLKEMPublicKey}; pub use mlkem_keys::{MLKEMPrivateKeyTrait, MLKEMPublicKeyTrait}; -pub use mlkem_keys::{MLKEMPublicKey, MLKEM512PublicKey, MLKEM768PublicKey, MLKEM1024PublicKey}; -pub use mlkem_keys::{MLKEMSeedPrivateKey, MLKEM512PrivateKey, MLKEM768PrivateKey, MLKEM1024PrivateKey}; /*** Exported constants ***/ pub use mlkem::ML_KEM_512_NAME; pub use mlkem::ML_KEM_768_NAME; pub use mlkem::ML_KEM_1024_NAME; -pub use mlkem::{MLKEM_SEED_LEN, MLKEM_SS_LEN, MLKEM_RND_LEN}; +pub use mlkem::{MLKEM_RND_LEN, MLKEM_SEED_LEN, MLKEM_SS_LEN}; -pub use mlkem::{MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512_CT_LEN}; -pub use mlkem::{MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM768_CT_LEN}; -pub use mlkem::{MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM1024_CT_LEN}; +pub use mlkem::{MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM512_SK_LEN}; +pub use mlkem::{MLKEM768_CT_LEN, MLKEM768_PK_LEN, MLKEM768_SK_LEN}; +pub use mlkem::{MLKEM1024_CT_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN}; // re-export just so it's visible to unit tests -pub use polynomial::Polynomial; \ No newline at end of file +pub use polynomial::Polynomial; diff --git a/crypto/mlkem_lowmemory/src/mlkem.rs b/crypto/mlkem_lowmemory/src/mlkem.rs index 13f9c9c..9a32ca8 100644 --- a/crypto/mlkem_lowmemory/src/mlkem.rs +++ b/crypto/mlkem_lowmemory/src/mlkem.rs @@ -4,7 +4,9 @@ use core::marker::PhantomData; use bouncycastle_core::errors::{KEMError}; use bouncycastle_core::key_material::{KeyMaterial, KeyType, KeyMaterialTrait}; -use bouncycastle_core::traits::{RNG, SecurityStrength, XOF, Algorithm, Hash, KEM}; +use bouncycastle_core::traits::{ + Algorithm, Hash, KEMDecapsulator, KEMEncapsulator, RNG, SecurityStrength, XOF, +}; use bouncycastle_rng::{HashDRBG_SHA512}; use bouncycastle_sha3::{SHA3_256, SHA3_512, SHAKE256}; use bouncycastle_utils::ct::{conditional_copy_bytes, ct_eq_bytes}; @@ -232,6 +234,15 @@ impl< T_PACKED_LEN, > { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [KEMEncapsulator] / [KEMDecapsulator] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), KEMError> { + Self::keygen_from_os_rng() + } + /// Should still be ok in FIPS mode pub fn keygen_from_os_rng() -> Result< (PK, SK), @@ -334,13 +345,13 @@ impl< /// Output: shared secret key 𝐾 ∈ 𝔹32 . /// Output: ciphertext 𝑐 ∈ 𝔹32(π‘‘π‘’π‘˜+𝑑𝑣). /// - /// Unlike the more public function exposed by [KEM::encaps], this returns the shared secret as raw bytes + /// Unlike the more public function exposed by [KEMEncapsulator::encaps], this returns the shared secret as raw bytes /// instead of wrapped in an appropriately-set [KeyMaterialTrait], so you're on your own for handling it properly. /// /// Note: this is an internal function that allows the caller to specify the encapsulation /// randomness (which is the message `m` to be encrypted by the underlying PKE scheme). /// This function should not be used directly unless you really have a - /// good reason. [KEM::encaps] should be used in 99.9% of cases. + /// good reason. [KEMEncapsulator::encaps] should be used in 99.9% of cases. /// The reason this is exposed publicly is: A) for unit testing that requires access /// to the deterministically reproducible function, and B) for operational environments /// that wish to provide randomness from their own source instead of the built-in RNG in bc-rust. @@ -644,7 +655,7 @@ impl< const dv: i16, const LAMBDA: i16, const T_PACKED_LEN: usize, -> KEM for MLKEM< +> KEMEncapsulator for MLKEM< PK_LEN, SK_LEN, FULL_SK_LEN, @@ -659,11 +670,6 @@ impl< LAMBDA, T_PACKED_LEN, > { - /// Generates a fresh key pair. - fn keygen() -> Result<(PK, SK), KEMError> { - Self::keygen_from_os_rng() - } - fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { let mut m = [0u8; 32]; HashDRBG_SHA512::new_from_os().next_bytes_out(&mut m)?; @@ -678,7 +684,37 @@ impl< Ok((ss_keymaterial, ct)) } +} +impl< + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + const k: usize, + const eta: i16, + const du: i16, + const dv: i16, + const LAMBDA: i16, + const T_PACKED_LEN: usize, +> KEMDecapsulator for MLKEM< + PK_LEN, + SK_LEN, + FULL_SK_LEN, + CT_LEN, + SS_LEN, + PK, + SK, + k, + eta, + du, + dv, + LAMBDA, + T_PACKED_LEN, +> { fn decaps(sk: &SK, ct: &[u8]) -> Result, KEMError> { if ct.len() != CT_LEN { diff --git a/crypto/mlkem_lowmemory/tests/mlkem_key_tests.rs b/crypto/mlkem_lowmemory/tests/mlkem_key_tests.rs index c7c942b..dc79729 100644 --- a/crypto/mlkem_lowmemory/tests/mlkem_key_tests.rs +++ b/crypto/mlkem_lowmemory/tests/mlkem_key_tests.rs @@ -1,12 +1,12 @@ #[cfg(test)] mod mlkem_key_tests { use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; - use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength}; + use bouncycastle_core::traits::{KEMPrivateKey, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; use bouncycastle_mlkem_lowmemory::mlkem::MLKEM512_FULL_SK_LEN; use bouncycastle_mlkem_lowmemory::{ - MLKEM_SS_LEN, MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM768_CT_LEN, - MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM1024_CT_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, + MLKEM512_PK_LEN, MLKEM512_SK_LEN, + MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, }; use bouncycastle_mlkem_lowmemory::{MLKEM512, MLKEM768, MLKEM1024}; use bouncycastle_mlkem_lowmemory::{ @@ -20,9 +20,9 @@ mod mlkem_key_tests { use bouncycastle_core_test_framework::kem::TestFrameworkKEMKeys; let tf = TestFrameworkKEMKeys::new(); - tf.test_keys::(); - tf.test_keys::(); - tf.test_keys::(); + tf.test_keys::(MLKEM512::keygen); + tf.test_keys::(MLKEM768::keygen); + tf.test_keys::(MLKEM1024::keygen); } #[test] diff --git a/crypto/mlkem_lowmemory/tests/mlkem_tests.rs b/crypto/mlkem_lowmemory/tests/mlkem_tests.rs index 328c90c..e93500d 100644 --- a/crypto/mlkem_lowmemory/tests/mlkem_tests.rs +++ b/crypto/mlkem_lowmemory/tests/mlkem_tests.rs @@ -3,7 +3,7 @@ mod mlkem_tests { use bouncycastle_core::errors::KEMError; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial512, KeyType}; - use bouncycastle_core::traits::{KEMPrivateKey, KEMPublicKey, SecurityStrength, KEM, XOF}; + use bouncycastle_core::traits::{KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength, XOF}; use bouncycastle_mlkem_lowmemory::{MLKEM512, MLKEM768, MLKEM1024, MLKEM_RND_LEN, MLKEM_SEED_LEN, Polynomial}; use bouncycastle_mlkem_lowmemory::{MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey, MLKEM1024PrivateKey, MLKEM1024PublicKey}; use bouncycastle_mlkem_lowmemory::{MLKEMPrivateKeyTrait, MLKEMTrait}; @@ -36,9 +36,9 @@ mod mlkem_tests { let tf = TestFrameworkKEM::new(false, true); - tf.test_kem::(false); - tf.test_kem::(false); - tf.test_kem::(false); + tf.test_kem::(MLKEM512::keygen, false); + tf.test_kem::(MLKEM768::keygen, false); + tf.test_kem::(MLKEM1024::keygen, false); } /// This runs the full bitflipping tests and takes about 1.5 mins.. diff --git a/crypto/mlkem_lowmemory/tests/wycheproof.rs b/crypto/mlkem_lowmemory/tests/wycheproof.rs index b331a3f..f5570c6 100644 --- a/crypto/mlkem_lowmemory/tests/wycheproof.rs +++ b/crypto/mlkem_lowmemory/tests/wycheproof.rs @@ -25,7 +25,7 @@ #![allow(dead_code)] use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{KEM, KEMPublicKey, SecurityStrength}; +use bouncycastle_core::traits::{KEMDecapsulator, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; use bouncycastle_mlkem_lowmemory::{ MLKEM512, MLKEM512PublicKey, MLKEM768, MLKEM768PublicKey, MLKEM1024, MLKEM1024PublicKey, diff --git a/mem_usage_benches/bench_mlkem_mem_usage.rs b/mem_usage_benches/bench_mlkem_mem_usage.rs index 494621f..0da3360 100644 --- a/mem_usage_benches/bench_mlkem_mem_usage.rs +++ b/mem_usage_benches/bench_mlkem_mem_usage.rs @@ -26,7 +26,7 @@ #![allow(unused_imports)] use bouncycastle::core::key_material::{KeyMaterial512, KeyType}; -use bouncycastle::core::traits::{KEMPublicKey, KEM}; +use bouncycastle::core::traits::{KEMDecapsulator, KEMEncapsulator, KEMPublicKey}; use bouncycastle::mlkem::mlkem::{MLKEMTrait}; use bouncycastle::mlkem::{MLKEM1024_CT_LEN, MLKEM1024_PK_LEN, MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM768_CT_LEN, MLKEM768_PK_LEN}; From dac74d0ef5b80c8132c8a302c1f8eb4d261a35ee Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sun, 7 Jun 2026 21:08:36 -0500 Subject: [PATCH 5/9] removed nightly feature from mldsa --- alpha_0.1.2_release_notes.md | 2 ++ crypto/core/src/lib.rs | 1 - crypto/mldsa/src/hash_mldsa.rs | 51 +++++++++++++++++++------------ crypto/mldsa/src/lib.rs | 11 +++---- crypto/rng/src/hash_drbg80090a.rs | 2 ++ crypto/rng/src/lib.rs | 2 -- 6 files changed, 39 insertions(+), 30 deletions(-) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index afb4af1..f4a4bad 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -21,6 +21,8 @@ appropriate. * Probably it makes sense to leave Hex and Base64 as requiring std; ... or maybe add a no_std version that uses fixed-sized blocks? +* Make this build on the stable compiler. IE Remove the rust-toolchain.toml file that builds with nightly. Will require + some refactoring. * Create a cargo feature #[cfg(feature='rng')] and put it around things like keygen that takes an rng so that the build dependency on bouncycastle_rng is optional. * Enhance the default HashDRBG instantiation to take in NIST-compatible CPU jitter entropy? Or not? Maybe this is the diff --git a/crypto/core/src/lib.rs b/crypto/core/src/lib.rs index cb0d8d8..379e54c 100644 --- a/crypto/core/src/lib.rs +++ b/crypto/core/src/lib.rs @@ -3,7 +3,6 @@ // todo -- this is the goal, but first need to remove all the Vec in favour of compile-time array sizing. // #![no_std] -#![feature(adt_const_params)] #![forbid(unsafe_code)] pub mod errors; diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 52fbdc8..8803fc6 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -94,7 +94,7 @@ use bouncycastle_core::traits::{ Algorithm, Hash, PHSignature, RNG, SecurityStrength, Signature, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha2::{SHA256, SHA512}; +use bouncycastle_sha2::{SHA256, SHA256_NAME, SHA512, SHA512_NAME}; use core::marker::PhantomData; // Imports needed only for docs @@ -126,7 +126,6 @@ pub const HASH_ML_DSA_87_WITH_SHA512_NAME: &str = "HashML-DSA-87_with_SHA512"; pub type HashMLDSA44_with_SHA256 = HashMLDSA< SHA256, 32, - SHA256_OID, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_SIG_LEN, @@ -160,7 +159,6 @@ impl Algorithm for HashMLDSA44_with_SHA256 { pub type HashMLDSA65_with_SHA256 = HashMLDSA< SHA256, 32, - SHA256_OID, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_SIG_LEN, @@ -194,7 +192,6 @@ impl Algorithm for HashMLDSA65_with_SHA256 { pub type HashMLDSA87_with_SHA256 = HashMLDSA< SHA256, 32, - SHA256_OID, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_SIG_LEN, @@ -228,7 +225,6 @@ impl Algorithm for HashMLDSA87_with_SHA256 { pub type HashMLDSA44_with_SHA512 = HashMLDSA< SHA512, 64, - SHA512_OID, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_SIG_LEN, @@ -262,7 +258,6 @@ impl Algorithm for HashMLDSA44_with_SHA512 { pub type HashMLDSA65_with_SHA512 = HashMLDSA< SHA512, 64, - SHA512_OID, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_SIG_LEN, @@ -296,7 +291,6 @@ impl Algorithm for HashMLDSA65_with_SHA512 { pub type HashMLDSA87_with_SHA512 = HashMLDSA< SHA512, 64, - SHA512_OID, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_SIG_LEN, @@ -332,9 +326,8 @@ impl Algorithm for HashMLDSA87_with_SHA512 { /// by specifying the hash function to use (in the verifier), and specifying the bytes of the OID to /// to use as its domain separator in constructing the message representative M'. pub struct HashMLDSA< - HASH: Hash + Default, + HASH: Hash + Algorithm + Default, const HASH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -381,9 +374,8 @@ pub struct HashMLDSA< } impl< - HASH: Hash + Default, + HASH: Hash + Algorithm + Default, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -410,7 +402,6 @@ impl< HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, @@ -574,7 +565,20 @@ impl< h.absorb(&[1u8]); h.absorb(&[ctx.len() as u8]); h.absorb(ctx); - h.absorb(oid); + + // this is all statics, so the branch should compile out. + // Really, this should be a generic param of HashMLDSA, but unsized_const_params is currently + // a nightly-only feature. + match HASH::ALG_NAME { + SHA256_NAME => h.absorb(SHA256_OID), + SHA512_NAME => h.absorb(SHA512_OID), + _ => { + return Err(SignatureError::GenericError( + "Unsupported hash algorithm; you need to add it to the switch", + )); + } + }; + h.absorb(ph); let mut mu = [0u8; MLDSA_MU_LEN]; let bytes_written = h.squeeze_out(&mut mu); @@ -697,7 +701,18 @@ impl< h.absorb(&[1u8]); h.absorb(&[ctx.len() as u8]); h.absorb(ctx); - h.absorb(oid); + // this is all statics, so the branch should compile out. + // Really, this should be a generic param of HashMLDSA, but unsized_const_params is currently + // a nightly-only feature. + match HASH::ALG_NAME { + SHA256_NAME => h.absorb(SHA256_OID), + SHA512_NAME => h.absorb(SHA512_OID), + _ => { + return Err(SignatureError::GenericError( + "Unsupported hash algorithm; you need to add it to the switch", + )); + } + }; h.absorb(ph); let mut mu = [0u8; MLDSA_MU_LEN]; _ = h.squeeze_out(&mut mu); @@ -771,12 +786,11 @@ impl< } impl< - HASH: Hash + Default, + HASH: Hash + Algorithm + Default, PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -800,7 +814,6 @@ impl< for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, @@ -981,9 +994,8 @@ impl< } impl< - HASH: Hash + Default, + HASH: Hash + Algorithm + Default, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -1010,7 +1022,6 @@ impl< for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, diff --git a/crypto/mldsa/src/lib.rs b/crypto/mldsa/src/lib.rs index 06e37f9..9c3a2b8 100644 --- a/crypto/mldsa/src/lib.rs +++ b/crypto/mldsa/src/lib.rs @@ -115,9 +115,6 @@ #![no_std] #![forbid(missing_docs)] #![forbid(unsafe_code)] -#![allow(incomplete_features)] // needed because currently generic_const_exprs is experimental -#![feature(generic_const_exprs)] -#![feature(adt_const_params)] // These are because I'm matching variable names exactly against FIPS 204, for example both 'K' and 'k', // or 'A' and 'a' are used and have specific meanings. // But need to tell the rust linter to not care. @@ -127,8 +124,10 @@ // MLDSA implementation, but I don't want accessed from outside, such as FIPS-internal functions. #![allow(private_bounds)] #![allow(private_interfaces)] -// Used in HashMLDSA -#![feature(unsized_const_params)] +// Used in HashMLDSA for oid: &'static [u8] params. +// #![allow(incomplete_features)] // needed because currently unsized_const_params is experimental +// #![feature(adt_const_params)] +// #![feature(unsized_const_params)] // imports needed just for docs #[allow(unused_imports)] @@ -136,8 +135,6 @@ use bouncycastle_core::key_material::KeyMaterialTrait; #[allow(unused_imports)] use bouncycastle_core::traits::Signature; -// todo -- re-run mutants - // todo -- crucible tests mod aux_functions; diff --git a/crypto/rng/src/hash_drbg80090a.rs b/crypto/rng/src/hash_drbg80090a.rs index 431d036..75b4177 100644 --- a/crypto/rng/src/hash_drbg80090a.rs +++ b/crypto/rng/src/hash_drbg80090a.rs @@ -67,6 +67,7 @@ const LARGEST_HASHER_OUTPUT_LEN: usize = 64; #[allow(private_bounds)] /// Implementation of the Hash_DRBG algorithm as specified in NIST SP 800-90Ar1. pub struct HashDRBG80090A { + _phantom: core::marker::PhantomData, // Rust is stupid. What's the point of having a generic parameter if we can't use constants inside it? // state: WorkingState, state: WorkingState, @@ -123,6 +124,7 @@ impl HashDRBG80090A { /// and relies on you to provide a strong seed.** pub fn new_unititialized() -> Self { Self { + _phantom: core::marker::PhantomData, state: WorkingState:: { v: [0u8; LARGEST_HASHER_OUTPUT_LEN], c: [0u8; LARGEST_HASHER_OUTPUT_LEN], diff --git a/crypto/rng/src/lib.rs b/crypto/rng/src/lib.rs index dbe62fb..43a65c1 100644 --- a/crypto/rng/src/lib.rs +++ b/crypto/rng/src/lib.rs @@ -28,8 +28,6 @@ //! cryptographic application. #![forbid(unsafe_code)] -#![allow(incomplete_features)] // Need this because generic_const_exprs is currently experimental. -#![feature(generic_const_exprs)] use crate::hash_drbg80090a::{ HashDRBG80090A, HashDRBG80090AParams_SHA256, HashDRBG80090AParams_SHA512, From 8845134a8c3a7bd0e021bc1e612ba03e9106c2bd Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 9 Jun 2026 18:56:01 -0500 Subject: [PATCH 6/9] adjusted the todo list # Conflicts: # alpha_0.1.2_release_notes.md --- alpha_0.1.2_release_notes.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index f4a4bad..9926d0d 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -54,9 +54,13 @@ # 0.1.2 Features / Changelog -* ML-DSA -* Low-Memory ML-DSA -- runs in about 1/10th of the usual memory (~ 30 kb of stack) with only minor performance impact. +* New algorithms added to crypto/ : + * mldsa (FIPS 204) + * mldsa-lowmemory -- runs in about 1/10th of the usual memory (~ 30 kb of stack) with comparable performance impact. + * mlkem (FIPS 203) + * mlkem-lowmemory -- runs in about 1/4th of the usual memory (~ 12 kb of stack) with comparable performance impact. * All public `*_out(.., out: &mut [u8])` functions now begin by zeroizing the entire output buffer with `.fill(0)`, preventing exposure of stale data in oversized output buffers or on early error returns. * Github issues resolved: - * #2, or whatever \ No newline at end of file + * #6: https://github.com/bcgit/bc-rust/issues/6, thanks to Q. T. Felix (github: @Quant-TheodoreFelix) + * #10: https://github.com/bcgit/bc-rust/issues/10, thanks to Nicola Tuveri (github: @romen) \ No newline at end of file From b9168c94285d11738d36dd52463e8dabbd6df414 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 10 Jun 2026 03:05:55 -0500 Subject: [PATCH 7/9] Split the Signature trait into Signer and SignatureVerifier --- cli/src/mldsa_cmd.rs | 2 +- crypto/core-test-framework/src/signature.rs | 146 +++++----- crypto/core/src/traits.rs | 96 +++++-- .../mldsa-lowmemory/benches/mldsa_benches.rs | 2 +- crypto/mldsa-lowmemory/src/hash_mldsa.rs | 260 +++++++++++++++--- crypto/mldsa-lowmemory/src/lib.rs | 7 +- crypto/mldsa-lowmemory/src/mldsa.rs | 113 ++++++-- crypto/mldsa-lowmemory/tests/bc_test_data.rs | 2 +- .../mldsa-lowmemory/tests/hash_mldsa_tests.rs | 22 +- .../mldsa-lowmemory/tests/mldsa_key_tests.rs | 8 +- crypto/mldsa-lowmemory/tests/mldsa_tests.rs | 8 +- crypto/mldsa-lowmemory/tests/wycheproof.rs | 2 +- crypto/mldsa/benches/mldsa_benches.rs | 2 +- crypto/mldsa/src/hash_mldsa.rs | 208 +++++++++++--- crypto/mldsa/src/lib.rs | 7 +- crypto/mldsa/src/mldsa.rs | 98 +++++-- crypto/mldsa/tests/bc_test_data.rs | 2 +- crypto/mldsa/tests/hash_mldsa_tests.rs | 22 +- crypto/mldsa/tests/mldsa_key_tests.rs | 12 +- crypto/mldsa/tests/mldsa_tests.rs | 8 +- crypto/mldsa/tests/wycheproof.rs | 2 +- mem_usage_benches/bench_mldsa_mem_usage.rs | 2 +- 22 files changed, 764 insertions(+), 267 deletions(-) diff --git a/cli/src/mldsa_cmd.rs b/cli/src/mldsa_cmd.rs index 8a1a4e3..e1b9f74 100644 --- a/cli/src/mldsa_cmd.rs +++ b/cli/src/mldsa_cmd.rs @@ -2,7 +2,7 @@ //! by using generics or macros. I just, haven't ... yet. use crate::helpers::{parse_seed, read_from_file, read_from_file_or_stdin, write_bytes_or_hex}; -use bouncycastle::core::traits::{Signature, SignaturePrivateKey, SignaturePublicKey}; +use bouncycastle::core::traits::{SignatureVerifier, Signer, SignaturePrivateKey, SignaturePublicKey}; use bouncycastle::hex; use bouncycastle::mldsa::{ HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA512, HashMLDSA87_with_SHA512, MLDSA_SEED_LEN, diff --git a/crypto/core-test-framework/src/signature.rs b/crypto/core-test-framework/src/signature.rs index d97dc0c..914ae44 100644 --- a/crypto/core-test-framework/src/signature.rs +++ b/crypto/core-test-framework/src/signature.rs @@ -1,7 +1,8 @@ use crate::DUMMY_SEED_1024; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::traits::{ - Hash, PHSignature, Signature, SignaturePrivateKey, SignaturePublicKey, + Hash, PHSignatureVerifier, PHSigner, SignaturePrivateKey, SignaturePublicKey, + SignatureVerifier, Signer, }; pub struct TestFrameworkSignature { @@ -18,54 +19,59 @@ impl TestFrameworkSignature { Self { alg_is_deterministic, alg_accepts_ctx } } - /// Test all the members of trait Hash against the given input-output pair. + /// Test all the members of traits [Signer] and [SignatureVerifier] against the given input-output pair. /// This gives good baseline test coverage, but is not exhaustive. + /// + /// Since key generation is not part of either signature trait, the caller supplies a + /// `keygen` function pointer (the inherent `keygen` associated function on the algorithm struct). pub fn test_signature< PK: SignaturePublicKey, SK: SignaturePrivateKey, - SigAlg: Signature, + SIGNER: Signer, + VERIFIER: SignatureVerifier, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, >( &self, + keygen: fn() -> Result<(PK, SK), SignatureError>, run_full_bitflipping_tests: bool, ) { let msg = b"The quick brown fox jumped over the lazy dog"; // Basic test - let (pk, sk) = SigAlg::keygen().unwrap(); - let sig_val = SigAlg::sign(&sk, msg, None).unwrap(); - SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); + let (pk, sk) = keygen().unwrap(); + let sig_val = SIGNER::sign(&sk, msg, None).unwrap(); + VERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); // Test non-determinism if !self.alg_is_deterministic { - let sig1 = SigAlg::sign(&sk, msg, None).unwrap(); - let sig2 = SigAlg::sign(&sk, msg, None).unwrap(); + let sig1 = SIGNER::sign(&sk, msg, None).unwrap(); + let sig2 = SIGNER::sign(&sk, msg, None).unwrap(); assert_ne!(sig1, sig2); } // uses ctx // success case - let sig = SigAlg::sign(&sk, msg, Some(b"test with ctx")).unwrap(); - SigAlg::verify(&pk, msg, Some(b"test with ctx"), &sig).unwrap(); + let sig = SIGNER::sign(&sk, msg, Some(b"test with ctx")).unwrap(); + VERIFIER::verify(&pk, msg, Some(b"test with ctx"), &sig).unwrap(); // but it had better produce something different if !self.alg_accepts_ctx { - let sig1 = SigAlg::sign(&sk, msg, None).unwrap(); - let sig2 = SigAlg::sign(&sk, msg, Some(&[0u8; 1])).unwrap(); + let sig1 = SIGNER::sign(&sk, msg, None).unwrap(); + let sig2 = SIGNER::sign(&sk, msg, Some(&[0u8; 1])).unwrap(); assert_ne!(sig1, sig2); } // Test that verification fails for broken signature value - let (pk, sk) = SigAlg::keygen().unwrap(); - let sig_val = SigAlg::sign(&sk, msg, None).unwrap(); + let (pk, sk) = keygen().unwrap(); + let sig_val = SIGNER::sign(&sk, msg, None).unwrap(); // spot-check let mut sig_val_copy = sig_val.clone(); sig_val_copy[8] ^= 0x0F; // should throw an Err - match SigAlg::verify(&pk, msg, None, &sig_val_copy) { + match VERIFIER::verify(&pk, msg, None, &sig_val_copy) { Err(SignatureError::SignatureVerificationFailed) => (), _ => panic!("This should have thrown an error but it didn't."), } @@ -78,7 +84,7 @@ impl TestFrameworkSignature { sig_val_copy[i] ^= 1 << j; // should throw an Err - match SigAlg::verify(&pk, msg, None, &sig_val_copy) { + match VERIFIER::verify(&pk, msg, None, &sig_val_copy) { Err(SignatureError::SignatureVerificationFailed) => (), _ => panic!( "This should have thrown an error but it didn't when byte {i} bit {j} of the signature was flipped" @@ -93,13 +99,13 @@ impl TestFrameworkSignature { // Success case let mut output = [0u8; SIG_LEN]; - let bytes_written = SigAlg::sign_out(&sk, msg, None, &mut output).unwrap(); + let bytes_written = SIGNER::sign_out(&sk, msg, None, &mut output).unwrap(); assert_eq!(bytes_written, SIG_LEN); - SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); + VERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); // test with a large message - let sig = SigAlg::sign(&sk, DUMMY_SEED_1024, None).unwrap(); - SigAlg::verify(&pk, DUMMY_SEED_1024, None, &sig).unwrap(); + let sig = SIGNER::sign(&sk, DUMMY_SEED_1024, None).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED_1024, None, &sig).unwrap(); // Test the streaming signing API // fn sign_init(&mut self, sk: &SK) -> Result<(), SignatureError>; @@ -108,37 +114,37 @@ impl TestFrameworkSignature { // fn sign_final_out(&mut self, msg_chunk: &[u8], ctx: &[u8], output: &mut [u8]) -> Result<(), SignatureError>; // First, test the streaming API with one call to .sign_update - let mut s = SigAlg::sign_init(&sk, Some(b"streaming API")).unwrap(); + let mut s = SIGNER::sign_init(&sk, Some(b"streaming API")).unwrap(); s.sign_update(DUMMY_SEED_1024); let sig_val = s.sign_final().unwrap(); - SigAlg::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); // Then with the message broken into chunks - let mut s = SigAlg::sign_init(&sk, Some(b"streaming API chunked")).unwrap(); + let mut s = SIGNER::sign_init(&sk, Some(b"streaming API chunked")).unwrap(); for msg_chunk in DUMMY_SEED_1024.chunks(100) { s.sign_update(msg_chunk); } let sig_val = s.sign_final().unwrap(); - SigAlg::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API chunked"), &sig_val).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API chunked"), &sig_val).unwrap(); // Test the streaming verification API // one-shot - let sig = SigAlg::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); - let mut v = SigAlg::verify_init(&pk, Some(b"streaming API")).unwrap(); + let sig = SIGNER::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); + let mut v = VERIFIER::verify_init(&pk, Some(b"streaming API")).unwrap(); v.verify_update(DUMMY_SEED_1024); v.verify_final(&sig).unwrap(); // chunked - let sig = SigAlg::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); - let mut v = SigAlg::verify_init(&pk, Some(b"streaming API")).unwrap(); + let sig = SIGNER::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); + let mut v = VERIFIER::verify_init(&pk, Some(b"streaming API")).unwrap(); for msg_chunk in DUMMY_SEED_1024.chunks(100) { v.verify_update(msg_chunk); } v.verify_final(&sig).unwrap(); // failure case for streaming verify - let sig = SigAlg::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); - let mut v = SigAlg::verify_init(&pk, Some(b"streaming API")).unwrap(); + let sig = SIGNER::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); + let mut v = VERIFIER::verify_init(&pk, Some(b"streaming API")).unwrap(); v.verify_update(b"this is the wrong message"); match v.verify_final(&sig) { Err(SignatureError::SignatureVerificationFailed) => (), @@ -146,25 +152,30 @@ impl TestFrameworkSignature { } // test sign_out version of streaming API - let mut s = SigAlg::sign_init(&sk, Some(b"streaming API")).unwrap(); + let mut s = SIGNER::sign_init(&sk, Some(b"streaming API")).unwrap(); s.sign_update(DUMMY_SEED_1024); let mut sig_val = [0u8; SIG_LEN]; let bytes_written = s.sign_final_out(&mut sig_val).unwrap(); assert_eq!(bytes_written, SIG_LEN); - SigAlg::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); // the ::verify API should accept a sig value that's too long and just ignore the extra bytes let mut sig_val_too_long = vec![1u8; SIG_LEN + 2]; sig_val_too_long[..SIG_LEN].copy_from_slice(&sig_val); - SigAlg::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); } - /// Test all the members of trait Hash against the given input-output pair. + /// Test all the members of traits [PHSigner] and [PHSignatureVerifier] against the given input-output pair. /// This gives good baseline test coverage, but is not exhaustive. + /// + /// Since key generation is not part of either signature trait, the caller supplies a + /// `keygen` function pointer (the inherent `keygen` associated function on the algorithm struct). pub fn test_ph_signature< PK: SignaturePublicKey, SK: SignaturePrivateKey, - SigAlg: PHSignature, + // todo split this into two params: SIGNER: Signer and VERIFIER: SignatureVerifier + PHSIGNER: PHSigner, + PHVERIFIER: PHSignatureVerifier, HASH: Hash + Default, const PK_LEN: usize, const SK_LEN: usize, @@ -172,43 +183,44 @@ impl TestFrameworkSignature { const PH_LEN: usize, >( &self, + keygen: fn() -> Result<(PK, SK), SignatureError>, run_full_bitflipping_tests: bool, ) { let msg = b"The quick brown fox jumped over the lazy dog"; // Basic test - let (pk, sk) = SigAlg::keygen().unwrap(); - let sig_val = SigAlg::sign(&sk, msg, None).unwrap(); - SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); + let (pk, sk) = keygen().unwrap(); + let sig_val = PHSIGNER::sign(&sk, msg, None).unwrap(); + PHVERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); // Test non-determinism if !self.alg_is_deterministic { - let sig1 = SigAlg::sign(&sk, msg, None).unwrap(); - let sig2 = SigAlg::sign(&sk, msg, None).unwrap(); + let sig1 = PHSIGNER::sign(&sk, msg, None).unwrap(); + let sig2 = PHSIGNER::sign(&sk, msg, None).unwrap(); assert_ne!(sig1, sig2); } // uses ctx // success case - let sig = SigAlg::sign(&sk, msg, Some(b"test with ctx")).unwrap(); - SigAlg::verify(&pk, msg, Some(b"test with ctx"), &sig).unwrap(); + let sig = PHSIGNER::sign(&sk, msg, Some(b"test with ctx")).unwrap(); + PHVERIFIER::verify(&pk, msg, Some(b"test with ctx"), &sig).unwrap(); // but it had better produce something different if !self.alg_accepts_ctx { - let sig1 = SigAlg::sign(&sk, msg, None).unwrap(); - let sig2 = SigAlg::sign(&sk, msg, Some(&[0u8; 1])).unwrap(); + let sig1 = PHSIGNER::sign(&sk, msg, None).unwrap(); + let sig2 = PHSIGNER::sign(&sk, msg, Some(&[0u8; 1])).unwrap(); assert_ne!(sig1, sig2); } // Test that verification fails for broken signature value - let (pk, sk) = SigAlg::keygen().unwrap(); - let sig_val = SigAlg::sign(&sk, msg, None).unwrap(); + let (pk, sk) = keygen().unwrap(); + let sig_val = PHSIGNER::sign(&sk, msg, None).unwrap(); // spot-check let mut sig_val_copy = sig_val.clone(); sig_val_copy[8] ^= 0x0F; // should throw an Err - match SigAlg::verify(&pk, msg, None, &sig_val_copy) { + match PHVERIFIER::verify(&pk, msg, None, &sig_val_copy) { Err(SignatureError::SignatureVerificationFailed) => (), _ => panic!("This should have thrown an error but it didn't."), } @@ -221,7 +233,7 @@ impl TestFrameworkSignature { sig_val_copy[i] ^= 1 << j; // should throw an Err - match SigAlg::verify(&pk, msg, None, &sig_val_copy) { + match PHVERIFIER::verify(&pk, msg, None, &sig_val_copy) { Err(SignatureError::SignatureVerificationFailed) => (), _ => panic!( "This should have thrown an error but it didn't when byte {i} bit {j} of the signature was flipped" @@ -236,37 +248,37 @@ impl TestFrameworkSignature { // Success case let mut output = [0u8; SIG_LEN]; - let bytes_written = SigAlg::sign_out(&sk, msg, None, &mut output).unwrap(); + let bytes_written = PHSIGNER::sign_out(&sk, msg, None, &mut output).unwrap(); assert_eq!(bytes_written, SIG_LEN); - SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); + PHVERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); // test with a large message - let sig = SigAlg::sign(&sk, DUMMY_SEED_1024, None).unwrap(); - SigAlg::verify(&pk, DUMMY_SEED_1024, None, &sig).unwrap(); + let sig = PHSIGNER::sign(&sk, DUMMY_SEED_1024, None).unwrap(); + PHVERIFIER::verify(&pk, DUMMY_SEED_1024, None, &sig).unwrap(); // the ::verify API should not accept a sig value that's too let mut sig_val_too_long = vec![1u8; SIG_LEN + 2]; sig_val_too_long[..SIG_LEN].copy_from_slice(&sig); - match SigAlg::verify(&pk, DUMMY_SEED_1024, None, &sig_val_too_long) { + match PHVERIFIER::verify(&pk, DUMMY_SEED_1024, None, &sig_val_too_long) { Err(SignatureError::LengthError(_)) => (), _ => panic!("Unexpected error"), } // sign_ph - let (pk, sk) = SigAlg::keygen().unwrap(); + let (pk, sk) = keygen().unwrap(); let ph: [u8; PH_LEN] = HASH::default().hash(msg)[..PH_LEN].try_into().unwrap(); - let sig_val = SigAlg::sign_ph(&sk, &ph, None).unwrap(); - SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); - SigAlg::verify_ph(&pk, &ph, None, &sig_val).unwrap(); + let sig_val = PHSIGNER::sign_ph(&sk, &ph, None).unwrap(); + PHVERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); + PHVERIFIER::verify_ph(&pk, &ph, None, &sig_val).unwrap(); // sign_ph_out - let (pk, sk) = SigAlg::keygen().unwrap(); + let (pk, sk) = keygen().unwrap(); let ph: [u8; PH_LEN] = HASH::default().hash(msg)[..PH_LEN].try_into().unwrap(); let mut sig_val = [0u8; SIG_LEN]; - let bytes_written = SigAlg::sign_ph_out(&sk, &ph, None, &mut sig_val).unwrap(); + let bytes_written = PHSIGNER::sign_ph_out(&sk, &ph, None, &mut sig_val).unwrap(); assert_eq!(bytes_written, SIG_LEN); - SigAlg::verify_ph(&pk, &ph, None, &sig_val).unwrap(); - SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); + PHVERIFIER::verify_ph(&pk, &ph, None, &sig_val).unwrap(); + PHVERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); } } @@ -277,31 +289,31 @@ impl TestFrameworkSignatureKeys { Self {} } + /// Since key generation is not part of either signature trait, the caller supplies a + /// `keygen` function pointer (the inherent `keygen` associated function on the algorithm struct). pub fn test_keys< PK: SignaturePublicKey, SK: SignaturePrivateKey, - SigAlg: Signature, const PK_LEN: usize, const SK_LEN: usize, - const SIG_LEN: usize, >( &self, + keygen: fn() -> Result<(PK, SK), SignatureError>, ) { - self.test_boundary_conditions::(); + self.test_boundary_conditions::(keygen); } /// Tests the correct behaviour on buffers too large / too small. fn test_boundary_conditions< PK: SignaturePublicKey, SK: SignaturePrivateKey, - SigAlg: Signature, const PK_LEN: usize, const SK_LEN: usize, - const SIG_LEN: usize, >( &self, + keygen: fn() -> Result<(PK, SK), SignatureError>, ) { - let (pk, sk) = SigAlg::keygen().unwrap(); + let (pk, sk) = keygen().unwrap(); let pk_bytes = pk.encode(); assert_eq!(pk_bytes.len(), PK_LEN); diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 089e282..21894a2 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -418,21 +418,22 @@ pub trait RNG: Default { /// A trait that forces an object to implement a zeroizing Drop() as well as Debug and Display that /// will not log the sensitive contents, even in error or crash-dump scenarios. -#[allow(drop_bounds)] // Since rust auto-implements Drop, there's a lint that explicitly bounding on Drop is useless. +// Since rust auto-implements Drop, there's a lint that explicitly bounding on Drop is useless. // I disagree because I want to force things that are secrets to manually implement Drop that zeroizes the data. // So I'm turning off this lint. +#[allow(drop_bounds)] pub trait Secret: Drop + Debug + Display {} -/// Pre-Hashed Signature is an extension to [Signature] that adds functionality specific to signature +/// Pre-Hashed Signer is an extension to [Signer] that adds functionality specific to signature /// primatives that can operate on a pre-hashed message instead of the full message. -pub trait PHSignature< +pub trait PHSigner< PK: SignaturePublicKey, SK: SignaturePrivateKey, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, const PH_LEN: usize, ->: Signature +>: Signer { /// Produce a signature for the provided pre-hashed message and context. /// @@ -471,6 +472,17 @@ pub trait PHSignature< ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN], ) -> Result; +} + +/// Pre-Hashed Signature Verifier is an extension to [SignatureVerifier] that adds functionality specific to signature +/// primatives that can operate on a pre-hashed message instead of the full message. +pub trait PHSignatureVerifier< + PK: SignaturePublicKey, + const PK_LEN: usize, + const SIG_LEN: usize, + const PH_LEN: usize, +>: SignatureVerifier +{ /// On success, returns Ok(()) /// On failure, returns Err([SignatureError::SignatureVerificationFailed]); may also return other types of [SignatureError] as appropriate (such as for invalid-length inputs). fn verify_ph( @@ -481,33 +493,59 @@ pub trait PHSignature< ) -> Result<(), SignatureError>; } +// todo: could the public and private key types impl Into> and From> +// todo: that automatically call the encode and from_bytes() ? + +/// A public key for a signature algorithm, often denoted "pk". +pub trait SignaturePublicKey: + PartialEq + Eq + Clone + Debug + Display + Sized +{ + /// Write it out to bytes in its standard encoding. + fn encode(&self) -> [u8; PK_LEN]; + /// Write it out to bytes in its standard encoding. + /// The entire output buffer is zeroized before the encoding is written. + fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize; + /// Read it in from bytes in its standard encoding. + fn from_bytes(bytes: &[u8]) -> Result; +} + +/// A private key for a signature algorithm, often denoted "sk" (for "secret key"). +pub trait SignaturePrivateKey: + PartialEq + Eq + Clone + Secret + Sized +{ + /// Write it out to bytes in its standard encoding. + fn encode(&self) -> [u8; SK_LEN]; + /// Write it out to bytes in its standard encoding. + /// The entire output buffer is zeroized before the encoding is written. + fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize; + /// Read it in from bytes in its standard encoding. + fn from_bytes(bytes: &[u8]) -> Result; +} + /// A digital signature algorithm is defined as a set of three operations: /// key generation, signing, and verification. /// -/// To avoid the use of dyn, this trait does not include key generation; you'll have to consult the -/// documentation for the underlying signature primitive for how to generate a key pair. +/// This trait represents the operations performed by the holder of the signing private key: +/// which include signing and key generation. Verification operations are performed by the corresponding +/// [SignatureVerifier] trait. +/// There are several reasons for this split: first is architectural; some complex algorithms may +/// benefit from having the signature generation and verification implementations split into separate modules. +/// Second is for compliance: sometimes a policy soft-deprecates an algorithm so that new signatures +/// can no longer be created, but existing signatures can still be verified. Splitting the traits +/// makes this policy easier to enforce. /// /// This high-level trait defines the operations over a generic signature algorithm that is assumed /// to source all its randomness from bouncycastle's default os-backed RNG. /// The underlying signature primitives will expose APIs that allow for specifying a specific RNG /// or deterministic seed values. /// -/// Here we statically-size the arrays used to encode public keys, private keys, and signature values +/// The arrays used to encode public keys, private keys, and signature values are statically-sized /// because this allows us to safely remove runtime checks for array lengths, which overall reduces /// the fallibility of the library. This design choice could make this trait complicated to apply /// to a signature algorithm that do not have fixed sizes for the encodings of these objects. -pub trait Signature< - PK: SignaturePublicKey, - SK: SignaturePrivateKey, - const PK_LEN: usize, - const SK_LEN: usize, - const SIG_LEN: usize, ->: Sized +pub trait Signer, const SK_LEN: usize, const SIG_LEN: usize>: + Sized { - /// Generate a keypair. - /// Error condition: Basically only on RNG failures - fn keygen() -> Result<(PK, SK), SignatureError>; - /// Produce a signature for the provided message and context. /// Both the `msg` and `ctx` accept zero-length byte arrays. /// @@ -557,7 +595,29 @@ pub trait Signature< /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. /// The entire output buffer is zeroized before the signature is written. fn sign_final_out(self, output: &mut [u8; SIG_LEN]) -> Result; +} +/// A digital signature algorithm is defined as a set of three operations: +/// key generation, signing, and verification. +/// +/// This trait represents the verification operations performed by the holder of the verification public key. +/// Keygen and signing operations are performed by the corresponding [Signer] trait. +/// There are several reasons for this split: first is architectural; some complex algorithms may +/// benefit from having the signature generation and verification implementations split into separate modules. +/// Second is for compliance: sometimes a policy soft-deprecates an algorithm so that new signatures +/// can no longer be created, but existing signatures can still be verified. Splitting the traits +/// makes this policy easier to enforce. +/// +/// Here we statically-size the arrays used to encode public keys, private keys, and signature values +/// because this allows us to safely remove runtime checks for array lengths, which overall reduces +/// the fallibility of the library. This design choice could make this trait complicated to apply +/// to a signature algorithm that do not have fixed sizes for the encodings of these objects. +pub trait SignatureVerifier< + PK: SignaturePublicKey, + const PK_LEN: usize, + const SIG_LEN: usize, +>: Sized +{ /// On success, returns Ok(()) /// On failure, returns Err([SignatureError::SignatureVerificationFailed]); may also return other types of [SignatureError] as appropriate (such as for invalid-length inputs). fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError>; diff --git a/crypto/mldsa-lowmemory/benches/mldsa_benches.rs b/crypto/mldsa-lowmemory/benches/mldsa_benches.rs index ec12a86..6927d0d 100644 --- a/crypto/mldsa-lowmemory/benches/mldsa_benches.rs +++ b/crypto/mldsa-lowmemory/benches/mldsa_benches.rs @@ -1,5 +1,5 @@ use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -use bouncycastle_core::traits::Signature; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ MLDSA44, MLDSA44_SIG_LEN, MLDSA65, MLDSA65_SIG_LEN, MLDSA87, MLDSA87_SIG_LEN, MLDSATrait, diff --git a/crypto/mldsa-lowmemory/src/hash_mldsa.rs b/crypto/mldsa-lowmemory/src/hash_mldsa.rs index ec0978f..1c9f9e8 100644 --- a/crypto/mldsa-lowmemory/src/hash_mldsa.rs +++ b/crypto/mldsa-lowmemory/src/hash_mldsa.rs @@ -3,11 +3,11 @@ //! mode of [MLDSA]; possibly because you have to digest the message before you know which public key //! will sign it. //! -//! HashML-DSA is a full signature algorithm implementing the [Signature] trait: +//! HashML-DSA is a full signature algorithm implementing the [Signer] and [SignatureVerifier] traits: //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSATrait, HashMLDSA65_with_SHA512, HashMLDSA44_with_SHA512}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -24,11 +24,13 @@ //! } //! ``` //! -//! But you also have access to the pre-hashed function available from [PHSignature]: +//! But you also have access to the pre-hashed function available from [PHSigner] and [PHSignatureVerifier]: //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::{Signature, PHSignature, Hash}; +//! use bouncycastle_core::traits::{ +//! Hash, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, +//! }; //! use bouncycastle_sha2::SHA512; //! use bouncycastle_mldsa_lowmemory::{MLDSATrait, HashMLDSA65_with_SHA512, HashMLDSA44_with_SHA512}; //! @@ -44,14 +46,14 @@ //! let sig = HashMLDSA65_with_SHA512::sign_ph(&sk, &ph, None).unwrap(); //! // This is the signature value that you can save to a file or whatever you need. //! -//! // This verifies either through the usual one-shot API of the [Signature] trait +//! // This verifies either through the usual one-shot API of the [SignatureVerifier] trait //! match HashMLDSA65_with_SHA512::verify(&pk, msg, None, &sig) { //! Ok(()) => println!("Signature is valid!"), //! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), //! Err(e) => panic!("Something else went wrong: {:?}", e), //! } //! -//! // Or though the verify_ph of the [PHSignature] trait +//! // Or though the verify_ph of the [PHSignatureVerifier] trait //! match HashMLDSA65_with_SHA512::verify_ph(&pk, &ph, None, &sig) { //! Ok(()) => println!("Signature is valid!"), //! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), @@ -61,7 +63,7 @@ //! //! Note that the [HashMLDSA] object is just a light wrapper around [MLDSA], and, for example, they share key types, //! so if you need the fancy keygen functions, just use them from [MLDSA]. -//! But a simple [HashMLDSA::keygen] is provided in order to have conformance to the [Signature] trait. +//! But a simple [HashMLDSA::keygen] is provided. use crate::mldsa::{H, MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSATrait}; use crate::mldsa::{ @@ -94,7 +96,8 @@ use crate::{ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ - Algorithm, Hash, PHSignature, RNG, SecurityStrength, Signature, XOF, + Algorithm, Hash, PHSignatureVerifier, PHSigner, RNG, SecurityStrength, SignatureVerifier, + Signer, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha2::{SHA256, SHA512}; @@ -511,6 +514,42 @@ impl< GAMMA1_MASK_LEN, > { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [Signer] / [SignatureVerifier] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Keys are interchangeable between MLDSA and HashMLDSA. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), SignatureError> { + MLDSA::< + PK_LEN, + SK_LEN, + FULL_SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + >::keygen() + } + /// Imports a secret key from a seed. pub fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError> { MLDSA::< @@ -644,9 +683,9 @@ impl< Ok(bytes_written) } - /// To be used for deterministic signing in conjunction with the [Signature::sign_init], - /// [Signature::sign_update], and [Signature::sign_final] flow. - /// Can be set anywhere after [Signature::sign_init] and before [Signature::sign_final] + /// To be used for deterministic signing in conjunction with the [Signer::sign_init], + /// [Signer::sign_update], and [Signer::sign_final] flow. + /// Can be set anywhere after [Signer::sign_init] and before [Signer::sign_final] pub fn set_signer_rnd(&mut self, rnd: [u8; 32]) { self.signer_rnd = Some(rnd); } @@ -736,7 +775,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> Signature +> Signer for HashMLDSA< HASH, PH_LEN, @@ -768,37 +807,6 @@ impl< GAMMA1_MASK_LEN, > { - /// Keygen, and keys in general, are interchangeable between MLDSA and HashMLDSA. - fn keygen() -> Result<(PK, SK), SignatureError> { - MLDSA::< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::keygen() - } - /// Algorithm 4 HashML-DSA.Sign(π‘ π‘˜, 𝑀 , 𝑐𝑑π‘₯, PH) /// Generate a β€œpre-hash” ML-DSA signature. fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { @@ -884,7 +892,89 @@ impl< unreachable!() } } +} +impl< + HASH: Hash + Default, + PK: MLDSAPublicKeyTrait + + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait< + k, + l, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > + MLDSAPrivateKeyInternalTrait< + LAMBDA, + GAMMA2, + k, + l, + ETA, + S1_PACKED_LEN, + S2_PACKED_LEN, + PK_LEN, + SK_LEN, + >, + const PH_LEN: usize, + const oid: &'static [u8], + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const SIG_LEN: usize, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const S1_PACKED_LEN: usize, + const S2_PACKED_LEN: usize, + const T1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, + const GAMMA1_MASK_LEN: usize, +> SignatureVerifier + for HashMLDSA< + HASH, + PH_LEN, + oid, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mut ph_m = [0u8; PH_LEN]; _ = HASH::default().hash_out(msg, &mut ph_m); @@ -969,7 +1059,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> PHSignature +> PHSigner for HashMLDSA< HASH, PH_LEN, @@ -1028,7 +1118,89 @@ impl< HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; Self::sign_ph_deterministic_out(sk, ctx, ph, rnd, output) } +} +impl< + HASH: Hash + Default, + const PH_LEN: usize, + const oid: &'static [u8], + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const SIG_LEN: usize, + PK: MLDSAPublicKeyTrait + + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait< + k, + l, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > + MLDSAPrivateKeyInternalTrait< + LAMBDA, + GAMMA2, + k, + l, + ETA, + S1_PACKED_LEN, + S2_PACKED_LEN, + PK_LEN, + SK_LEN, + >, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const S1_PACKED_LEN: usize, + const S2_PACKED_LEN: usize, + const T1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, + const GAMMA1_MASK_LEN: usize, +> PHSignatureVerifier + for HashMLDSA< + HASH, + PH_LEN, + oid, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify_ph( pk: &PK, ph: &[u8; PH_LEN], diff --git a/crypto/mldsa-lowmemory/src/lib.rs b/crypto/mldsa-lowmemory/src/lib.rs index 4463d77..a03106a 100644 --- a/crypto/mldsa-lowmemory/src/lib.rs +++ b/crypto/mldsa-lowmemory/src/lib.rs @@ -128,7 +128,6 @@ //! ## Generating Keys //! //! ```rust -//! use bouncycastle_core::traits::Signature; //! use bouncycastle_mldsa_lowmemory::MLDSA65; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -153,13 +152,13 @@ //! //! See [MLDSATrait] and [MLDSATrait::sign_mu_deterministic_from_seed] for an API flow that uses a merged //! keygen-and-sign function to provide improved speed and memory performance compared with making -//! separate calls to [MLDSATrait::keygen_from_seed] followed by [Signature::sign]. +//! separate calls to [MLDSATrait::keygen_from_seed] followed by [Signer::sign]. //! //! ## Generating and Verifying Signatures //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait}; //! //! let msg = b"The quick brown fox"; @@ -221,7 +220,7 @@ #[allow(unused_imports)] use bouncycastle_core::key_material::KeyMaterialTrait; #[allow(unused_imports)] -use bouncycastle_core::traits::Signature; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; // todo -- re-run mutants diff --git a/crypto/mldsa-lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs index dc6ae2e..5768e3f 100644 --- a/crypto/mldsa-lowmemory/src/mldsa.rs +++ b/crypto/mldsa-lowmemory/src/mldsa.rs @@ -9,7 +9,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -51,7 +51,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -97,7 +97,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, _) = MLDSA65::keygen().unwrap(); @@ -116,7 +116,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, _) = MLDSA65::keygen().unwrap(); @@ -136,7 +136,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -181,7 +181,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait}; //! //! let msg = b"The quick brown fox"; @@ -220,7 +220,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -265,7 +265,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType, KeyMaterialTrait}; //! use bouncycastle_hex as hex; //! use bouncycastle_mldsa_lowmemory::{MLDSA44, MLDSA44_SIG_LEN, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; @@ -324,7 +324,7 @@ use crate::{ }; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, Signature, XOF}; +use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, XOF}; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::{SHAKE128, SHAKE256}; use core::marker::PhantomData; @@ -335,7 +335,7 @@ use crate::hash_mldsa; #[allow(unused_imports)] use bouncycastle_core::key_material::KeyMaterial256; #[allow(unused_imports)] -use bouncycastle_core::traits::PHSignature; +use bouncycastle_core::traits::{PHSignatureVerifier, PHSigner}; /*** Constants ***/ /// @@ -718,6 +718,15 @@ impl< GAMMA1_MASK_LEN, > { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [Signer] / [SignatureVerifier] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), SignatureError> { + Self::keygen_from_os_rng() + } + /// Should still be ok in FIPS mode pub fn keygen_from_os_rng() -> Result<(PK, SK), SignatureError> { let mut seed = KeyMaterial::<32>::new(); @@ -1544,7 +1553,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> Signature +> Signer for MLDSA< PK_LEN, SK_LEN, @@ -1573,10 +1582,6 @@ impl< GAMMA1_MASK_LEN, > { - fn keygen() -> Result<(PK, SK), SignatureError> { - Self::keygen_from_os_rng() - } - fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { let mut out = [0u8; SIG_LEN]; Self::sign_out(sk, msg, ctx, &mut out)?; @@ -1654,7 +1659,83 @@ impl< unreachable!() } } +} +impl< + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const SIG_LEN: usize, + PK: MLDSAPublicKeyTrait + + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait< + k, + l, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > + MLDSAPrivateKeyInternalTrait< + LAMBDA, + GAMMA2, + k, + l, + ETA, + S1_PACKED_LEN, + S2_PACKED_LEN, + PK_LEN, + SK_LEN, + >, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const S1_PACKED_LEN: usize, + const S2_PACKED_LEN: usize, + const T1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, + const GAMMA1_MASK_LEN: usize, +> SignatureVerifier + for MLDSA< + PK_LEN, + SK_LEN, + FULL_SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)?; @@ -1711,7 +1792,7 @@ impl< /// Note: this struct is only exposed for "pure" ML-DSA and not for HashML-DSA because HashML-DSA /// does not benefit from allowing external construction of the message representative mu. /// You can get the same behaviour by computing the pre-hash `ph` with the appropriate hash function -/// and providing that to HashMLDSA via [PHSignature::sign_ph]. +/// and providing that to HashMLDSA via [PHSigner::sign_ph]. pub struct MuBuilder { h: H, } diff --git a/crypto/mldsa-lowmemory/tests/bc_test_data.rs b/crypto/mldsa-lowmemory/tests/bc_test_data.rs index 706c82a..fdc5c56 100644 --- a/crypto/mldsa-lowmemory/tests/bc_test_data.rs +++ b/crypto/mldsa-lowmemory/tests/bc_test_data.rs @@ -18,7 +18,7 @@ mod bc_test_data { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Hash, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + Hash, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, }; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ diff --git a/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs index 1380296..d5b1bdf 100644 --- a/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs @@ -1,4 +1,4 @@ -use bouncycastle_core::traits::Signature; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; use bouncycastle_hex as hex; #[cfg(test)] @@ -6,7 +6,7 @@ mod hash_mldsa_tests { use super::*; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; - use bouncycastle_core::traits::{Hash, PHSignature}; + use bouncycastle_core::traits::{Hash, PHSignatureVerifier}; use bouncycastle_core_test_framework::signature::TestFrameworkSignature; use bouncycastle_mldsa_lowmemory::{ HashMLDSA44_with_SHA256, HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA256, @@ -24,19 +24,19 @@ mod hash_mldsa_tests { let tf = TestFrameworkSignature::new(false, true); // Test HashML-DSA-SHA512 as a regular signature alg - tf.test_signature::(false); - tf.test_signature::(false); - tf.test_signature::(false); + tf.test_signature::(HashMLDSA44_with_SHA512::keygen, false); + tf.test_signature::(HashMLDSA65_with_SHA512::keygen, false); + tf.test_signature::(HashMLDSA87_with_SHA512::keygen, false); // Test HashML-DSA-SHA256 as a ph signature alg - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); + tf.test_ph_signature::(HashMLDSA44_with_SHA256::keygen, false); + tf.test_ph_signature::(HashMLDSA65_with_SHA256::keygen, false); + tf.test_ph_signature::(HashMLDSA87_with_SHA256::keygen, false); // Test HashML-DSA-SHA512 as a ph signature alg - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); + tf.test_ph_signature::(HashMLDSA44_with_SHA512::keygen, false); + tf.test_ph_signature::(HashMLDSA65_with_SHA512::keygen, false); + tf.test_ph_signature::(HashMLDSA87_with_SHA512::keygen, false); } #[test] diff --git a/crypto/mldsa-lowmemory/tests/mldsa_key_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_key_tests.rs index b224f9b..f5ab896 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_key_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_key_tests.rs @@ -6,7 +6,7 @@ mod mldsa_key_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + SecurityStrength, SignaturePrivateKey, SignaturePublicKey, }; use bouncycastle_core_test_framework::signature::TestFrameworkSignatureKeys; use bouncycastle_hex as hex; @@ -24,9 +24,9 @@ mod mldsa_key_tests { fn core_framework_tests() { let tf = TestFrameworkSignatureKeys::new(); - tf.test_keys::(); - tf.test_keys::(); - tf.test_keys::(); + tf.test_keys::(MLDSA44::keygen); + tf.test_keys::(MLDSA65::keygen); + tf.test_keys::(MLDSA87::keygen); } #[test] diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index b3eb8c3..e45ed76 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -5,7 +5,7 @@ mod mldsa_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - RNG, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::signature::*; @@ -22,9 +22,9 @@ mod mldsa_tests { fn test_framework_signature() { let tf = TestFrameworkSignature::new(false, true); - tf.test_signature::(false); - tf.test_signature::(false); - tf.test_signature::(false); + tf.test_signature::(MLDSA44::keygen, false); + tf.test_signature::(MLDSA65::keygen, false); + tf.test_signature::(MLDSA87::keygen, false); } /// This runs the full bitflipping tests and takes several minutes. diff --git a/crypto/mldsa-lowmemory/tests/wycheproof.rs b/crypto/mldsa-lowmemory/tests/wycheproof.rs index ca9df24..080efdf 100644 --- a/crypto/mldsa-lowmemory/tests/wycheproof.rs +++ b/crypto/mldsa-lowmemory/tests/wycheproof.rs @@ -23,7 +23,7 @@ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{SecurityStrength, Signature, SignaturePublicKey}; +use bouncycastle_core::traits::{SecurityStrength, SignaturePublicKey, SignatureVerifier}; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ MLDSA44, MLDSA44PublicKey, MLDSA65, MLDSA65PublicKey, MLDSA87, MLDSA87PublicKey, diff --git a/crypto/mldsa/benches/mldsa_benches.rs b/crypto/mldsa/benches/mldsa_benches.rs index aa8f055..87be0fe 100644 --- a/crypto/mldsa/benches/mldsa_benches.rs +++ b/crypto/mldsa/benches/mldsa_benches.rs @@ -1,5 +1,5 @@ use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -use bouncycastle_core::traits::Signature; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ MLDSA44, MLDSA44_SIG_LEN, MLDSA44PrivateKeyExpanded, MLDSA44PublicKeyExpanded, MLDSA65, diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 8803fc6..9a4dc3d 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -3,12 +3,12 @@ //! mode of [MLDSA]; possibly because you have to digest the message before you know which public key //! will sign it. //! -//! HashML-DSA is a full signature algorithm implementing the [Signature] trait: +//! HashML-DSA is a full signature algorithm implementing the [Signer] and [SignatureVerifier] traits: //! //! ```rust //! use bouncycastle_core::errors::SignatureError; //! use bouncycastle_mldsa::{HashMLDSA65_with_SHA512, MLDSATrait, HashMLDSA44_with_SHA512}; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; //! @@ -24,12 +24,14 @@ //! } //! ``` //! -//! But you also have access to the pre-hashed function available from [PHSignature]: +//! But you also have access to the pre-hashed functions available from [PHSigner] and [PHVerifier]: //! //! ```rust //! use bouncycastle_core::errors::SignatureError; //! use bouncycastle_mldsa::{HashMLDSA65_with_SHA512, MLDSATrait, HashMLDSA44_with_SHA512}; -//! use bouncycastle_core::traits::{Signature, PHSignature, Hash}; +//! use bouncycastle_core::traits::{ +//! Hash, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, +//! }; //! use bouncycastle_sha2::SHA512; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -44,14 +46,14 @@ //! let sig = HashMLDSA65_with_SHA512::sign_ph(&sk, &ph, None).unwrap(); //! // This is the signature value that you can save to a file or whatever you need. //! -//! // This verifies either through the usual one-shot API of the [Signature] trait +//! // This verifies either through the usual one-shot API of the [SignatureVerifier] trait //! match HashMLDSA65_with_SHA512::verify(&pk, msg, None, &sig) { //! Ok(()) => println!("Signature is valid!"), //! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), //! Err(e) => panic!("Something else went wrong: {:?}", e), //! } //! -//! // Or though the verify_ph of the [PHSignature] trait +//! // Or though the verify_ph of the [PHSignatureVerifier] trait //! match HashMLDSA65_with_SHA512::verify_ph(&pk, &ph, None, &sig) { //! Ok(()) => println!("Signature is valid!"), //! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), @@ -61,7 +63,7 @@ //! //! Note that the [HashMLDSA] object is just a light wrapper around [MLDSA], and, for example, they share key types, //! so if you need the fancy keygen functions, just use them from [MLDSA]. -//! But a simple [HashMLDSA::keygen] is provided in order to have conformance to the [Signature] trait. +//! But a simple [HashMLDSA::keygen] is provided. use crate::mldsa::{H, MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSATrait}; use crate::mldsa::{ @@ -91,7 +93,8 @@ use crate::{ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ - Algorithm, Hash, PHSignature, RNG, SecurityStrength, Signature, XOF, + Algorithm, Hash, PHSignatureVerifier, PHSigner, RNG, SecurityStrength, SignatureVerifier, + Signer, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha2::{SHA256, SHA256_NAME, SHA512, SHA512_NAME}; @@ -425,6 +428,38 @@ impl< GAMMA1_MASK_LEN, > { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [Signer] / [SignatureVerifier] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Keygen, and keys in general, are interchangeable between MLDSA and HashMLDSA. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), SignatureError> { + MLDSA::< + PK_LEN, + SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + >::keygen() + } + /// Imports a secret key from a seed. pub fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError> { MLDSA::< @@ -451,7 +486,7 @@ impl< GAMMA1_MASK_LEN, >::keygen_internal(seed) } - /// Same as [Signature::sign], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [Signer::sign], but signs from an [MLDSAPrivateKeyExpanded]. pub fn sign_with_expanded_key( sk: &MLDSAPrivateKeyExpanded, msg: &[u8], @@ -462,7 +497,7 @@ impl< Ok(out) } - /// Same as [Signature::sign_out], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [Signer::sign_out], but signs from an [MLDSAPrivateKeyExpanded]. pub fn sign_with_expanded_key_out( sk: &MLDSAPrivateKeyExpanded, msg: &[u8], @@ -475,7 +510,7 @@ impl< _ = HASH::default().hash_out(msg, &mut ph_m); Self::sign_ph_with_expanded_key_out(sk, &ph_m, ctx, output) } - /// Same as [PHSignature::sign_ph], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [PHSigner::sign_ph], but signs from an [MLDSAPrivateKeyExpanded]. pub fn sign_ph_with_expanded_key( sk: &MLDSAPrivateKeyExpanded, ph: &[u8; PH_LEN], @@ -486,7 +521,7 @@ impl< Ok(out) } - /// Same as [PHSignature::sign_ph_out], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [PHSigner::sign_ph_out], but signs from an [MLDSAPrivateKeyExpanded]. pub fn sign_ph_with_expanded_key_out( sk: &MLDSAPrivateKeyExpanded, ph: &[u8; PH_LEN], @@ -615,9 +650,9 @@ impl< Ok(bytes_written) } - /// To be used for deterministic signing in conjunction with the [Signature::sign_init], - /// [Signature::sign_update], and [Signature::sign_final] flow. - /// Can be set anywhere after [Signature::sign_init] and before [Signature::sign_final] + /// To be used for deterministic signing in conjunction with the [Signer::sign_init], + /// [Signer::sign_update], and [Signer::sign_final] flow. + /// Can be set anywhere after [Signer::sign_init] and before [Signer::sign_final] pub fn set_signer_rnd(&mut self, rnd: [u8; 32]) { self.signer_rnd = Some(rnd); } @@ -656,7 +691,7 @@ impl< ctx_len, }) } - /// Same as [Signature::verify], but verifies from an [MLDSAPublicKeyExpanded]. + /// Same as [SignatureVerifier::verify], but verifies from an [MLDSAPublicKeyExpanded]. pub fn verify_with_expanded_key( pk: &MLDSAPublicKeyExpanded, msg: &[u8], @@ -810,7 +845,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> Signature +> Signer for HashMLDSA< HASH, PH_LEN, @@ -837,33 +872,6 @@ impl< GAMMA1_MASK_LEN, > { - /// Keygen, and keys in general, are interchangeable between MLDSA and HashMLDSA. - fn keygen() -> Result<(PK, SK), SignatureError> { - MLDSA::< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::keygen() - } - /// Algorithm 4 HashML-DSA.Sign(π‘ π‘˜, 𝑀 , 𝑐𝑑π‘₯, PH) /// Generate a β€œpre-hash” ML-DSA signature. fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { @@ -957,7 +965,62 @@ impl< unreachable!() } } +} +impl< + HASH: Hash + Default, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const PH_LEN: usize, + const oid: &'static [u8], + const PK_LEN: usize, + const SK_LEN: usize, + const SIG_LEN: usize, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, + const GAMMA1_MASK_LEN: usize, +> SignatureVerifier + for HashMLDSA< + HASH, + PH_LEN, + oid, + PK_LEN, + SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mut ph_m = [0u8; PH_LEN]; _ = HASH::default().hash_out(msg, &mut ph_m); @@ -1018,7 +1081,7 @@ impl< const GAMMA1_MASK_LEN: usize, const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, -> PHSignature +> PHSigner for HashMLDSA< HASH, PH_LEN, @@ -1072,7 +1135,62 @@ impl< HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; Self::sign_ph_deterministic_out(sk, None, ctx, ph, rnd, output) } +} +impl< + HASH: Hash + Default, + const PH_LEN: usize, + const oid: &'static [u8], + const PK_LEN: usize, + const SK_LEN: usize, + const SIG_LEN: usize, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MASK_LEN: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, +> PHSignatureVerifier + for HashMLDSA< + HASH, + PH_LEN, + oid, + PK_LEN, + SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify_ph( pk: &PK, ph: &[u8; PH_LEN], diff --git a/crypto/mldsa/src/lib.rs b/crypto/mldsa/src/lib.rs index 9c3a2b8..c304171 100644 --- a/crypto/mldsa/src/lib.rs +++ b/crypto/mldsa/src/lib.rs @@ -15,7 +15,6 @@ //! //! ```rust //! use bouncycastle_mldsa::MLDSA65; -//! use bouncycastle_core::traits::Signature; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); //! ``` @@ -39,13 +38,13 @@ //! //! See [MLDSATrait] and [MLDSATrait::sign_mu_deterministic_from_seed] for an API flow that uses a merged //! keygen-and-sign function to provide improved speed and memory performance compared with making -//! separate calls to [MLDSATrait::keygen_from_seed] followed by [Signature::sign]. +//! separate calls to [MLDSATrait::keygen_from_seed] followed by [Signer::sign]. //! //! ## Generating and Verifying Signatures //! //! ```rust //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait}; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_core::errors::SignatureError; //! //! let msg = b"The quick brown fox"; @@ -133,7 +132,7 @@ #[allow(unused_imports)] use bouncycastle_core::key_material::KeyMaterialTrait; #[allow(unused_imports)] -use bouncycastle_core::traits::Signature; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; // todo -- crucible tests diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index dd8eb0f..ca41c81 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -9,7 +9,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -51,7 +51,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -98,7 +98,7 @@ //! ```rust //! use bouncycastle_core::errors::SignatureError; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! //! let (pk, _) = MLDSA65::keygen().unwrap(); //! @@ -116,7 +116,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let (pk, _) = MLDSA65::keygen().unwrap(); @@ -136,7 +136,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -182,7 +182,7 @@ //! ```rust //! use bouncycastle_core::errors::SignatureError; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait}; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! //! let msg = b"The quick brown fox"; //! let ctx = b"FooTextDocumentFormat"; @@ -220,7 +220,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -258,7 +258,7 @@ //! ```rust //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait}; //! use bouncycastle_mldsa::{MLDSA65PublicKeyExpanded, MLDSA65PrivateKeyExpanded}; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_core::errors::SignatureError; //! //! let msg = b"The quick brown fox"; @@ -303,7 +303,7 @@ //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType, KeyMaterialTrait}; //! use bouncycastle_hex as hex; //! use bouncycastle_mldsa::{MLDSA44, MLDSA44_SIG_LEN, MLDSATrait, MLDSAPublicKeyTrait, MuBuilder}; @@ -375,7 +375,7 @@ //! ```rust //! use bouncycastle_mldsa::{MLDSA65, MLDSATrait}; //! use bouncycastle_mldsa::{MLDSA65PublicKeyExpanded, MLDSA65PrivateKeyExpanded}; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_core::errors::SignatureError; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -412,7 +412,7 @@ use crate::{ }; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, Signature, XOF}; +use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, XOF}; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::{SHAKE128, SHAKE256}; use core::marker::PhantomData; @@ -421,7 +421,7 @@ use core::marker::PhantomData; #[allow(unused_imports)] use crate::hash_mldsa; #[allow(unused_imports)] -use bouncycastle_core::traits::PHSignature; +use bouncycastle_core::traits::{PHSignatureVerifier, PHSigner}; /*** Constants ***/ @@ -728,6 +728,15 @@ impl< GAMMA1_MASK_LEN, > { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [Signer] / [SignatureVerifier] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), SignatureError> { + Self::keygen_from_os_rng() + } + /// Should still be ok in FIPS mode pub fn keygen_from_os_rng() -> Result<(PK, SK), SignatureError> { let mut seed = KeyMaterial256::new(); @@ -1722,7 +1731,7 @@ pub trait MLDSATrait< msg: &[u8], ctx: Option<&[u8]>, ) -> Result<[u8; 64], SignatureError>; - /// Same as [Signature::sign], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [Signer::sign], but signs from an [MLDSAPrivateKeyExpanded]. fn sign_with_expanded_key( sk: &MLDSAPrivateKeyExpanded, msg: &[u8], @@ -1760,13 +1769,13 @@ pub trait MLDSATrait< mu: &[u8; 64], output: &mut [u8; SIG_LEN], ) -> Result; - /// Same as [Signature::sign_mu], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [MLDSATrait::sign_mu], but signs from an [MLDSAPrivateKeyExpanded]. fn sign_mu_with_expanded_key( sk: &MLDSAPrivateKeyExpanded, A_hat: Option<&Matrix>, mu: &[u8; 64], ) -> Result<[u8; SIG_LEN], SignatureError>; - /// Same as [Signature::sign_mu_out], but signs from an [MLDSAPrivateKeyExpanded]. + /// Same as [MLDSATrait::sign_mu_out], but signs from an [MLDSAPrivateKeyExpanded]. fn sign_mu_with_expanded_key_out( sk: &MLDSAPrivateKeyExpanded, A_hat: Option<&Matrix>, @@ -1856,7 +1865,7 @@ pub trait MLDSATrait< seed: &KeyMaterial<32>, ctx: Option<&[u8]>, ) -> Result; - /// Same as [Signature::verify], but signs from an expanded key object. + /// Same as [SignatureVerifier::verify], but signs from an expanded key object. fn verify_with_expanded_key( pk: &MLDSAPublicKeyExpanded, msg: &[u8], @@ -1898,7 +1907,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> Signature +> Signer for MLDSA< PK_LEN, SK_LEN, @@ -1923,10 +1932,6 @@ impl< GAMMA1_MASK_LEN, > { - fn keygen() -> Result<(PK, SK), SignatureError> { - Self::keygen_from_os_rng() - } - fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { let mut out = [0u8; SIG_LEN]; Self::sign_out(sk, msg, ctx, &mut out)?; @@ -2005,7 +2010,56 @@ impl< unreachable!() } } +} +impl< + const PK_LEN: usize, + const SK_LEN: usize, + const SIG_LEN: usize, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, + const GAMMA1_MASK_LEN: usize, +> SignatureVerifier + for MLDSA< + PK_LEN, + SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)?; @@ -2062,7 +2116,7 @@ impl< /// Note: this struct is only exposed for "pure" ML-DSA and not for HashML-DSA because HashML-DSA /// does not benefit from allowing external construction of the message representative mu. /// You can get the same behaviour by computing the pre-hash `ph` with the appropriate hash function -/// and providing that to HashMLDSA via [PHSignature::sign_ph]. +/// and providing that to HashMLDSA via [PHSigner::sign_ph]. pub struct MuBuilder { h: H, } diff --git a/crypto/mldsa/tests/bc_test_data.rs b/crypto/mldsa/tests/bc_test_data.rs index 77ae858..a732489 100644 --- a/crypto/mldsa/tests/bc_test_data.rs +++ b/crypto/mldsa/tests/bc_test_data.rs @@ -14,7 +14,7 @@ mod bc_test_data { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Hash, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + Hash, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, }; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ diff --git a/crypto/mldsa/tests/hash_mldsa_tests.rs b/crypto/mldsa/tests/hash_mldsa_tests.rs index 4301d7b..ecdfb7c 100644 --- a/crypto/mldsa/tests/hash_mldsa_tests.rs +++ b/crypto/mldsa/tests/hash_mldsa_tests.rs @@ -2,7 +2,9 @@ mod hash_mldsa_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; - use bouncycastle_core::traits::{Hash, PHSignature, Signature}; + use bouncycastle_core::traits::{ + Hash, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, + }; use bouncycastle_core_test_framework::signature::TestFrameworkSignature; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ @@ -21,19 +23,19 @@ mod hash_mldsa_tests { let tf = TestFrameworkSignature::new(false, true); // Test HashML-DSA-SHA512 as a regular signature alg - tf.test_signature::(false); - tf.test_signature::(false); - tf.test_signature::(false); + tf.test_signature::(HashMLDSA44_with_SHA512::keygen, false); + tf.test_signature::(HashMLDSA65_with_SHA512::keygen, false); + tf.test_signature::(HashMLDSA87_with_SHA512::keygen, false); // Test HashML-DSA-SHA256 as a ph signature alg - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); + tf.test_ph_signature::(HashMLDSA44_with_SHA256::keygen, false); + tf.test_ph_signature::(HashMLDSA65_with_SHA256::keygen, false); + tf.test_ph_signature::(HashMLDSA87_with_SHA256::keygen, false); // Test HashML-DSA-SHA512 as a ph signature alg - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); - tf.test_ph_signature::(false); + tf.test_ph_signature::(HashMLDSA44_with_SHA512::keygen, false); + tf.test_ph_signature::(HashMLDSA65_with_SHA512::keygen, false); + tf.test_ph_signature::(HashMLDSA87_with_SHA512::keygen, false); } #[test] diff --git a/crypto/mldsa/tests/mldsa_key_tests.rs b/crypto/mldsa/tests/mldsa_key_tests.rs index c2451a0..476a5b6 100644 --- a/crypto/mldsa/tests/mldsa_key_tests.rs +++ b/crypto/mldsa/tests/mldsa_key_tests.rs @@ -3,7 +3,7 @@ mod mldsa_key_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + SecurityStrength, SignaturePrivateKey, SignaturePublicKey, }; use bouncycastle_core_test_framework::signature::TestFrameworkSignatureKeys; use bouncycastle_hex as hex; @@ -15,17 +15,17 @@ mod mldsa_key_tests { MLDSAPrivateKeyTrait, MLDSATrait, }; use bouncycastle_mldsa::{ - MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN, MLDSA65_PK_LEN, MLDSA65_SIG_LEN, - MLDSA65_SK_LEN, MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN, + MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA87_PK_LEN, + MLDSA87_SK_LEN, }; #[test] fn core_framework_tests() { let tf = TestFrameworkSignatureKeys::new(); - tf.test_keys::(); - tf.test_keys::(); - tf.test_keys::(); + tf.test_keys::(MLDSA44::keygen); + tf.test_keys::(MLDSA65::keygen); + tf.test_keys::(MLDSA87::keygen); } #[test] diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index b5a1b5f..b87a0a8 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -5,7 +5,7 @@ mod mldsa_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - RNG, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::signature::*; @@ -44,9 +44,9 @@ mod mldsa_tests { fn test_framework_signature() { let tf = TestFrameworkSignature::new(false, true); - tf.test_signature::(false); - tf.test_signature::(false); - tf.test_signature::(false); + tf.test_signature::(MLDSA44::keygen, false); + tf.test_signature::(MLDSA65::keygen, false); + tf.test_signature::(MLDSA87::keygen, false); } /// This runs the full bitflipping tests and takes several minutes. diff --git a/crypto/mldsa/tests/wycheproof.rs b/crypto/mldsa/tests/wycheproof.rs index bc7fd34..abe6842 100644 --- a/crypto/mldsa/tests/wycheproof.rs +++ b/crypto/mldsa/tests/wycheproof.rs @@ -20,7 +20,7 @@ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, }; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ diff --git a/mem_usage_benches/bench_mldsa_mem_usage.rs b/mem_usage_benches/bench_mldsa_mem_usage.rs index 6dc8351..9f3ff49 100644 --- a/mem_usage_benches/bench_mldsa_mem_usage.rs +++ b/mem_usage_benches/bench_mldsa_mem_usage.rs @@ -21,7 +21,7 @@ #![allow(unused_imports)] use bouncycastle::core::key_material::{KeyMaterial256, KeyType}; -use bouncycastle::core::traits::{Signature, SignaturePrivateKey, SignaturePublicKey}; +use bouncycastle::core::traits::{SignatureVerifier, SignaturePrivateKey, SignaturePublicKey}; use bouncycastle::hex; use bouncycastle::mldsa::MLDSA44_SIG_LEN; From 7724605d11cd380ad06518b43492a77089bf100c Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 10 Jun 2026 13:00:11 -0500 Subject: [PATCH 8/9] Refactored the KEM trait into KEMEncapsuloter and KEMDecapsulator --- cli/src/mlkem_cmd.rs | 2 +- crypto/core-test-framework/src/kem.rs | 56 ++++++------- crypto/core/src/traits.rs | 78 ++++++++++--------- crypto/mldsa/src/hash_mldsa.rs | 8 +- .../mlkem-lowmemory/benches/mlkem_benches.rs | 2 +- crypto/mlkem-lowmemory/src/lib.rs | 3 +- crypto/mlkem-lowmemory/src/mlkem.rs | 58 +++++++++++--- .../mlkem-lowmemory/tests/mlkem_key_tests.rs | 12 +-- crypto/mlkem-lowmemory/tests/mlkem_tests.rs | 10 ++- crypto/mlkem-lowmemory/tests/wycheproof.rs | 2 +- crypto/mlkem/benches/mlkem_benches.rs | 2 +- crypto/mlkem/src/lib.rs | 3 +- crypto/mlkem/src/mlkem.rs | 51 ++++++++---- crypto/mlkem/tests/bc_test_data.rs | 2 +- crypto/mlkem/tests/mlkem_key_tests.rs | 14 ++-- crypto/mlkem/tests/mlkem_tests.rs | 10 ++- crypto/mlkem/tests/wycheproof.rs | 2 +- mem_usage_benches/bench_mlkem_mem_usage.rs | 9 +-- 18 files changed, 193 insertions(+), 131 deletions(-) diff --git a/cli/src/mlkem_cmd.rs b/cli/src/mlkem_cmd.rs index 2ac50e2..67214da 100644 --- a/cli/src/mlkem_cmd.rs +++ b/cli/src/mlkem_cmd.rs @@ -6,7 +6,7 @@ use crate::helpers::{ write_bytes_or_hex_to_file, }; use bouncycastle::core::key_material::KeyMaterialTrait; -use bouncycastle::core::traits::{KEM, KEMPrivateKey, KEMPublicKey}; +use bouncycastle::core::traits::{KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey}; use bouncycastle::hex; use bouncycastle::mlkem::{ MLKEM512, MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512PrivateKey, diff --git a/crypto/core-test-framework/src/kem.rs b/crypto/core-test-framework/src/kem.rs index cc16593..4509684 100644 --- a/crypto/core-test-framework/src/kem.rs +++ b/crypto/core-test-framework/src/kem.rs @@ -1,5 +1,5 @@ use bouncycastle_core::errors::KEMError; -use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey}; +use bouncycastle_core::traits::{KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey}; pub struct TestFrameworkKEM { // Put any config options here @@ -16,43 +16,48 @@ impl TestFrameworkKEM { Self { alg_is_deterministic, is_implicitly_rejecting } } - /// Test all the members of trait Hash against the given input-output pair. + /// Test all the members of traits [KEMEncapsulator] and [KEMDecapsulator] against the given input-output pair. /// This gives good baseline test coverage, but is not exhaustive. + /// + /// Since key generation is not part of either KEM trait, the caller supplies a + /// `keygen` function pointer (the inherent `keygen` associated function on the algorithm struct). pub fn test_kem< PK: KEMPublicKey, SK: KEMPrivateKey, - KEMAlg: KEM, + ENCAPSULATOR: KEMEncapsulator, + DECAPSULATOR: KEMDecapsulator, const PK_LEN: usize, const SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, >( &self, + keygen: fn() -> Result<(PK, SK), KEMError>, run_full_bitflipping_tests: bool, ) { // Basic test - let (pk, sk) = KEMAlg::keygen().unwrap(); - let (ss, ct) = KEMAlg::encaps(&pk).unwrap(); - let ss1 = KEMAlg::decaps(&sk, &ct).unwrap(); + let (pk, sk) = keygen().unwrap(); + let (ss, ct) = ENCAPSULATOR::encaps(&pk).unwrap(); + let ss1 = DECAPSULATOR::decaps(&sk, &ct).unwrap(); assert_eq!(ss, ss1); // Test non-determinism if !self.alg_is_deterministic { - let (ss1, ct1) = KEMAlg::encaps(&pk).unwrap(); - let (ss2, ct2) = KEMAlg::encaps(&pk).unwrap(); + let (ss1, ct1) = ENCAPSULATOR::encaps(&pk).unwrap(); + let (ss2, ct2) = ENCAPSULATOR::encaps(&pk).unwrap(); assert_ne!(ss1, ss2); assert_ne!(ct1, ct2); } // Test that decaps fails for broken ct value - let (pk, sk) = KEMAlg::keygen().unwrap(); - let (ss, mut ct) = KEMAlg::encaps(&pk).unwrap(); + let (pk, sk) = keygen().unwrap(); + let (ss, mut ct) = ENCAPSULATOR::encaps(&pk).unwrap(); ct[17] ^= 0xFF; if self.is_implicitly_rejecting { - let ss2 = KEMAlg::decaps(&sk, &ct).unwrap(); + let ss2 = DECAPSULATOR::decaps(&sk, &ct).unwrap(); assert_ne!(ss, ss2); } else { - match KEMAlg::decaps(&sk, &ct) { + match DECAPSULATOR::decaps(&sk, &ct) { Err(KEMError::DecapsulationFailed) => /* good */ { @@ -71,10 +76,10 @@ impl TestFrameworkKEM { // should throw an Err if self.is_implicitly_rejecting { - let ss2 = KEMAlg::decaps(&sk, &ct_copy).unwrap(); + let ss2 = DECAPSULATOR::decaps(&sk, &ct_copy).unwrap(); assert_ne!(ss, ss2); } else { - match KEMAlg::decaps(&sk, &ct) { + match DECAPSULATOR::decaps(&sk, &ct) { Err(KEMError::DecapsulationFailed) => /* good */ { @@ -88,11 +93,10 @@ impl TestFrameworkKEM { } // test ct the wrong length - let (pk, sk) = KEMAlg::keygen().unwrap(); - let (_ss, ct) = KEMAlg::encaps(&pk).unwrap(); - + let (pk, sk) = keygen().unwrap(); + let (_ss, ct) = ENCAPSULATOR::encaps(&pk).unwrap(); // too short - match KEMAlg::decaps(&sk, &ct[..CT_LEN - 1]) { + match DECAPSULATOR::decaps(&sk, &ct[..CT_LEN - 1]) { Err(KEMError::LengthError(_)) => { /* good */ } _ => panic!("This should have thrown an error but it didn't."), }; @@ -100,7 +104,7 @@ impl TestFrameworkKEM { // too long let mut long_ct = vec![1u8; CT_LEN + 2]; long_ct.as_mut_slice()[..CT_LEN].copy_from_slice(&ct); - match KEMAlg::decaps(&sk, &long_ct) { + match DECAPSULATOR::decaps(&sk, &long_ct) { Err(KEMError::LengthError(_)) => { /* good */ } _ => panic!("This should have thrown an error but it didn't."), }; @@ -114,33 +118,31 @@ impl TestFrameworkKEMKeys { Self {} } + /// Since key generation is not part of either KEM trait, the caller supplies a + /// `keygen` function pointer (the inherent `keygen` associated function on the algorithm struct). pub fn test_keys< PK: KEMPublicKey, SK: KEMPrivateKey, - KEMAlg: KEM, const PK_LEN: usize, const SK_LEN: usize, - const CT_LEN: usize, - const SS_LEN: usize, >( &self, + keygen: fn() -> Result<(PK, SK), KEMError>, ) { - self.test_boundary_conditions::(); + self.test_boundary_conditions::(keygen); } /// Tests the correct behaviour on buffers too large / too small. fn test_boundary_conditions< PK: KEMPublicKey, SK: KEMPrivateKey, - KEMAlg: KEM, const PK_LEN: usize, const SK_LEN: usize, - const CT_LEN: usize, - const SS_LEN: usize, >( &self, + keygen: fn() -> Result<(PK, SK), KEMError>, ) { - let (pk, sk) = KEMAlg::keygen().unwrap(); + let (pk, sk) = keygen().unwrap(); let pk_bytes = pk.encode(); assert_eq!(pk_bytes.len(), PK_LEN); diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 21894a2..5247bbd 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -182,24 +182,57 @@ pub trait KDF: Default { fn max_security_strength(&self) -> SecurityStrength; } -/// A Key Encapsulation Mechanism -pub trait KEM< +/// A Key Encapsulation Mechanism (KEM) is defined as a set of three operations: +/// key generation, encapsulation, and decapsulation. +/// +/// This trait represents the encapsulation operation performed by the holder of the public key. +/// Decapsulation operations are performed by the corresponding [KEMDecapsulator] trait, and key +/// generation is provided as an inherent associated function directly on the algorithm struct. +/// There are several reasons for this split: first is architectural; some complex algorithms may +/// benefit from having the encapsulation and decapsulation implementations split into separate modules. +/// Second is for compliance: sometimes a policy soft-deprecates an algorithm so that new ciphertexts +/// can no longer be created, but existing ciphertexts can still be decapsulated. Splitting the traits +/// makes this policy easier to enforce. +/// +/// The arrays used to encode public keys, ciphertexts, and shared secrets are statically-sized +/// because this allows us to safely remove runtime checks for array lengths, which overall reduces +/// the fallibility of the library. This design choice could make this trait complicated to apply +/// to a KEM algorithm that does not have fixed sizes for the encodings of these objects. +pub trait KEMEncapsulator< PK: KEMPublicKey, - SK: KEMPrivateKey, const PK_LEN: usize, - const SK_LEN: usize, const CT_LEN: usize, const SS_LEN: usize, >: Sized { - /// Generate a keypair. - /// Error condition: Basically only on RNG failures - fn keygen() -> Result<(PK, SK), KEMError>; - /// Performs an encapsulation against the given public key. /// Returns the ciphertext and derived shared secret. fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; +} +/// A Key Encapsulation Mechanism (KEM) is defined as a set of three operations: +/// key generation, encapsulation, and decapsulation. +/// +/// This trait represents the decapsulation operation performed by the holder of the private key. +/// Encapsulation operations are performed by the corresponding [KEMEncapsulator] trait, and key +/// generation is provided as an inherent associated function directly on the algorithm struct. +/// There are several reasons for this split: first is architectural; some complex algorithms may +/// benefit from having the encapsulation and decapsulation implementations split into separate modules. +/// Second is for compliance: sometimes a policy soft-deprecates an algorithm so that new ciphertexts +/// can no longer be created, but existing ciphertexts can still be decapsulated. Splitting the traits +/// makes this policy easier to enforce. +/// +/// The arrays used to encode private keys, ciphertexts, and shared secrets are statically-sized +/// because this allows us to safely remove runtime checks for array lengths, which overall reduces +/// the fallibility of the library. This design choice could make this trait complicated to apply +/// to a KEM algorithm that does not have fixed sizes for the encodings of these objects. +pub trait KEMDecapsulator< + SK: KEMPrivateKey, + const SK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, +>: Sized +{ /// Performs a decapsulation of the given ciphertext. /// Returns the derived shared secret. fn decaps(sk: &SK, ct: &[u8]) -> Result, KEMError>; @@ -635,35 +668,6 @@ pub trait SignatureVerifier< fn verify_final(self, sig: &[u8]) -> Result<(), SignatureError>; } -// todo: could the public and private key types impl Into> and From> -// todo: that automatically call the encode and from_bytes() ? - -/// A public key for a signature algorithm, often denoted "pk". -pub trait SignaturePublicKey: - PartialEq + Eq + Clone + Debug + Display + Sized -{ - /// Write it out to bytes in its standard encoding. - fn encode(&self) -> [u8; PK_LEN]; - /// Write it out to bytes in its standard encoding. - /// The entire output buffer is zeroized before the encoding is written. - fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize; - /// Read it in from bytes in its standard encoding. - fn from_bytes(bytes: &[u8]) -> Result; -} - -/// A private key for a signature algorithm, often denoted "sk" (for "secret key"). -pub trait SignaturePrivateKey: - PartialEq + Eq + Clone + Secret + Sized -{ - /// Write it out to bytes in its standard encoding. - fn encode(&self) -> [u8; SK_LEN]; - /// Write it out to bytes in its standard encoding. - /// The entire output buffer is zeroized before the encoding is written. - fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize; - /// Read it in from bytes in its standard encoding. - fn from_bytes(bytes: &[u8]) -> Result; -} - /// Extensible Output Functions (XOFs) are similar to hash functions, except that they can produce output of arbitrary length. /// The naming used for the functions of this trait are borrowed from the SHA3-style sponge constructions that split XOF operation /// into two phases: an absorb phase in which an arbitrary amount of input is provided to the XOF, diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 9a4dc3d..b7e3988 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -968,12 +968,11 @@ impl< } impl< - HASH: Hash + Default, + HASH: Hash + Algorithm + Default, PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -997,7 +996,6 @@ impl< for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, @@ -1138,9 +1136,8 @@ impl< } impl< - HASH: Hash + Default, + HASH: Hash + Algorithm + Default, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -1167,7 +1164,6 @@ impl< for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, diff --git a/crypto/mlkem-lowmemory/benches/mlkem_benches.rs b/crypto/mlkem-lowmemory/benches/mlkem_benches.rs index 1ecd65a..8ea83ba 100644 --- a/crypto/mlkem-lowmemory/benches/mlkem_benches.rs +++ b/crypto/mlkem-lowmemory/benches/mlkem_benches.rs @@ -1,5 +1,5 @@ use bouncycastle_core::key_material::{KeyMaterial512, KeyType}; -use bouncycastle_core::traits::KEM; +use bouncycastle_core::traits::KEMDecapsulator; use bouncycastle_hex as hex; use bouncycastle_mlkem_lowmemory::{ MLKEM_RND_LEN, MLKEM512, MLKEM512_CT_LEN, MLKEM768, MLKEM768_CT_LEN, MLKEM1024, diff --git a/crypto/mlkem-lowmemory/src/lib.rs b/crypto/mlkem-lowmemory/src/lib.rs index 137afb7..0f70fc5 100644 --- a/crypto/mlkem-lowmemory/src/lib.rs +++ b/crypto/mlkem-lowmemory/src/lib.rs @@ -155,7 +155,6 @@ //! //! ```rust //! use bouncycastle_mlkem_lowmemory::MLKEM768; -//! use bouncycastle_core::traits::KEM; //! //! let (pk, sk) = MLKEM768::keygen().unwrap(); //! ``` @@ -185,7 +184,7 @@ //! //! ```rust //! use bouncycastle_mlkem_lowmemory::{MLKEM768, MLKEMTrait}; -//! use bouncycastle_core::traits::KEM; +//! use bouncycastle_core::traits::{KEMEncapsulator, KEMDecapsulator}; //! use bouncycastle_core::errors::KEMError; //! //! let (pk, sk) = MLKEM768::keygen().unwrap(); diff --git a/crypto/mlkem-lowmemory/src/mlkem.rs b/crypto/mlkem-lowmemory/src/mlkem.rs index 3d75cb3..eca60d4 100644 --- a/crypto/mlkem-lowmemory/src/mlkem.rs +++ b/crypto/mlkem-lowmemory/src/mlkem.rs @@ -14,7 +14,9 @@ use crate::mlkem_keys::{MLKEMPublicKeyInternalTrait, MLKEMPublicKeyTrait}; use crate::polynomial::Polynomial; use bouncycastle_core::errors::KEMError; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Algorithm, Hash, KEM, RNG, SecurityStrength, XOF}; +use bouncycastle_core::traits::{ + Algorithm, Hash, KEMDecapsulator, KEMEncapsulator, RNG, SecurityStrength, XOF, +}; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::{SHA3_256, SHA3_512, SHAKE256}; use bouncycastle_utils::ct::{conditional_copy_bytes, ct_eq_bytes}; @@ -233,6 +235,15 @@ impl< T_PACKED_LEN, > { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [KEMEncapsulator] / [KEMDecapsulator] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), KEMError> { + Self::keygen_from_os_rng() + } + /// Should still be ok in FIPS mode pub fn keygen_from_os_rng() -> Result<(PK, SK), KEMError> { let mut seed = KeyMaterial::<64>::new(); @@ -333,13 +344,13 @@ impl< /// Output: shared secret key 𝐾 ∈ 𝔹32 . /// Output: ciphertext 𝑐 ∈ 𝔹32(π‘‘π‘’π‘˜+𝑑𝑣). /// - /// Unlike the more public function exposed by [KEM::encaps], this returns the shared secret as raw bytes + /// Unlike the more public function exposed by [KEMEncapsulator::encaps], this returns the shared secret as raw bytes /// instead of wrapped in an appropriately-set [KeyMaterialTrait], so you're on your own for handling it properly. /// /// Note: this is an internal function that allows the caller to specify the encapsulation /// randomness (which is the message `m` to be encrypted by the underlying PKE scheme). /// This function should not be used directly unless you really have a - /// good reason. [KEM::encaps] should be used in 99.9% of cases. + /// good reason. [KEMEncapsulator::encaps] should be used in 99.9% of cases. /// The reason this is exposed publicly is: A) for unit testing that requires access /// to the deterministically reproducible function, and B) for operational environments /// that wish to provide randomness from their own source instead of the built-in RNG in bc-rust. @@ -657,7 +668,7 @@ impl< const dv: i16, const LAMBDA: i16, const T_PACKED_LEN: usize, -> KEM +> KEMEncapsulator for MLKEM< PK_LEN, SK_LEN, @@ -674,11 +685,6 @@ impl< T_PACKED_LEN, > { - /// Generates a fresh key pair. - fn keygen() -> Result<(PK, SK), KEMError> { - Self::keygen_from_os_rng() - } - fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { let mut m = [0u8; 32]; HashDRBG_SHA512::new_from_os().next_bytes_out(&mut m)?; @@ -693,7 +699,41 @@ impl< Ok((ss_keymaterial, ct)) } +} +impl< + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, + PK: MLKEMPublicKeyTrait + + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, + const k: usize, + const eta: i16, + const du: i16, + const dv: i16, + const LAMBDA: i16, + const T_PACKED_LEN: usize, +> KEMDecapsulator + for MLKEM< + PK_LEN, + SK_LEN, + FULL_SK_LEN, + CT_LEN, + SS_LEN, + PK, + SK, + k, + eta, + du, + dv, + LAMBDA, + T_PACKED_LEN, + > +{ fn decaps(sk: &SK, ct: &[u8]) -> Result, KEMError> { if ct.len() != CT_LEN { return Err(KEMError::LengthError("Invalid ciphertext length")); diff --git a/crypto/mlkem-lowmemory/tests/mlkem_key_tests.rs b/crypto/mlkem-lowmemory/tests/mlkem_key_tests.rs index 8a72ef9..6275f5e 100644 --- a/crypto/mlkem-lowmemory/tests/mlkem_key_tests.rs +++ b/crypto/mlkem-lowmemory/tests/mlkem_key_tests.rs @@ -1,12 +1,12 @@ #[cfg(test)] mod mlkem_key_tests { use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; - use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength}; + use bouncycastle_core::traits::{KEMPrivateKey, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; use bouncycastle_mlkem_lowmemory::mlkem::MLKEM512_FULL_SK_LEN; use bouncycastle_mlkem_lowmemory::{ - MLKEM_SS_LEN, MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM768_CT_LEN, - MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM1024_CT_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, + MLKEM512_PK_LEN, MLKEM512_SK_LEN, + MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, }; use bouncycastle_mlkem_lowmemory::{MLKEM512, MLKEM768, MLKEM1024}; use bouncycastle_mlkem_lowmemory::{ @@ -20,9 +20,9 @@ mod mlkem_key_tests { use bouncycastle_core_test_framework::kem::TestFrameworkKEMKeys; let tf = TestFrameworkKEMKeys::new(); - tf.test_keys::(); - tf.test_keys::(); - tf.test_keys::(); + tf.test_keys::(MLKEM512::keygen); + tf.test_keys::(MLKEM768::keygen); + tf.test_keys::(MLKEM1024::keygen); } #[test] diff --git a/crypto/mlkem-lowmemory/tests/mlkem_tests.rs b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs index ff8cc5c..71cc449 100644 --- a/crypto/mlkem-lowmemory/tests/mlkem_tests.rs +++ b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs @@ -3,7 +3,9 @@ mod mlkem_tests { use bouncycastle_core::errors::KEMError; use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; - use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength, XOF}; + use bouncycastle_core::traits::{ + KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength, XOF, + }; use bouncycastle_hex as hex; use bouncycastle_mlkem_lowmemory::mlkem::{ MLKEM512_FULL_SK_LEN, MLKEM768_FULL_SK_LEN, MLKEM1024_FULL_SK_LEN, @@ -46,9 +48,9 @@ mod mlkem_tests { let tf = TestFrameworkKEM::new(false, true); - tf.test_kem::(false); - tf.test_kem::(false); - tf.test_kem::(false); + tf.test_kem::(MLKEM512::keygen, false); + tf.test_kem::(MLKEM768::keygen, false); + tf.test_kem::(MLKEM1024::keygen, false); } /// This runs the full bitflipping tests and takes about 1.5 mins.. diff --git a/crypto/mlkem-lowmemory/tests/wycheproof.rs b/crypto/mlkem-lowmemory/tests/wycheproof.rs index b331a3f..f5570c6 100644 --- a/crypto/mlkem-lowmemory/tests/wycheproof.rs +++ b/crypto/mlkem-lowmemory/tests/wycheproof.rs @@ -25,7 +25,7 @@ #![allow(dead_code)] use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{KEM, KEMPublicKey, SecurityStrength}; +use bouncycastle_core::traits::{KEMDecapsulator, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; use bouncycastle_mlkem_lowmemory::{ MLKEM512, MLKEM512PublicKey, MLKEM768, MLKEM768PublicKey, MLKEM1024, MLKEM1024PublicKey, diff --git a/crypto/mlkem/benches/mlkem_benches.rs b/crypto/mlkem/benches/mlkem_benches.rs index 5330f73..b445005 100644 --- a/crypto/mlkem/benches/mlkem_benches.rs +++ b/crypto/mlkem/benches/mlkem_benches.rs @@ -1,5 +1,5 @@ use bouncycastle_core::key_material::{KeyMaterial512, KeyType}; -use bouncycastle_core::traits::KEM; +use bouncycastle_core::traits::KEMDecapsulator; use bouncycastle_hex as hex; use bouncycastle_mlkem::{ MLKEM_RND_LEN, MLKEM512, MLKEM512_CT_LEN, MLKEM512PrivateKeyExpanded, MLKEM768, diff --git a/crypto/mlkem/src/lib.rs b/crypto/mlkem/src/lib.rs index 4de7d42..28142b6 100644 --- a/crypto/mlkem/src/lib.rs +++ b/crypto/mlkem/src/lib.rs @@ -46,7 +46,6 @@ //! //! ```rust //! use bouncycastle_mlkem::MLKEM768; -//! use bouncycastle_core::traits::KEM; //! //! let (pk, sk) = MLKEM768::keygen().unwrap(); //! ``` @@ -76,7 +75,7 @@ //! //! ```rust //! use bouncycastle_mlkem::{MLKEM768, MLKEMTrait}; -//! use bouncycastle_core::traits::KEM; +//! use bouncycastle_core::traits::{KEMEncapsulator, KEMDecapsulator}; //! use bouncycastle_core::errors::KEMError; //! //! let (pk, sk) = MLKEM768::keygen().unwrap(); diff --git a/crypto/mlkem/src/mlkem.rs b/crypto/mlkem/src/mlkem.rs index bd31e43..c87e896 100644 --- a/crypto/mlkem/src/mlkem.rs +++ b/crypto/mlkem/src/mlkem.rs @@ -30,7 +30,6 @@ //! ```rust //! use bouncycastle_mlkem::{MLKEM768, MLKEMTrait}; //! use bouncycastle_mlkem::{MLKEM768PublicKeyExpanded, MLKEM768PrivateKeyExpanded}; -//! use bouncycastle_core::traits::KEM; //! use bouncycastle_core::errors::KEMError; //! //! let (pk, sk) = MLKEM768::keygen().unwrap(); @@ -60,7 +59,7 @@ //! //! ```rust //! use bouncycastle_mlkem::{MLKEM768, MLKEMTrait}; -//! use bouncycastle_core::traits::KEM; +//! use bouncycastle_core::traits::KEMEncapsulator; //! use bouncycastle_core::errors::KEMError; //! use bouncycastle_core::key_material::{KeyMaterial512, KeyType}; //! use bouncycastle_hex as hex; @@ -104,7 +103,7 @@ //! //! ```rust //! use bouncycastle_mlkem::{MLKEM768, MLKEMTrait}; -//! use bouncycastle_core::traits::{KEM}; +//! use bouncycastle_core::traits::KEMDecapsulator; //! use bouncycastle_core::errors::KEMError; //! use bouncycastle_core::key_material::KeyMaterialTrait; //! @@ -142,12 +141,13 @@ use crate::mlkem_keys::{MLKEMPrivateKeyInternalTrait, MLKEMPrivateKeyTrait}; use crate::polynomial::Polynomial; use bouncycastle_core::errors::KEMError; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Algorithm, Hash, KEM, RNG, SecurityStrength, XOF}; +use bouncycastle_core::traits::{ + Algorithm, Hash, KEMDecapsulator, KEMEncapsulator, RNG, SecurityStrength, XOF, +}; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::{SHA3_256, SHA3_512, SHAKE256}; use bouncycastle_utils::ct::{conditional_copy_bytes, ct_eq_bytes}; use core::marker::PhantomData; - /*** Constants ***/ /// @@ -319,6 +319,15 @@ impl< const LAMBDA: i16, > MLKEM { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [KEMEncapsulator] / [KEMDecapsulator] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), KEMError> { + Self::keygen_from_os_rng() + } + /// Should still be ok in FIPS mode pub fn keygen_from_os_rng() -> Result<(PK, SK), KEMError> { let mut seed = KeyMaterial::<64>::new(); @@ -527,13 +536,13 @@ impl< /// Alternatively, you can use a [MLKEMPublicKeyExpanded] with [MLKEM::encaps_for_expanded_key]. /// If you specify None, the function will compute A_hat internally and everything will work fine. /// - /// Unlike the more public function exposed by [KEM::encaps], this returns the shared secret as raw bytes + /// Unlike the more public function exposed by [KEMEncapsulator::encaps], this returns the shared secret as raw bytes /// instead of wrapped in an appropriately-set [KeyMaterialTrait], so you're on your own for handling it properly. /// /// Note: this is an internal function that allows the caller to specify the encapsulation /// randomness (which is the message `m` to be encrypted by the underlying PKE scheme). /// This function should not be used directly unless you really have a - /// good reason. [KEM::encaps] should be used in 99.9% of cases. + /// good reason. [KEMEncapsulator::encaps] should be used in 99.9% of cases. /// The reason this is exposed publicly is: A) for unit testing that requires access /// to the deterministically reproducible function, and B) for operational environments /// that wish to provide randomness from their own source instead of the built-in RNG in bc-rust. @@ -843,12 +852,12 @@ pub trait MLKEMTrait< /// Returns either `()` or [KEMError::ConsistencyCheckFailed]. fn keypair_consistency_check(pk: &PK, sk: &SK) -> Result<(), KEMError>; - /// Same as [KEM::encaps], but acts on an [MLKEMPublicKeyExpanded]. + /// Same as [KEMEncapsulator::encaps], but acts on an [MLKEMPublicKeyExpanded]. fn encaps_for_expanded_key( pk: &MLKEMPublicKeyExpanded, ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; - /// Same as [KEM::decaps], but acts on an [MLKEMPrivateKeyExpanded]. + /// Same as [KEMDecapsulator::decaps], but acts on an [MLKEMPrivateKeyExpanded]. fn decaps_with_expanded_key( sk: &MLKEMPrivateKeyExpanded, ct: &[u8], @@ -868,14 +877,9 @@ impl< const du: i16, const dv: i16, const LAMBDA: i16, -> KEM +> KEMEncapsulator for MLKEM { - /// Generates a fresh key pair. - fn keygen() -> Result<(PK, SK), KEMError> { - Self::keygen_from_os_rng() - } - /// Performs an encapsulation against the given public key, using the library's default internal RNG. /// Returns (shared_secret_key, ciphertext) /// The derived shared secret key is returned as a KeyMaterial with the SecurityStrength set to @@ -889,7 +893,24 @@ impl< fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { Self::encaps_for_expanded_key(&MLKEMPublicKeyExpanded::::from(pk)) } +} +impl< + const PK_LEN: usize, + const SK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, + PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, + const k: usize, + const eta: i16, + const du: i16, + const dv: i16, + const LAMBDA: i16, +> KEMDecapsulator + for MLKEM +{ /// Performs a decapsulation of the given ciphertext. /// Returns the shared secret key. /// The derived shared secret key is returned as a KeyMaterial with the SecurityStrength set to diff --git a/crypto/mlkem/tests/bc_test_data.rs b/crypto/mlkem/tests/bc_test_data.rs index 5c14a28..036c85c 100644 --- a/crypto/mlkem/tests/bc_test_data.rs +++ b/crypto/mlkem/tests/bc_test_data.rs @@ -5,7 +5,7 @@ #[cfg(test)] mod bc_test_data { use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; - use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength}; + use bouncycastle_core::traits::{KEMDecapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; use bouncycastle_mlkem::{ MLKEM512, MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512PrivateKey, MLKEM512PublicKey, diff --git a/crypto/mlkem/tests/mlkem_key_tests.rs b/crypto/mlkem/tests/mlkem_key_tests.rs index 7317d50..4a4f3de 100644 --- a/crypto/mlkem/tests/mlkem_key_tests.rs +++ b/crypto/mlkem/tests/mlkem_key_tests.rs @@ -2,12 +2,12 @@ mod mlkem_key_tests { use bouncycastle_core::errors::KEMError; use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; - use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength}; + use bouncycastle_core::traits::{KEMPrivateKey, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; use bouncycastle_mlkem::{ - MLKEM_SS_LEN, MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM512_SK_LEN, - MLKEM512PrivateKeyExpanded, MLKEM512PublicKeyExpanded, MLKEM768_CT_LEN, MLKEM768_PK_LEN, - MLKEM768_SK_LEN, MLKEM1024_CT_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, + MLKEM512_PK_LEN, MLKEM512_SK_LEN, + MLKEM512PrivateKeyExpanded, MLKEM512PublicKeyExpanded, MLKEM768_PK_LEN, + MLKEM768_SK_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEMPrivateKeyTrait, MLKEMPublicKeyTrait, MLKEMTrait, }; use bouncycastle_mlkem::{MLKEM512, MLKEM768, MLKEM1024}; @@ -22,9 +22,9 @@ mod mlkem_key_tests { let tf = TestFrameworkKEMKeys::new(); - tf.test_keys::(); - tf.test_keys::(); - tf.test_keys::(); + tf.test_keys::(MLKEM512::keygen); + tf.test_keys::(MLKEM768::keygen); + tf.test_keys::(MLKEM1024::keygen); } #[test] diff --git a/crypto/mlkem/tests/mlkem_tests.rs b/crypto/mlkem/tests/mlkem_tests.rs index c24abd2..d5aa274 100644 --- a/crypto/mlkem/tests/mlkem_tests.rs +++ b/crypto/mlkem/tests/mlkem_tests.rs @@ -3,7 +3,9 @@ mod mlkem_tests { use bouncycastle_core::errors::KEMError; use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; - use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength, XOF}; + use bouncycastle_core::traits::{ + KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength, XOF, + }; use bouncycastle_hex as hex; use bouncycastle_mlkem::{MLKEM_RND_LEN, MLKEM512, MLKEM768, MLKEM1024, Polynomial}; use bouncycastle_mlkem::{ @@ -41,9 +43,9 @@ mod mlkem_tests { let tf = TestFrameworkKEM::new(false, true); - tf.test_kem::(false); - tf.test_kem::(false); - tf.test_kem::(false); + tf.test_kem::(MLKEM512::keygen, false); + tf.test_kem::(MLKEM768::keygen, false); + tf.test_kem::(MLKEM1024::keygen, false); } /// This runs the full bitflipping tests and takes about 30s.. diff --git a/crypto/mlkem/tests/wycheproof.rs b/crypto/mlkem/tests/wycheproof.rs index ee3ddc9..a141e63 100644 --- a/crypto/mlkem/tests/wycheproof.rs +++ b/crypto/mlkem/tests/wycheproof.rs @@ -21,7 +21,7 @@ #![allow(dead_code)] use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength}; +use bouncycastle_core::traits::{KEMDecapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; use bouncycastle_mlkem::{ MLKEM512, MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768, MLKEM768PrivateKey, diff --git a/mem_usage_benches/bench_mlkem_mem_usage.rs b/mem_usage_benches/bench_mlkem_mem_usage.rs index bd93ac4..99aafbc 100644 --- a/mem_usage_benches/bench_mlkem_mem_usage.rs +++ b/mem_usage_benches/bench_mlkem_mem_usage.rs @@ -26,12 +26,9 @@ #![allow(unused_imports)] use bouncycastle::core::key_material::{KeyMaterial512, KeyType}; -use bouncycastle::core::traits::{KEM, KEMPublicKey}; -use bouncycastle::mlkem::mlkem::MLKEMTrait; -use bouncycastle::mlkem::{ - MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM768_CT_LEN, MLKEM768_PK_LEN, MLKEM1024_CT_LEN, - MLKEM1024_PK_LEN, -}; +use bouncycastle::core::traits::{KEMDecapsulator, KEMEncapsulator, KEMPublicKey}; +use bouncycastle::mlkem::mlkem::{MLKEMTrait}; +use bouncycastle::mlkem::{MLKEM1024_CT_LEN, MLKEM1024_PK_LEN, MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM768_CT_LEN, MLKEM768_PK_LEN}; /// This exists so I can use /usr/bin/time to measure the base memory footprint of the cargo bench harness fn bench_do_nothing() { From de25c0120f4b81122e6f3cf4b1d148db87cfb5d3 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sat, 13 Jun 2026 14:39:34 -0500 Subject: [PATCH 9/9] Exposed inner functions of ml-kem for crucible tests --- crypto/mldsa-lowmemory/src/lib.rs | 4 ---- crypto/mldsa/src/lib.rs | 2 -- crypto/mlkem-lowmemory/src/lib.rs | 4 ---- crypto/mlkem-lowmemory/src/mlkem.rs | 5 +++++ crypto/mlkem/src/aux_functions.rs | 16 ++++++++++------ crypto/mlkem/src/lib.rs | 8 ++------ crypto/mlkem/src/mlkem.rs | 20 ++++++-------------- crypto/mlkem/src/polynomial.rs | 18 ++++++++++-------- 8 files changed, 33 insertions(+), 44 deletions(-) diff --git a/crypto/mldsa-lowmemory/src/lib.rs b/crypto/mldsa-lowmemory/src/lib.rs index a03106a..5c23325 100644 --- a/crypto/mldsa-lowmemory/src/lib.rs +++ b/crypto/mldsa-lowmemory/src/lib.rs @@ -222,10 +222,6 @@ use bouncycastle_core::key_material::KeyMaterialTrait; #[allow(unused_imports)] use bouncycastle_core::traits::{SignatureVerifier, Signer}; -// todo -- re-run mutants - -// todo -- crucible tests - mod aux_functions; pub mod hash_mldsa; mod low_memory_helpers; diff --git a/crypto/mldsa/src/lib.rs b/crypto/mldsa/src/lib.rs index c304171..ba33b1d 100644 --- a/crypto/mldsa/src/lib.rs +++ b/crypto/mldsa/src/lib.rs @@ -134,8 +134,6 @@ use bouncycastle_core::key_material::KeyMaterialTrait; #[allow(unused_imports)] use bouncycastle_core::traits::{SignatureVerifier, Signer}; -// todo -- crucible tests - mod aux_functions; pub mod hash_mldsa; mod matrix; diff --git a/crypto/mlkem-lowmemory/src/lib.rs b/crypto/mlkem-lowmemory/src/lib.rs index 0f70fc5..6435f73 100644 --- a/crypto/mlkem-lowmemory/src/lib.rs +++ b/crypto/mlkem-lowmemory/src/lib.rs @@ -241,10 +241,6 @@ #[allow(unused_imports)] use bouncycastle_core::key_material::KeyMaterialTrait; -// todo -- re-run mutants - -// todo -- crucible tests - mod aux_functions; mod low_memory_helpers; pub mod mlkem; diff --git a/crypto/mlkem-lowmemory/src/mlkem.rs b/crypto/mlkem-lowmemory/src/mlkem.rs index eca60d4..98eca08 100644 --- a/crypto/mlkem-lowmemory/src/mlkem.rs +++ b/crypto/mlkem-lowmemory/src/mlkem.rs @@ -734,6 +734,11 @@ impl< T_PACKED_LEN, > { + /// Performs a decapsulation of the given ciphertext. + /// Returns the shared secret key. + /// The derived shared secret key is returned as a KeyMaterial with the SecurityStrength set to + /// the security level of the ML-KEM parameter set. + /// As ML-KEM is an implicitly-rejecting KEM, this returns an error only if the ciphertext is invalid (ie the wrong length).. fn decaps(sk: &SK, ct: &[u8]) -> Result, KEMError> { if ct.len() != CT_LEN { return Err(KEMError::LengthError("Invalid ciphertext length")); diff --git a/crypto/mlkem/src/aux_functions.rs b/crypto/mlkem/src/aux_functions.rs index 0d331ef..7fd9711 100644 --- a/crypto/mlkem/src/aux_functions.rs +++ b/crypto/mlkem/src/aux_functions.rs @@ -22,9 +22,10 @@ pub(crate) fn expandA(rho: &[u8; 32]) -> Matrix { /// Algorithm 5 ByteEncode_d(𝐹) /// Encodes an array of 𝑑-bit integers into a byte array for 1 ≀ 𝑑 ≀ 12. -/// Input: integer array 𝐹 ∈ β„€_M^256 , where π‘š = 2^𝑑 if 𝑑 < 12, and π‘š = π‘ž if 𝑑 = 12. -/// Output: byte array 𝐡 ∈ 𝔹32𝑑 . -pub(crate) fn byte_encode(F: &Polynomial) -> [u8; PACK_LEN] { +/// Input: integer array 𝐹 ∈ β„€_M^256, where π‘š = 2^𝑑 if 𝑑 < 12, and π‘š = π‘ž if 𝑑 = 12. +/// Output: byte array 𝐡 ∈ 𝔹32𝑑. +/// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. +pub fn byte_encode(F: &Polynomial) -> [u8; PACK_LEN] { debug_assert_eq!(PACK_LEN, 32 * d); let mut B = [0u8; PACK_LEN]; @@ -65,7 +66,8 @@ pub(crate) fn byte_encode(F: &Polynomial) /// Decodes a byte array into an array of 𝑑-bit integers for 1 ≀ 𝑑 ≀ 12. /// Input: byte array 𝐡 ∈ 𝔹32𝑑 . /// Output: integer array 𝐹 ∈ β„€256 , where π‘š = 2𝑑 if 𝑑 < 12 and π‘š = π‘ž if 𝑑 = 12. -pub(crate) fn byte_decode(B: &[u8; PACK_LEN]) -> Polynomial { +/// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. +pub fn byte_decode(B: &[u8; PACK_LEN]) -> Polynomial { debug_assert_eq!(PACK_LEN, 32 * d); let mut F = Polynomial::new(); @@ -85,7 +87,8 @@ pub(crate) fn byte_decode(B: &[u8; PACK_L /// Takes a 32-byte seed and two indices as input and outputs a pseudorandom element of π‘‡π‘ž. /// Input: byte array 𝐡 ∈ 𝔹34 . β–· a 32-byte seed along with two indices /// Output: array π‘Ž_hat ∈ β„€256 β–· the coefficients of the NTT of a polynomial -pub(crate) fn sample_ntt(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { +/// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. +pub fn sample_ntt(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { let mut a_hat = Polynomial::new(); // 1: ctx ← XOF.Init() @@ -154,7 +157,8 @@ pub(crate) fn sample_ntt(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { /// Takes a seed as input and outputs a pseudorandom sample from the distribution Dπœ‚(π‘…π‘ž). /// Input: byte array 𝐡 ∈ 𝔹64πœ‚ . /// Output: array 𝑓 ∈ β„€256 β–· the coefficients of the sampled polynomial -pub(crate) fn sample_poly_cbd(bytes: &[u8]) -> Polynomial { +/// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. +pub fn sample_poly_cbd(bytes: &[u8]) -> Polynomial { debug_assert_eq!(bytes.len(), 64 * eta as usize); let mut f = Polynomial::new(); diff --git a/crypto/mlkem/src/lib.rs b/crypto/mlkem/src/lib.rs index 28142b6..3e4b9c6 100644 --- a/crypto/mlkem/src/lib.rs +++ b/crypto/mlkem/src/lib.rs @@ -155,15 +155,11 @@ #[allow(unused_imports)] use bouncycastle_core::key_material::KeyMaterialTrait; -// todo -- re-run mutants - -// todo -- crucible tests - -mod aux_functions; +pub mod aux_functions; mod matrix; pub mod mlkem; mod mlkem_keys; -mod polynomial; +pub mod polynomial; /*** Exported types ***/ pub use mlkem::{MLKEM, MLKEM512, MLKEM768, MLKEM1024, MLKEMTrait}; diff --git a/crypto/mlkem/src/mlkem.rs b/crypto/mlkem/src/mlkem.rs index 4be646b..d370ea4 100644 --- a/crypto/mlkem/src/mlkem.rs +++ b/crypto/mlkem/src/mlkem.rs @@ -901,29 +901,21 @@ impl< const CT_LEN: usize, const SS_LEN: usize, PK: MLKEMPublicKeyTrait + MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const k: usize, const eta: i16, const du: i16, const dv: i16, const LAMBDA: i16, -> KEMDecapsulator for MLKEM< - PK_LEN, - SK_LEN, - CT_LEN, - SS_LEN, - PK, - SK, - k, - eta, - du, - dv, - LAMBDA, -> { +> KEMDecapsulator + for MLKEM +{ /// Performs a decapsulation of the given ciphertext. /// Returns the shared secret key. /// The derived shared secret key is returned as a KeyMaterial with the SecurityStrength set to /// the security level of the ML-KEM parameter set. + /// As ML-KEM is an implicitly-rejecting KEM, this returns an error only if the ciphertext is invalid (ie the wrong length).. fn decaps(sk: &SK, ct: &[u8]) -> Result, KEMError> { Self::decaps_with_expanded_key( &MLKEMPrivateKeyExpanded::::from(sk), diff --git a/crypto/mlkem/src/polynomial.rs b/crypto/mlkem/src/polynomial.rs index 951cc13..fba4b39 100644 --- a/crypto/mlkem/src/polynomial.rs +++ b/crypto/mlkem/src/polynomial.rs @@ -11,12 +11,11 @@ use crate::mlkem::{N, q}; use bouncycastle_core::traits::Secret; /// A polynomial over the ML-KEM ring. -/// Dev note: this doesn't strictly need to be pub ... ie there's no good reason for a caller to use this class directly, -/// but in order to test the Debug and Display traits, you need STD, so those can't be tested from inline tests in this file -/// and the real unit tests are in a different crate, so here we are. +/// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. #[derive(Clone)] pub struct Polynomial { - pub(crate) coeffs: [i16; N], + /// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. + pub coeffs: [i16; N], } /// Convenience function to avoid ".0" all over the place. @@ -235,7 +234,8 @@ impl Polynomial { /// Computes the NTT representation 𝑓_hat of the given polynomial 𝑓 ∈ π‘…π‘ž. /// Input: array 𝑓 ∈ β„€256 β–· the coefficients of the input polynomial /// Output: array 𝑓_hat ∈ β„€256 β–· the coefficients of the NTT of the input polynomial - pub(crate) fn ntt(&mut self) { + /// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. + pub fn ntt(&mut self) { let mut len = 128; let mut k = 1; @@ -261,8 +261,9 @@ impl Polynomial { /// Computes the polynomial 𝑓 ∈ π‘…π‘ž that corresponds to the given NTT representation 𝑓 ∈ π‘‡π‘ž. /// Input: array 𝑓 ∈ β„€256 β–· the coefficients of input NTT representation /// Output: array 𝑓 ∈ β„€256 β–· the coefficients of the inverse NTT of the input - pub(crate) fn inv_ntt(&mut self) { - // FIPS 203 ALg 10 wants you to copy f_hat into f, and then act of f + /// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. + pub fn inv_ntt(&mut self) { + // FIPS 203 Alg 10 wants you to copy f_hat into f, and then act on f // but we're going to do this in-place for memory-saving reasons. let mut len = 2; @@ -299,7 +300,8 @@ impl Polynomial { /// /// Borrowed from: /// https://github.com/pq-crystals/kyber/blob/main/ref/poly.c#L290 -pub(crate) fn base_mult_montgomery(a: &Polynomial, b: &Polynomial) -> Polynomial { +/// Note: this is exposed publicly only for testing purposes and there is no good reason to use it in production code. +pub fn base_mult_montgomery(a: &Polynomial, b: &Polynomial) -> Polynomial { let mut r = Polynomial::new(); for i in 0..(N / 4) {