diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index afb4af1..9926d0d 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 @@ -52,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 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/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-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/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/core/src/traits.rs b/crypto/core/src/traits.rs index 089e282..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>; @@ -418,21 +451,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 +505,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 +526,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 +628,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>; @@ -575,35 +668,6 @@ pub trait Signature< 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-lowmemory/benches/mldsa_benches.rs b/crypto/mldsa-lowmemory/benches/mldsa_benches.rs index ec12a86..ec82468 100644 --- a/crypto/mldsa-lowmemory/benches/mldsa_benches.rs +++ b/crypto/mldsa-lowmemory/benches/mldsa_benches.rs @@ -1,11 +1,11 @@ use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -use bouncycastle_core::traits::Signature; +use std::hint::black_box; +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, }; use criterion::{Criterion, criterion_group, criterion_main}; -use std::hint::black_box; fn bench_mldsa_keygen(c: &mut Criterion) { let mut group = c.benchmark_group("KeyGen"); 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..5c23325 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,11 +220,7 @@ #[allow(unused_imports)] use bouncycastle_core::key_material::KeyMaterialTrait; #[allow(unused_imports)] -use bouncycastle_core::traits::Signature; - -// todo -- re-run mutants - -// todo -- crucible tests +use bouncycastle_core::traits::{SignatureVerifier, Signer}; mod aux_functions; pub mod hash_mldsa; 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..c15ef82 100644 --- a/crypto/mldsa/benches/mldsa_benches.rs +++ b/crypto/mldsa/benches/mldsa_benches.rs @@ -1,5 +1,6 @@ use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -use bouncycastle_core::traits::Signature; +use std::hint::black_box; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ MLDSA44, MLDSA44_SIG_LEN, MLDSA44PrivateKeyExpanded, MLDSA44PublicKeyExpanded, MLDSA65, @@ -8,7 +9,6 @@ use bouncycastle_mldsa::{ MLDSATrait, }; use criterion::{Criterion, criterion_group, criterion_main}; -use std::hint::black_box; fn bench_mldsa_keygen(c: &mut Criterion) { let mut group = c.benchmark_group("KeyGen"); diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 52fbdc8..b7e3988 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,10 +93,11 @@ 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}; +use bouncycastle_sha2::{SHA256, SHA256_NAME, SHA512, SHA512_NAME}; use core::marker::PhantomData; // Imports needed only for docs @@ -126,7 +129,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 +162,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 +195,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 +228,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 +261,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 +294,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 +329,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 +377,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 +405,6 @@ impl< HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, @@ -434,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::< @@ -460,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], @@ -471,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], @@ -484,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], @@ -495,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], @@ -574,7 +600,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); @@ -611,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); } @@ -652,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], @@ -697,7 +736,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 +821,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, @@ -796,11 +845,10 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> Signature +> Signer for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, @@ -824,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> { @@ -944,7 +965,60 @@ impl< unreachable!() } } +} +impl< + HASH: Hash + Algorithm + Default, + PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait + + MLDSAPrivateKeyInternalTrait, + const PH_LEN: usize, + 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, + 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); @@ -981,9 +1055,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, @@ -1006,11 +1079,10 @@ impl< const GAMMA1_MASK_LEN: usize, const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, -> PHSignature +> PHSigner for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, SIG_LEN, @@ -1061,7 +1133,60 @@ 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 + Algorithm + Default, + const PH_LEN: usize, + 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, + 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 06e37f9..ba33b1d 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"; @@ -115,9 +114,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,18 +123,16 @@ // 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)] use bouncycastle_core::key_material::KeyMaterialTrait; #[allow(unused_imports)] -use bouncycastle_core::traits::Signature; - -// todo -- re-run mutants - -// todo -- crucible tests +use bouncycastle_core::traits::{SignatureVerifier, Signer}; mod aux_functions; pub mod hash_mldsa; 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/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..6435f73 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(); @@ -242,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 3d75cb3..98eca08 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,46 @@ 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, + > +{ + /// 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-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/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 4de7d42..3e4b9c6 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(); @@ -156,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 bd31e43..d370ea4 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,11 +893,29 @@ 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 /// 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) { 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/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, 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; 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() {