From 3baa30b5751078b995c9a5c7cda349d79dda1211 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Thu, 4 Jun 2026 11:56:07 -0500 Subject: [PATCH 01/35] squash merge of ML-DSA and ML-KEM testing work --- QUALITY_AND_STYLE.md | 88 +- alpha_0.1.2_release_notes.md | 50 +- crypto/core-test-framework/src/signature.rs | 38 +- crypto/mldsa/Cargo.toml | 1 + crypto/mldsa/src/aux_functions.rs | 46 +- crypto/mldsa/src/hash_mldsa.rs | 52 +- crypto/mldsa/src/mldsa.rs | 32 +- crypto/mldsa/src/mldsa_keys.rs | 183 +- crypto/mldsa/src/polynomial.rs | 10 +- crypto/mldsa/tests/bc_test_data.rs | 1559 ++++++++++------- crypto/mldsa/tests/hash_mldsa_tests.rs | 114 +- crypto/mldsa/tests/mldsa_key_tests.rs | 278 ++- crypto/mldsa/tests/mldsa_tests.rs | 96 +- crypto/mldsa/tests/wycheproof.rs | 1119 ++++++++++++ crypto/mldsa_lowmemory/Cargo.toml | 1 + crypto/mldsa_lowmemory/src/aux_functions.rs | 51 +- crypto/mldsa_lowmemory/src/hash_mldsa.rs | 99 +- .../mldsa_lowmemory/src/low_memory_helpers.rs | 52 +- crypto/mldsa_lowmemory/src/mldsa.rs | 706 +++++--- crypto/mldsa_lowmemory/src/mldsa_keys.rs | 413 +++-- crypto/mldsa_lowmemory/src/polynomial.rs | 74 +- crypto/mldsa_lowmemory/tests/bc_test_data.rs | 1491 +++++++++------- .../mldsa_lowmemory/tests/hash_mldsa_tests.rs | 239 ++- .../mldsa_lowmemory/tests/mldsa_key_tests.rs | 116 +- crypto/mldsa_lowmemory/tests/mldsa_tests.rs | 389 ++-- crypto/mldsa_lowmemory/tests/wycheproof.rs | 756 ++++++++ crypto/mlkem/src/aux_functions.rs | 89 +- crypto/mlkem/src/mlkem_keys.rs | 356 ++-- crypto/mlkem/src/polynomial.rs | 48 +- crypto/mlkem/tests/bc_test_data.rs | 145 +- crypto/mlkem/tests/mlkem_key_tests.rs | 182 +- crypto/mlkem_lowmemory/src/aux_functions.rs | 31 +- crypto/mlkem_lowmemory/src/polynomial.rs | 102 +- .../mlkem_lowmemory/tests/mlkem_key_tests.rs | 66 +- 34 files changed, 6403 insertions(+), 2669 deletions(-) create mode 100644 crypto/mldsa/tests/wycheproof.rs create mode 100644 crypto/mldsa_lowmemory/tests/wycheproof.rs diff --git a/QUALITY_AND_STYLE.md b/QUALITY_AND_STYLE.md index 94036d0..2937ec7 100644 --- a/QUALITY_AND_STYLE.md +++ b/QUALITY_AND_STYLE.md @@ -1,75 +1,119 @@ This document lists general quality and style guidelines used across the library. Hint: ask an AI to help review your PR against this style guide. - # Architecture The Bounce Castle Rust project should be broken up into individual modular crates named `bouncycastle_*`. -The project aims to be completely self-contained with zero external dependencies in the runtime code. External dependencies are ok in test or benchmarking code. +The project aims to be completely self-contained with zero external dependencies in the runtime code. External +dependencies are ok in test or benchmarking code. lib.rs for all crates needs to contain: `#![forbid(missing_docs)]`, `#![no_std]`. - All primitives must be accompanied by a CLI in `/cli`. - # Quality ## Tests -All crates must have tests in `src/tests`. Part of writing code that treats future maintainers as malicious is that all functions that form part of the public interface should have their expected behaviour fully constrained with tests. In other words, any behaviour change of the library that could cause a change in a calling application should also cause a test in bc-rust to fail. An excellent tool for achieving this is `cargo mutants` which must be run on every crate and each failed mutant must be investigated. We do not require `cargo mutants` to be clean because it's reasonably common, especially in low-level crypto code, that there are multiple correct ways to write the same code; for example where swapping an OR for an XOR results in functionally equivalent code. +All crates must have tests in `src/tests`. Part of writing code that treats future maintainers as malicious is that all +functions that form part of the public interface should have their expected behaviour fully constrained with tests. In +other words, any behaviour change of the library that could cause a change in a calling application should also cause a +test in bc-rust to fail. An excellent tool for achieving this is `cargo mutants` which must be run on every crate and +each failed mutant must be investigated. We do not require `cargo mutants` to be clean because it's reasonably common, +especially in low-level crypto code, that there are multiple correct ways to write the same code; for example where +swapping an OR for an XOR results in functionally equivalent code. -Where the behaviour of a function is critical to test but cannot be tested from outside the crate because it is on a private function, in-line tests in the source file should be used. +Where the behaviour of a function is critical to test but cannot be tested from outside the crate because it is on a +private function, in-line tests in the source file should be used. -All traits in `bouncycastle-core` must have corresponding tests in `bouncycastle-core-test-framework` that exercise all behaviours and error conditions that are comment to all implementations of that trait. +All traits in `bouncycastle-core` must have corresponding tests in `bouncycastle-core-test-framework` that exercise all +behaviours and error conditions that are comment to all implementations of that trait. +All crypto algorithms must have tests against the bc-test-data repo and against wycheproof. ## Performance Benchmarks -Any crate that contains an algorithm were runtime matters must have cargo-compatible performance benchmarks in a `/benches` folder. +Any crate that contains an algorithm were runtime matters must have cargo-compatible performance benchmarks in a +`/benches` folder. -The benches must cover all algorithms. If there are multiple variants of an algorithm with different performance characteristics (such as with pre-expanded keys), then these must each be benchmarked separately. Separate benchmarks should not be written for different APIs for accessing the same underlying implementation; such as one-shot and streaming APIs that use the same core algorithm implementation. +The benches must cover all algorithms. If there are multiple variants of an algorithm with different performance +characteristics (such as with pre-expanded keys), then these must each be benchmarked separately. Separate benchmarks +should not be written for different APIs for accessing the same underlying implementation; such as one-shot and +streaming APIs that use the same core algorithm implementation. ## Stack Usage Benchmarks -Bouncy Castle Rust cares about the peak stack memory usage of its algorithms. Crates should be accompanied by a memory usage test harness in `/mem_usage_benches`. +Bouncy Castle Rust cares about the peak stack memory usage of its algorithms. Crates should be accompanied by a memory +usage test harness in `/mem_usage_benches`. # Style -Part of writing code that treats future maintainers as malicious is good inline comments. Anything even remotely tricky, or where naive modification would put it out of alignment with, for example, sample code in an RFC or FIPS spec should be commented line-by-line with the corresponding lines from the spec. This also helps with code review and certification. Any deviations from the spec should be noted and explained / justified. A good rule-of-thumb is to ask yourself whether this function would take 6-months-from-now-you more than 10 minutes to understand thoroughly, and are there comments you could add that would help future you get back up to speed faster about what this code is doing and which parts were done for a very specific reason and should not be changed on a whim. - +Part of writing code that treats future maintainers as malicious is good inline comments. Anything even remotely tricky, +or where naive modification would put it out of alignment with, for example, sample code in an RFC or FIPS spec should +be commented line-by-line with the corresponding lines from the spec. This also helps with code review and +certification. Any deviations from the spec should be noted and explained / justified. A good rule-of-thumb is to ask +yourself whether this function would take 6-months-from-now-you more than 10 minutes to understand thoroughly, and are +there comments you could add that would help future you get back up to speed faster about what this code is doing and +which parts were done for a very specific reason and should not be changed on a whim. ## APIs -Where possible, primitives should expose "one-shot APIs" that simply take data and return a result as a static member function that does not require object instantiation. +Where possible, primitives should expose "one-shot APIs" that simply take data and return a result as a static member +function that does not require object instantiation. -Other version of Bouncy Castle have a design pattern where stateful objects follow a pattern of new() -> init() -> do_update() -> do_final(), and then optionally reset() that sets the object back to an unitialized state. Instead, bc-rust does not have init() functions (moving this logic into new() or from() as appropriate), and consequently it also does not have reset(). Also, we take advantage of the rust borrow checker's syntax so that all do_final() functions are actually final, in other words they must take ownership of self `do_final(self, ...)` so that no subsequent calls can be made to this object (as opposed to the usual pattern of taking a ref to self as in `do_update(&self, ...)`). These tricks go a long way to reducing fallibility since now in general there is no (or very very little) object state to track and return errors about. +Other version of Bouncy Castle have a design pattern where stateful objects follow a pattern of new() -> init() -> +do_update() -> do_final(), and then optionally reset() that sets the object back to an unitialized state. Instead, +bc-rust does not have init() functions (moving this logic into new() or from() as appropriate), and consequently it also +does not have reset(). Also, we take advantage of the rust borrow checker's syntax so that all do_final() functions are +actually final, in other words they must take ownership of self `do_final(self, ...)` so that no subsequent calls can be +made to this object (as opposed to the usual pattern of taking a ref to self as in `do_update(&self, ...)`). These +tricks go a long way to reducing fallibility since now in general there is no (or very very little) object state to +track and return errors about. Any struct that holds sensitive data must impl the `core::Secret` trait and all associated super-traits. ## Fallibility -As much as humanly possible, Result and unwrap() should be used for "Bad input data" type things and not "Programmer didn't read the docs" type things. +As much as humanly possible, Result and unwrap() should be used for "Bad input data" type things and not "Programmer +didn't read the docs" type things. -`.unwrap()` causes system crashes. The use of `.unwrap()` should always be preceeded by testing that we're in a state where we know the call will succeed, or else there should be an inline comment explaining why the `.unwrap()` will always succeed. +`.unwrap()` causes system crashes. The use of `.unwrap()` should always be preceeded by testing that we're in a state +where we know the call will succeed, or else there should be an inline comment explaining why the `.unwrap()` will +always succeed. -Also, we want to avoid forcing users of the library from needing excessive amounts of `.unwrap()`. To this end, any function that returns a `Result` should be inspected closely to ensure that +Also, we want to avoid forcing users of the library from needing excessive amounts of `.unwrap()`. To this end, any +function that returns a `Result` should be inspected closely to ensure that -Therefore, public APIs should aim to avoid the use of Result if it is not strictly needed. This generally means that returning a `Result` is only used for instances where bad data was handed in to the function, or where something unrecoverable happened, like the internal RNG failed to initialize. `Result` must never be thrown out of convenience to the maintainer of bc-rust -- instead, get creative about how to check for and resolve error conditions within the function so that valid input will always produce valid output. Also, the rust language has a lot of features for turning runtime error conditions into compile-time error conditions. For example, if you find yourself taking in a reference to bytes `in: &[u8]` and then checking its length `if in.len() != LEN { return Err() }`, stop and instead change the function signature to `in: &[u8; LEN]` so that it is simply impossible for the caller to hand you data of the wrong length (this also has a small performance benefit since you don't need to do that if-check). In other contexts it might be possible to use rust typing system to track state change of an object instead of carrying a member variable that tracks it. +Therefore, public APIs should aim to avoid the use of Result if it is not strictly needed. This generally means that +returning a `Result` is only used for instances where bad data was handed in to the function, or where something +unrecoverable happened, like the internal RNG failed to initialize. `Result` must never be thrown out of convenience to +the maintainer of bc-rust -- instead, get creative about how to check for and resolve error conditions within the +function so that valid input will always produce valid output. Also, the rust language has a lot of features for turning +runtime error conditions into compile-time error conditions. For example, if you find yourself taking in a reference to +bytes `in: &[u8]` and then checking its length `if in.len() != LEN { return Err() }`, stop and instead change the +function signature to `in: &[u8; LEN]` so that it is simply impossible for the caller to hand you data of the wrong +length (this also has a small performance benefit since you don't need to do that if-check). In other contexts it might +be possible to use rust typing system to track state change of an object instead of carrying a member variable that +tracks it. -Use `./dev_scripts/quality_stats.sh` to see the fallibility metrics for the crate you're working on and try to get those numbers down. +Use `./dev_scripts/quality_stats.sh` to see the fallibility metrics for the crate you're working on and try to get those +numbers down. # Docs ## Usage Examples -The crate docs needs a section "Usage Examples" with sample code for all the major usage patterns of the primitives in the crate. +The crate docs needs a section "Usage Examples" with sample code for all the major usage patterns of the primitives in +the crate. ## Memory Usage -The crate docs needs a section "Memory Usage" with a table of the stack memory usage of each algorithm or primitive in the crate. +The crate docs needs a section "Memory Usage" with a table of the stack memory usage of each algorithm or primitive in +the crate. ## Security Considerations -Most crates should have a "Security Considerations" section that documents any footguns where the user of this crate could undermine their own security; for example where providing a seed or a nonce that is not truly random would completely undermine the algorithm. \ No newline at end of file +Most crates should have a "Security Considerations" section that documents any footguns where the user of this crate +could undermine their own security; for example where providing a seed or a nonce that is not truly random would +completely undermine the algorithm. \ No newline at end of file diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index 652f08f..41a6069 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -1,57 +1,21 @@ # TODO - [remove this section before publication] [ ] EdDSA -[ ] ML-DSA & ML-KEM -* Look a close look at Kris' ICMC slides on low_memory -* lowmemory: Play with packing Polynomials into 12 / 23 bits. -* Polynomial (and maybe Matrix and Vec) might want custom indexing -https://doc.rust-lang.org/core/ops/trait.Index.html -https://doc.rust-lang.org/core/ops/trait.IndexMut.html -* Check the crate release checklist -* Run Crucible testing -* Run Wycheproof tests -[ ] Check out Megan's email May 13: "I was wondering if there might be scope for a closure based approach that could -guarantee encapsulation of the state change from safe to hazardous back to safe again." -[ ] Anywhere that you have an `_out(.. out: &mut [u8])`, start by zeroizing it with .fill(0); .. a good task for Claude? -[ ] Go back to previous algs and apply memory optimization tricks like unnamed scopes. And add a docs section "Memory -Usage" that measures with valgrind. +[ ] ML-DSA + EdDSA Composite [ ] Ensure that all crates have `#![forbid(missing_docs)]` [ ] Apply Secret trait consistently across the library --> study the `Zeroize` trait in RustCrypto [ ] Change all "[u8;0]" to "[]" throughout the code and docs ... or better yet, change the APIs to take an Option<> -[ ] Change all `-> Vec` to `-> [u8; CONST_LEN]`, and the `output: &mut [u8]` to `output: &mut [u8; CONST_LEN]` where -appropriate. -* After doing that, do I even need the _out() versions? Since everything is no_std now. -* Probably it makes sense to leave Hex and Base64 as requiring std; ... or maybe add a no_std version that uses -fixed-sized blocks? -[ ] For all higher-level algorithms, put a cargo #[cfg(feature='rng')] around the keygen that takes an rng so that the -dependency on bouncycastle_rng is optional. +[ ] For all higher-level algorithms, put a cargo #[cfg(feature='rng')] around the keygen that takes an rng so that the dependency on bouncycastle_rng is optional. [ ] Enhance the default HashDRBG instantiation to take in NIST-compatible CPU jitter entropy -[ ] Get an opinion from Bob Beck or Dennis about the factories ... Are they worth it? Michael Richardson says Very Yes. - -* Add factories for ML-DSA and ML-KEM - [ ] Add back the Memoable trait from nursery (maybe under a different name) that lets you serialize out the - intermediate state, especially important for SHA2, SHA3, and HMAC because TLS needs to be able to fork a state, - finalize() a copy and then keep feeding the other copy. -* Add unit tests. - [ ] Make crypto one crate - [ ] Do some science about perf impacts of acting on a local hard-copy vs acting in-place on some specific bit of - memory -* Bob suggests: feed a function in question to GodBolt. - [ ] Change the tone of the documentation (both the crate docs and the inline comments) to be less individual ("I" - statements) and be more factual ("it is", or "the project", or "the bc-rust library" as appropriate). - [ ] Relax the requirement on XOF that once you start squeezing, you can't absorb anymore. ... this might need to be - specifically forbidden in FIPS mode. - [ ] Do a pass over KeyMaterial, taking into account Dennis Jackson's input (maybe ping him for a phone call?) - [ ] Need a rust expert: I use a bunch of #![feature(_)]'s that are only available in nightly. ... what should I do - about that? - [ ] Deal with as many of the inline TODOs as possible - [ ] Open github issues +[ ] Get an opinion from Bob Beck or Dennis about the factories ... Are they worth it? +[ ] Do a pass over KeyMaterial, taking into account Dennis Jackson's input (maybe ping him for a phone call?) +[ ] Need a rust expert: I use a bunch of #![feature(_)]'s that are only available in nightly. ... what should I do about that? +[ ] Open github issues # 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. * Github issues resolved: - * #2, or whatever \ No newline at end of file + * #2, or whatever \ No newline at end of file diff --git a/crypto/core-test-framework/src/signature.rs b/crypto/core-test-framework/src/signature.rs index 1ee694e..d97dc0c 100644 --- a/crypto/core-test-framework/src/signature.rs +++ b/crypto/core-test-framework/src/signature.rs @@ -1,6 +1,8 @@ use crate::DUMMY_SEED_1024; use bouncycastle_core::errors::SignatureError; -use bouncycastle_core::traits::{PHSignature, Signature, SignaturePrivateKey, SignaturePublicKey}; +use bouncycastle_core::traits::{ + Hash, PHSignature, Signature, SignaturePrivateKey, SignaturePublicKey, +}; pub struct TestFrameworkSignature { // Put any config options here @@ -106,7 +108,7 @@ 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 = SigAlg::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(); @@ -151,20 +153,19 @@ impl TestFrameworkSignature { assert_eq!(bytes_written, SIG_LEN); SigAlg::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(); } - /// Test all the members of trait Hash against the given input-output pair. /// This gives good baseline test coverage, but is not exhaustive. pub fn test_ph_signature< PK: SignaturePublicKey, SK: SignaturePrivateKey, SigAlg: PHSignature, + HASH: Hash + Default, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -243,7 +244,6 @@ impl TestFrameworkSignature { let sig = SigAlg::sign(&sk, DUMMY_SEED_1024, None).unwrap(); SigAlg::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); @@ -251,15 +251,30 @@ impl TestFrameworkSignature { Err(SignatureError::LengthError(_)) => (), _ => panic!("Unexpected error"), } + + // sign_ph + let (pk, sk) = SigAlg::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(); + + // sign_ph_out + let (pk, sk) = SigAlg::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(); + assert_eq!(bytes_written, SIG_LEN); + SigAlg::verify_ph(&pk, &ph, None, &sig_val).unwrap(); + SigAlg::verify(&pk, msg, None, &sig_val).unwrap(); } } pub struct TestFrameworkSignatureKeys {} impl TestFrameworkSignatureKeys { - pub fn new() -> Self { - Self { } + Self {} } pub fn test_keys< @@ -269,7 +284,9 @@ impl TestFrameworkSignatureKeys { const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - >(&self) { + >( + &self, + ) { self.test_boundary_conditions::(); } @@ -281,7 +298,9 @@ impl TestFrameworkSignatureKeys { const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, - >(&self) { + >( + &self, + ) { let (pk, sk) = SigAlg::keygen().unwrap(); let pk_bytes = pk.encode(); @@ -300,7 +319,6 @@ impl TestFrameworkSignatureKeys { _ => panic!("Should have failed"), } - let sk_bytes = sk.encode(); assert_eq!(sk_bytes.len(), SK_LEN); // too short diff --git a/crypto/mldsa/Cargo.toml b/crypto/mldsa/Cargo.toml index 8828d9d..cddc8c6 100644 --- a/crypto/mldsa/Cargo.toml +++ b/crypto/mldsa/Cargo.toml @@ -15,6 +15,7 @@ bouncycastle-core-test-framework.workspace = true bouncycastle-hex.workspace = true bouncycastle-rng.workspace = true criterion.workspace = true +serde_json = "1.0" [[bench]] name = "mldsa_benches" diff --git a/crypto/mldsa/src/aux_functions.rs b/crypto/mldsa/src/aux_functions.rs index 720a480..8303063 100644 --- a/crypto/mldsa/src/aux_functions.rs +++ b/crypto/mldsa/src/aux_functions.rs @@ -1,7 +1,7 @@ //! Implements auxiliary functions for ML-DSA as defined in Section 7 of FIPS 204. use crate::matrix::{Matrix, Vector}; -use crate::mldsa::{q_inv, G, H}; +use crate::mldsa::{G, H, q_inv}; use crate::mldsa::{ MLDSA44_GAMMA1, MLDSA44_GAMMA2, MLDSA65_GAMMA1, MLDSA65_GAMMA2, N, POLY_T0PACKED_LEN, POLY_T1PACKED_LEN, d, q, @@ -24,6 +24,7 @@ pub(crate) fn coeff_from_three_bytes(b: &[u8; 3]) -> Result { let z: i32 = ((b2_prime as i32) << 16) | ((b[1] as i32) << 8) | (b[0] as i32); + // mutants note: yeah, this could be z <= q and nothing changes because z == q is the same as z == 0 if z < q { Ok(z) } else { Err(()) } } @@ -308,8 +309,7 @@ pub(crate) fn bit_unpack_t0(a: &[u8; POLY_T0PACKED_LEN]) -> Polynomial { t0[8 * i + 6] = ((((a[13 * i + 9] as i32) >> 6) | (a[13 * i + 10] as i32) << 2) | ((a[13 * i + 11] as i32) << 10)) & 0x1FFF; - t0[8 * i + 7] = - (((a[13 * i + 11] as i32) >> 3) | ((a[13 * i + 12] as i32) << 5)) & 0x1FFF; + t0[8 * i + 7] = (((a[13 * i + 11] as i32) >> 3) | ((a[13 * i + 12] as i32) << 5)) & 0x1FFF; t0[8 * i] = (1 << (d - 1)) - t0[8 * i]; t0[8 * i + 1] = (1 << (d - 1)) - t0[8 * i + 1]; @@ -464,6 +464,8 @@ pub(crate) fn sig_decode< // β–· reconstruct 𝐑[𝑖] for i in 0..k { // 4: if 𝑦[πœ” + 𝑖] < Index or 𝑦[πœ” + 𝑖] > πœ” then return βŠ₯ + // todo: this needs a specific test for malformed signature values. Maybe crucible coveres this case? + // ... could hide an assert here and see if it triggers. if sig[pos + (OMEGA as usize) + i] < (idx as u8) || sig[pos + (OMEGA as usize) + i] > OMEGA as u8 { @@ -816,8 +818,8 @@ pub(crate) fn decompose(r: i32) -> (i32, i32) { r1 = r - r0 * 2 * GAMMA2; // mutants note: the choice of (q - 1) is a bit arbitrary in that after doing the bit-shifting, - // this seems to work out mathematically equivalent if you do q/2, or (q+3)/2, but we'll leave it as (q-1)/2 - // since that's algorithmically correct, and just ignore the mutants results. + // this seems to work out mathematically equivalent if you do q/2, or (q+3)/2, but we'll leave it as (q-1)/2 + // since that's algorithmically correct, and just ignore the mutants results. r1 -= (((q - 1) / 2 - r1) >> 31) & q; (r0, r1) @@ -860,13 +862,8 @@ pub(crate) fn make_hint(z: i32, r: i32) -> i32 { // if r1 != v1 { 1 } else { 0 } // By the powers of someone much more clever than me, this is equivalent. - - if z <= GAMMA2 || z > q - GAMMA2 || (z == q - GAMMA2 && r == 0) - { - 0 - } else { - 1 - } + // mutants note: we do not have KATs that exercise all branches of this if + if z <= GAMMA2 || z > q - GAMMA2 || (z == q - GAMMA2 && r == 0) { 0 } else { 1 } } /// Creates the hint vector from two Vector's, and also returns its hamming weight (ie the number of 1's). @@ -880,6 +877,8 @@ pub(crate) fn make_hint_vecs( for i in 0..k { let (w, c) = r.vec[i].make_hint::(&s.vec[i]); out.vec[i] = w; + + // mutants note: this chains up to hint_hamming_weight > OMEGA and there is no test KAT that triggers this branch count += c; } @@ -913,11 +912,7 @@ pub(super) fn use_hint(a: i32, hint: i32) -> i32 { MLDSA65_GAMMA2 => { // mutants note: this passes unit tests if it's a1 >= 0 // we'll leave it like this because it matches the spec - if a1 > 0 { - (a0 + 1) & 15 - } else { - (a0 - 1) & 15 - } + if a1 > 0 { (a0 + 1) & 15 } else { (a0 - 1) & 15 } } _ => { panic!("Invalid GAMMA2 value") @@ -967,7 +962,7 @@ pub(crate) fn multiply_ntt(a: &Polynomial, b: &Polynomial) -> Polynomial { /// of expressions of the form c = a * b (mod q). /// The output is not necessarily less than q in absolute value, but it is less than 2q in absolute value pub(crate) fn montgomery_reduce(a: i64) -> i32 { - debug_assert!(a > - ((q as i64) <<31) && a < ((q as i64) <<31)); + debug_assert!(a > -((q as i64) << 31) && a < ((q as i64) << 31)); // 2: 𝑑 ← ((π‘Ž mod 2^32) β‹… QINV) mod 2^32 let t: i32 = (a as i32).wrapping_mul(q_inv); @@ -980,23 +975,21 @@ pub(crate) fn conditional_add_q(a: i32) -> i32 { a + ((a >> 31) & q) } - #[test] /// These are the results it's giving; I'm not sure if these are "correct" or not. fn test_conditional_add_q() { - assert_eq!(conditional_add_q(-q -1), -1); + assert_eq!(conditional_add_q(-q - 1), -1); assert_eq!(conditional_add_q(-q), 0); - assert_eq!(conditional_add_q(-q -2), -2); - assert_eq!(conditional_add_q(-q +1), 1); - assert_eq!(conditional_add_q(-1), q-1); + assert_eq!(conditional_add_q(-q - 2), -2); + assert_eq!(conditional_add_q(-q + 1), 1); + assert_eq!(conditional_add_q(-1), q - 1); assert_eq!(conditional_add_q(0), 0); assert_eq!(conditional_add_q(1), 1); - assert_eq!(conditional_add_q(q -1), q-1); + assert_eq!(conditional_add_q(q - 1), q - 1); assert_eq!(conditional_add_q(q), q); - assert_eq!(conditional_add_q(q +1), q+1); + assert_eq!(conditional_add_q(q + 1), q + 1); } - /// Constants for NTT pub(crate) const ZETAS: [i32; 256] = [ 0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468, 1826347, 2353451, -359251, @@ -1026,4 +1019,3 @@ pub(crate) const ZETAS: [i32; 256] = [ -2235985, -420899, -2286327, 183443, -976891, 1612842, -3545687, -554416, 3919660, -48306, -1362209, 3937738, 1400424, -846154, 1976782, ]; - diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index c4fa85d..780bd76 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -145,9 +145,9 @@ pub type HashMLDSA44_with_SHA256 = HashMLDSA< MLDSA44_POLY_Z_PACKED_LEN, MLDSA44_POLY_W1_PACKED_LEN, MLDSA44_LAMBDA_over_4, - MLDSA44_GAMMA1_MASK_LEN, MLDSA44_GAMMA1_MINUS_BETA, MLDSA44_GAMMA2_MINUS_BETA, + MLDSA44_GAMMA1_MASK_LEN, >; impl Algorithm for HashMLDSA44_with_SHA256 { @@ -179,9 +179,9 @@ pub type HashMLDSA65_with_SHA256 = HashMLDSA< MLDSA65_POLY_Z_PACKED_LEN, MLDSA65_POLY_W1_PACKED_LEN, MLDSA65_LAMBDA_over_4, - MLDSA65_GAMMA1_MASK_LEN, MLDSA65_GAMMA1_MINUS_BETA, MLDSA65_GAMMA2_MINUS_BETA, + MLDSA65_GAMMA1_MASK_LEN, >; impl Algorithm for HashMLDSA65_with_SHA256 { @@ -213,9 +213,9 @@ pub type HashMLDSA87_with_SHA256 = HashMLDSA< MLDSA87_POLY_Z_PACKED_LEN, MLDSA87_POLY_W1_PACKED_LEN, MLDSA87_LAMBDA_over_4, - MLDSA87_GAMMA1_MASK_LEN, MLDSA87_GAMMA1_MINUS_BETA, MLDSA87_GAMMA2_MINUS_BETA, + MLDSA87_GAMMA1_MASK_LEN, >; impl Algorithm for HashMLDSA87_with_SHA256 { @@ -247,9 +247,9 @@ pub type HashMLDSA44_with_SHA512 = HashMLDSA< MLDSA44_POLY_Z_PACKED_LEN, MLDSA44_POLY_W1_PACKED_LEN, MLDSA44_LAMBDA_over_4, - MLDSA44_GAMMA1_MASK_LEN, MLDSA44_GAMMA1_MINUS_BETA, MLDSA44_GAMMA2_MINUS_BETA, + MLDSA44_GAMMA1_MASK_LEN, >; impl Algorithm for HashMLDSA44_with_SHA512 { @@ -281,9 +281,9 @@ pub type HashMLDSA65_with_SHA512 = HashMLDSA< MLDSA65_POLY_Z_PACKED_LEN, MLDSA65_POLY_W1_PACKED_LEN, MLDSA65_LAMBDA_over_4, - MLDSA65_GAMMA1_MASK_LEN, MLDSA65_GAMMA1_MINUS_BETA, MLDSA65_GAMMA2_MINUS_BETA, + MLDSA65_GAMMA1_MASK_LEN, >; impl Algorithm for HashMLDSA65_with_SHA512 { @@ -315,9 +315,9 @@ pub type HashMLDSA87_with_SHA512 = HashMLDSA< MLDSA87_POLY_Z_PACKED_LEN, MLDSA87_POLY_W1_PACKED_LEN, MLDSA87_LAMBDA_over_4, - MLDSA87_GAMMA1_MASK_LEN, MLDSA87_GAMMA1_MINUS_BETA, MLDSA87_GAMMA2_MINUS_BETA, + MLDSA87_GAMMA1_MASK_LEN, >; impl Algorithm for HashMLDSA87_with_SHA512 { @@ -354,9 +354,9 @@ pub struct HashMLDSA< 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, + const GAMMA1_MASK_LEN: usize, > { _phantom: PhantomData<(PK, SK)>, @@ -429,9 +429,9 @@ impl< POLY_Z_PACKED_LEN, POLY_W1_PACKED_LEN, LAMBDA_over_4, - GAMMA1_MASK_LEN, GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, > { /// Imports a secret key from a seed. @@ -455,9 +455,9 @@ impl< POLY_Z_PACKED_LEN, POLY_W1_PACKED_LEN, LAMBDA_over_4, - GAMMA1_MASK_LEN, GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, >::keygen_internal(seed) } /// Same as [Signature::sign], but signs from an [MLDSAPrivateKeyExpanded]. @@ -597,9 +597,9 @@ impl< POLY_Z_PACKED_LEN, POLY_W1_PACKED_LEN, LAMBDA_over_4, - GAMMA1_MASK_LEN, GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, >::sign_mu_deterministic_out(sk, A_hat, &mu, rnd, output)?; Ok(bytes_written) @@ -720,9 +720,9 @@ impl< POLY_Z_PACKED_LEN, POLY_W1_PACKED_LEN, LAMBDA_over_4, - GAMMA1_MASK_LEN, GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, >::verify_mu_internal(pk, A_hat, &mu, sig_sized) { Ok(()) @@ -750,9 +750,9 @@ impl< POLY_Z_PACKED_LEN, POLY_W1_PACKED_LEN, LAMBDA_over_4, - GAMMA1_MASK_LEN, GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, >::verify_mu_internal(pk, &pk.A_hat(), &mu, sig_sized) { Ok(()) @@ -787,9 +787,9 @@ impl< 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, + const GAMMA1_MASK_LEN: usize, > Signature for HashMLDSA< HASH, @@ -813,9 +813,9 @@ impl< POLY_Z_PACKED_LEN, POLY_W1_PACKED_LEN, LAMBDA_over_4, - GAMMA1_MASK_LEN, GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, > { /// Keygen, and keys in general, are interchangeable between MLDSA and HashMLDSA. @@ -839,9 +839,9 @@ impl< POLY_Z_PACKED_LEN, POLY_W1_PACKED_LEN, LAMBDA_over_4, - GAMMA1_MASK_LEN, GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, >::keygen() } @@ -898,21 +898,9 @@ impl< )); } - if output.len() < SIG_LEN { - return Err(SignatureError::LengthError( - "Output buffer insufficient size to hold signature", - )); - } - let output_sized: &mut [u8; SIG_LEN] = output[..SIG_LEN].as_mut().try_into().unwrap(); - if self.sk.is_some() { if self.signer_rnd.is_none() { - Self::sign_ph_out( - &self.sk.unwrap(), - &ph, - Some(&self.ctx[..self.ctx_len]), - output_sized, - ) + Self::sign_ph_out(&self.sk.unwrap(), &ph, Some(&self.ctx[..self.ctx_len]), output) } else { Self::sign_ph_deterministic_out( &self.sk.unwrap(), @@ -920,7 +908,7 @@ impl< Some(&self.ctx[..self.ctx_len]), &ph, self.signer_rnd.unwrap(), - output_sized, + output, ) } } else if self.seed.is_some() { @@ -940,7 +928,7 @@ impl< Some(&self.ctx[..self.ctx_len]), &ph, rnd, - output_sized, + output, ) } else { unreachable!() @@ -1031,9 +1019,9 @@ impl< POLY_Z_PACKED_LEN, POLY_W1_PACKED_LEN, LAMBDA_over_4, - GAMMA1_MASK_LEN, GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, > { fn sign_ph( @@ -1042,7 +1030,7 @@ impl< ctx: Option<&[u8]>, ) -> Result<[u8; SIG_LEN], SignatureError> { let mut out = [0u8; SIG_LEN]; - Self::sign_out(sk, ph, ctx, &mut out)?; + Self::sign_ph_out(sk, ph, ctx, &mut out)?; Ok(out) } diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index d634272..d207f01 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -566,9 +566,9 @@ pub type MLDSA44 = MLDSA< MLDSA44_POLY_Z_PACKED_LEN, MLDSA44_POLY_W1_PACKED_LEN, MLDSA44_LAMBDA_over_4, - MLDSA44_GAMMA1_MASK_LEN, MLDSA44_GAMMA1_MINUS_BETA, MLDSA44_GAMMA2_MINUS_BETA, + MLDSA44_GAMMA1_MASK_LEN, >; impl Algorithm for MLDSA44 { @@ -596,9 +596,9 @@ pub type MLDSA65 = MLDSA< MLDSA65_POLY_Z_PACKED_LEN, MLDSA65_POLY_W1_PACKED_LEN, MLDSA65_LAMBDA_over_4, - MLDSA65_GAMMA1_MASK_LEN, MLDSA65_GAMMA1_MINUS_BETA, MLDSA65_GAMMA2_MINUS_BETA, + MLDSA65_GAMMA1_MASK_LEN, >; impl Algorithm for MLDSA65 { @@ -626,9 +626,9 @@ pub type MLDSA87 = MLDSA< MLDSA87_POLY_Z_PACKED_LEN, MLDSA87_POLY_W1_PACKED_LEN, MLDSA87_LAMBDA_over_4, - MLDSA87_GAMMA1_MASK_LEN, MLDSA87_GAMMA1_MINUS_BETA, MLDSA87_GAMMA2_MINUS_BETA, + MLDSA87_GAMMA1_MASK_LEN, >; impl Algorithm for MLDSA87 { @@ -659,9 +659,9 @@ pub struct MLDSA< const POLY_VEC_H_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, + const GAMMA1_MASK_LEN: usize, > { _phantom: PhantomData<(PK, SK)>, @@ -700,9 +700,9 @@ impl< 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, + const GAMMA1_MASK_LEN: usize, > MLDSA< PK_LEN, @@ -723,9 +723,9 @@ impl< POLY_Z_PACKED_LEN, POLY_W1_PACKED_LEN, LAMBDA_over_4, - GAMMA1_MASK_LEN, GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, > { /// Should still be ok in FIPS mode @@ -888,6 +888,7 @@ impl< loop { // FIPS 204 s. 6.2 allows: // "Implementations may limit the number of iterations in this loop to not exceed a finite maximum value." + // mutants note: there is no test for this because we don't know of a KAT that will exceed this limit. if kappa > 1000 * k as u16 { return Err(SignatureError::GenericError( "Rejection sampling loop exceeded max iterations, try again with a different signing nonce.", @@ -971,13 +972,14 @@ impl< continue; }; - // 25: βŸ¨βŸ¨π‘π­0⟩⟩ ← NTTβˆ’1(𝑐_hat * 𝐭0Μ‚_hat ) + // 25: βŸ¨βŸ¨π‘π­0⟩⟩ ← NTTβˆ’1(𝑐_hat * 𝐭0Μ‚_hat) let mut ct0 = sk.t0_hat().scalar_vector_ntt(&c_hat); ct0.inv_ntt(); // 28 (first half): if ||βŸ¨βŸ¨π‘π­0⟩⟩||∞ β‰₯ 𝛾2 or the number of 1’s in 𝐑 is greater than πœ”, then (z, h) ← βŠ₯ // out-of-order on purpose for performance reasons: // might as well do the rejection sampling check before any extra heavy computation + // mutants note: there is currently no unit test that triggers this branch if ct0.check_norm::() { kappa += l as u16; continue; @@ -996,6 +998,7 @@ impl< }; // 28 (second half): if ||βŸ¨βŸ¨π‘π­0⟩⟩||∞ β‰₯ 𝛾2 or the number of 1’s in 𝐑 is greater than πœ”, then (z, h) ← βŠ₯ + // mutants note: there is no test KAT that triggers this branch if hint_hamming_weight > OMEGA { kappa += l as u16; continue; @@ -1040,9 +1043,9 @@ impl< 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, + const GAMMA1_MASK_LEN: usize, > MLDSATrait for MLDSA< PK_LEN, @@ -1063,9 +1066,9 @@ impl< POLY_Z_PACKED_LEN, POLY_W1_PACKED_LEN, LAMBDA_over_4, - GAMMA1_MASK_LEN, GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, > { fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError> { @@ -1217,6 +1220,8 @@ impl< rnd: [u8; 32], output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + // This has been kept as clean as possible for correspondence with the FIPS, // but things have been moved around so that unnamed scopes can be used to limit how many // stack variables are alive at the same time. @@ -1310,6 +1315,7 @@ impl< loop { // FIPS 204 s. 6.2 allows: // "Implementations may limit the number of iterations in this loop to not exceed a finite maximum value." + // mutants note: there is no test for this because we don't know of a KAT that will exceed this limit. if kappa > 1000 * k as u16 { return Err(SignatureError::GenericError( "Rejection sampling loop exceeded max iterations, try again with a different signing nonce.", @@ -1444,6 +1450,7 @@ impl< // Alg 7; 28 (first half): if ||βŸ¨βŸ¨π‘π­0⟩⟩||∞ β‰₯ 𝛾2 or the number of 1’s in 𝐑 is greater than πœ”, then (z, h) ← βŠ₯ // out-of-order on purpose for performance reasons: // might as well do the rejection sampling check before any extra heavy computation + // mutants note: there is currently no unit test that triggers this branch if ct0.check_norm::() { kappa += l as u16; continue; @@ -1462,6 +1469,7 @@ impl< }; // Alg 7; 28 (second half): if ||βŸ¨βŸ¨π‘π­0⟩⟩||∞ β‰₯ 𝛾2 or the number of 1’s in 𝐑 is greater than πœ”, then (z, h) ← βŠ₯ + // mutants note: there is currently no unit test that triggers this branch if hint_hamming_weight > OMEGA { kappa += l as u16; continue; @@ -1877,9 +1885,9 @@ impl< 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, + const GAMMA1_MASK_LEN: usize, > Signature for MLDSA< PK_LEN, @@ -1900,9 +1908,9 @@ impl< POLY_Z_PACKED_LEN, POLY_W1_PACKED_LEN, LAMBDA_over_4, - GAMMA1_MASK_LEN, GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, > { fn keygen() -> Result<(PK, SK), SignatureError> { @@ -1990,7 +1998,7 @@ impl< if sig.len() != SIG_LEN { return Err(SignatureError::LengthError("Signature value is not the correct length.")); } - if Self::verify_mu_internal(pk, &pk.A_hat(), &mu, &sig[..SIG_LEN].try_into().unwrap()) { + if Self::verify_mu_internal(pk, &pk.A_hat(), &mu, &sig.try_into().unwrap()) { Ok(()) } else { Err(SignatureError::SignatureVerificationFailed) diff --git a/crypto/mldsa/src/mldsa_keys.rs b/crypto/mldsa/src/mldsa_keys.rs index 708ca49..96a3a5d 100644 --- a/crypto/mldsa/src/mldsa_keys.rs +++ b/crypto/mldsa/src/mldsa_keys.rs @@ -92,6 +92,8 @@ impl MLDSAPublicKey usize { + out.fill(0); + out[0..32].copy_from_slice(&self.rho); let (pk_chunks, last_chunk) = out[32..].as_chunks_mut::(); @@ -420,6 +422,73 @@ pub struct MLDSAPrivateKey< seed: Option>, } +impl + MLDSAPrivateKey +{ + /// Algorithm 24 skEncode(𝜌, 𝐾, π‘‘π‘Ÿ, 𝐬1, 𝐬2, 𝐭0) + /// Encodes a secret key for ML-DSA into a byte string. + /// Input: 𝜌 ∈ 𝔹32, 𝐾 ∈ 𝔹32, π‘‘π‘Ÿ ∈ 𝔹64 , 𝐬1 ∈ 𝑅ℓ with coefficients in [βˆ’πœ‚, πœ‚], 𝐬2 ∈ π‘…π‘˜ with + /// coefficients in [βˆ’πœ‚, πœ‚], 𝐭0 ∈ π‘…π‘˜ with coefficients in [βˆ’2π‘‘βˆ’1 + 1, 2π‘‘βˆ’1]. + /// Output: Private key π‘ π‘˜ ∈ 𝔹32+32+64+32β‹…((π‘˜+β„“)β‹…bitlen (2πœ‚)+π‘‘π‘˜). + fn sk_encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { + // counter of progress along the output buffer + let mut off: usize = 0; + + out[0..32].copy_from_slice(&self.rho); + out[32..64].copy_from_slice(&self.K); + out[64..128].copy_from_slice(&self.tr); + off += 128; + + let mut buf = [0u8; 32 * 4]; // largest possible buffer + let eta_pack_len = bitlen_eta(eta); + + let sk_chunks = out[off..off + l * bitlen_eta(eta)].chunks_mut(bitlen_eta(eta)); + debug_assert_eq!(sk_chunks.len(), l); + for (sk_chunk, s1_hat_i) in sk_chunks.into_iter().zip(&self.s1_hat.vec) { + // Deviation from the FIPS: + // We are holding these in ntt form, so need to convert back to standard form + let mut s1_hat_i = s1_hat_i.clone(); + s1_hat_i.reduce(); + s1_hat_i.inv_ntt(); + let s1_i = s1_hat_i; + + bit_pack_eta::(&s1_i, &mut buf); + sk_chunk.copy_from_slice(&buf[..eta_pack_len]); + } + off += l * bitlen_eta(eta); + + let sk_chunks = out[off..off + k * bitlen_eta(eta)].chunks_mut(bitlen_eta(eta)); + debug_assert_eq!(sk_chunks.len(), k); + for (sk_chunk, s2_hat_i) in sk_chunks.into_iter().zip(&self.s2_hat.vec) { + // Deviation from the FIPS: + // We are holding these in ntt form, so need to convert back to standard form + let mut s2_hat_i = s2_hat_i.clone(); + s2_hat_i.reduce(); + s2_hat_i.inv_ntt(); + let s2_i = s2_hat_i; + + bit_pack_eta::(&s2_i, &mut buf); + sk_chunk.copy_from_slice(&buf[..eta_pack_len]); + } + off += k * bitlen_eta(eta); + + let sk_chunks = out[off..off + k * POLY_T0PACKED_LEN].chunks_mut(POLY_T0PACKED_LEN); + debug_assert_eq!(sk_chunks.len(), k); + for (sk_chunk, t0_hat_i) in sk_chunks.into_iter().zip(&self.t0_hat.vec) { + // Deviation from the FIPS: + // We are holding these in ntt form, so need to convert back to standard form + let mut t0_hat_i = t0_hat_i.clone(); + t0_hat_i.reduce(); + t0_hat_i.inv_ntt(); + let t0_i = t0_hat_i; + + sk_chunk.copy_from_slice(&bit_pack_t0(&t0_i)); + } + + SK_LEN + } +} + /// General trait for all ML-DSA private keys types. pub trait MLDSAPrivateKeyTrait< const k: usize, @@ -430,7 +499,7 @@ pub trait MLDSAPrivateKeyTrait< >: SignaturePrivateKey { /// Get a ref to the seed, if there is one stored with this private key - fn seed(&self) -> &Option>; + fn seed(&self) -> Option<&KeyMaterial<32>>; /// Get a ref to the key hash `tr`. fn tr(&self) -> &[u8; 64]; @@ -440,18 +509,6 @@ pub trait MLDSAPrivateKeyTrait< /// This is a partial implementation of keygen_internal(), and probably not allowed in FIPS mode. fn derive_pk(&self) -> MLDSAPublicKey; - /// Algorithm 24 skEncode(𝜌, 𝐾, π‘‘π‘Ÿ, 𝐬1, 𝐬2, 𝐭0) - /// Encodes a secret key for ML-DSA into a byte string. - /// Input: 𝜌 ∈ 𝔹32, 𝐾 ∈ 𝔹32, π‘‘π‘Ÿ ∈ 𝔹64 , 𝐬1 ∈ 𝑅ℓ with coefficients in [βˆ’πœ‚, πœ‚], 𝐬2 ∈ π‘…π‘˜ with - /// coefficients in [βˆ’πœ‚, πœ‚], 𝐭0 ∈ π‘…π‘˜ with coefficients in [βˆ’2π‘‘βˆ’1 + 1, 2π‘‘βˆ’1]. - /// Output: Private key π‘ π‘˜ ∈ 𝔹32+32+64+32β‹…((π‘˜+β„“)β‹…bitlen (2πœ‚)+π‘‘π‘˜). - fn sk_encode(&self) -> [u8; SK_LEN]; - /// Algorithm 24 skEncode(𝜌, 𝐾, π‘‘π‘Ÿ, 𝐬1, 𝐬2, 𝐭0) - /// Encodes a secret key for ML-DSA into a byte string. - /// Input: 𝜌 ∈ 𝔹32, 𝐾 ∈ 𝔹32, π‘‘π‘Ÿ ∈ 𝔹64 , 𝐬1 ∈ 𝑅ℓ with coefficients in [βˆ’πœ‚, πœ‚], 𝐬2 ∈ π‘…π‘˜ with - /// coefficients in [βˆ’πœ‚, πœ‚], 𝐭0 ∈ π‘…π‘˜ with coefficients in [βˆ’2π‘‘βˆ’1 + 1, 2π‘‘βˆ’1]. - /// Output: Private key π‘ π‘˜ ∈ 𝔹32+32+64+32β‹…((π‘˜+β„“)β‹…bitlen (2πœ‚)+π‘‘π‘˜). - fn sk_encode_out(&self, out: &mut [u8; SK_LEN]) -> usize; /// Algorithm 25 skDecode(π‘ π‘˜) /// Reverses the procedure skEncode. /// Input: Private key π‘ π‘˜ ∈ 𝔹32+32+64+32β‹…((β„“+π‘˜)β‹…bitlen (2πœ‚)+π‘‘π‘˜). @@ -495,8 +552,11 @@ pub(crate) trait MLDSAPrivateKeyInternalTrait< impl MLDSAPrivateKeyTrait for MLDSAPrivateKey { - fn seed(&self) -> &Option> { - &self.seed + fn seed(&self) -> Option<&KeyMaterial<32>> { + match self.seed { + Some(_) => self.seed.as_ref(), + None => None, + } } fn tr(&self) -> &[u8; 64] { @@ -538,79 +598,6 @@ impl::new(self.rho.clone(), t1) } - /// Algorithm 24 skEncode(𝜌, 𝐾, π‘‘π‘Ÿ, 𝐬1, 𝐬2, 𝐭0) - /// Encodes a secret key for ML-DSA into a byte string. - /// Input: 𝜌 ∈ 𝔹32, 𝐾 ∈ 𝔹32, π‘‘π‘Ÿ ∈ 𝔹64 , 𝐬1 ∈ 𝑅ℓ with coefficients in [βˆ’πœ‚, πœ‚], 𝐬2 ∈ π‘…π‘˜ with - /// coefficients in [βˆ’πœ‚, πœ‚], 𝐭0 ∈ π‘…π‘˜ with coefficients in [βˆ’2π‘‘βˆ’1 + 1, 2π‘‘βˆ’1]. - /// Output: Private key π‘ π‘˜ ∈ 𝔹32+32+64+32β‹…((π‘˜+β„“)β‹…bitlen (2πœ‚)+π‘‘π‘˜). - fn sk_encode(&self) -> [u8; SK_LEN] { - let mut out = [0u8; SK_LEN]; - let bytes_written = self.sk_encode_out(&mut out); - debug_assert_eq!(bytes_written, SK_LEN); - out - } - /// Algorithm 24 skEncode(𝜌, 𝐾, π‘‘π‘Ÿ, 𝐬1, 𝐬2, 𝐭0) - /// Encodes a secret key for ML-DSA into a byte string. - /// Input: 𝜌 ∈ 𝔹32, 𝐾 ∈ 𝔹32, π‘‘π‘Ÿ ∈ 𝔹64 , 𝐬1 ∈ 𝑅ℓ with coefficients in [βˆ’πœ‚, πœ‚], 𝐬2 ∈ π‘…π‘˜ with - /// coefficients in [βˆ’πœ‚, πœ‚], 𝐭0 ∈ π‘…π‘˜ with coefficients in [βˆ’2π‘‘βˆ’1 + 1, 2π‘‘βˆ’1]. - /// Output: Private key π‘ π‘˜ ∈ 𝔹32+32+64+32β‹…((π‘˜+β„“)β‹…bitlen (2πœ‚)+π‘‘π‘˜). - fn sk_encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { - // counter of progress along the output buffer - let mut off: usize = 0; - - out[0..32].copy_from_slice(&self.rho); - out[32..64].copy_from_slice(&self.K); - out[64..128].copy_from_slice(&self.tr); - off += 128; - - let mut buf = [0u8; 32 * 4]; // largest possible buffer - let eta_pack_len = bitlen_eta(eta); - - let sk_chunks = out[off..off + l * bitlen_eta(eta)].chunks_mut(bitlen_eta(eta)); - debug_assert_eq!(sk_chunks.len(), l); - for (sk_chunk, s1_hat_i) in sk_chunks.into_iter().zip(&self.s1_hat.vec) { - // Deviation from the FIPS: - // We are holding these in ntt form, so need to convert back to standard form - let mut s1_hat_i = s1_hat_i.clone(); - s1_hat_i.reduce(); - s1_hat_i.inv_ntt(); - let s1_i = s1_hat_i; - - bit_pack_eta::(&s1_i, &mut buf); - sk_chunk.copy_from_slice(&buf[..eta_pack_len]); - } - off += l * bitlen_eta(eta); - - let sk_chunks = out[off..off + k * bitlen_eta(eta)].chunks_mut(bitlen_eta(eta)); - debug_assert_eq!(sk_chunks.len(), k); - for (sk_chunk, s2_hat_i) in sk_chunks.into_iter().zip(&self.s2_hat.vec) { - // Deviation from the FIPS: - // We are holding these in ntt form, so need to convert back to standard form - let mut s2_hat_i = s2_hat_i.clone(); - s2_hat_i.reduce(); - s2_hat_i.inv_ntt(); - let s2_i = s2_hat_i; - - bit_pack_eta::(&s2_i, &mut buf); - sk_chunk.copy_from_slice(&buf[..eta_pack_len]); - } - off += k * bitlen_eta(eta); - - let sk_chunks = out[off..off + k * POLY_T0PACKED_LEN].chunks_mut(POLY_T0PACKED_LEN); - debug_assert_eq!(sk_chunks.len(), k); - for (sk_chunk, t0_hat_i) in sk_chunks.into_iter().zip(&self.t0_hat.vec) { - // Deviation from the FIPS: - // We are holding these in ntt form, so need to convert back to standard form - let mut t0_hat_i = t0_hat_i.clone(); - t0_hat_i.reduce(); - t0_hat_i.inv_ntt(); - let t0_i = t0_hat_i; - - sk_chunk.copy_from_slice(&bit_pack_t0(&t0_i)); - } - - SK_LEN - } fn sk_decode(sk: &[u8; SK_LEN]) -> Result { let rho = sk[0..32].try_into().unwrap(); let K = sk[32..64].try_into().unwrap(); @@ -725,7 +712,11 @@ impl for MLDSAPrivateKey { fn encode(&self) -> [u8; SK_LEN] { - self.sk_encode() + let mut out = [0u8; SK_LEN]; + let bytes_written = self.sk_encode_out(&mut out); + debug_assert_eq!(bytes_written, SK_LEN); + + out } fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { @@ -753,8 +744,8 @@ impl { fn eq(&self, other: &Self) -> bool { - let self_encoded = self.sk_encode(); - let other_encoded = other.sk_encode(); + let self_encoded = self.encode(); + let other_encoded = other.encode(); bouncycastle_utils::ct::ct_eq_bytes(self_encoded.as_ref(), other_encoded.as_ref()) } } @@ -1006,7 +997,7 @@ impl< > MLDSAPrivateKeyTrait for MLDSAPrivateKeyExpanded { - fn seed(&self) -> &Option> { + fn seed(&self) -> Option<&KeyMaterial<32>> { self.sk.seed() } @@ -1022,14 +1013,6 @@ impl< self.sk.derive_pk() } - fn sk_encode(&self) -> [u8; SK_LEN] { - self.sk.sk_encode() - } - - fn sk_encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { - self.sk.sk_encode_out(out) - } - fn sk_decode(sk: &[u8; SK_LEN]) -> Result { let sk1 = SK::sk_decode(sk)?; let A_hat = sk1.derive_pk().A_hat(); diff --git a/crypto/mldsa/src/polynomial.rs b/crypto/mldsa/src/polynomial.rs index b419b61..d98836f 100644 --- a/crypto/mldsa/src/polynomial.rs +++ b/crypto/mldsa/src/polynomial.rs @@ -40,7 +40,7 @@ impl Polynomial { *x = conditional_add_q(*x); } } - + pub(crate) fn reduce(&mut self) { for i in 0..N { self[i] = montgomery_reduce(self[i] as i64); @@ -84,15 +84,15 @@ impl Polynomial { // Fine that this is not constant-time (returns true early) because it is used in a rejection loop. // IE the early quit here leads to rejection and continuing to the top of the rejection loop, or failing the signature validation. // So the i32 that we just checked in a non-constant-time manner is about to get thrown away. - + // Note: this formulation of the check_norm function usually requires this bounds check // if bound > (q - 1) / 8 { // return true; // } // but since BOUND is a constant here, we'll just do a debug_assert to make sure the value is what we expect. debug_assert!(BOUND <= (q - 1) / 8); - - + + let mut t: i32; for x in self.coeffs.iter() { t = *x >> 31; @@ -118,6 +118,8 @@ impl Polynomial { for i in 0..N { let x = make_hint::(self[i], r[i]); out[i] = x; + + // mutants note: this chains up to hint_hamming_weight > OMEGA and there is no test KAT that triggers this branch count += x; } diff --git a/crypto/mldsa/tests/bc_test_data.rs b/crypto/mldsa/tests/bc_test_data.rs index a504188..1b305c1 100644 --- a/crypto/mldsa/tests/bc_test_data.rs +++ b/crypto/mldsa/tests/bc_test_data.rs @@ -8,650 +8,923 @@ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::traits::XOF; use bouncycastle_sha3::SHAKE256; -// commenting out because mutants copies to a /tmp dir, and so the relative links to ../bc-test-data break #[cfg(test)] -// mod bc_test_data { -// use std::{fs}; -// use std::path::Path; -// use bouncycastle_core::errors::SignatureError; -// use bouncycastle_hex as hex; -// use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial256, KeyType}; -// use bouncycastle_core::traits::{Hash, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey}; -// use bouncycastle_mldsa::{HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA512, HashMLDSA87_with_SHA512, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyTrait, MLDSATrait, MLDSA44, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA65, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA87, MLDSA87_PK_LEN, MLDSA87_SK_LEN}; -// use bouncycastle_sha2::SHA512; -// use crate::BustedMuBuilder; -// -// const TEST_DATA_PATH_RELATIVE: &str = "../../../bc-test-data/pqc/crypto/mldsa"; -// const TEST_DATA_PATH: &str = "../bc-test-data/pqc/crypto/mldsa"; -// -// // commenting out because mutants copies to a /tmp dir, and so the relative links to ../bc-test-data break -// #[test] -// #[allow(non_snake_case)] -// fn ML_DSA_keyGen() { -// let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { -// fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/ML-DSA-keyGen.txt").unwrap() -// } else if Path::new(TEST_DATA_PATH).exists() { -// fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-keyGen.txt").unwrap() -// } else { -// println!("Current working directory: {:?}", std::env::current_dir().unwrap()); -// panic!("Test data directory not found") -// }; -// -// let test_cases = KeyGenTestCase::parse(contents); -// -// for test_case in test_cases { -// test_case.run(); -// } -// } -// -// #[derive(Clone)] -// struct KeyGenTestCase { -// vs_id: u32, -// algorithm: String, -// mode: String, -// revision: String, -// is_sample: bool, -// tg_id: u32, -// test_type: String, -// parameter_set: String, -// tc_id: u32, -// seed: String, -// pk: String, -// sk: String, -// } -// -// impl KeyGenTestCase { -// fn new() -> Self { -// Self{ vs_id: 0, algorithm: String::new(), mode: String::new(), revision: String::new(), is_sample: false, tg_id: 0, test_type: String::new(), parameter_set: String::new(), tc_id: 0, seed: String::new(), pk: String::new(), sk: String::new()} -// } -// -// fn is_full(&self) -> bool { -// !self.algorithm.is_empty() -// } -// -// fn parse(data: String) -> Vec { -// let mut test_cases = Vec::::new(); -// let mut test_case = KeyGenTestCase::new(); -// for line in data.lines() { -// let (tag, value) = match line.split_once(" = ") { -// Some(pair) => pair, -// None => { -// if test_case.is_full() { test_cases.push(test_case.clone()); } -// continue; -// } -// }; -// -// match tag { -// "vsId" => test_case.vs_id = value.parse().unwrap(), -// "algorithm" => test_case.algorithm = value.to_string(), -// "mode" => test_case.mode = value.to_string(), -// "revision" => test_case.revision = value.to_string(), -// "isSample" => test_case.is_sample = value.parse().unwrap(), -// "tgId" => test_case.tg_id = value.parse().unwrap(), -// "testType" => test_case.test_type = value.to_string(), -// "parameterSet" => test_case.parameter_set = value.to_string(), -// "tcId" => test_case.tc_id = value.parse().unwrap(), -// "seed" => test_case.seed = value.to_string(), -// "pk" => test_case.pk = value.to_string(), -// "sk" => test_case.sk = value.to_string(), -// val => panic!("Invalid tag: {}", val), -// } -// } -// -// test_cases -// } -// -// fn run(&self) { -// assert_eq!(self.mode, "keyGen"); -// -// let mut seed = KeyMaterial256::from_bytes_as_type( -// &hex::decode(&self.seed).unwrap(), -// KeyType::Seed, -// ).unwrap(); -// // for the purposes of the test cases, accept an all-zero seed -// seed.allow_hazardous_operations(); -// seed.set_key_type(KeyType::Seed).unwrap(); -// seed.set_security_strength(SecurityStrength::_256bit).unwrap(); -// seed.drop_hazardous_operations(); -// -// match self.parameter_set.as_str() { -// "ML-DSA-44" => { -// let (pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLDSA44_PK_LEN] = hex::decode(&self.pk).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLDSA44_SK_LEN] = hex::decode(&self.sk).unwrap().try_into().unwrap(); -// assert_eq!(sk.encode(), sk_sized); -// }, -// "ML-DSA-65" => { -// let (pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLDSA65_PK_LEN] = hex::decode(&self.pk).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLDSA65_SK_LEN] = hex::decode(&self.sk).unwrap().try_into().unwrap(); -// assert_eq!(sk.encode(), sk_sized); -// }, -// "ML-DSA-87" => { -// let (pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLDSA87_PK_LEN] = hex::decode(&self.pk).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLDSA87_SK_LEN] = hex::decode(&self.sk).unwrap().try_into().unwrap(); -// assert_eq!(sk.encode(), sk_sized); -// }, -// val => panic!("Invalid parameter set: {}", val), -// -// } -// } -// } -// -// // commenting out because mutants copies to a /tmp dir, and so the relative links to ../bc-test-data break -// #[test] -// #[allow(non_snake_case)] -// fn ML_DSA_sigGen() { -// let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { -// fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/ML-DSA-sigGen.txt").unwrap() -// } else { -// fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-sigGen.txt").unwrap() -// }; -// -// let test_cases = SigGenTestCase::parse(contents); -// -// let num_tests = test_cases.len(); -// for test_case in test_cases { -// test_case.run(); -// } -// -// println!("SUCCESS! ML-DSA-sigGen test cases passed: {}!", num_tests); -// } -// -// #[derive(Clone)] -// struct SigGenTestCase { -// vs_id: u32, -// algorithm: String, -// mode: String, -// revision: String, -// is_sample: bool, -// tg_id: u32, -// test_type: String, -// parameter_set: String, -// deterministic: bool, -// tc_id: u32, -// sk: String, -// message: String, -// rnd: String, -// signature: String, -// } -// -// impl SigGenTestCase { -// fn new() -> Self { -// Self{ vs_id: 0, algorithm: String::new(), mode: String::new(), revision: String::new(), is_sample: false, tg_id: 0, test_type: String::new(), parameter_set: String::new(), deterministic: false, tc_id: 0, sk: String::new(), message: String::new(), rnd: String::new(), signature: String::new()} -// } -// -// fn is_full(&self) -> bool { -// !self.algorithm.is_empty() -// } -// -// fn parse(data: String) -> Vec { -// let mut test_cases = Vec::::new(); -// let mut test_case = SigGenTestCase::new(); -// for line in data.lines() { -// let (tag, value) = match line.split_once(" = ") { -// Some(pair) => pair, -// None => { -// if test_case.is_full(){ test_cases.push(test_case.clone()); } -// continue; -// } -// }; -// -// match tag { -// "vsId" => test_case.vs_id = value.parse().unwrap(), -// "algorithm" => test_case.algorithm = value.to_string(), -// "mode" => test_case.mode = value.to_string(), -// "revision" => test_case.revision = value.to_string(), -// "isSample" => test_case.is_sample = value.parse().unwrap(), -// "tgId" => test_case.tg_id = value.parse().unwrap(), -// "testType" => test_case.test_type = value.to_string(), -// "parameterSet" => test_case.parameter_set = value.to_string(), -// "deterministic" => test_case.deterministic = value.parse().unwrap(), -// "tcId" => test_case.tc_id = value.parse().unwrap(), -// "sk" => test_case.sk = value.to_string(), -// "message" => test_case.message = value.to_string(), -// "rnd" => test_case.rnd = value.to_string(), -// "signature" => test_case.signature = value.to_string(), -// val => panic!("Invalid tag: {}", val), -// } -// } -// -// test_cases -// } -// -// fn run(&self) { -// assert_eq!(self.mode, "sigGen"); -// -// let rnd = if self.deterministic{ [0u8; 32] } else { hex::decode(&self.rnd).unwrap().as_slice().try_into().unwrap() }; -// -// match self.parameter_set.as_str() { -// "ML-DSA-44" => { -// let sk = MLDSA44PrivateKey::from_bytes(&hex::decode(&self.sk).unwrap()).unwrap(); -// -// // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() -// // so need to manually compute mu -// // let mu = MLDSA44::compute_mu_from_tr( -// // &hex::decode(&self.message).unwrap(), -// // None, -// // sk.tr(), -// // ).unwrap(); -// let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); -// mb.do_update(&hex::decode(&self.message).unwrap()); -// let mu = mb.do_final(); -// -// let sig = MLDSA44::sign_mu_deterministic(&sk, None, &mu, rnd).unwrap(); -// assert_eq!(&sig, &*hex::decode(&self.signature).unwrap(), "ML-DSA-sigGen params: {}, vsId: {}, tgId: {}, tcId: {}", self.parameter_set, self.vs_id, self.tg_id, self.tc_id); -// }, -// "ML-DSA-65" => { -// let sk = MLDSA65PrivateKey::from_bytes(&hex::decode(&self.sk).unwrap()).unwrap(); -// -// // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() -// // so need to manually compute mu -// // let mu = MLDSA65::compute_mu_from_tr( -// // &hex::decode(&self.message).unwrap(), -// // None, -// // sk.tr(), -// // ).unwrap(); -// let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); -// mb.do_update(&hex::decode(&self.message).unwrap()); -// let mu = mb.do_final(); -// -// let sig = MLDSA65::sign_mu_deterministic(&sk, None, &mu, rnd).unwrap(); -// assert_eq!(&sig, &*hex::decode(&self.signature).unwrap()); -// }, -// "ML-DSA-87" => { -// let sk = MLDSA87PrivateKey::from_bytes(&hex::decode(&self.sk).unwrap()).unwrap(); -// -// // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() -// // so need to manually compute mu -// // let mu = MLDSA87::compute_mu_from_tr( -// // &hex::decode(&self.message).unwrap(), -// // None, -// // sk.tr(), -// // ).unwrap(); -// let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); -// mb.do_update(&hex::decode(&self.message).unwrap()); -// let mu = mb.do_final(); -// -// let sig = MLDSA87::sign_mu_deterministic(&sk, None, &mu, rnd).unwrap(); -// assert_eq!(&sig, &*hex::decode(&self.signature).unwrap()); -// }, -// val => panic!("Invalid parameter set: {}", val), -// -// } -// } -// } -// -// // This is also against the non-compliant mu that doesn't have a ctx, which I don't have an easy way to test -// // #[test] -// #[allow(unused)] -// #[allow(non_snake_case)] -// fn ML_DSA_sigVer() { -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-sigVer.txt").unwrap(); -// let test_cases = SigVerTestCase::parse(contents); -// -// for test_case in test_cases { -// test_case.run(); -// } -// } -// -// #[derive(Clone)] -// struct SigVerTestCase { -// vs_id: u32, -// algorithm: String, -// mode: String, -// revision: String, -// is_sample: bool, -// tg_id: u32, -// test_type: String, -// parameter_set: String, -// pk: String, -// tc_id: u32, -// message: String, -// signature: String, -// test_passed: bool, -// } -// -// impl SigVerTestCase { -// fn new() -> Self { -// Self{ vs_id: 0, algorithm: String::new(), mode: String::new(), revision: String::new(), is_sample: false, tg_id: 0, test_type: String::new(), parameter_set: String::new(), tc_id: 0, pk: String::new(), message: String::new(), signature: String::new(), test_passed: false} -// } -// -// fn is_full(&self) -> bool { -// !self.algorithm.is_empty() -// } -// -// fn parse(data: String) -> Vec { -// let mut test_cases = Vec::::new(); -// let mut test_case = SigVerTestCase::new(); -// for line in data.lines() { -// let (tag, value) = match line.split_once(" = ") { -// Some(pair) => pair, -// None => { -// if test_case.is_full() { test_cases.push(test_case.clone()); } -// continue; -// } -// }; -// -// match tag { -// "vsId" => test_case.vs_id = value.parse().unwrap(), -// "algorithm" => test_case.algorithm = value.to_string(), -// "mode" => test_case.mode = value.to_string(), -// "revision" => test_case.revision = value.to_string(), -// "isSample" => test_case.is_sample = value.parse().unwrap(), -// "tgId" => test_case.tg_id = value.parse().unwrap(), -// "testType" => test_case.test_type = value.to_string(), -// "parameterSet" => test_case.parameter_set = value.to_string(), -// "pk" => test_case.pk = value.to_string(), -// "tcId" => test_case.tc_id = value.parse().unwrap(), -// "message" => test_case.message = value.to_string(), -// "signature" => test_case.signature = value.to_string(), -// "testPassed" => test_case.test_passed = value.parse().unwrap(), -// val => panic!("Invalid tag: {}", val), -// } -// } -// -// test_cases -// } -// -// fn run(&self) { -// assert_eq!(self.mode, "sigVer"); -// -// match self.parameter_set.as_str() { -// "ML-DSA-44" => { -// let pk = MLDSA44PublicKey::from_bytes(&hex::decode(&self.pk).unwrap()).unwrap(); -// -// match MLDSA44::verify(&pk, &hex::decode(&self.message).unwrap(), None, &hex::decode(&self.signature).unwrap()) { -// Ok(()) => if !self.test_passed { panic!("Verification succeeded when it shouldn't have!") }, -// Err(SignatureError::SignatureVerificationFailed) => { if self.test_passed { panic!("Verification failed when it shouldn't have! vsId: {}, tgId: {}, tcId: {}", self.vs_id, self.tg_id, self.tc_id) } }, -// _ => panic!("An unexpected error occurred") -// } -// }, -// "ML-DSA-65" => { -// let pk = MLDSA65PublicKey::from_bytes(&hex::decode(&self.pk).unwrap()).unwrap(); -// -// match MLDSA65::verify(&pk, &hex::decode(&self.message).unwrap(), None, &hex::decode(&self.signature).unwrap()) { -// Ok(()) => if self.test_passed { /* good */ } else { panic!("Verification succeeded when it shouldn't have!") }, -// Err(SignatureError::SignatureVerificationFailed) => { if !self.test_passed {} else { panic!("Verification failed when it should have!") } }, -// _ => panic!("An unexpected error occurred") -// } -// }, -// "ML-DSA-87" => { -// let pk = MLDSA87PublicKey::from_bytes(&hex::decode(&self.pk).unwrap()).unwrap(); -// -// match MLDSA87::verify(&pk, &hex::decode(&self.message).unwrap(), None, &hex::decode(&self.signature).unwrap()) { -// Ok(()) => if self.test_passed { /* good */ } else { panic!("Verification succeeded when it shouldn't have!") }, -// Err(SignatureError::SignatureVerificationFailed) => { if !self.test_passed {} else { panic!("Verification failed when it should have!") } }, -// _ => panic!("An unexpected error occurred") -// } -// }, -// val => panic!("Invalid parameter set: {}", val), -// -// } -// } -// } -// -// // not working, not totally sure why -// // #[test] -// #[allow(unused)] -// #[allow(non_snake_case)] -// fn ML_DSA_rsp() { -// // MLDsa44 -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa44.rsp").unwrap(); -// let test_cases = MldsaRspTestCase::::parse(contents); -// for test_case in test_cases { -// test_case.run("MLDsa44"); -// } -// -// // MLDsa65 -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa65.rsp").unwrap(); -// let test_cases = MldsaRspTestCase::::parse(contents); -// for test_case in test_cases { -// test_case.run("MLDsa65"); -// } -// -// -// // MLDsa87 -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa87.rsp").unwrap(); -// let test_cases = MldsaRspTestCase::::parse(contents); -// for test_case in test_cases { -// test_case.run("MLDsa87"); -// } -// -// -// // MLDsa44 -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa44sha512.rsp").unwrap(); -// let test_cases = MldsaRspTestCase::::parse(contents); -// for test_case in test_cases { -// test_case.run("MLDsa44"); -// } -// -// // MLDsa65 -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa65sha512.rsp").unwrap(); -// let test_cases = MldsaRspTestCase::::parse(contents); -// for test_case in test_cases { -// test_case.run("MlDsa65"); -// } -// -// -// // MLDsa87 -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa87sha512.rsp").unwrap(); -// let test_cases = MldsaRspTestCase::::parse(contents); -// for test_case in test_cases { -// test_case.run("MlDsa87"); -// } -// } -// -// #[derive(Clone)] -// struct MldsaRspTestCase { -// count: u32, -// seed: String, -// mlen: u32, -// msg: String, -// pk: String, -// sk: String, -// smlen: u32, -// sm: String, -// message_hash: String, -// message_prime: String, -// context: String, -// } -// -// impl MldsaRspTestCase { -// fn new() -> Self { -// Self{ count: 0, seed: String::new(), mlen: 0, msg: String::new(), pk: String::new(), sk: String::new(), smlen: 0, sm: String::new(), message_hash: String::new(), message_prime: String::new(), context: String::new()} -// } -// -// fn is_full(&self) -> bool { -// !self.seed.is_empty() -// } -// -// fn parse(data: String) -> Vec> { -// let mut test_cases = Vec::<>::new(); -// let mut test_case = MldsaRspTestCase::new(); -// for line in data.lines() { -// let (tag, value) = match line.split_once(" = ") { -// Some(pair) => pair, -// None => { -// if test_case.is_full() { test_cases.push(test_case.clone()); } -// continue; -// } -// }; -// -// match tag { -// "count" => test_case.count = value.parse().unwrap(), -// "seed" => test_case.seed = value.to_string(), -// "mlen" => test_case.mlen = value.parse().unwrap(), -// "msg" => test_case.msg = value.to_string(), -// "pk" => test_case.pk = value.to_string(), -// "sk" => test_case.sk = value.to_string(), -// "smlen" => test_case.smlen = value.parse().unwrap(), -// "sm" => test_case.sm = value.to_string(), -// "message_hash" => test_case.message_hash = value.to_string(), -// "message_prime" => test_case.message_prime = value.to_string(), -// "context" => { -// test_case.context = value.to_string(); -// if test_case.context == "zero_length" || test_case.context == "none" { -// test_case.context = String::new(); -// } -// }, -// val => panic!("Invalid tag: {}", val), -// } -// } -// -// test_cases -// } -// -// fn run(&self, parameter_set: &str) { -// match parameter_set { -// "MLDsa44" => { -// let mut seed = KeyMaterial256::from_bytes_as_type( -// &hex::decode(&self.seed).unwrap(), -// KeyType::Seed, -// ).unwrap(); -// // for the purposes of the test cases, accept an all-zero seed -// seed.allow_hazardous_operations(); -// seed.set_key_type(KeyType::Seed).unwrap(); -// seed.set_security_strength(SecurityStrength::_256bit).unwrap(); -// seed.drop_hazardous_operations(); -// -// -// let (pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLDSA44_PK_LEN] = hex::decode(&self.pk).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLDSA44_SK_LEN] = hex::decode(&self.sk).unwrap().try_into().unwrap(); -// assert_eq!(sk.encode(), sk_sized); -// -// if IS_HASH_MLDSA { -// // we're only testing SHA512 -// let ph: [u8; 64] = SHA512::new().hash(&hex::decode(&self.msg).unwrap()).as_slice().try_into().unwrap(); -// assert_eq!(ph, &*hex::decode(&self.message_hash).unwrap()); -// -// let sig = HashMLDSA44_with_SHA512::sign_ph_deterministic( -// &sk, None, Some(&*hex::decode(&self.context).unwrap()), &ph, [0u8; 32]).unwrap(); -// assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); -// -// HashMLDSA44_with_SHA512::verify(&pk, -// &*hex::decode(&self.msg).unwrap(), -// Some(&*hex::decode(&self.context).unwrap()), -// &sig).expect(&format!("paramSet: {}, is_hash: {}, count: {}", parameter_set, IS_HASH_MLDSA, self.count)); -// } else { -// // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() -// // so need to manually compute mu -// let mu = MLDSA65::compute_mu_from_tr( -// sk.tr(), -// &hex::decode(&self.msg).unwrap(), -// Some(&hex::decode(&self.context).unwrap()), -// ).unwrap(); -// -// let sig = MLDSA44::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); -// assert_eq!(sig, &*hex::decode(&self.sm).unwrap(), "paramSet: {}, count: {}", parameter_set, self.count); -// -// MLDSA44::verify(&pk, &hex::decode(&self.msg).unwrap(), Some(&hex::decode(&self.context).unwrap()), &sig).unwrap(); -// } -// }, -// "MlDsa65" | "MLDsa65" => { -// let mut seed = KeyMaterial256::from_bytes_as_type( -// &hex::decode(&self.seed).unwrap(), -// KeyType::Seed, -// ).unwrap(); -// // for the purposes of the test cases, accept an all-zero seed -// seed.allow_hazardous_operations(); -// seed.set_key_type(KeyType::Seed).unwrap(); -// seed.set_security_strength(SecurityStrength::_256bit).unwrap(); -// seed.drop_hazardous_operations(); -// -// let (pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLDSA65_PK_LEN] = hex::decode(&self.pk).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLDSA65_SK_LEN] = hex::decode(&self.sk).unwrap().try_into().unwrap(); -// assert_eq!(sk.encode(), sk_sized); -// -// if IS_HASH_MLDSA { -// // we're only testing SHA512 -// let ph: [u8; 64] = SHA512::new().hash(&hex::decode(&self.msg).unwrap()).as_slice().try_into().unwrap(); -// assert_eq!(ph, &*hex::decode(&self.message_hash).unwrap()); -// -// let sig = HashMLDSA65_with_SHA512::sign_ph_deterministic( -// &sk, None, Some(&*hex::decode(&self.context).unwrap()), &ph, [0u8; 32]).unwrap(); -// assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); -// -// HashMLDSA65_with_SHA512::verify(&pk, -// &*hex::decode(&self.message_hash).unwrap(), -// Some(&*hex::decode(&self.context).unwrap()), -// &sig).expect(&format!("paramSet: {}, isHash: {}, count: {}", parameter_set, IS_HASH_MLDSA, self.count)); -// } else { -// // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() -// // so need to manually compute mu -// let mu = MLDSA65::compute_mu_from_tr( -// sk.tr(), -// &hex::decode(&self.msg).unwrap(), -// Some(&hex::decode(&self.context).unwrap()), -// ).unwrap(); -// -// let sig = MLDSA65::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); -// assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); -// -// MLDSA65::verify(&pk, &hex::decode(&self.msg).unwrap(), Some(&hex::decode(&self.context).unwrap()), &sig).unwrap(); -// } -// }, -// "MLDsa87" => { -// let mut seed = KeyMaterial256::from_bytes_as_type( -// &hex::decode(&self.seed).unwrap(), -// KeyType::Seed, -// ).unwrap(); -// // for the purposes of the test cases, accept an all-zero seed -// seed.allow_hazardous_operations(); -// seed.set_key_type(KeyType::Seed).unwrap(); -// seed.set_security_strength(SecurityStrength::_256bit).unwrap(); -// seed.drop_hazardous_operations(); -// -// let (pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLDSA87_PK_LEN] = hex::decode(&self.pk).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLDSA87_SK_LEN] = hex::decode(&self.sk).unwrap().try_into().unwrap(); -// assert_eq!(sk.encode(), sk_sized); -// -// -// if IS_HASH_MLDSA { -// // we're only testing SHA512 -// let ph: [u8; 64] = SHA512::new().hash(&hex::decode(&self.msg).unwrap()).as_slice().try_into().unwrap(); -// assert_eq!(ph, &*hex::decode(&self.message_hash).unwrap()); -// -// let sig = HashMLDSA87_with_SHA512::sign_ph_deterministic( -// &sk, None, Some(&*hex::decode(&self.context).unwrap()), &ph, [0u8; 32]).unwrap(); -// assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); -// -// HashMLDSA87_with_SHA512::verify(&pk, -// &*hex::decode(&self.message_hash).unwrap(), -// Some(&*hex::decode(&self.context).unwrap()), -// &sig).unwrap(); -// } else { -// // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() -// // so need to manually compute mu -// let mu = MLDSA65::compute_mu_from_tr( -// sk.tr(), -// &hex::decode(&self.msg).unwrap(), -// Some(&hex::decode(&self.context).unwrap()), -// ).unwrap(); -// -// let sig = MLDSA87::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); -// assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); -// -// MLDSA87::verify(&pk, &hex::decode(&self.msg).unwrap(), Some(&hex::decode(&self.context).unwrap()), &sig).unwrap(); -// } -// }, -// val => panic!("Invalid parameter set: {}", val), -// } -// } -// } -// } +mod bc_test_data { + use crate::BustedMuBuilder; + use bouncycastle_core::errors::SignatureError; + use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{ + Hash, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + }; + use bouncycastle_hex as hex; + use bouncycastle_mldsa::{ + HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA512, HashMLDSA87_with_SHA512, MLDSA44, + MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65, + MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87, + MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyTrait, + MLDSATrait, + }; + use bouncycastle_sha2::SHA512; + use std::fs; + use std::path::Path; + use std::process::exit; + use std::sync::Once; + const TEST_DATA_PATH_RELATIVE: &str = "../../../bc-test-data/pqc/crypto/mldsa"; + const TEST_DATA_PATH: &str = "../bc-test-data/pqc/crypto/mldsa"; + static TEST_DATA_CHECK: Once = Once::new(); + + fn test_for_presence_of_test_data() { + TEST_DATA_CHECK.call_once(|| { + if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + println!("bc-test-data found at: {:?}", TEST_DATA_PATH_RELATIVE); + } else if !Path::new(TEST_DATA_PATH).exists() { + println!("bc-test-data found at: {:?}", TEST_DATA_PATH); + } else { + println!("bc-test-data directory not found"); + exit(0); + } + }); + } + + #[test] + #[allow(non_snake_case)] + fn ML_DSA_keyGen() { + test_for_presence_of_test_data(); + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/ML-DSA-keyGen.txt").unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-keyGen.txt").unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = KeyGenTestCase::parse(contents); + + for test_case in test_cases { + test_case.run(); + } + } + + #[derive(Clone)] + struct KeyGenTestCase { + vs_id: u32, + algorithm: String, + mode: String, + revision: String, + is_sample: bool, + tg_id: u32, + test_type: String, + parameter_set: String, + tc_id: u32, + seed: String, + pk: String, + sk: String, + } + + impl KeyGenTestCase { + fn new() -> Self { + Self { + vs_id: 0, + algorithm: String::new(), + mode: String::new(), + revision: String::new(), + is_sample: false, + tg_id: 0, + test_type: String::new(), + parameter_set: String::new(), + tc_id: 0, + seed: String::new(), + pk: String::new(), + sk: String::new(), + } + } + + fn is_full(&self) -> bool { + !self.algorithm.is_empty() + } + + fn parse(data: String) -> Vec { + let mut test_cases = Vec::::new(); + let mut test_case = KeyGenTestCase::new(); + for line in data.lines() { + let (tag, value) = match line.split_once(" = ") { + Some(pair) => pair, + None => { + if test_case.is_full() { + test_cases.push(test_case.clone()); + } + continue; + } + }; + + match tag { + "vsId" => test_case.vs_id = value.parse().unwrap(), + "algorithm" => test_case.algorithm = value.to_string(), + "mode" => test_case.mode = value.to_string(), + "revision" => test_case.revision = value.to_string(), + "isSample" => test_case.is_sample = value.parse().unwrap(), + "tgId" => test_case.tg_id = value.parse().unwrap(), + "testType" => test_case.test_type = value.to_string(), + "parameterSet" => test_case.parameter_set = value.to_string(), + "tcId" => test_case.tc_id = value.parse().unwrap(), + "seed" => test_case.seed = value.to_string(), + "pk" => test_case.pk = value.to_string(), + "sk" => test_case.sk = value.to_string(), + val => panic!("Invalid tag: {}", val), + } + } + + test_cases + } + + fn run(&self) { + assert_eq!(self.mode, "keyGen"); + + let mut seed = KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) + .unwrap(); + // for the purposes of the test cases, accept an all-zero seed + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit).unwrap(); + seed.drop_hazardous_operations(); + + match self.parameter_set.as_str() { + "ML-DSA-44" => { + let (pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLDSA44_PK_LEN] = + hex::decode(&self.pk).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLDSA44_SK_LEN] = + hex::decode(&self.sk).unwrap().try_into().unwrap(); + assert_eq!(sk.encode(), sk_sized); + } + "ML-DSA-65" => { + let (pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLDSA65_PK_LEN] = + hex::decode(&self.pk).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLDSA65_SK_LEN] = + hex::decode(&self.sk).unwrap().try_into().unwrap(); + assert_eq!(sk.encode(), sk_sized); + } + "ML-DSA-87" => { + let (pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLDSA87_PK_LEN] = + hex::decode(&self.pk).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLDSA87_SK_LEN] = + hex::decode(&self.sk).unwrap().try_into().unwrap(); + assert_eq!(sk.encode(), sk_sized); + } + val => panic!("Invalid parameter set: {}", val), + } + } + } + + #[test] + #[allow(non_snake_case)] + fn ML_DSA_sigGen() { + test_for_presence_of_test_data(); + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/ML-DSA-sigGen.txt").unwrap() + } else { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-sigGen.txt").unwrap() + }; + + let test_cases = SigGenTestCase::parse(contents); + + let num_tests = test_cases.len(); + for test_case in test_cases { + test_case.run(); + } + + println!("SUCCESS! ML-DSA-sigGen test cases passed: {}!", num_tests); + } + + #[derive(Clone)] + struct SigGenTestCase { + vs_id: u32, + algorithm: String, + mode: String, + revision: String, + is_sample: bool, + tg_id: u32, + test_type: String, + parameter_set: String, + deterministic: bool, + tc_id: u32, + sk: String, + message: String, + rnd: String, + signature: String, + } + + impl SigGenTestCase { + fn new() -> Self { + Self { + vs_id: 0, + algorithm: String::new(), + mode: String::new(), + revision: String::new(), + is_sample: false, + tg_id: 0, + test_type: String::new(), + parameter_set: String::new(), + deterministic: false, + tc_id: 0, + sk: String::new(), + message: String::new(), + rnd: String::new(), + signature: String::new(), + } + } + + fn is_full(&self) -> bool { + !self.algorithm.is_empty() + } + + fn parse(data: String) -> Vec { + let mut test_cases = Vec::::new(); + let mut test_case = SigGenTestCase::new(); + for line in data.lines() { + let (tag, value) = match line.split_once(" = ") { + Some(pair) => pair, + None => { + if test_case.is_full() { + test_cases.push(test_case.clone()); + } + continue; + } + }; + + match tag { + "vsId" => test_case.vs_id = value.parse().unwrap(), + "algorithm" => test_case.algorithm = value.to_string(), + "mode" => test_case.mode = value.to_string(), + "revision" => test_case.revision = value.to_string(), + "isSample" => test_case.is_sample = value.parse().unwrap(), + "tgId" => test_case.tg_id = value.parse().unwrap(), + "testType" => test_case.test_type = value.to_string(), + "parameterSet" => test_case.parameter_set = value.to_string(), + "deterministic" => test_case.deterministic = value.parse().unwrap(), + "tcId" => test_case.tc_id = value.parse().unwrap(), + "sk" => test_case.sk = value.to_string(), + "message" => test_case.message = value.to_string(), + "rnd" => test_case.rnd = value.to_string(), + "signature" => test_case.signature = value.to_string(), + val => panic!("Invalid tag: {}", val), + } + } + + test_cases + } + + fn run(&self) { + assert_eq!(self.mode, "sigGen"); + + let rnd = if self.deterministic { + [0u8; 32] + } else { + hex::decode(&self.rnd).unwrap().as_slice().try_into().unwrap() + }; + + match self.parameter_set.as_str() { + "ML-DSA-44" => { + let sk = + MLDSA44PrivateKey::from_bytes(&hex::decode(&self.sk).unwrap()).unwrap(); + + // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() + // so need to manually compute mu + // let mu = MLDSA44::compute_mu_from_tr( + // &hex::decode(&self.message).unwrap(), + // None, + // sk.tr(), + // ).unwrap(); + let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); + mb.do_update(&hex::decode(&self.message).unwrap()); + let mu = mb.do_final(); + + let sig = MLDSA44::sign_mu_deterministic(&sk, None, &mu, rnd).unwrap(); + assert_eq!( + &sig, + &*hex::decode(&self.signature).unwrap(), + "ML-DSA-sigGen params: {}, vsId: {}, tgId: {}, tcId: {}", + self.parameter_set, + self.vs_id, + self.tg_id, + self.tc_id + ); + } + "ML-DSA-65" => { + let sk = + MLDSA65PrivateKey::from_bytes(&hex::decode(&self.sk).unwrap()).unwrap(); + + // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() + // so need to manually compute mu + // let mu = MLDSA65::compute_mu_from_tr( + // &hex::decode(&self.message).unwrap(), + // None, + // sk.tr(), + // ).unwrap(); + let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); + mb.do_update(&hex::decode(&self.message).unwrap()); + let mu = mb.do_final(); + + let sig = MLDSA65::sign_mu_deterministic(&sk, None, &mu, rnd).unwrap(); + assert_eq!(&sig, &*hex::decode(&self.signature).unwrap()); + } + "ML-DSA-87" => { + let sk = + MLDSA87PrivateKey::from_bytes(&hex::decode(&self.sk).unwrap()).unwrap(); + + // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() + // so need to manually compute mu + // let mu = MLDSA87::compute_mu_from_tr( + // &hex::decode(&self.message).unwrap(), + // None, + // sk.tr(), + // ).unwrap(); + let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); + mb.do_update(&hex::decode(&self.message).unwrap()); + let mu = mb.do_final(); + + let sig = MLDSA87::sign_mu_deterministic(&sk, None, &mu, rnd).unwrap(); + assert_eq!(&sig, &*hex::decode(&self.signature).unwrap()); + } + val => panic!("Invalid parameter set: {}", val), + } + } + } + + // this seems buggy and I'm not sure why. Possibly because the bc-test-data was written against Round 3 Dilithium and not ML-DSA. + // todo -- debug + // #[test] + #[allow(unused)] + #[allow(non_snake_case)] + fn ML_DSA_sigVer() { + test_for_presence_of_test_data(); + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/ML-DSA-sigVer.txt").unwrap() + } else { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-sigVer.txt").unwrap() + }; + + let test_cases = SigVerTestCase::parse(contents); + + for test_case in test_cases { + test_case.run(); + } + } + + #[derive(Clone)] + struct SigVerTestCase { + vs_id: u32, + algorithm: String, + mode: String, + revision: String, + is_sample: bool, + tg_id: u32, + test_type: String, + parameter_set: String, + pk: String, + tc_id: u32, + message: String, + signature: String, + test_passed: bool, + } + + impl SigVerTestCase { + fn new() -> Self { + Self { + vs_id: 0, + algorithm: String::new(), + mode: String::new(), + revision: String::new(), + is_sample: false, + tg_id: 0, + test_type: String::new(), + parameter_set: String::new(), + tc_id: 0, + pk: String::new(), + message: String::new(), + signature: String::new(), + test_passed: false, + } + } + + fn is_full(&self) -> bool { + !self.algorithm.is_empty() + } + + fn parse(data: String) -> Vec { + let mut test_cases = Vec::::new(); + let mut test_case = SigVerTestCase::new(); + for line in data.lines() { + let (tag, value) = match line.split_once(" = ") { + Some(pair) => pair, + None => { + if test_case.is_full() { + test_cases.push(test_case.clone()); + } + continue; + } + }; + + match tag { + "vsId" => test_case.vs_id = value.parse().unwrap(), + "algorithm" => test_case.algorithm = value.to_string(), + "mode" => test_case.mode = value.to_string(), + "revision" => test_case.revision = value.to_string(), + "isSample" => test_case.is_sample = value.parse().unwrap(), + "tgId" => test_case.tg_id = value.parse().unwrap(), + "testType" => test_case.test_type = value.to_string(), + "parameterSet" => test_case.parameter_set = value.to_string(), + "pk" => test_case.pk = value.to_string(), + "tcId" => test_case.tc_id = value.parse().unwrap(), + "message" => test_case.message = value.to_string(), + "signature" => test_case.signature = value.to_string(), + "testPassed" => test_case.test_passed = value.parse().unwrap(), + val => panic!("Invalid tag: {}", val), + } + } + + test_cases + } + + fn run(&self) { + assert_eq!(self.mode, "sigVer"); + + match self.parameter_set.as_str() { + "ML-DSA-44" => { + let pk = MLDSA44PublicKey::from_bytes(&hex::decode(&self.pk).unwrap()).unwrap(); + + // No ctx because the bc-test-data tests were written against an earlier version of the spec + // that didn't have it. + match MLDSA44::verify( + &pk, + &hex::decode(&self.message).unwrap(), + None, + &hex::decode(&self.signature).unwrap(), + ) { + Ok(()) => { + if !self.test_passed { + panic!("Verification succeeded when it shouldn't have!") + } + } + Err(SignatureError::SignatureVerificationFailed) => { + if self.test_passed { + panic!( + "Verification failed when it shouldn't have! vsId: {}, tgId: {}, tcId: {}", + self.vs_id, self.tg_id, self.tc_id + ) + } + } + _ => panic!("An unexpected error occurred"), + } + } + "ML-DSA-65" => { + let pk = MLDSA65PublicKey::from_bytes(&hex::decode(&self.pk).unwrap()).unwrap(); + + match MLDSA65::verify( + &pk, + &hex::decode(&self.message).unwrap(), + None, + &hex::decode(&self.signature).unwrap(), + ) { + Ok(()) => { + if self.test_passed { /* good */ + } else { + panic!("Verification succeeded when it shouldn't have!") + } + } + Err(SignatureError::SignatureVerificationFailed) => { + if !self.test_passed { + } else { + panic!("Verification failed when it should have!") + } + } + _ => panic!("An unexpected error occurred"), + } + } + "ML-DSA-87" => { + let pk = MLDSA87PublicKey::from_bytes(&hex::decode(&self.pk).unwrap()).unwrap(); + + match MLDSA87::verify( + &pk, + &hex::decode(&self.message).unwrap(), + None, + &hex::decode(&self.signature).unwrap(), + ) { + Ok(()) => { + if self.test_passed { /* good */ + } else { + panic!("Verification succeeded when it shouldn't have!") + } + } + Err(SignatureError::SignatureVerificationFailed) => { + if !self.test_passed { + } else { + panic!("Verification failed when it should have!") + } + } + _ => panic!("An unexpected error occurred"), + } + } + val => panic!("Invalid parameter set: {}", val), + } + } + } + + // this seems buggy and I'm not sure why. Possibly because the bc-test-data was written against Round 3 Dilithium and not ML-DSA. + // todo -- debug + // #[test] + #[allow(unused)] + #[allow(non_snake_case)] + fn ML_DSA_rsp() { + test_for_presence_of_test_data(); + + // MLDsa44 + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa44.rsp").unwrap() + } else { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa44.rsp").unwrap() + }; + + let test_cases = MldsaRspTestCase::::parse(contents); + for test_case in test_cases { + test_case.run("MLDsa44"); + } + + // MLDsa65 + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa65.rsp").unwrap() + } else { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa65.rsp").unwrap() + }; + + let test_cases = MldsaRspTestCase::::parse(contents); + for test_case in test_cases { + test_case.run("MLDsa65"); + } + + // MLDsa87 + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa87.rsp").unwrap() + } else { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa87.rsp").unwrap() + }; + + let test_cases = MldsaRspTestCase::::parse(contents); + for test_case in test_cases { + test_case.run("MLDsa87"); + } + + // MLDsa44sha512 + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa44sha512.rsp").unwrap() + } else { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa44sha512.rsp").unwrap() + }; + + let test_cases = MldsaRspTestCase::::parse(contents); + for test_case in test_cases { + test_case.run("MLDsa44"); + } + + // MLDsa65sha512 + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa65sha512.rsp").unwrap() + } else { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa65sha512.rsp").unwrap() + }; + + let test_cases = MldsaRspTestCase::::parse(contents); + for test_case in test_cases { + test_case.run("MlDsa65"); + } + + // MLDsa87sha512 + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa87sha512.rsp").unwrap() + } else { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa87sha512.rsp").unwrap() + }; + + let test_cases = MldsaRspTestCase::::parse(contents); + for test_case in test_cases { + test_case.run("MlDsa87"); + } + } + + #[derive(Clone)] + struct MldsaRspTestCase { + count: u32, + seed: String, + mlen: u32, + msg: String, + pk: String, + sk: String, + smlen: u32, + sm: String, + message_hash: String, + message_prime: String, + context: String, + } + + impl MldsaRspTestCase { + fn new() -> Self { + Self { + count: 0, + seed: String::new(), + mlen: 0, + msg: String::new(), + pk: String::new(), + sk: String::new(), + smlen: 0, + sm: String::new(), + message_hash: String::new(), + message_prime: String::new(), + context: String::new(), + } + } + + fn is_full(&self) -> bool { + !self.seed.is_empty() + } + + fn parse(data: String) -> Vec> { + let mut test_cases = Vec::new(); + let mut test_case = MldsaRspTestCase::new(); + for line in data.lines() { + let (tag, value) = match line.split_once(" = ") { + Some(pair) => pair, + None => { + if test_case.is_full() { + test_cases.push(test_case.clone()); + } + continue; + } + }; + + match tag { + "count" => test_case.count = value.parse().unwrap(), + "seed" => test_case.seed = value.to_string(), + "mlen" => test_case.mlen = value.parse().unwrap(), + "msg" => test_case.msg = value.to_string(), + "pk" => test_case.pk = value.to_string(), + "sk" => test_case.sk = value.to_string(), + "smlen" => test_case.smlen = value.parse().unwrap(), + "sm" => test_case.sm = value.to_string(), + "message_hash" => test_case.message_hash = value.to_string(), + "message_prime" => test_case.message_prime = value.to_string(), + "context" => { + test_case.context = value.to_string(); + if test_case.context == "zero_length" || test_case.context == "none" { + test_case.context = String::new(); + } + } + val => panic!("Invalid tag: {}", val), + } + } + + test_cases + } + + fn run(&self, parameter_set: &str) { + match parameter_set { + "MLDsa44" => { + let mut seed = KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) + .unwrap(); + // for the purposes of the test cases, accept an all-zero seed + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit).unwrap(); + seed.drop_hazardous_operations(); + + let (pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLDSA44_PK_LEN] = + hex::decode(&self.pk).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLDSA44_SK_LEN] = + hex::decode(&self.sk).unwrap().try_into().unwrap(); + assert_eq!(sk.encode(), sk_sized); + + if IS_HASH_MLDSA { + // we're only testing SHA512 + let ph: [u8; 64] = SHA512::new() + .hash(&hex::decode(&self.msg).unwrap()) + .as_slice() + .try_into() + .unwrap(); + assert_eq!(ph, &*hex::decode(&self.message_hash).unwrap()); + + let sig = HashMLDSA44_with_SHA512::sign_ph_deterministic( + &sk, + None, + Some(&*hex::decode(&self.context).unwrap()), + &ph, + [0u8; 32], + ) + .unwrap(); + assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); + + HashMLDSA44_with_SHA512::verify( + &pk, + &*hex::decode(&self.msg).unwrap(), + Some(&*hex::decode(&self.context).unwrap()), + &sig, + ) + .expect(&format!( + "paramSet: {}, is_hash: {}, count: {}", + parameter_set, IS_HASH_MLDSA, self.count + )); + } else { + // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() + // so need to manually compute mu + let mu = MLDSA65::compute_mu_from_tr( + sk.tr(), + &hex::decode(&self.msg).unwrap(), + Some(&hex::decode(&self.context).unwrap()), + ) + .unwrap(); + + let sig = + MLDSA44::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); + assert_eq!( + sig, + &*hex::decode(&self.sm).unwrap(), + "paramSet: {}, count: {}", + parameter_set, + self.count + ); + + MLDSA44::verify( + &pk, + &hex::decode(&self.msg).unwrap(), + Some(&hex::decode(&self.context).unwrap()), + &sig, + ) + .unwrap(); + } + } + "MlDsa65" | "MLDsa65" => { + let mut seed = KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) + .unwrap(); + // for the purposes of the test cases, accept an all-zero seed + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit).unwrap(); + seed.drop_hazardous_operations(); + + let (pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLDSA65_PK_LEN] = + hex::decode(&self.pk).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLDSA65_SK_LEN] = + hex::decode(&self.sk).unwrap().try_into().unwrap(); + assert_eq!(sk.encode(), sk_sized); + + if IS_HASH_MLDSA { + // we're only testing SHA512 + let ph: [u8; 64] = SHA512::new() + .hash(&hex::decode(&self.msg).unwrap()) + .as_slice() + .try_into() + .unwrap(); + assert_eq!(ph, &*hex::decode(&self.message_hash).unwrap()); + + let sig = HashMLDSA65_with_SHA512::sign_ph_deterministic( + &sk, + None, + Some(&*hex::decode(&self.context).unwrap()), + &ph, + [0u8; 32], + ) + .unwrap(); + assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); + + HashMLDSA65_with_SHA512::verify( + &pk, + &*hex::decode(&self.message_hash).unwrap(), + Some(&*hex::decode(&self.context).unwrap()), + &sig, + ) + .expect(&format!( + "paramSet: {}, isHash: {}, count: {}", + parameter_set, IS_HASH_MLDSA, self.count + )); + } else { + // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() + // so need to manually compute mu + let mu = MLDSA65::compute_mu_from_tr( + sk.tr(), + &hex::decode(&self.msg).unwrap(), + Some(&hex::decode(&self.context).unwrap()), + ) + .unwrap(); + + let sig = + MLDSA65::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); + + MLDSA65::verify( + &pk, + &hex::decode(&self.msg).unwrap(), + Some(&hex::decode(&self.context).unwrap()), + &sig, + ) + .unwrap(); + } + } + "MLDsa87" => { + let mut seed = KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) + .unwrap(); + // for the purposes of the test cases, accept an all-zero seed + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit).unwrap(); + seed.drop_hazardous_operations(); + + let (pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLDSA87_PK_LEN] = + hex::decode(&self.pk).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLDSA87_SK_LEN] = + hex::decode(&self.sk).unwrap().try_into().unwrap(); + assert_eq!(sk.encode(), sk_sized); + + if IS_HASH_MLDSA { + // we're only testing SHA512 + let ph: [u8; 64] = SHA512::new() + .hash(&hex::decode(&self.msg).unwrap()) + .as_slice() + .try_into() + .unwrap(); + assert_eq!(ph, &*hex::decode(&self.message_hash).unwrap()); + + let sig = HashMLDSA87_with_SHA512::sign_ph_deterministic( + &sk, + None, + Some(&*hex::decode(&self.context).unwrap()), + &ph, + [0u8; 32], + ) + .unwrap(); + assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); + + HashMLDSA87_with_SHA512::verify( + &pk, + &*hex::decode(&self.message_hash).unwrap(), + Some(&*hex::decode(&self.context).unwrap()), + &sig, + ) + .unwrap(); + } else { + // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() + // so need to manually compute mu + let mu = MLDSA65::compute_mu_from_tr( + sk.tr(), + &hex::decode(&self.msg).unwrap(), + Some(&hex::decode(&self.context).unwrap()), + ) + .unwrap(); + + let sig = + MLDSA87::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); + + MLDSA87::verify( + &pk, + &hex::decode(&self.msg).unwrap(), + Some(&hex::decode(&self.context).unwrap()), + &sig, + ) + .unwrap(); + } + } + val => panic!("Invalid parameter set: {}", val), + } + } + } +} /// This builds a "busted" mu where the ctx is absent (not 0-length, but actually not there) /// just for the sake of compatibility with the bc-test-data tests @@ -671,7 +944,7 @@ impl BustedMuBuilder { } /// This function requires the public key hash `tr`, which can be computed from the public key using [MLDSAPublicKey::compute_tr]. - pub fn do_init(tr: &[u8; 64], /*ctx: Option<&[u8]>*/) -> Result { + pub fn do_init(tr: &[u8; 64] /*ctx: Option<&[u8]>*/) -> Result { // let ctx = match ctx { // Some(ctx) => ctx, // None => &[] @@ -714,4 +987,4 @@ impl BustedMuBuilder { mu } -} \ No newline at end of file +} diff --git a/crypto/mldsa/tests/hash_mldsa_tests.rs b/crypto/mldsa/tests/hash_mldsa_tests.rs index 10d293e..4301d7b 100644 --- a/crypto/mldsa/tests/hash_mldsa_tests.rs +++ b/crypto/mldsa/tests/hash_mldsa_tests.rs @@ -2,7 +2,7 @@ mod hash_mldsa_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; - use bouncycastle_core::traits::{Hash, Signature}; + use bouncycastle_core::traits::{Hash, PHSignature, Signature}; use bouncycastle_core_test_framework::signature::TestFrameworkSignature; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ @@ -14,7 +14,7 @@ mod hash_mldsa_tests { use bouncycastle_mldsa::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; use bouncycastle_mldsa::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; use bouncycastle_mldsa::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; - use bouncycastle_sha2::SHA512; + use bouncycastle_sha2::{SHA256, SHA512}; #[test] fn core_framework_signature() { @@ -26,14 +26,14 @@ mod hash_mldsa_tests { tf.test_signature::(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::(false); + tf.test_ph_signature::(false); + tf.test_ph_signature::(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::(false); + tf.test_ph_signature::(false); + tf.test_ph_signature::(false); } #[test] @@ -67,7 +67,7 @@ mod hash_mldsa_tests { _ => panic!("Expected error for sig too short"), } - // too long + // sig too long let mut sig_too_long = [0u8; MLDSA44_SIG_LEN + 2]; sig_too_long[..MLDSA44_SIG_LEN].copy_from_slice(&sig); sig_too_long[MLDSA44_SIG_LEN..].copy_from_slice(&[1u8, 0u8]); @@ -75,6 +75,79 @@ mod hash_mldsa_tests { Err(SignatureError::LengthError(_)) => { /* good */ } _ => panic!("Expected error for sig too short"), } + + // sign_ph_deterministic ctx just right at 255 + let sig = HashMLDSA44_with_SHA512::sign_ph_deterministic( + &sk, + None, + /*ctx*/ Some(&[1u8; 255]), + /*ph*/ &[2u8; 64], + [3u8; 32], + ) + .unwrap(); + HashMLDSA44_with_SHA512::verify_ph(&pk, &[2u8; 64], Some(&[1u8; 255]), &sig).unwrap(); + + // sign_ph_deterministic ctx too long + match HashMLDSA44_with_SHA512::sign_ph_deterministic( + &sk, + None, + /*ctx*/ Some(&[1u8; 256]), + /*ph*/ &[2u8; 64], + [3u8; 32], + ) { + Err(SignatureError::LengthError(_)) => { /* good */ } + _ => panic!("Expected error"), + } + + // sign_ph_deterministic ctx just right at 255 + let sig = HashMLDSA44_with_SHA512::sign_ph_deterministic( + &sk, + None, + Some(&[1u8; 255]), + &[2u8; 64], + [3u8; 32], + ) + .unwrap(); + HashMLDSA44_with_SHA512::verify_ph(&pk, &[2u8; 64], Some(&[1u8; 255]), &sig).unwrap(); + + // sign_ph_deterministic ctx too long + match HashMLDSA44_with_SHA512::sign_ph_deterministic( + &sk, + None, + Some(&[2u8; 256]), + &[2u8; 64], + [3u8; 32], + ) { + Err(SignatureError::LengthError(_)) => { /* good */ } + _ => panic!("Expected error"), + } + + // verify_ph ctx too long + match HashMLDSA44_with_SHA512::verify_ph(&pk, &[2u8; 64], Some(&[2u8; 256]), &sig) { + Err(SignatureError::LengthError(_)) => { /* good */ } + _ => panic!("Expected error"), + } + } + + #[test] + fn test_ph() { + let msg = b"The quick brown fox jumped over the lazy dog"; + + let (pk, sk) = HashMLDSA44_with_SHA256::keygen().unwrap(); + let ph: [u8; 32] = SHA256::default().hash(msg)[..32].try_into().unwrap(); + let sig_val = HashMLDSA44_with_SHA256::sign_ph(&sk, &ph, None).unwrap(); + HashMLDSA44_with_SHA256::verify_ph(&pk, &ph, None, &sig_val).unwrap(); + HashMLDSA44_with_SHA256::verify(&pk, msg, None, &sig_val).unwrap(); + + // sign_ph_out + let (pk, sk) = HashMLDSA44_with_SHA256::keygen().unwrap(); + let ph: [u8; 32] = SHA256::default().hash(msg)[..32].try_into().unwrap(); + let mut sig_val = [0u8; MLDSA44_SIG_LEN]; + let bytes_written = + HashMLDSA44_with_SHA256::sign_ph_out(&sk, &ph, None, &mut sig_val).unwrap(); + assert_eq!(bytes_written, MLDSA44_SIG_LEN); + HashMLDSA44_with_SHA256::verify_ph(&pk, &ph, None, &sig_val).unwrap(); + HashMLDSA44_with_SHA256::verify(&pk, msg, None, &sig_val).unwrap(); } #[test] @@ -129,6 +202,21 @@ mod hash_mldsa_tests { let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); let sig = HashMLDSA87_with_SHA512::sign_ph_deterministic(&sk, None, None, &ph, rnd).unwrap(); + + assert_eq!(&sig, expected_sig.as_slice()); + + // with ctx + let expected_sig = hex::decode("213c4f1ee1e26373499a705a1bfaab92ad7f5cdb9b29c8838816abc078e3aff3bd67ca6541bab93c0ec533c8acd215ab9e1b35a5a3ca4aee64f2562f1160c6102ce6dd274016cee192b2745dac6772bc20e1e050aa1cb5d64bdf1e6e4ea812fd44b5d44d98b472f66a2b3521aa1ad917a94186258ca0e42143892eff70be17b68d0066652bb96b8867e53cc2889db8b4210e296195cbb44c6611bf572aa40732e92a7fdb79e15fbc8321fc028d9f3385af0720b8a84eefa5675d0d9980ed78f64d5d368c7f8f0cb0fe9189514ebb6b3cb7d8f25ba9180525c82ea3d3953de9029dc53fd8a66f8d4f812b8c536cddd576e3f021d2baa01caae5872dce9da75aa9991110e5081825adab38c53d8482abfa04897ecd0905691b376ad06071dec0b18ca8f3c77d222006c57a753417bd03dcdd8518b16d56edce148b70ded34444f9a3816bc938b14f5f1220076b3569668ccaf99a7567b90b1e7c29b7a6a26b6e12d9a2ad0a5ee2bf87249c87f70d732e68427646ae295579d284181b0603c75507bb1d0e1ed1b00d06b48569c2882cde3078594a2bfa7d3bbfe17528507911e4af5cb443e85e04cb5f78503cfd43a49856cc45624108f72225bf91ffd2f3fde47411870c585062955254dea26d774df2e14a93d66e9948d00ce23bf365fb7e708c85db93097321734ba8d4cc752f4c17e908ab99284f017e631afa3a34be474b20ea2d530778d3f070848db0d88bfbce1dab12bd2dcb47d7ed33f0ed48d55ff4d16fe60275a8d885c63807fb2433864a016eddb3f0ba3f4abdbeb9a76e124fc81a7b4e2e2fb2cb9f6580175eb2db4640eca230f51c4400f147fb7b5cdca3e29c91bcfeefb400ff3c77526b13a3939b0658cefa34754bdb0914d0a7fdedc95d6f40c8a2fc74c0b36c8e4a1337351df0e7402d4494b3770e80aa41fea78536473236a8229b1d2d1fa936b0a9d023d4f8c6b8b553702ac47c74493d753cca3cd9fc3c9be7a97920216e0d13a1a2d6443bc0ce85a6c4160ded6e75e428e52b481ac15972c89ef9072f383ebc94b822dfc463ca540eda14212018ad87322558e09a25be72e6c3eb11409fc4c750f92e14756f1551233bb6dbdf5b26b4b101d7c44e5c6241373b0f83f2e25d666d61b154a5544c3607cacbf569e41011cadc870584416ae532c1c7a47eee17b7d5253fce7588fc28f09c48edf825add943b982b996b7e73112f48d8984fd331ea4fcdfa3d70bb591614fda037ee515ef1038bf7ef8acf17d1f310b0e3d8e98b03f4a9e73e4cddc512a236d2d47f08518d4d069b6e370732a5f6bfddd7f1dcadf64b90cf381f41cdc600e67727c7264b969811a234e8b66351fd6c5dd36185dc8cb883cabadf6d74a7bc6ff813a4d64d4de4205fc9eeffc1312a40275e329f84b75f64302ee80ebb7fdb3d74ccc16fa8b2ddd55e2bb6712f9f4e015a2580b311afa8ec30b8f1f258430e7d7c45cb05de4ef68072bae302a1da26906c234ffa43874041b2226f5142b4fb1b2a79d122f3cb0909ec5c530563fcf4464406ec54f489ab9964949669078f6b2261f1ccd2efc8a2440f97e956da919b03f027aaf0553bffa2f9b6c997fe013a581f53043595b1a86a51a1e98cad9550a79ac8faa2813b1a7d1d238c8c0ef8ae8399c74050c96d05fb1740fb09d2fce49cc2fed955d455f42f24535236499b36d9c7815916c084922df523e8b6a83c31c062b89b332adfd5af45acacb51fc3d75097518a8033aca238ecb59f7defc07a647c31712d0095c77fefe8b0ed248f98cfddd1be5a4e480161942271755293b4d0ef462797b05c36c316af817f7120c03d45883462f33a5f2ba3de277c34b2c51a193a87611291acc02c1adf7ed076078a5ec43e1b040bb5dc851413c093730dfa6ef8b275f24c3bc69d5807c27aa172006dbbbf88592630f1b1d4c35c6a670a16b5a4b3e600d0378c181eb3238e443188a0d629a0bf3e632acf0a9e755d64086e24747f1141b7bb1220cd0236aaf317c0fabab43a9ae001e9897830aefb7ffeba844cf02e929746f068e7370827b7929ca22f1d9f5d532608c1e0847843e051c10e2cab84df928ee5315d2e7317c41cd6d3aaf437fd89eb030f7c5f051d60a6576f42f855ec49007f50dd8d300832591ccf983bca9c9c152232c4d672d5d31d7b2f2730af0247cd0b2ef5421751b0c65e5fcab3483706eda5f67461524f80f04049b6e725e061b37c79b15a28a177a8f3b44029820d83acf747ae0a00bb096ead483cfec37f5ee249eb41fea4d607946f35165b21fa4ac1ff831b7ec177cf413dba3cd7cc16755b07f1917e0b61581f6974eaa78a4cd58941ec11658eea54e7f2c92f16f0db47f785b6b831241dbdc8acf428727ffaf9e229c85e4cada02cf42f16582279485bb72e6070d75d5b80b12737f9fb23866ba09c4c9c222153c555b72daceb19633715c875c7ccf9e11c2d3f2d62fa0ed53d545e8763bce260386107d95419311795ec0ac1f4a910c2bc4b4aefec2356ec96e847766898d1d52ec160fcf64d21d93315cfc7371f08594e8cdcf16fc5630eedd93062cafb1658494dd58d6cc1d4bfd9a9d1e22f1642842fba0574eda075c8831eb122664f06aa1cd345922faf87d0188db80d94689ca157cff03d4f52285c0e5a735dce00c37bf7836af992412614336b1bea7650853f158c5f1e73cff825c9e43a30a6496fa80a77e5147fb89399139447e8df9771c11282c9a2a1b12829dfa397c14a621c5ff564b86201f524c1beb1af5731489f23c56abdbd595e85e8e41932f1d3dd4663a9961bd8eeeac8b0228ba41324e7da88ad57a75654a331baf3c867be9c12b8cdce95358f75a0de570a03990c205dfe281f2af61f2458b0da3d9712002667f8d75957bfd03124300280d7b5d82fbe914003a401fd781f22461795590f93150ca0b9d6c865e5dabfeffa238a57e9dfbca35a0206116cf1f353e1292d02719116381e15f372114edd4dcddee6749604035ace9af15a912c88d36475d66bd5b716bd957aecf6ca76c9c42db81df8ca64e86ebc198daad2b6074635d979edbeec999fe27d251b732a1d3b4d418cef442be5c2e04026a4387520dc23c49f16142b157548174ead74f73e188b6b8c8a1df79b905966da8c31ac3cb28e56abb693b786d1183486d30fcdb2858ea46ca72488cd07597451c974e2f6c6862ffdd2f8d26125ca88608dc3ecca8331c799aac4aeb6c24a6cd88c892b8d1ab2edb276d9a841b9ab830a0ebaaeaa4d8555f812bcc2fec4673e4c9c90cd1342525a6f489531c3605111f4551696a6b91a7bac6cfd0dfe6f0f50203081743465b627bb3b5b6d2d5d901414e639fb1e3f2f9fd16335a656d7c8a999dadb7bbc7cad5f0fafc0000000000000000000000000000000000000012212b3d").unwrap(); + let (_pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); + + let ctx = b"testing streaming API"; + + // since I'm not exposing a sign_deterministic() that does the ph computation internally and takes an rnd, + // we have to compute the ph manually here + let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); + let sig = + HashMLDSA44_with_SHA512::sign_ph_deterministic(&sk, None, Some(ctx), &ph, rnd).unwrap(); + assert_eq!(&sig, expected_sig.as_slice()); } @@ -201,6 +289,14 @@ mod hash_mldsa_tests { let pk_expanded = MLDSA44PublicKeyExpanded::from(&pk); HashMLDSA44_with_SHA256::verify_with_expanded_key(&pk_expanded, msg, None, &sig).unwrap(); + // negative case + match HashMLDSA44_with_SHA256::verify_with_expanded_key( + &pk_expanded, msg, None, &[1u8; MLDSA44_SIG_LEN], + ) { + Err(SignatureError::SignatureVerificationFailed) => { /* good */ } + _ => panic!("Expected error"), + } + // test also sign_ph_with_expanded_key // since I'm not exposing a sign_deterministic() that does the ph computation internally and takes an rnd, diff --git a/crypto/mldsa/tests/mldsa_key_tests.rs b/crypto/mldsa/tests/mldsa_key_tests.rs index 0fa7c61..c2451a0 100644 --- a/crypto/mldsa/tests/mldsa_key_tests.rs +++ b/crypto/mldsa/tests/mldsa_key_tests.rs @@ -1,8 +1,10 @@ #[cfg(test)] mod mldsa_key_tests { use bouncycastle_core::errors::SignatureError; - use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; - use bouncycastle_core::traits::{Signature, SignaturePrivateKey, SignaturePublicKey}; + use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{ + SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + }; use bouncycastle_core_test_framework::signature::TestFrameworkSignatureKeys; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ @@ -111,6 +113,10 @@ mod mldsa_key_tests { assert_eq!(pk1_bytes, pk1_bytes_out); assert_eq!(sk1_bytes, sk1_bytes_out); + + let bytes_written = sk1_expanded.encode_out(&mut sk1_bytes_out); + assert_eq!(bytes_written, MLDSA65_SK_LEN); + assert_eq!(sk1_expanded.encode(), sk2_expanded.encode()); } #[test] @@ -122,15 +128,85 @@ mod mldsa_key_tests { ) .unwrap(); + // todo change mlkem to also hold the whole keymaterial? + let (_pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); - assert!(sk.seed().is_some()); - assert_eq!(sk.seed().as_ref().unwrap(), &seed); + assert_eq!(sk.seed(), Some(&seed)); + + // it'll reject a keyen with a seed too weak, and preserve the seed otherwise + let mut seed128 = seed.clone(); + seed128.allow_hazardous_operations(); + seed128.set_security_strength(SecurityStrength::_128bit).unwrap(); + seed128.drop_hazardous_operations(); + + let mut seed192 = seed.clone(); + seed192.allow_hazardous_operations(); + seed192.set_security_strength(SecurityStrength::_192bit).unwrap(); + seed192.drop_hazardous_operations(); + + let mut seed256 = seed.clone(); + seed256.allow_hazardous_operations(); + seed256.set_security_strength(SecurityStrength::_256bit).unwrap(); + seed256.drop_hazardous_operations(); + + // MLDSA44 + let (_pk, sk) = MLDSA44::keygen_from_seed(&seed128).unwrap(); + assert_eq!(sk.seed(), Some(&seed128)); + + let (_pk, sk) = MLDSA44::keygen_from_seed(&seed192).unwrap(); + assert_eq!(sk.seed(), Some(&seed192)); + + let (_pk, sk) = MLDSA44::keygen_from_seed(&seed256).unwrap(); + assert_eq!(sk.seed(), Some(&seed256)); + + // MLDSA65 + match MLDSA65::keygen_from_seed(&seed128) { + Err(SignatureError::KeyGenError(_)) => { /* good */ } + _ => { + panic!("unexpected error") + } + } + + let (_pk, sk) = MLDSA65::keygen_from_seed(&seed192).unwrap(); + assert_eq!(sk.seed(), Some(&seed192)); + + let (_pk, sk) = MLDSA65::keygen_from_seed(&seed256).unwrap(); + assert_eq!(sk.seed(), Some(&seed256)); + + // MLDSA87 + match MLDSA87::keygen_from_seed(&seed128) { + Err(SignatureError::KeyGenError(_)) => { /* good */ } + _ => { + panic!("unexpected error") + } + } + match MLDSA87::keygen_from_seed(&seed192) { + Err(SignatureError::KeyGenError(_)) => { /* good */ } + _ => { + panic!("unexpected error") + } + } + let (_pk, sk) = MLDSA87::keygen_from_seed(&seed256).unwrap(); + assert_eq!(sk.seed(), Some(&seed256)); // now load a key from bytes so that it doesn't have a seed + let (_pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); let sk_bytes = sk.encode(); - let sk2 = MLDSA44PrivateKey::from_bytes(&sk_bytes).unwrap(); - assert!(sk2.seed().is_none()); + let sk_no_seed = MLDSA44PrivateKey::from_bytes(&sk_bytes).unwrap(); + assert!(sk_no_seed.seed().is_none()); + + /* Expanded key */ + let (_pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); + let sk_expanded = MLDSA44PrivateKeyExpanded::from(&sk); + match sk_expanded.seed() { + Some(s) => assert_eq!(s, &seed), + None => panic!("Expected expanded key to have seed"), + } + + // now try an expanded key that doesn't have a seed + let sk_expanded_no_seed = MLDSA44PrivateKeyExpanded::from(&sk_no_seed); + assert!(sk_expanded_no_seed.seed().is_none()); } #[test] @@ -213,96 +289,118 @@ mod mldsa_key_tests { #[test] fn test_eq() { // MLDSA-44 - - let (pk, sk) = MLDSA44::keygen().unwrap(); - - // basic equality checks - assert_eq!(pk, pk); - assert_eq!(pk, pk.clone()); - assert_eq!(pk, MLDSA44PublicKey::from_bytes(&pk.encode()).unwrap()); - - assert_eq!(sk, sk); - assert_eq!(sk, sk.clone()); - assert_eq!(sk, MLDSA44PrivateKey::from_bytes(&sk.sk_encode()).unwrap()); - - // inequality checks - let mut bytes = pk.encode(); - bytes[17] ^= 0x01; - assert_ne!(pk, MLDSA44PublicKey::from_bytes(&bytes).unwrap()); - - let mut bytes = sk.encode(); - bytes[17] ^= 0x01; - assert_ne!(sk, MLDSA44PrivateKey::from_bytes(&bytes).unwrap()); + { + let (pk, sk) = MLDSA44::keygen().unwrap(); + + // basic equality checks + assert_eq!(pk, pk); + assert_eq!(pk, pk.clone()); + assert_eq!(pk, MLDSA44PublicKey::from_bytes(&pk.encode()).unwrap()); + + assert_eq!(sk, sk); + assert_eq!(sk, sk.clone()); + assert_eq!(sk, MLDSA44PrivateKey::from_bytes(&sk.encode()).unwrap()); + + let mut sk_bytes = [0u8; MLDSA44_SK_LEN]; + sk.encode_out(&mut sk_bytes); + assert_eq!(sk_bytes, sk.encode()); + + // inequality checks + let mut bytes = pk.encode(); + bytes[17] ^= 0x01; + assert_ne!(pk, MLDSA44PublicKey::from_bytes(&bytes).unwrap()); + + let mut bytes = sk.encode(); + bytes[17] ^= 0x01; + assert_ne!(sk, MLDSA44PrivateKey::from_bytes(&bytes).unwrap()); + } // MLDSA-65 - - let (pk, sk) = MLDSA65::keygen().unwrap(); - - // basic equality checks - assert_eq!(pk, pk); - assert_eq!(pk, pk.clone()); - assert_eq!(pk, MLDSA65PublicKey::from_bytes(&pk.encode()).unwrap()); - - assert_eq!(sk, sk); - assert_eq!(sk, sk.clone()); - assert_eq!(sk, MLDSA65PrivateKey::from_bytes(&sk.sk_encode()).unwrap()); - - // inequality checks - let mut bytes = pk.encode(); - bytes[17] ^= 0x01; - assert_ne!(pk, MLDSA65PublicKey::from_bytes(&bytes).unwrap()); - - let mut bytes = sk.encode(); - bytes[17] ^= 0x01; - assert_ne!(sk, MLDSA65PrivateKey::from_bytes(&bytes).unwrap()); + mldsa65(); + fn mldsa65() { + let (pk, sk) = MLDSA65::keygen().unwrap(); + + // basic equality checks + assert_eq!(pk, pk); + assert_eq!(pk, pk.clone()); + assert_eq!(pk, MLDSA65PublicKey::from_bytes(&pk.encode()).unwrap()); + + assert_eq!(sk, sk); + assert_eq!(sk, sk.clone()); + assert_eq!(sk, MLDSA65PrivateKey::from_bytes(&sk.encode()).unwrap()); + + // inequality checks + let mut bytes = pk.encode(); + bytes[17] ^= 0x01; + assert_ne!(pk, MLDSA65PublicKey::from_bytes(&bytes).unwrap()); + + let mut bytes = sk.encode(); + bytes[17] ^= 0x01; + assert_ne!(sk, MLDSA65PrivateKey::from_bytes(&bytes).unwrap()); + } // MLDSA-87 - - let (pk, sk) = MLDSA87::keygen().unwrap(); - - // basic equality checks - assert_eq!(pk, pk); - assert_eq!(pk, pk.clone()); - assert_eq!(pk, MLDSA87PublicKey::from_bytes(&pk.encode()).unwrap()); - - assert_eq!(sk, sk); - assert_eq!(sk, sk.clone()); - assert_eq!(sk, MLDSA87PrivateKey::from_bytes(&sk.sk_encode()).unwrap()); - - // inequality checks - let mut bytes = pk.encode(); - bytes[17] ^= 0x01; - assert_ne!(pk, MLDSA87PublicKey::from_bytes(&bytes).unwrap()); - - let mut bytes = sk.encode(); - bytes[17] ^= 0x01; - assert_ne!(sk, MLDSA87PrivateKey::from_bytes(&bytes).unwrap()); + mldsa87(); + fn mldsa87() { + let (pk, sk) = MLDSA87::keygen().unwrap(); + + // basic equality checks + assert_eq!(pk, pk); + assert_eq!(pk, pk.clone()); + assert_eq!(pk, MLDSA87PublicKey::from_bytes(&pk.encode()).unwrap()); + + assert_eq!(sk, sk); + assert_eq!(sk, sk.clone()); + assert_eq!(sk, MLDSA87PrivateKey::from_bytes(&sk.encode()).unwrap()); + + // inequality checks + let mut bytes = pk.encode(); + bytes[17] ^= 0x01; + assert_ne!(pk, MLDSA87PublicKey::from_bytes(&bytes).unwrap()); + + let mut bytes = sk.encode(); + bytes[17] ^= 0x01; + assert_ne!(sk, MLDSA87PrivateKey::from_bytes(&bytes).unwrap()); + } /* Expanded keys */ - - let (pk, sk) = MLDSA65::keygen().unwrap(); - let pk_expanded = MLDSA65PublicKeyExpanded::from_bytes(&pk.encode()).unwrap(); - let sk_expanded = MLDSA65PrivateKeyExpanded::from_bytes(&sk.sk_encode()).unwrap(); - - // basic equality checks - assert_eq!(pk_expanded, pk_expanded); - assert_eq!(pk_expanded, pk_expanded.clone()); - assert_eq!(pk_expanded, MLDSA65PublicKeyExpanded::from_bytes(&pk.encode()).unwrap()); - assert_eq!(pk_expanded.encode(), pk.encode()); - - assert_eq!(sk_expanded, sk_expanded); - assert_eq!(sk_expanded, sk_expanded.clone()); - assert_eq!(sk_expanded, MLDSA65PrivateKeyExpanded::from_bytes(&sk.sk_encode()).unwrap()); - assert_eq!(sk_expanded.encode(), sk.encode()); - - // inequality checks - let mut bytes = pk.encode(); - bytes[17] ^= 0x01; - assert_ne!(pk_expanded, MLDSA65PublicKeyExpanded::from_bytes(&bytes).unwrap()); - - let mut bytes = sk.encode(); - bytes[17] ^= 0x01; - assert_ne!(sk_expanded, MLDSA65PrivateKeyExpanded::from_bytes(&bytes).unwrap()); + expanded_keys(); + fn expanded_keys() { + let (pk, sk) = MLDSA65::keygen().unwrap(); + let pk_expanded = MLDSA65PublicKeyExpanded::from_bytes(&pk.encode()).unwrap(); + let sk_expanded = MLDSA65PrivateKeyExpanded::from_bytes(&sk.encode()).unwrap(); + + // basic equality checks + assert_eq!(pk_expanded, pk_expanded); + assert_eq!(pk_expanded, pk_expanded.clone()); + assert_eq!(pk_expanded, MLDSA65PublicKeyExpanded::from_bytes(&pk.encode()).unwrap()); + assert_eq!(pk_expanded.encode(), pk.encode()); + + assert_eq!(sk_expanded, sk_expanded); + assert_eq!(sk_expanded, sk_expanded.clone()); + assert_eq!(sk_expanded, MLDSA65PrivateKeyExpanded::from_bytes(&sk.encode()).unwrap()); + assert_eq!(sk_expanded.encode(), sk.encode()); + assert_eq!(sk_expanded.encode(), sk.encode()); + + let mut sk_bytes = [0u8; MLDSA65_SK_LEN]; + let bytes_writen = sk_expanded.encode_out(&mut sk_bytes); + assert_eq!(bytes_writen, MLDSA65_SK_LEN); + assert_eq!(sk_bytes, sk.encode()); + + assert_eq!(sk_expanded, sk_expanded); + assert_eq!(sk_expanded, sk_expanded.clone()); + assert_eq!(sk_expanded, MLDSA65PrivateKeyExpanded::from_bytes(&sk.encode()).unwrap()); + assert_eq!(sk_expanded.encode(), sk.encode()); + + // inequality checks + let mut bytes = pk.encode(); + bytes[17] ^= 0x01; + assert_ne!(pk_expanded, MLDSA65PublicKeyExpanded::from_bytes(&bytes).unwrap()); + + let mut bytes = sk.encode(); + bytes[17] ^= 0x01; + assert_ne!(sk_expanded, MLDSA65PrivateKeyExpanded::from_bytes(&bytes).unwrap()); + } } /// Tests that no private data is displayed diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index e6f4191..ef015eb 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -4,7 +4,9 @@ mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; - use bouncycastle_core::traits::{RNG, Signature, SignaturePrivateKey, SignaturePublicKey}; + use bouncycastle_core::traits::{ + RNG, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; @@ -80,7 +82,7 @@ mod mldsa_tests { // Decode and re-encode the sk, make sure you get the same thing let expected_sk = MLDSA44PrivateKey::from_bytes(&expected_sk_bytes).unwrap(); - let sk_bytes = expected_sk.sk_encode(); + let sk_bytes = expected_sk.encode(); assert_eq!(sk_bytes.len(), expected_sk_bytes.len()); assert_eq!(sk_bytes, expected_sk_bytes.as_slice()); @@ -92,7 +94,7 @@ mod mldsa_tests { // run keygen from seed let (derived_pk, derived_sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); - let sk_bytes = derived_sk.sk_encode(); + let sk_bytes = derived_sk.encode(); assert_eq!(sk_bytes.len(), expected_sk_bytes.len()); assert_eq!(sk_bytes, expected_sk_bytes.as_slice()); @@ -121,7 +123,7 @@ mod mldsa_tests { // Decode and re-encode the sk, make sure you get the same thing let decoded_sk = MLDSA65PrivateKey::from_bytes(&expected_sk_bytes).unwrap(); - let sk_bytes = decoded_sk.sk_encode(); + let sk_bytes = decoded_sk.encode(); assert_eq!(sk_bytes.len(), expected_sk_bytes.len()); assert_eq!(sk_bytes, expected_sk_bytes.as_slice()); @@ -133,7 +135,7 @@ mod mldsa_tests { // run keygen from seed let (derived_pk, derived_sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); - let sk_bytes = derived_sk.sk_encode(); + let sk_bytes = derived_sk.encode(); assert_eq!(sk_bytes.len(), expected_sk_bytes.len()); assert_eq!(sk_bytes, expected_sk_bytes.as_slice()); @@ -162,7 +164,7 @@ mod mldsa_tests { // Decode and re-encode the sk, make sure you get the same thing let decoded_sk = MLDSA87PrivateKey::from_bytes(&expected_sk_bytes).unwrap(); - let sk_bytes = decoded_sk.sk_encode(); + let sk_bytes = decoded_sk.encode(); assert_eq!(sk_bytes.len(), expected_sk_bytes.len()); assert_eq!(sk_bytes, expected_sk_bytes.as_slice()); @@ -174,7 +176,7 @@ mod mldsa_tests { // run keygen from seed let (derived_pk, derived_sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); - let sk_bytes = derived_sk.sk_encode(); + let sk_bytes = derived_sk.encode(); assert_eq!(sk_bytes.len(), expected_sk_bytes.len()); assert_eq!(sk_bytes, expected_sk_bytes.as_slice()); @@ -210,13 +212,14 @@ mod mldsa_tests { KeyType::Seed, ) .unwrap(); + /* MLDSA44 */ let expected_sk_bytes: [u8; MLDSA44_SK_LEN] = hex::decode("d7b2b47254aae0db45e7930d4a98d2c97d8f1397d1789dafa17024b316e9bec939ce0f7f77f8db5644dcda366bfe4734bd95f435ff9a613aa54aa41c2c694c04329a07b1fabb48f52a309f11a1898f848e2322ffe623ec810db3bee33685854a88269da320d5120bfcfe89a18e30f7114d83aa404a646b6c997389860d12522ee0006e2384819186619b260d118664d4a62822184482402898146148a6614c4248a19208c2382951244808a125c2083108c47120140914836c18a78084106ec9c07022b56408b0610c070498124451886959004622932041062e42b64c01164914284c41a85180460a5116515a0820022244dc9849d13251e13065d3c08592a85112a1640039220946621cc70cd9086dd0062652408580443091062c50c80924c5841a966d4a982c99066da4443220a7645a326e11b57020926124138e04852c0a4872c8a051d3082a99208058242024074e59148810a46460c06de0b28d1b1909203422c024410943710a212061a2015222521b80809a340013934dd3322922170a9892691a14512027219cc02062a2814818691a854d8344695b2041031242cb184601a90d0c023183b0215a224ac89205d9906904306a4b064ad2b2011c404081423252327254a6405a18100c321292c2805212625c82280bb46c03428d53100c14010ee1365288842491020a63462620062911c228d0204802b36ca236095a8648cbb4618b4662c440821a890910024d24b24520122524c90588288cc9c04d5948220a276ec134644c90605b445082864943880443b28c603080a2882d84a46d8ca629d0c68442064689885100a98d01498de4380da4068dd3947142b26c1a84611ba32842b42808a0711ac531e0a04c013765242862142890091061d940221b3360090292d02481200408491844a3222d5c8844149808a446610195640b390a0c9450ca406ad2b220c0380182308e13b908918084148829c0189112350da02422e20406d9c2850428121cc989180272d24029c20812d8062a9994719bb8682384291a2289144511dc82445096450c4484c0b2049aa60543862c44326e88442120a84c9a3070e3b82d63268803254903438c48a809ca147253344e1243081ba704593022d99480e234228142129c302a9434266104452426281346094a326d11280918b82562281113410d41b21190844c8b1212a2c688c9c030220606d2188e848630904452128831d9207113c52843060e033060cca6845826524c88011ef72562c85ffa43acfa49217f2b172d7bbc14620e6d980a71aabbdf0c45e9a206ecb1423fee15decc17601300149d9223cd6e6c6e1fa8e41fc7c64938ab68905fd3dcda50d87082e7d0d71d1bc9b2b84c85523ca8fe6cad294adf83be15b108ff721d0cc87bc3dd3a7590184b0e845663a91fc9e1c3c53a61d867420b04f092355753bc65a06368fd41295fd09924132c6f91f67964c142674a725c343914c4cecf58c074bcaf4558c97bf7911e07aa6d0938f2ee2bb3c1a8c595d635e84342fdea01dc24b211ad2fc281cf77e59110c7abc54bf0c86d480b9be276471dc9d603cee98cfdab3e9fcfb703793560549ea4450fa7b33fb9169c44b4d25fb9c457f49791cd3da03eac96095813c105132ccda4e63e49228cd23d8a1f37856f142d93b90db09f82af89258c63aab8047a80c036c9357ea2046f8dc6354f0c5295f342bb417d3cfeb0b1fd33622c29e14cbbd92e1363c65ebd4504b7512329b9670e32e1b2c67a54e7f1a55f8b9f9ea04e8ca3a705e62a3c5e637374afb7aeb6ddea612cde28f01a202d7aa4e34722d27dd3f9b89894d019fd5d4d7119efe3723bba104cb8bb0981e074de3afe200daaaead826cc45f244dbf431afab34efbdf782474d2fd57118f646214934ed99cba3b003e8d67a3836f6f19fc41910ce5163ee3ae99eb84d514eb761e63684ea56f9791d2dd4aac6e6168b948c817f75a222acb0e8cdc03cc4afe8f67157e1a363b7faeff9f172b98913677c5a1dd085e9ee4c22052c1af58193116673dcd3bfc5f34b855dcc6c77885649e9e71f43d4aea0f4b72ca7eda0578ba13d31a658d2d060a9a66ff69ed1be7997a2fb1d2723d38f9bfabe18f8e7b3cda906e4e9b5e942c8eaeb296070ebfd364947a940cc978bed66b37749e6d5dcd7be8c494440e2b84cecfefb98c0bedfb3c41e3359d2cd7197fbe720c48aa6c6b6465c1ee63e3569c2adc744491370b7f7826fe0b77a1d19d64101d032b918106b42d2ef73747e5601fe4ba50f23ede521f031a817d15294a43722e8378784b6db0cf1ba9e8ae911d9201b9ce9cc3019c6f5c27cb98da26144b64225a7c932b30f761e78a2d59a1d8b83ec6344a2f6dd47e765706d00bf4a79a6a926c3ba91d812c8f2c797ab1796709e5d16856778293529f0286d015c3b5399619642a333e9e593d6e3f5353994208e9e6a332851d7f652522a928b917e27e2d6d42137dfe2ebfa6fb1c67b26c0254528685f7ebdbe315a68eaa2da769e8a9f42d3e60007c71330926b2c0012d83ead4e4fd1ed872ccd1972201d2b027f3545ac2d30cd78bc1d740feccbc6fc2a0446c6e30eac51f5a69098aa2d447f2085b4e4e4b92ccc26921d2de478518cd090ce267aea2d27ada57fd88b4976d89fb843cdccf49a76ca2679e6801bfa7fb031896fb50629704b9923936bb5dd385311121cadfb11995e59b73034cf67ed03ab813867648d025828087e949a9afd16b95d72d99b1edca257aac132ffb7a0709aed5a9c0ff05fb0f2bbf28409eed7b5f5801be964ced019e1cb7851d3851f10290674e19ffb008b301c4acf641a2bb14216e1d69cabf52b5ef227496b0f30799a855d117fad3744a6fa33503ea798b52ddd7ee5426609dbfcd3f0c13b164d6c051f7ed4a119719a712e388d328402081ff1354b554d2c237afed3b151c4ba8e9f4bdeb8499a3066e26bbc69e8af089dec71731d1dc529eab17ef7374734c0fe475494c83836bdd34a03b9bc89914716061bfb98ec6e61c3ed4438edcaf25243c647086b9ea7018b0d9a8a0b00cecb00abde2498d69c2336101a772cbe4f571523f51bd05882cdf358b849cc140aa1faf22423a12851ce0e33fd48975a4959fa5c5fe418c93908191ab6e741b77bfe02cbd698ee795c466d615619e6441382c6eac01834ee9ab73cea80bbe235c78da91bd79b6f82f899785d68700d393e675c2224d6b7a1ad21320495679adaed70167b50866713a53109db7b6f7d81304ecdfd83b319b1ef248306b45ad29e7ddcc863dac56048b5d69ea175011f7614c00a86a863cde1872a8932878b9ac7e1ac5bda4997b72064f0cd75f4c814e034de11acb9013cf7ea926b4e7eaace070c7ba2188efad2e431e1223d45dd05c4d8403c2e45cee6413ecbe7527e873e455c4e610a61839aacc0bd56d2483e78f298b66a478eb2f558cbafca86be847baeb02c5b216c8cd88fea4df249b09e670a20703abac24b0a91abc4a5646601442ba10becfd30993880051d07f56a05a9379e7a8e6befee3f22faa106398f7706006e42e9be1ef89d25c272f11a95095c587d713732284de9dbd3c7217b0689e21d8eb0ff69668").unwrap() .try_into().unwrap(); let expected_pk_bytes: [u8; MLDSA44_PK_LEN] = hex::decode("d7b2b47254aae0db45e7930d4a98d2c97d8f1397d1789dafa17024b316e9bec94fc9946d42f19b79a7413bbaa33e7149cb42ed5115693ac041facb988adeb5fe0e1d8631184995b592c397d2294e2e14f90aa414ba3826899ac43f4cccacbc26e9a832b95118d5cb433cbef9660b00138e0817f61e762ca274c36ad554eb22aac1162e4ab01acba1e38c4efd8f80b65b333d0f72e55dfe71ce9c1ebb9889e7c56106c0fd73803a2aecfeafded7aa3cb2ceda54d12bd8cd36a78cf975943b47abd25e880ac452e5742ed1e8d1a82afa86e590c758c15ae4d2840d92bca1a5090f40496597fca7d8b9513f1a1bda6e950aaa98de467507d4a4f5a4f0599216582c3572f62eda8905ab3581670c4a02777a33e0ca7295fd8f4ff6d1a0a3a7683d65f5f5f7fc60da023e826c5f92144c02f7d1ba1075987553ea9367fcd76d990b7fa99cd45afdb8836d43e459f5187df058479709a01ea6835935fa70460990cd3dc1ba401ba94bab1dde41ac67ab3319dcaca06048d4c4eef27ee13a9c17d0538f430f2d642dc2415660de78877d8d8abc72523978c042e4285f4319846c44126242976844c10e556ba215b5a719e59d0c6b2a96d39859071fdcc2cde7524a7bedae54e85b318e854e8fe2b2f3edfac9719128270aafd1e5044c3a4fdafd9ff31f90784b8e8e4596144a0daf586511d3d9962b9ea95af197b4e5fc60f2b1ed15de3a5bef5f89bdc79d91051d9b2816e74fa54531efdc1cbe74d448857f476bcd58f21c0b653b3b76a4e076a6559a302718555cc63f74859aabab925f023861ca8cd0f7badb2871f67d55326d7451135ad45f4a1ba69118fbb2c8a30eec9392ef3f977066c9add5c710cc647b1514d217d958c7017c3e90fd20c04e674b90486e9370a31a001d32f473979e4906749e7e477fa0b74508f8a5f2378312b83c25bd388ca0b0fff7478baf42b71667edaac97c46b129643e586e5b055a0c211946d4f36e675bed5860fa042a315d9826164d6a9237c35a5fbf495490a5bd4df248b95c4aae7784b605673166ac4245b5b4b082a09e9323e62f2078c5b76783446defd736ad3a3702d49b089844900a61833397bc4419b30d7a97a0b387c1911474c4d41b53e32a977acb6f0ea75db65bb39e59e701e76957def6f2d44559c31a77122b5204e3b5c219f1688b14ed0bc0b801b3e6e82dcd43e9c0e9f41744cd9815bd1bc8820d8bb123f04facd1b1b685dd5a2b1b8dbbf3ed933670f095a180b4f192d08b10b8fabbdfcc2b24518e32eea0a5e0c904ca844780083f3b0cd2d0b8b6af67bc355b9494025dc7b0a78fa80e3a2dbfeb51328851d6078198e9493651ae787ec0251f922ba30e9f51df62a6d72784cf3dd205393176dfa324a512bd94970a36dd34a514a86791f0eb36f0145b09ab64651b4a0313b299611a2a1c48891627598768a3114060ba4443486df51522a1ce88b30985c216f8e6ed178dd567b304a0d4cafba882a28342f17a9aa26ae58db630083d2c358fdf566c3f5d62a428567bc9ea8ce95caa0f35474b0bfa8f339a250ab4dfcf2083be8eefbc1055e18fe15370eecb260566d83ff06b211aaec43ca29b54ccd00f8815a2465ef0b46515cc7e41f3124f09efff739309ab58b29a1459a00bce5038e938c9678f72eb0e4ee5fdaae66d9f8573fc97fc42b4959f4bf8b61d78433e86b0335d6e9191c4d8bf487b3905c108cfd6ac24b0ceb7dcb7cf51f84d0ed687b95eaeb1c533c06f0d97023d92a70825837b59ba6cb7d4e56b0a87c203862ae8f315ba5925e8edefa679369a2202766151f16a965f9f81ece76cc070b55869e4db9784cf05c830b3242c8312").unwrap() .try_into().unwrap(); let (derived_pk, derived_sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); - assert_eq!(derived_sk.sk_encode(), expected_sk_bytes.as_slice()); + assert_eq!(derived_sk.encode(), expected_sk_bytes.as_slice()); assert_eq!(derived_pk.encode(), expected_pk_bytes.as_slice()); // success case KeyType: BytesFullEntropy @@ -262,6 +265,7 @@ mod mldsa_tests { let rnd = if !MLDSA44_KAT1.deterministic { let mut rnd = [0u8; 32]; bouncycastle_rng::DefaultRNG::default().next_bytes_out(&mut rnd).unwrap(); + rnd } else { [0u8; 32] @@ -481,7 +485,7 @@ mod mldsa_tests { } #[test] - fn test_sign_mu_deterministic_from_seed_out() { + fn test_sign_mu_deterministic_from_seed() { // I don't have a KAT, so I'll test against the regular implementation // ML-DSA-44 @@ -530,6 +534,10 @@ mod mldsa_tests { ) .unwrap(); assert_eq!(&expected_mu, &mu); + + let sig = MLDSA44::sign_mu_deterministic_from_seed(&seed, &mu, rnd).unwrap(); + assert_eq!(&sig, &expected_sig); + let mut sig = [0u8; MLDSA44_SIG_LEN]; let bytes_written = MLDSA44::sign_mu_deterministic_from_seed_out(&seed, &mu, rnd, &mut sig).unwrap(); @@ -571,6 +579,35 @@ mod mldsa_tests { _ => panic!("Expected KeyGenError"), }; + // success case: seed SecurityStrength is exactly right + let mut low_security_seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), + KeyType::Seed, + ) + .unwrap(); + low_security_seed.allow_hazardous_operations(); + low_security_seed.set_security_strength(SecurityStrength::_192bit).unwrap(); + low_security_seed.drop_hazardous_operations(); + // a 128bit secure seed should be rejected by MLDSA87 + MLDSA65::sign_mu_deterministic_from_seed(&low_security_seed, &mu, rnd).unwrap(); + + // seed SecurityStrength is too low + let mut low_security_seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), + KeyType::Seed, + ) + .unwrap(); + low_security_seed.allow_hazardous_operations(); + low_security_seed.set_security_strength(SecurityStrength::_128bit).unwrap(); + low_security_seed.drop_hazardous_operations(); + // a 128bit secure seed should be rejected by MLDSA87 + match MLDSA87::sign_mu_deterministic_from_seed(&low_security_seed, &mu, rnd) { + Err(SignatureError::KeyGenError(_)) => { /* good */ } + _ => panic!("Expected KeyGenError"), + }; + // test the streaming API on the same value let mut s = @@ -661,6 +698,47 @@ mod mldsa_tests { Err(SignatureError::LengthError(_)) => { /* good */ } _ => panic!("Expected error for sig too long"), } + + /* GAMMA boundary conditions */ + + // test to pin MLDSA65_GAMMA1_MINUS_BETA (produces the wrong sig val if MLDSA65_GAMMA1_MINUS_BETA is wrong) + let sk = MLDSA65PrivateKey::from_bytes(&hex::decode("FAC3F0A1C095D7F14FE867233429E7FBECC3E56C1C47C5776F60FCC952271C16D701337CC4DB701037240B2CC96700E76D912695D01ECFD144A1A4480C9A7AFC6AEBFF85B31A51CEDCF03C089DB78D57D5736998094CED527BF62306D163DD076746390725A575F658812850340A6A22F21D1A4488F81CC9C51E34C7195E24E62743418082738460173216564147714141447322747378303064332088852786387656485685067602315841385530303073748288551114488671126472324211001632768375217888105730287103413366631348063456705643028124418424362865147136784028750605280522354664402012156867463840124312702746274317185820204146418146260428165381878680661671606625835681411564078617545272781118153404548772767065605505661113346550212253001708208871437608544687461742554212252531618480788375428626465682802665176276452816778253827888713776811675325120157277407836344676441366411518042352072878407260148473360521722835364503831153320866470060388717774746280482741018426182467855676081768764502460378420511274573338543580057135466786576641657141853703461273673864078756846882878275266371317370221447843022470612874036187668341702635480511522823511802187750128755208466178350262551186287764073638143070751153170178714610652685824431071335605871487821771381233638082075233653821363404721684841385534088177538652001225820118825132746376784420308558673853184746535462708561415062307838047410305386755301311878544252655175427735642725125852462520300512237334101417850348822685720422134232331416163464745560268223741265100778127023844048536253761851061180163560244303674215200615686636064370316373872671703546376007034507065834571177568604078030182100382824024244355146222000521887855141800051638076135301876078282783854476883578855568168226642036114282711266780675104628153263815046834464683021517741886573830273710441308840745348777606127672844722386630210200025302263135713023513144334831232011681183146135820156511536562848603567353564216431366041187872531665824337234157375170402400366431125740874338582777401587182726007834342527487856427375026483665335500311348218318404831768517434724867376286526385537375022315084547851162348038628580233345112714217535531172243054028425076334167574124642505645643703357747258152772101731762154367415443402180013310507803185561248014235117864462560741807827475862248680132084577068144204516541344387456176453454137741684473663420305220270013380651567340128517050186661163071768085126283043061688144133578020758648635074223303518423535803274805136544782015667213011858810228662670086040427500742786645441425845543080676585818187373073177826648377838253361800836061614077137087700227656343025125400007421502577613446081061602370815457845047316447020371536762643681172462742533741043757221578526022521413633247403254078850774373623685012472806652753134016260818410320635707047651107738122602141422051241873175027656625672307441218576154467471554748806141478270237022035541450228312172183426588573284554185388468111581188733140148403753651886533044825265188582527046117408104630522280766840883618814145722050535737005141474643706767817456415644868337004352380220517858504287578807218203860374826463502187671017880147716606130536501355309DEDE7F9572F3915A1C1B014B2FB3D7591DB0BED20443D420E8D4D3458381AF3506F93C6CB3561449E50A9E3349DC9B8CAD8AB1ECBF946CF6ECBBBC11A8B32B295D1E3CB23DF662E8A8625EE91ACCC74EE9AB059FB12A46AAEC343FF1B21BED35E135F46D05EE11877126A9961C897BFEA0D552E03BCB313AD1BEF5B21FFF09005417B0F531120D037D1106C4FFF3692C607108563B272D054D98299D1FEC0646536152FD6F915FBBD8ED1E864904149F919DA295CB79F4EE6A9123E82FC5DF0BC379743B0A650D8D26799B02C0A6F278B323BB7F58811E75CDDD6BA27200DD7BD5B94F6AF7AC76B791F12A1C464E2E112A591A12E3EE569C7B0BBF726C634737A7388D676BF36DA496E3A6DD4744D83D5379642261B61D6A08A164BE30360BEC7E05473A66CCDE29DE5F68006932739DD876E29954419750B6C2E282432E0E900F30C6DEFA5042216C398189F1072F3738DEA06AB44F7611BD995430B2204FCA4474A2A8DFAFC44607F9AABB80D5FF235BA93A74BCE8297D45A35322116117A5BD9C960A18758040EC670D8F0B5F75515DFAD91A60FE8F26A5048F9A955210B6CFFC7F6C963531E9B6EB0DAC6D744E63A22AC9E2935FCBD8FFDC6D1D5621266A338FFD3E28257C5F04B209DD1013C2EA6FBE2D197BBBC9B8F1A6DB84E361F1E1D6235566A289861E193CF51604CE29DB66C546C20CD3D769E128BE0C10C7E8EA640F23778F426DE99A675726CB837EBF6F2EDFD886E7522FEA3E9AE793E34B702493C6575828021B8A4E1DAB64DE98DB52126120B8B192AA8977143E11E7819A2C116598CA301670C51569432DC1C86270F72E474CC35B4A7621F0EF883BD9B187DF4EBF2BECB7487BE331A45E9CB43E5DB9225B21724D60CFFBE5A3817765B5DB2E111AE6445BFEDD6631A1FE8048E2008E5DF450E0FE622319217567AB202BE5F6E9D8859CADEB2210784D9FB1778BA039A98A2C47435EBA76EF170B9ACB2D3884D64831E910D2CE1E8346881490FEABFC5882C7CF9D6101916E4021B9CB723189345049B5048400307E0236F177329EE6CF9A67848A4D7C98349A94A68400EA141BDA866F5071F3F4A7B8B96372C5442B43A003962DA6A5F07B9A572E7EB8A057BE06C7AF19D4EA728B7BF7F0AA493C413190CEE7D9AA90033378A2FEAFB9F1F360A904BFFA62FE1161240FCCAD33473A93FDAD26DAADBBA4CD05BD1F5897C40FD60BBEA8ED0404347131B2251D4CE06A65BB5EC31BFF5D03CFEAFFD7D06D09CFE8D88706A0AED09DFBCD273C65E1797D65136325A78E56CC4836B39EBD5A964229D2E9A163FE0E12BF0355D6265D01B7F94874E579119AA7E8C7EA69C09A2FA46069FA64CF0CD2ADDE12ADCE6B422676C0F05951B5C85404E25455505730986415B693604F557E09A6C6F48F5C193BF7DDC0ADFFC91CCBE0D3F7577F9AF85BC039CF2787ACE3010A413E3A0613C323B00C5A42776789382EF804BA918646A27ADFF1C3BDB8D86D4D1237ADF4190FDA2D87510ED68CF9234A766EFAFF779EF9FC4F8337E147FB5C83227E97F67D497F675DA66D55A88E9611459D98473DBDDC0730B2F8C6FA606A22E26679E68BCA03A50DF3731992A97063A4A6AFFF8C268290422A9D094B85BD6851177BE2BC04FFC64CD96E866731B552DD3C779D49C7781BEB8DA3F85B942A9780E47675B11E5DD77ED1F343775E876A737CFA3CBDF5CBBD7614A3EABE4DB919BC3E13ADE651F45133A04DC5DE0DDBC0BE01DDD615BFE6666769CC663F4941131A80DE835C851EBC67DFA48024C07FFB50F5C7E0A5686D1AA9EA22E45F6DE2A07DFEF28E90B00C24074897A76FFBAAEDE6BBB9A0DC76D35E2163169C426B1F5221D0A33F06C25072A2B164521736C441D4547EBE8AC4C408D30EA34ABF595BCC4CDF6157E86D8F8C5950E9EF27290EA077D812941A1DBBB4C6D06D52583DF55547F657346373CA37BFE9CDD6010CB5B0033F643EE0D6C9052699FC8E2739B69E1896BA14C7BDAAA7305F5F424350979F60868CB7E9884C818444C0FC0481216D06AF22076B726DB17A496CD45E9BA344C9F49AAE12E2771DDC9AD40908A00F8A19B85F120DCB1BE238E77E15ABB6E24E4E8BC55A68DFA7D473ACF7519EB85F714C0F49908BF2A6F70AAFF77A3DF2500992E304E98F6641505B1A5DF02C66118492354EA720A7078827A1B769B308440864FDE478F312DD497F7C507AEEB9AE85933607F20E4FD1906FB6FD074B8935D0038A2793489136E8305131FB95296A42CCB66F4F90F7214642929DC6E6843CE5B83E58E3F22CA8A5586A1D93527D75691B1E485953FE1D3E56E1C3071965B8E8EBBFE0F3FAB5F24FC245D2AFC423AD67DBD22E69ED2619E42CA0AF8F30C06D969CD7C69AD0783E17D018DEF90E75198EFADE2AE031FAC1905D5911D198635A1972D9CD8C1E99F049FBD96E6F88A25773B9F6048B6C136B4110DA4F587EA5C27700BBB6800854C9A4D1375F9777E09B1E53AFAF73EECC5E7711F7B19E7026408DAA10E93B8D916ACB0A4AD57848C2B9F767C48709382A2CC57B9DF56264B8EA91A529B4ACC7BE8DD3F3DF30276CD459CAF32F65118B6914E22507C2D5E032038BC272442FF16A80224EE35A4CA90B2564EA78B3856858D985644B683F62FF5AFC04F4F6FAED8434B6CF4EAF116FE231FB7931968D7BBA18BA347628304EB13EC8BF83FAE853E208FD276EA60B2DA751B058D82EDEEB4099BE90A581B52415B3E043B39D570C6E3996F42733A1459A5E3667009FF1124C106D2ACA45107D242ED4FF486F1991A01B5E471D6D360C19D3D064B578147A82DEA65FE0F8B7897B7347038522A2EBEB7ED217E43C33D5B7ADDD9942B43F9D5B8D87416C425F3E6CA3BCD3D995051C02EA4EE84BCB3B877F27FB16993522A91539D3FF43925671FB256573017302DDCA5A562FFCEC07A1204560252C78C2D797DD4EBF300922D2891AF7D220D32C3AF68F20CC096F6D5F9038E5AE7BAD090F99F9AE7FF3A615EC4A7F1F759A52428F94DBD496A81B0B71278EE4127AA72599C97618BF1D053C471E85D888C2971D816E5147BB07AB2192FA473641495CE64596D543D37DF340181EC609E8EA8F72676C2DCDA357C19051A8642D99FA00F962D28B4830E81C8D0CD2DF26ABE514803C8192608E29B9ACB3B150A664C474C6E064F1BCAAD0A08344E911FBF8425A0893D71325012113B8F2FD74FC0E2966AAFD72F280D18D8D02F779B0B2C61DD5E4385B5B82242AC327C3DDB6AB8F288E8DDA24F1E5812A111860058DEA190208928F1AD208974AA2EDD53BAFF400888AA9D74C0071C65C30AFE4A02B99876621A0542BBCE337ECFC06F0A236C3E9C672B48E61EA70CDF82421C14B7F0D25474ED2B04DA1C570598B14CFF438BBC44AB3C931F47D65DD0E767DCD7DDA522CC4CB07EB78962A50D281BEC69B54A77D26C902D3759560DAB544404BE0AA3F80D3B84EC4731DFFD1579533004D2D1EBA09148AA84C93B6A2049AD019A0BDAB9F99389FA4").unwrap()).unwrap(); + // because this is from the bc-test-data with busted mu values, need to fix the (busted) mu + let mu = hex::decode("dd6438e69fdbe810789fb22940fb451c28ac570c8e195f4aebc22db848cabe970a76c92011702d672478fc09f2bdc10b0b7c2b1752a9d99ea7ac3a6381684540").unwrap(); + let expected_sig = hex::decode( + "C3263802CBF2818D55BAAE9C2BE8C1EE0123802BDAB7C87CF9E6CF06E2A8823E750FFC8FB917F9052275A2CC68C2ADF4CFC78D0144FD3E96694C116CE1672BBCA6A3E1F6BBE8A0C34E5C1874DE1798CFC7766FE1BA906A1C6EF40EAB1A914705C3B0F14ED8D212B81D400281F68644B2EF72330DA2BD16C792D8160896177E920EB8E198AB5A38F5FFC5088663D0D0E0EB30C342F41B886FF209C8939CA424C2C24380E4F5AD805242CD25F4AC445AB883CD5826FA4135F4D549B12662C687B0FAFF068F8C18077B463E910E7A17B3BA9A76418C79FC53CADC75E48E977EAEA1FE748EDF0A1C0CDF9D037812B1BE505BD39E4EDA489CD56369A1B3F793554D7DD815AB1A7DE536018F0E0D73B95C8EBA07626D92B0365F6F71FAEA07E1E80FEEA666EB57865D6D13E46B801FECE841A2179BA8DA1FC1C1AA53743F187F426DFA34510E11AF1D9570FAC5DF77C9BF7ACE64BB130DB2192959091DDFEED580C722ACE89F84729540EEE8CFF6D8E4AF91C1B0EBB42A65F47B80F4A300431D7DEC90BE739E31C9906D7403B82C196266D4FCC6659C5AA549D0ADC3AAAD1F9DB7C80E17839957DF682C10B1938DD9A292C65E0146566B1EE24529C925691BAC109CB308C4303C3513FF5B62275E84C8C94DD6CFBF2E0F9F0B735FEFC44095FA2E01009C37C33BBDF082B305AD479C743076EC2EFAC0176F15A0EFBFA504894A3F8C6B7ACB096182B71131F46B1EC7343C9DE78F85CC0352F97C188667898ACB4EB4173D715FEA3B69DCB1B47904C8AB51EB4915357995B5F69F979776C6CBF114BCB0A154D82D17A9251ED545FDBE7E04AB06B99662D18673F08D933CA0B84FBEBE17F783D4039078465C378E9AEF18423C7B86C411DF9B4A77A2BC8804C4E5A5F9BC5777166C221F60107D2107AB55BB53E354267F8329B71A77BFF920EA94D9240B436E8CBA65940C0734D6928F57257A16154BFA5D8C9459979DEF567EE0965239C0DBDF122723636F2C5C5CAC6C607BDB9FDE8B8557A687E3A68970BFFAD695BADA74CE3C681B81AC8CE3747B844B7530A606F6320ABE91F16E79EFFBCB8446319D888D957252836BE6D88C018EED2F5C46369FE88CAEE624905E71C41252B70F115F781E0D4D851D2820718DA03FA1E9F9490AA06D74E3D15A44CFC98E57AB49A63285451C2A43D5ACA6733952814C05391431052DD9FE5D4AFAFE7027E5E5C8E9247FD5B574B438C4E541D6F408964BFEACC54FF873C6976F4383AE2517D4860E3D775280DE63D2EB01709282170E33A2F5FBD80937B0D23188C978BA370B5BB154FE0F2FA0361A2F1E46FBAFE6494FEAFF26097EBD2E307301C8649FAF82B57788B62E6C69DE6F6B3ECCDF17C10CBA41C3D1A8025F5BE98D65F2B37AC2527BA60E99B89FDA9028845F6EE6E20C2C8E2E8D8627C4D5859CD141920133D4D4B3AC8B91C1B14B6F6B8DE35E9674F312B938DADB90C335931081DB90640883512125BD793467C78AB84208146EAE0D62004E51031377BBDE89AA0AF0609E9AE315EA8A043C368167073422018900F9C653535A96FF73DA466377CF462314F361E08ABA6CACAB9AA666633FC6602D645CEEF305829B90AFC8BBDA9F8E40EFFD62FC31A1492914E22DE711DC3102D64AF46D13ED766CF36E51629409F69EC4DF98CA97EC93DA4BA215C567C73104FDF6863229E2A2C9A3CA11DD3C8D505CF88944428F9D500029B770FDEB16388F63C50FCAD1953E2AC759730E7CAA930362274B6A3E0ABE1211613F3C36FEFD09454401B6110D95201BD022A3B6C68DD054AE0B0C547AFF5C7421EBF0879098112286C962A6164C799A7A20CCF3D4189B2AA240F895C54CB2F9E6C2C1E2BA9BEB185CCBD82CEC60A49D8CBBD3C26AC0BC5C43E2D5672E21F19ACFF857217A243D4C90600D3737EE78638AD6239C3AF4E79FCE030DDB0527EE428ADA0C1C9EB867593092CD1A5B77347FEF4541B71AB33932F8F1BE31AA475B9F4BBC07F218377E436E348F47607D9140E6114A05D2A072409C390EBC79231CB6DC2E3A698776F9C32B44C10F156AF450A8DAD6DEC0D294392DF6E17DDD312BB22997CF1D8A6EC55DFE401EBA486CF9C8C0E6E713D48C0CA8CD26EAF88DC0B34F86F35B6617C45173131779ED98E880BBA3520835F29604B6F32AB1DA8034F6F559E81050BBCD7833B0D71E90101339386AE3BC653CA50F37BBBEB64377987BD892CA51280499B6EB051345EF9BF1F6EE766142491F96E27787ED8991338F60D2A4BAABF43ACE7A6072C6BEB6D0FFF79A3B956DD5CF6348F6E02104E1F9F33B2AEDFFA6B833A00CB21BAD6AD04047DD845180131506080272925960DA888E393B43EA5770530347EAF9E9812E87A317D1BA66C61CACD55A8FFD6355BB39E596D33F4E8355A6B51C4E70697D4C5BE9FFE64FB11EA714D81B20677DBA268E8A9A5F61E8E268DDE6F6FC93F3FAD688312885681E0F67474689680532DB88E178AAF2FDD19E37B091020FF01008BF0FA8017075C2F02B5E7DCACFD1F337A24815C6D9A9E1678EE2EE1721A61ED774A7DE98051F799D4F3A5407C1F725B618AD3B0C17FD13D762005C143A9AD78B13DF4B376D924BD9AE37502446D2263046BC7CE97CB6371CBA72FE0224FE71D501884E9AA820F92E1CCC3F7F4D57214F0842C96B85BFEFC3767B81AD76F778B00EB2BAE3C99D75EAF295901B9DB273F4288FA4EA74650B90D114B081B1D061D97EF211A84726756396F36BCACC45A8EF3F57E2331EE4FBCEED55C48605B4A3D842067933F8AD6D72C1793B17515977EEB8404CB55388EA3CB43F504A5325870473C14287DE22D55015391D973A9556B93B0943C64F5CE8DCD365C1F1DB7B56B2FC91BF5552E5233C28755E366BCF303B33CE2FE608A5BF6348EAB272E4FC6B77F13367E12085E008C8CE17095727D83266CC321ACCC9C8B58F2D149C205A4DC6D3A599CBA0E3FC4EE042C3461445A536FB9AC09D2FEA87C7E0E8BE39B2D0894EA1E203CAECC5F79F13ACAA917401E75E8F36CBA4D34EB639725FE41E64912F48A70D1BA47309D23E40DBCFF986568B8536B5F464C5B169D5E0F90ADE7F46004098E5880340DB738A7B9C849A71D01B8F8F9FEA8B3B331210553C469A5EE257B4EB02EC8932787C54086C5DE6A82096F0AB32B716BBCBCAF26BAAA792931D946FA1D59EE8A9924484E38B02F89D12971903C675B9FE420DACB1C0172351A3A44376E6AF6F25B261173E98B04DD2E49BD795A17E9C3E1D7DD1EECAEC4B8A5AE4B266E4DC0618E41AB8F82A4F652D2A07814DDEBA856B6000EDAA8A8A0542466A83243AD8128B1C468215EB58CC436BBE6B7A8D8CB12FA78131E1887B3253BD623055D72B13E175AB7DB00E8D7680360990C1D9EAD06F04F51F739734DEDCB8AA5ED3BCFEFB49D5C89ED291C4AF1EC09BDFD24029B7A219067DAF493C53809BAAAF830C8D4A3D643AA1B0CFABE58D0B1A5E9C0357AC421459FF0B0766CF82E08F95BF8B6C65D81BFC342A07BA51C914D7F1FFFAE1E069BEA449EC9BCF898BC29CAF4FDDDE6846361A4393F6C6987C766037526A9FE42008FB59DEE597547B8F61C62085BCA0686B6237C384107ADF094C91A230D783F55D39F1E57128A92F0FF0CD92D6B454DF4A3AFA2E1E4DF2C6C9E3CA3B304E27A8D7F44C75EEF09F475ECE74B07A80DBE1B4F2F309756BCFEC0C2D78DEA4CDF3BC864FADBEAA1B9475BC7EA4ED80D26B14B61EDE93AA2A777C8403DAA7BEC36C5503A2114A97CCA8B04565F17A4F0DFAA4985A5FCBB4398323233ABCC728CB2E0D1E2CCD36645E5915985D39ADAE871AA5B1CF3B001FDC7D9F0BD82FCE569A5C6BBD216E09C11CC6AAE067686E95BB28C441F189AFA83A9E117D944927C4A76FED756A35E5EE0B0DF4159187CFFE5F6EC19F8DAB04791E04FA0A3DF55EC95B96D7AEE099867987986D3FF1422DFDA40100C226F8A24468393F21E11D3B583DD8A5FEED01832ACD1E24E2D5A8CE16C24629B9964F8B4E0B2B0F30601FA688711065190665ACEA6A971DF8FB708C056D68540A78FA750B43E0D54086502D4B19171F61B51AC075F7F09260B95F59BF343E1210BCD39C04B13267122B6CD78D2578EA3534EDF243D0FD520A212EBA25407E2B8119068B2CD1D1BA672E5CC73BE9676C9E2D906D44DEBA9573E7FC909E69BD9CCAEBE6600E30D3588CB8EE2E5CE95C65F3FD24E4D527CE764FEF539639155086FE564B607A1082A74CDE886527F8D821E42AE88281A1CFF38EB5159955C1326C2D425D1C09205128E0386CA5C71B911896543799C3B1ADE8391E4370519DB17122A1A5EB6338208984E7347FC174D6DC4D59AF64767115A344A11E2049CDA0AE0BE6D7AE0592FC3468DB58E29CD3010E3EB17F736EE0E6062BF1F345A804F1733C73FE30C70B6FB625D00E529A400C069074235969485142D37F3D963D8492EA4026867ADE6108D982E20B188504A6724E6448992A3F7962AC2F01222BBE0715069FDE7D9DD93A6AC8AD321163FDBF9429EB2DD38F44BCA086AEA91642D4C85DFCF322F0B822B6EF73C570B564DD721FC3E73ED50A7FC88644C4340951C20B1E2C4A65B0BEC3C5E10E4C5F769CABD7E571748485A3BFD0D2204748DAE3E5187A7D97A7000000000000000000000000000000030D151D2328", + ).unwrap(); + + let sig = + MLDSA65::sign_mu_deterministic(&sk, None, mu.as_slice().try_into().unwrap(), [0u8; 32]) + .unwrap(); + assert_eq!(&sig, expected_sig.as_slice()); + + // test to pin MLDSA44_GAMMA2_MINUS_BETA (produces the wrong sig val if MLDSA44_GAMMA2_MINUS_BETA is wrong) + let sk = MLDSA44PrivateKey::from_bytes(&hex::decode("1B8664E94372A180CAD8C3D93F57FC16F452EF524D2DACEF0A50C0DDED23FBC052EA7330489B17CA2FE4234FB0F9CCA63B6185963852374EE10116AB38AAE2A922693A91188E5F5057AB18485D98B87EB21CCFDF5E9F4B5C5787F5043988EF6790A47D70515D93C48C28A855111D73F245898C877BA4E6A646057579A111F59C1BC629D82652D388901B2961C2C270A3B68054329299362691889122320418918820132D03C28D8C144200B825E0A88198A210031328D4884D1126690AC38D1B1510089051C8142D02B390C8440564040800290CA4480EC4424D1A960409A58010472D42346911A4401481884830411B122589808981222E63164D11C165DB064C0A430412374AD19271400846409271001748C9124894064952C06D1C316AC0484C19B69103A70950100208460458084C49224EE4484559048D514265D99220E2A41162C231D01620C31800D4A45008390A22A67018B36882049103440642364E09B3484A8448DA382E5CA810E01810182466D446681B489102384D8B18510397259A400A00436992C40C129285C9C05060486450988884386220808D919268233401A0B8405B0662E4C42119B74099B265E2400860462AA0B40D1A3248CC06318304489BC6114A484503A4010CB68D11860111984CA21410CC2666E0A02C94B01183182C904805A4320860B29122B66142886DC9222D24C171A42211C9A08814854023034582A25183B6711BC88DD0480292387220B041238790C8081203C0099436918BA2244A422959042D48260194322860928C00A844D21264A0A6240C9870C4A6042134209CA4099B128A1B3249249651A022281B44640AB36C029969C4C2710B396D0B964509B6209CC87150142A80B620CB28121A93684B382E01350A88160520983181322104426E83A6481014495C304DCB1826000390A4386103314E02B3008A14440AC78111C5704018021426650BC68C53C4708098481B9044C242680C124E2049284A3671C9B08911A77049982403C0300A83654B426D99345110B82C021092999429A0124E98184D50242061908981282661B28108A6651B22450A342C9C984C4B382DC4086292188D41C46CE2C491D902419C1231C2120CA040440CB12D22062DA3901102286C580066801228C9083101872554244902C62CCCB28502168CD8422E24416D1C20924A0660892826D2329092922C03451258B2099B2481183804243390C2B44C5AB485C814610A188503852988409203A580C9C0E30E0791B8E694262282FDD2FA69AD5CA94D015922BB63BC690FD546BC1ACFA0E79B02E7D1C1E92F419DA68C30CB12C736E216FC72C394883FBF40CFF538A43D08CE8A390DC7B78542199A54EEFE8FCD49225596FC97B27CF8BDDB57209E48FB628909CF889D38855CFE04AB04486378D17DC5780E81168B5E1DF462FC2D43860031B59D80AF95AFF0278210B9D96023E8A88FF0E4403D710895C830049AF4B8BCB57C5972D067A579C8C0021E5C870E1EE10629518191E283E796B58700061BA656B9385F54AA6917EEF52E60669B7B4F4FD36063DFB7822CDB4F19D9DA4E2F41E4BAE8C5274E7A9EBDB80BC9555687827A10224D9044DB2E45A89F582A716B4D3B131AF7DBCEA95DB88D84C73281D4CA7961F752FFA520BA3FDCC245331849768CE4D4B1FC330DCE474F31558764395F97FFC847BE9147DC22A3021F666A4327FF7FBEF007683A84C9FD99F5088703784AFFB3D86F216405BB9218DF05BB4CFA100145C9358EDE1A0F4C69AD23F7BA56FE8755F711067D7D43CF9FE0A93D642383DB3A0086CF7FCCA3FC59379D416DACBCEB7B49DFF554748EE36DFBCC07D15270A0388DA88360E6CA476DCFF56DE0B7B359609E58A728EE4B3ABCC296E42CB76034C7E13BD0B5208EE8CDF451F6CCCE87E9A21C3DCB28B8B9805CA7115065757B3C6D2A39B28B8610A3F30092C2E9F43A744372FFDF6B5F69DA6589860C520882FBEC130B28E1788572252A5995EB00B4B008931220B27488FEF5D04BC28C1FFA9DB5D0F1ED49CC4D775EB3DEC26ACD92BBB8A33A195A0C2F5AB7386BEA3AC9A9EE90C762E005DEF59348B0B1A6A373CCCD0BCD4125E0602EAA2BE057603020C2DC849513333C019A22210B55BCDAD54279BFEC5F998C1D14F7590B31B403564ED2E54AD007EDA4F2DE06AB0315E4C3384BE2C5392BB9FF1D41C770A767E4947FF7B609CFEAFF54DC672E866BDB861B81CAD3CC646DC325A5E1CD37C45FD0E29D6C2117CDF4C8B3082249C4F8D3E736A1CC0628D5FCB322711D78B881E7AABF4FE8967C5D584296DE520B818966A9490C82063D0C3C743208B25EB69BAEF6CC65E3CFF0C0C02997F328D2F1D307D421A87EE8A2737C8D5E7F63141DE2E03F5BB1EA20C829C4A00407F2E500BA2877DA65D64B29E42F097164F09F69C9587E4F1A1A88F619B6D19F210C01D35443F21C7C987658DCFF4163C8646F7671D04ACCE322E32513737B35944D2E70D62924F922A35AE86347843CA971FDFE616FF0DF4C5F77C699B14A555E0D41FF39BA7BD156751E73EC64A25E80E93F59217A5AC8B2162CBA9416E54F04447A99F8073D881BDA47B7D8CAEC135C0D09A028DD2A3EA74BAEFDCECA0D08B8FA701B9ED25B59F39BAB7ECA16A9ACD0446827829EBD9393E8DEC95C2395704F8CAED2155CBE604B047F94A650A981BAA93F070C3142011BF4F1561DFFC4D2F2B7144AC4CCC8DB17FBC3811C421961B156DF1FDE2893FE87902385B0FB5538E996FC40CC9B5995297DB905C262E25145A3C35A6B6F2014CC9DDAF4EE05DED72B3D436E4A6711BBA6B802D0CBC4ED44AB923644CF9E4BA4F24EFE1427E44D0A3FA98C1AA92F37E56440BA282AE0A77831650AE866C86BB2AA49EDDA7AF27B13A612F6A185265D93097679A7ED5A3A38B4B40F16CF5B11B99E2F5B529B940A4583D38B46D388167B34458CAF13682B6070D4FC37700E0222B04B9C1CCFE8FA0BB2B193E743E93C38DFB5029561287C782D9A0BAEE67705C1DF3AB2D8A456EB7C3F5E5D117108C3C15C3F4D2D382A6F69A5716621D004E5F510C4226E4B6BF76C36E649DA5D6EAE2690B1ADA69D8570DFADDE392CE5D9AF4AC94085903C52B0CAA61856150DACA070342754BC3C71AF32F8EB6D5CDF10F0B20471971569E7F1490A32E6F4DAF129A6CAE4DBC977C6B0D18102B9F18226ADF28F168B24D307753BA24574808444E488D812E9E97827261801AB2DD2F62EA4F4C552E54735A018D7ACCB666F6FBA8BF9EA8280876591B8E73D6303B2F1E585BB86F4DA1769503E144D3BD503BA460802D9287CB7B2238C2FBF3490DCDE4D35F02A63463A4F488632070345CA7C19604FED48A7102562965BFD87F2776357B3D1B17AE98CEEC0159FBBE604AEE9D92570E526CF6A5806D1BFD3092007D3DE29B9860DA1DE62184B4F576A8E515BAB346E2A2A4DB1338F4490400CA5A3EBE9AC3A8173C1991839F3A62DF7B903F777B0E5608C7396719639C8BBE3CF8CB371A43F7C436796259728ED11C3AA32BA97FB0B9C708785FD00345543EB8CF195A442A42F6AFBBBCF597CA8230B019C283777830E70C19DCF018C7C0561A7F2FE").unwrap()).unwrap(); + // because this is from the bc-test-data with busted mu values, need to fix the (busted) mu + let mu = hex::decode("35fd0d24022864a6f16159a654290be0c26688439c30c979d14b9553299429cd7c01f9f9835121149c254f2615805f64a32feb0607d87e9417f2900e0d86e351").unwrap(); + let expected_sig = hex::decode( + "20E8368CE256DCE0BD48E92EB0C35821EBE95B1388A861DAF45ACD482BBD95AA3FFBCB71B01E95FEA987EF538F300EDB7B8211698B5190AC5214C681EA39C78CA5EAA17AE994CDAEA07C186342E50A85A5FF292E19922286A96C42B4A998420C9CF0743F36ABF758E695166F04E0FADF9922C48188BB0F5AF0FCF2072C409AB2128ACA60E2780EB6DC1F12A979D85AE7484DA9F13534FD77C57554DC1BE883F1D1551E1FD750B03708D3FBAC87677100EABFDEAFD6DCAD15D1B7B7DC57928916F2EB68C8DC169274401E67A1CDD8E6A0F7122D6733A4FA86AC099446522C9E0ED60E244530A5DEE67E2A07E802145B783C59B9D873BB21A40477A307A84E2BF35EB1A66C42E18B8227B95623B1BB21590AEFF8D36C4B0EF6168D667037DC52B88C6F2FC5C1B1C232E2516B213B5F54A757880AF90ED73FE076A1C0613FF93415EC517632B7D10E2C1B7AB67B4DD50EF4E04C8910E088B8800DD50D04E276171C45C2B755BDE1BED2E740FA6D28D52130C6A6EB8BCFC3B3FFB815B8F82C1DCBF1320AE1B8627B2A893407159B8A1C684051E37A47E61776B9A80A53D3F41473A719B0D60CF8C25546C1EA4878362DA2C29F94617FEDDEC531A1D8E59BE133C033C0C85E3A476F7D0A27216E269A410A9EB7ED154A974C434C06024472D896D6EE24472D915C0C6544DFF73A0AE32C1365042906C54545EFC28EE2B272AE7A3D9F04EE7F975B1B1C04587CB42130421E026E58E45F27FA3ED02D8121F48C82587A968D0DC14C3C84E10F853D486DF6B3D997D5958BBB2A97F57CCC7702BB29EA359BD62E2ACF5A77D16E3A9B0B82BB94B3F6E75871D44D873E0A5BCF7BBF4131A0A9551FC1F504D25CAD2998FC213CD999A8F031667856E67E3FBC74D400C14F7C826FB676FB9735DDFCEDEC534E920386FD7CA810028DEDFE6902C65AC329753AE2AB7D8F63C154FDFF0FEEEB4E6C9026C4508C5A7973B1DC6746FB75E819E976DED75E6473D4DB7E6F018F587EAEA112080CEB7CD91CFC001763CACFCAD115D80246935D97DCD5820D3BD28FCB3582AB2AC40500C7636CC6A69A5542ED845103B21E0FBD05914C734F7048974298776CD8C5E8F2B018469668660ADD8211E3878F5A4F3E40A9BB27B846276F505718973E6AC5B15C0E1AC01D316F93CEF58D5E360909BC374276B4345011BED44C5F2159D5CDE2E0CFB4CDFECB51D676A46055D6C6A7F733E05D5D1B13B5F6F49FD435BADFA5965AB32275F8434C05E0DFF1DF18E5BD575F42F35B24A6E7C5A8A3BD4132AE96D67F2938738904D6C7C6DF612DFA2EB64FE9505EE47CD708D69222D24B5E109C36D4B7044C2BBC9E3554693915119737BAE15D7BA6B65C43E73D4552442D893ED78E1C08BF91231E5F1780079CE01A9C86FA8EC38A12EDEA62243F188AD182A988E7364BF1DEBD958121179346E0C4D164739C35D4D51D5E84587A5636E8FBBE116AC3F39AD92A5A9458C98CC56396D37519C7D229CCF732C254C8AB46101FB79038F06DD00EF23DE921FEF86CC17A7F6A17EDACA42B4910FC8119D1B5FC2DEE2D365BCDB7DCBB1281EAD990230915F8CAFB11E3C9D00E723DB001723B1DD7CF94568417EDEC07398A67FF7B27FF28F778DFF71C349871097BAD91D16688300834E5113573B982AC37FA2C75EF807FC7E616A92C386E6E49759B428556CACFD9E73A91F8EBB1DEE8863DA836EFB161714C68B3CE44034E0823A86C7B5AEF4B7DB7708E04DE158A94D1835573F7E973FF19DCFA95764B0263DFAC46DF55F39C3C98602BFCA12953A300F4702587EC845C65F40C46298008B1AFFC5CC4436516B74B9C740E05601CCF67F3E913F4E53DFFF4776E33EAD531B0DEE7C43692B99D6B4EFE9354675712BAA24235B62B64AC5D3EA1D1E7CBD1B14F67B45BC07F78E4F12E322C4060CA3A6D8F5323FE4B6239F5B01D4381D31F0610209EE83E3E726121E543525F10468006C0FBFF6F6E93E62ECE69E2A98F4E4CEABAC0DE867CE60EDA11C25DF160F367BAF6E54987F678F43847465D5AD1D9947CFA888CBA92B45D1A367890ABAC3FBD50F3E7ED4AF97C1F29C927C339C973A1009ECBB9F6954D4EB8FB30601C5052E7608F6EE48CE00B246E9F47A3E5991F250BF7506BD577C41F14E41570DF2C50E8087BB912E84C5F4E8C51A9870D027029809133FDB22F311E9A71F61CB71F6981D71F45AA80BC922777E655B63E6C14C8706BAA424B9C8C7CFFCC38DEAD82FD95D916E71AA3825B95088698A5C03B18B766299E4CCF71C4A959724308BC2AEDC7AF7C630B92A8B5CACD810A504A261DA7A527EBE24D23985809A5BE891C48926458E0B2890E5FA9695D5F48AF59CF21C37D150433BA777724AA7BDEDB170156BB3A6D9E56FBADA473B279996DEC61E80408636BF546F967FB0AF8ED9E5081F0A4A8B532E40D0C4BC55AC7A4F37F603CB6426B5D36B0B34D231C9AA83666B9C63DF5F497A67B0FB040D106C01BE00D0B1A3E4291F2156D1E637A3A9A766794D683AE106B639B5213337F8A1FBD924F7B4D9566A7C0A009B8E1925DC2FBC1FB3385A42FBB868388BBE6EF55942F9D5ED2C30992FFE514EEFA7F145E150F94C001E8D08904FC19401E871379B79AC79F39A7EF9E738BECA86C9078A6FCB62CD1D84E23708AD98FD2E759E7C841C40A2241DDF864D75B400CB5C6B03502D016C491615A05316F9586414694A67DE2C4D509025B1671FAEFF5B5CD262FBAB6234673E7D7D062016CE7D4B54C813A1AA10E6C1859C8EEEFDB81A138B2378EFB821DA5BAD135FE4E65363AF136F8E6C6D1640D4F8FF491B0FABF3E4D5668E931E39B5928E6ED7792719222E479FBCC018D755068E3C5C66E305E9C52BAAD0019B35B21F1A3B2F1DCEE98B9D0FB8E28E95CCD36838D65282B87F03D0CD5C096164B2C24E149D9128976688518790B30C087F9786C7F8F2E797082D839467C3AE8F076EC973D333515D12CA3CF9C50772AA3434829AB45B1F63EEE12769812B95F7C8ED33E5CBB49851142737562E9EED8ABD525D4C9423CD48BAF00B58343AD12F3EDD0D55A8895974FD99201CB1A0AE42EFC728A3D0D691B78FB85D3FB9210AC0D75B0BEC391AF0731921DF2E1DA5CA56FC6A68A0FE99525B85635665E0F290B98018201558C29F197100ECD37B9112924F60707E4B30BCBC5B7187A09DA0628FAFCCD930F545570FA835D911C9E35B1B73ADF15F7432ABAB1682201A93EDC842400381C645BFC729C8DE1C06AA2171EAACD88E919F9CA772EBEAFEF4D616FA5C973963181807844A3C84A57767F9AACB6BBCAD3FA24333D585C70737C7F8486888D919FA1A9B2C1D2DEDFF7FD1422536D8B8D9CABB0DCEC000B141C31393D4249566587B1D3DAE7FEFF000000000000000000000000000000000B232E40", + ).unwrap(); + + let sig = + MLDSA44::sign_mu_deterministic(&sk, None, mu.as_slice().try_into().unwrap(), [0u8; 32]) + .unwrap(); + assert_eq!(&sig, expected_sig.as_slice()); + + // test to pin MLDSA87_GAMMA2_MINUS_BETA (produces the wrong sig val if MLDSA87_GAMMA2_MINUS_BETA is wrong) + let sk = MLDSA87PrivateKey::from_bytes(&hex::decode("381286DCCAFF85CC31536E8CFA4D6FFF74AA12D0004D0BC826F54D3EF0F632090C06B8D3D562D3EA6C5D9E486AEE773AB7CB8A08382A055C6339BC650523ABA7176BB4169C6294FC52FEC2E43BA13210D3CAF23124DF2D0B4F7BE6327B48DE145071503F0E9EAEF1136A0005F6F5B03FCAD1AF83C10EA38D288934417E86F3D69CB69044064090063248C6895AB0019B080D99189012892CE008495A0609031005C42668C0C64540C80D80164D58388A18A40C0804720AB55020110293B09061348D4AC40182006E089750DBB02823938CA28688421000511066932200D8A2805BB05003255023064D9B184E83448282148103454A540020DA048ECCC24011904D6348801C004244300922A6314B860C0A4985A1184C18888112442D531690E280249486488C300909997119030DC09820E20664618089E4B01041208484349023058601A83123A63103050464226954006C1AA930884052A082402221690449411A83050B278AC4003200B521CC345083A20D60C84019330E00162941A82DCB446592844C024631611608E2842988023121B4844B288921187188C0011103201238280BA6112303490107629C9625C416445236708338929022084AC60C1B00648202324024315B867100104DD1228E58404C1082119AC245DBB204D0084E5BA20D20245159342821B004C8166DD3260419B10100486010A90CD122489914404C288624080E1B436C49A00C59942051108CA2466822A1714A982140366E24A1251C3421E3060A91C40098185223865114C38DD2002D00B03189A8409B940860A0701898488AA4449000621295280C10055292705A260A1C80211913281339421C0146E31030D9848C13202D99462E0C10881384302431712447418132860B948110112C5838898314825110711A982904260C599230102072C23666CAB669E406861C8469843012C49421241820DA12520BC3214B900DDBA004118808DCB465123085C1448E1C11859226128910840B1864E2286998880C90A6011B08495C180261C88DA1184621C109199389244925503625198490922030E28651C49685CA460410456A1B3862632809A202641BB341CB369054262C1C054E1042718348649A4605A2086041A2900A110151366CC49208C0C444A1C080D9408404C0708032851C43608CC4309CA6800A404E63481019B830CAB82944160461348E84184A24181013019188362C93246612C06463961000C3909B9600438685E1188E18C6059CA064844429108445C9000281266AE11804E01004591426E3328023C405E198710C16328896640C33825B2245C9082619236E44224C12126AE4B811C98845100732DA40825BB2316392895C1826218265639820D30022802220DB9624E29440E2042D2432424832295828461A9145D2988D21999010112D5A866D93A844CC42481282288C988422A791249001113270104045E12610A11411A3820D0BB64DE138019A046124C76412496E1BA004A3B08C49026E5C0069214144C8082913C83018940560228A0AC9258286444A102509361018C32111302A1BB5092047614C10511A422699A40041426ED40681DCB48122020A00336D53C284138751D9C851C238325C809101174013366D64A86019962022A38858B02C08078002170ACBA670213805D0222624A640A1A08D181350E0B42CE0225001A6886444011B29890B000092026C48B62508B60060926019953110B311D022215B006C80B008D18205133924DBC049544820C318069C102AC888645A248018C04D0A038010A891A4086461880C0B002A02B98D24220EDB246D21C76DA4126A58026190B2311935858B462102C144C9864998B2641210486110899896850816050B1980E2C6080886610B040D1A1880A4B80104C52564449021109291202863242120066A4CA208190222529491120886933206C49211CA000461162A00000E43022E98928881228E0C456511882102849149C21010920D8B126E10B42014234101A521A2C220813291D2344580324A1C29824906824B2060001060D0322A43208050342D1013010A094ED0C06448262050886013A07000037114948420A30D21055282A8911149125A342CDC880849C8288306650B16501489048C3626A408709C908DA0100AC90060D046718BA208190760CB8085A1008E4A222A01858192844C98B22164C8000199887457577111119A9C0D695E5008224F1F8D3B293D5067D12EA5D9B1ACD120060BE64483AA869B34C317DC13C55FA59FB1939381A21B8E043795F95E4ECB1592217A42B07F3C9731ED0ABFB2CD487CDCF6F956D5DECAA394378DCCB079CE795E96A9BDE857502A1EE1DBB3D6400EFE25F81EA9AA71A2124C05A0E97A271BF8B166385A3AB1E01E235AF62546933E80906B47627F0DD508B22CD0FB34DC2459B21C1437DDBEF93F603CEF19929437CDC85AEA07303F84662E64973F65BA31B0142FA866EBD1CD858AC538E9A598EB1A2A80D463E84125461926FD53DA52C9D53EC1C8EE2E5FC63A7D3259FC69C7A973421A9B4B3BCF4E938450C17ED123D7428C6D17CBC6EDCC4673750B726A36E187A42221BBE039A9EAE4A01528B5CA3074A7448193E8F29C65AB065A551FA7827ADD240C06AAD74692AEE6C44BDD6EAB9F0DC88D3564C3EA09AEA275507B8B085DC4B3BBA5EE7E9A35A3F25FBF2855A76383DC498935BEFC14D32A0EF90EB2F604C0DADFA4B5C7D8FD79BAA318F68B2A529944F87CC4FA80F60C4BC351A95E00EFD0A0D8ECD2F998F30030CF6D9BEEF2F7D63077DBAFC7AD5E7667F8FA671E5B69BC0B0435C80B28ED28D8D884FD60766E030A570282D8110419820D437FBA52C82CC99C94631B5C8AA9B681DEC98957A2E19C6123721B0DD4722D62F7722B3678E5752DD91C4D0EF6A932960EC1312724C56F58F21D0BFCA70340DD4C48ACC5606387663313472E8194B65FCC048A858D6480DD9C2B83841E85205E6C5E9A95776BA29835BBBF8E567200C84E5499CDFBA85355A2067A170BB5897889A73C204CA5A93118E51A4595281A80B952CB35D5589673270142CDE60402FCA4C8D9E437134DA79E7944522FB72D35F3FDFF2C15D4585AF10C3B64A7A40F2418A336F4EE66CCB98AB0D1B082C62802C87A9778B86D9F304847A76420507978FAC7987E8BF667A8E40F15FA1D9B0DD6299458BA196D24E9C6906C26D04708B08F99FDD9D110094D5B36F48213B213C66F5F55F97D4BFB500157E03A074AF090B7609204A71CD1642D11BF34C76400C124D55CF7A80DA98FFCF52AB2B229720357ED071616BD8E03D7276561ADA1A746C4EC37C0B21F964DCE7B1AA303A9CD7CB8108D7E18E32DB22BCB89FB7E6408324ABF03E296572648FD2E335CD4463A64EC5CBDC8AD7953618D8B2E309F04A74302562FDE3CFFE805C4BB4C661EF131D37674AD6F9C5BBF2290727AA67EA3848C48E3C3CA5A35009FB91C0CC383005604C096422407F4337C17D63D885592A5F15F9851F5BF27EB5AB5095B95AC69C2F36A1664B0AE71E4BB131C3A576B41155A2F997BCBCA494584788ACF2F5CCBAE28D09E69F3A39C2CC394B3465358E1F2A23D2E8A3083E3E26E90121553309A1B8E24932CB64FE659EBCF38AF0D1A8819FDA08CF4F2B03D79208061E113F9B89A8B9563E5163C6CBCC9BC2048C15E2AE39CC2B64ABD6DD6CCE5D01ACCD8CC08A4F266491446C1CB9337DAEEFC6777CBEB3291F60CBCC3194706492B7F953009DD5E70245180651DA918647F05A2902F4719F6D97CF3AF5D855FD1A1C76C29910D518771C604AD7B802E264F39BAF16AD772B0EC9DA32FCA34A85FE2E2447CAF2EE925EFF5A442EE9DA327C38EAA22B8E67DE4A7AFC312CBFDEB44856F09015237E59D26244631A8B14AFED134805DEF32FF49058C72C2A7A4B77518EE2B162DAF6B90F1C96AF5225453C5E4D8CD401C6FE1C626D4CA037F380BF288DC428B9680B595E10BDDC9D8A2D2BC85DC316BD9239D557535F66B3802FFEA1095DD4DC2F1A9A1E6190619BC5A2E063EF8C95066F5AF92B3D99A418E26C68F8AB464374FF3A789F07B2C5A99084C7275DDC481BC1BBBE8BD6171DABEC4A5B01F115EBFADAB26C9AC17A4D4EBE37BC294F0ADE593A0DD4AAC258D106457748FDF0059752442E4B96F1294D6D92F6D9657E131D0288EB3AED6C89390E05DC84FA5EC0EAE33F4DA4DF2A7614FF1B4ADBBBC9ACE849366AB82D5902BF7C5C66C095BD92DABB145BCDF8A744FAD2FCF44AD868C349A6DF2F509EE92687123E39DDA96A735AAF57C09F5EEEDFE3CFBC0B9C05B5D51E2F66F8C0E77AB0302AE603DEC8CFCC79485948C012BFDF7D0B9E8FF0D6545EBC781054183785D95173510E65E0E52CEDBB241D8E23C997DA523B974C53ED07A8DA63958768C9520DA7EE6B7093721EDD8633B6192E12D19065F63EB79504B92C7BC31F176DBF4FF21F5DDE5E414FE6555C8D76A2A77B3B3CE1EF545CFF4E776EF9B440506392ECBDED301490678328FFA2D83DE6099B750C3C99A9797A14FA372A57BB2CB72BDBFC640144D1C100899E2E12F2A23147C0B30E4F162184E1E69C1FA770B46D213F52CB4B36864289FCC3DAD1D54216EF9D9FC81B709D6045DCE41E91E5A0C8B5518EEAAE07D2574DB1D53ACA5B2A4A2FCD6237388A86B4C58B4200511103CCCB2420E62789C13F87C82A90751E121788274C028D2106D5FBB0D9D3128618FCD7DC5B4801C01A2410ADA95A7CA902A8F03E2D538D783AFEE2B7DC674C1A71DCAFCDFFCCB00273B942D79A5435DA1CFB2C44C3FB3A4E221AA1B2868EC9F4FA43A522562AAC34BBDA07D337A12748A1B5120D176CD117110D4A7061BA6AEF3EFBAD21E998B78192214380FD84A5D08F9B11282A15BD9226AA54F019213AB6C0BFE8291ADF09B680A16770EAFB2B7042274EEB896DC7B6903113862B1BD5C523787693E65B6C2A85845768A3BC54DD7A02C4ECE6CA3DBAD2D83DC65CA1DBF7C3744EE4E4748507DCE7F913F4332CF5214D8EFC1B4E67C86AEDF09D545C354FFE6B8A4B8F15E765A8F8CB13B24062A17084C52E82964CF6657C808326ABAAFF60545BADEA8A44DAF0A038EDFA6F4A66C57C721A6ED2E8736EF92F4BB8D230DA58DAD187ABF58DFD17FD11A9764D3608D288AA2EF3F1DE94DD7DF07E829D5D07CAB1FFADDDB4DD460D9FF3DAAE1D918AF2AE195B0F95CFD4E1543B4920B20CC808C6473C58933B370C45E47416E973F42FE6FC9C4FB5FA95DDE7D8325A8837648625F722EF95B0D08803FF0299E6A8108122A1C7172FA96ABB263240FC560CF3E1BFFCB2ACA913A9788D5C02EA0EFAD07BEF958A144B15A902AFD13A0AF976044065068C77100066F68982C0B98BB77F814AB624EED9C84531969DB1257C7AEC11DC682434809C7594A02116D6840DD99630629C150CB0CEFDA1E851F35C50CEEE6689B8450E6E9C4F8DAC4F487AE4DFCC60B8641A780BBD724036C21B38BEB1C8ED30EE9AA03DF899678FFFECE5200DD2270A9332B9557E577BB7E5B3378E4512F0BF439CA3BC16E8575DC46B2E497988C7C6F4EF529A730D7D418FF548AA7710F6F3230503D92A8B6228FD19CDC53F1A40165BA1F3EF1FC68BD5717AAF0810AA56AB90604BC0DF466D92403C1B9CCE0B2662D415EA69A1BE094AA5583DFC472480EE0430A93B21BE5B1BD4E27026DF5ACBE51D891B0E49F70DDE93FF43CE43A4F9B82AB951DE14A8A9BF4650FC38D769F4850688103D0D3E4A9BA645DAEC785A571D573ED6ABB22D987C2B52C5102CAB5B14610196B8D71DE69103E59B5C2A682390290B508A693CF87AD0214DC8DF2541FFAC89B38E19D703AC52D1624216FAB0227BC1045A34FD658111F92DB7B97FDF1CDC56FF7CABDA8BC26EF8347A92A2D8A3C56C150C5E3094F3D34D4CEC3C371602907419D8259DA0C74E32F63A9225852B5C9208785E56D0F4DB3349F5DBD06A10397965453D4E70D2E1E7FE56AD98143720BF25FD8204497D0A5EE1F37BD8AD4364D9BAAF2BE2BF519316D1314E0D4801CB90FE52F66A82FCB4708EFDE4FD45F70C38E70B59206C221B42E840B72FE1FD0DE5C7B0B8C0BD1B6561899FC5771764733108B69F81888FDA9E8CF60044F3BDD3289637F6ACBAA5B9B1145DFF8CE247B438320535C514BCBD445146BC00EF8E1DF3CDE218775DBB135FDBE92B7F303B1C344FDDC2E381420544DC9694DBB142704A0F092BA281537176F17B0C1561F0BE320D79677887DEE7C27E01232BAD43D49D9D1FC1A6D8C887443FDD756EE7561C24FFE0FAEEE66DE9A6F05CF687EF4CF456C7DD624DF017E11F66231793FE81C958AF0DD89C0D088B17DF94304659B79E9F9C5196BC1A9F22CB5698C751B829427CFB437B16FE1E3960A70B223E7C46CFD9F1C6195386A170AA07074D7BF9F634CD834939F8520B73D2648D1994CE80FC17E549DFC87CC6164A9CFD77E6E1968D9C9B582FB70736B6163D1E01C4632D594E3A3532E61E1CE87FE5AB5A2E11F98224E95D7136B34D814F138CC5C557690B57DF3EFCE187CE9A01D1CDDE645DD10814E3B3FDD69947A0746C68D9F77B9AFC61474C55CFF7EAB24AA5643E6B451A4C6D84C2E6E3D7015C6F00FB264D55D8B2CFF5F87DB3050E7DA34F7612EE116A088180BD0E1F3AA1BD63B2CC029B3E96C9EFF4C2D1A2C53F25084503639819E5A6E73BC07EFA5212CD9075A6547710A1237AC4991445ECC76D0C11BA557E0BDBD7651A4A6E66E3F9DBCD532C2301BDBDF63307AB425E6AB902164925BC424C5E7B2892DBBED51E0AB5BF33976243D5AB4DB7272EC292D1DBFC494372B51526C9DC1380C3BA563D2CCFA3A20EE7F800ED8C2A7D199D2EA6F1FF7F4B6B4AC51041C974D740FE7E1EA1D7311B81E5ADBB600CDF62BE0").unwrap()).unwrap(); + // because this is from the bc-test-data with busted mu values, need to fix the (busted) mu + let mu = hex::decode("d6961b5a9cc085939e493d4cfabfabb4442604d4058c4bfea3fb5c25cd7a3e4551b34bd9735be6e05925132e0aac4aa374534867289a6af4503baf449e6f6b73").unwrap(); + let expected_sig = hex::decode( + "1A9FF550C3F682A043D145B8A8ED2A2C2F92914E1D3E4F9C52EFD2A478EB028ED77C2E9C9450909F7A4032EA4059B51EAD6D9F61A8EA867AFCB794CA32AAD3180675DA90252E11952E936F2ECC62E1386BDCCD12DC22D23A2E21FCB009486376C85E2CA99C51D8F68AE93506120BEF1CCBC38E1A5DDF940E37DD0EB650503B969F9760D0F19448164FDBF0983077DA415648D70289BF6163C32DCC6AA8CC3B7ACF261237EC70D216FD157C1ACA6BA135470CCB4FFDC1710644AC9DD5A67E037FDBFDCE2A4627555D48AD1C8E537928B6A954FC006C5A34ED3483E2D241B618F59FD8481F69B6199E91A6545CFD485ED8665A82F75E36B27662562BC0D729D19426CD4AC19DAD71AE298FCFC278BB3B89B28E7713BAB58C4DC7BEEFA8444B6AC5CBF252F71CC7146D6EB43E0E9981C971BA9EA12BDD4BED8C20CB4BF91E805F93307AA6DEDFD083307CCF9FD540E674A40D68DB0538C8598CEE8A7186B65C4A71A4B053CACCA88887F96101547029FC74765DDE92ED69956B30C09BECEDE6DD947A7C25FF087C30AE4DACDB9496C51EEAC61C5C6B9D3AA48E255999A1F76811455B567188F26778D8C62758E1A9FA884CBCF72E04C7A6B4BDB13B208158C67D5B6A2CF60F8D27FDFE5D2E37947203D0C204D7937A10420DED707F73DE8771F7866B9537E1265101EDDC3D7C192A3B209B0E5A0D72495F1AB0C776FBBE892DA53A0638F98392E816B7C0DF714DDE62B473DEDE5876555112A554BA9EB37A363D78290C0B8CA56DDB600F0773CDF68898876BD34714F5F6C1FD38D50FDDFE778621133F14A1270E6C20E089753269D5B590318AD9AAEBD4BADA6EE5C48044CB11CFD14D153A8EE69335EA6D0C7435C33B94CE2C6E97F1D3CB80A70F66FD06F0A480FEE71B7B6A6E2DC7741269D85F05EAFDBD22D5DF71AFD9AB2D24F24729DF492926525F03336B515CCA54BB08989C16D42C1DEF1996E99659BB2F9F96004C258EF140E5306FD8CA549C99267B237584C733921192E89C8EBFB9934C3335B722F7E3EB519924A3F02A2B7E08D8C23F49C1F1262968D77887C1DB0D9941B203882C1703A8B345503E0E3CA472D7D7E11330D8B53704B8C59D12CF280857065031BA223B0D59C87C85AD214339DB60D6A2ECB4B908C07F0E2B285D0DEAA8CBF7B2BBCED1A3AA443C9580D1C7A6C5D618F6E1C5CB60E81F8E4397373850F5C18FECB489261932CCABA6CF42840DEB52A62CF0D829E33C2D4685ACB4F5CDD3A1B98D2777C09AF4DD926297E6B22037A37026062A6A42187976758B7406032B50A42D9028BFF12737F2E2CD1BAE8F78D10D87AAD2FCB4524B083D5542151A520A5B3C850F2879760875B0C034D714B7890B9BB7E86FFD946C9C4E583D0B7B1AE0A29553C50C3C4C30AEA7DE67BB9DC08E7845BA2ED6B6D6BBB1F65DAB6D269CE4B625B05A4BA5BB7272E4E7976AE6ACBE1AD43CD73DBBD215E9F603363919802497A7DC9B52C084BCD5E441C0B11FBB53142E56D1F182686614C97233A176741FE46E2F5BB749BE5A34790FF242331BA007EA53291487C96F998A2927C25863030DB6AD1860252DC2834DC3612E84F2499F39856DA4EADD223C5D2CC610026E54657ED6B9DD718DE6299979F76C0BED0933CE1F820D65F32614CE3DDFEF863A1A894534A9B93B5A0D54EBE30094BA323C5874BADBAAC2485D83363793D2CB96E129CB6C46C8A504797276C313034068539FCA7FD5929151B1D38F93243B93A772FCE64381534F9ECAF57899397E9DF6DF6CB9619B7CEC5EC498B020E708889C762776EA9436BD86B23550FD3F5D61CBDA5D1B40C629662D8D987B208B6A2015ACED52288A555E149688737323B5B5B0E6EDBE1D70A1D127113A39DAAABD281FC18109FDCD90C49FB5647E69A8EA116AAAAB1E0E5D73286DE32437B41ABAB779B20BDEDCD92F6D6D683587D33228D4A83DCCAFAFD6334680F4F9F370A5EDF4A70683BBEDBC064021DFEC2E47034275B8416D085B4086BB374FFF58E2F33E0D0920F638C9752098207C5E28341D8AD70DCA78CA47A2268F28B3350DD69B2EE15A7AF278C01EBE1DFAA8AD4739F7E5E408E2A39FCE7B8FCA51EDD592B8D2766FED9919425EC7CD80C4836795C924222D73AC0B380E00BFD20ABB704959CCA10B5EFFD9A0F72ADC823C74E90E6E95B2A3DE9D8B9F1C1A161D64E7393B259990D2BB4636B02A216ED12D2AA0B5E26C0BFB7D34B3230C6090DF5422F2A6E80DC8DE9DA3B5A1CDA4971E96C7DBB7F6992A60AD02B14C5E7CD6C9977DA0D3DA84CB07810325581F9D7BED0BEEC12E6C30338D903BF0F655EE8AF3779C87265911032433690CA981D0CCBC4F98869F316B6918B3135D405B34A3C7069112FF17E44554ECBEA8DC61269EFB5E58EF2AB1FEAA15B426879A344CE7DCE621082370F0F4F9B0E2602E942A1BB156B6DCEA3D9DD2B4FDE908E91B1913CC8BB6FB46961B9669EDEB713843C2ADB164EABB9D9615C4AB97AD17143891EEB6529C1C0E1542C0A36F70E93DA415D9ECACD6DAA5465840084C99F8F68CD236A8EE23B8A14EEB186909F6A9A4B1D1D2360DD548E57CBBF41C8A86FBCA6679E55341BAEA6F2F01898A7DE07E4BD91F6674DB7B7FD0667154DF75E543DABDDDBB4283BA0F4D8E38108BBD4BD65B82D9C84F0D8CD2E559D01E399C621056323B8CC79C53B8B8C9DEABAD2C0BE1DFC067EFCE38C591287D61545C36F36B81E7ACA5F00A68926C61090ABAA6B48F19DF830F4D94E65673CA03F50ECE3FA3BF6C235498D402E4B0521135255D85000260421C2D28D0D560C535CE6CBAA62E0491C5B305352CF1BA8E8A33F76F22D6CD72A15D07D6930DCB507EB88AFEDA15E810FC728602312B3053CFD2D8314DDE6A63083248480680A2882E173B8D89C14646FBD844A5C790DBA9B842A41F48B6BED1FB4AFE6826F95082A33C9568A40AA4B5B40CF1E723F90AF1A1DC5FE08C91B5D24B8BC1FFABA36B308A6C2184DBFC3B3D36916B2B744239D548F4F5A05254EC548F3437520609C5E562C82BEB9D3ECF1C1F9AF5E84DF9AEA3998B23D51741CDD936C24E23F1DEC3DC58BAE1FEF9A2F1B71A2604B699E212AFD349944DB53A39554CFA94E0EB747722674707A6F99F0204EE68AA20094DE58AC97F733FA76C928DBD668F80584F6FEDB7218BA74E126A0316B5FCF38F970484117B88BC8545ED9FD67466E7777E66B0F41F593E46135665096DA1CC75C90437A01379925F3327983207CE482F6AE3780B1265D6054CD2AE6B0418D2857D59FA7E94FC32AB5E94CD2C83452F420C8861F55B0A5A6BDC8C5C4157B33D6AE11C3C9C2E8919A3D09B9ED8FC09594D365A7E85F3D9647E324116518F6CDA1ADA250F194A87AFCE0D9D60B768AFFF8FCC7057AC73FB520DC707F9BC7408EFBCC3F85D5F618941441CED77C09B3E697141DE2AD6C3A4551884EE1E88D196E1AEA08285D47D81C219BE827D07891F8273BA4B3A26C3813EEA482674EA8EB4FCFBF04EC0C9428A7686816B53EE0749FDE60F50460B672D58C06F103304C28908FCD079A12574B170ABFC7E1F39FED5131D0672ECB3983810C316DD38E2FEF6DC99FB7FB5315F2D6496CB45E1385EABB69537628D9DFB0FAC95B5E5D4B05488B18E0068B0407CC737B0E711A0179EEDA1C7FAED33AFC5AF4B8C318B98244C97255563BAD20784AADE6A618388FFAE1C286A4C5EB891E236FE57FF414D619E8BAFFC84C225C59D02534721490A50BD39C163A1EEB6D811646E54D426AE9B3449C3E428ECEAC6E23F6CBD8EA06D9682385554DEC853C5C0ECAF26F5CF5BC6EABC4A957215FC497084E9A8CAFC00ACD228B4F8245F9230FAF8306635DB89663C5D9A31C648948500FBE0771E2BF15C52395468CF50C6C8953DBB6DE252E6649C6D2D060EE34F53960B5B7EA0981B7BEF3D0A1D64853D9EE06EC4D73F4A75DAC5E5A2B59682E6B8293E74ADA3322C1204FA66C6E8B5EAFD796305AA884249D00E3DAB6267A87844D595B3BEC430BB416BD4A4F38505F046425ECA84495DC0928DC4D9FA18BFB800C1B0578B2EECE2B97153A1C296E36036F9366E3443B236E4E124F82EC405CE2ACA48DF43E543C514727CE182B34D1131E366E394D07F9C72022FAD6E4AEB12CD675E7EB837A7EB13BF4D9DE553DD19DFBD29A6E2157B58D893F3E4B574AF01F103989D8FE2FAEE269BAED7094BE606678719E6E82DA97D3DB43B3FD72863180E1B4D7BE55A721301B1D81963BD37C3259D55FA6D43FBC217A77B38B83CEAA4853A921EC7F138F666BFBBFA49B4672DDBBF1CEC212B530771A39EA244B547F28375936A3E8F692216358A944542AF2D559D6FA216A5CC60DBC892D2BF38CAF1745F5CF6336754A21378F25DE5F472FDD8A1B33189B5134647F7965FE0E573C508B61980DF059B5D9B3AFFCE632230FE873F6462E533D4528DB8B03DD464D3020B5DC22525AE33AE1FC345DC2486D5647863ECE3DD49EAE7A5AE128AD8E39065F77EEE3456653E3FDA5135A45A365B8043F2F019825BA9CEBDBA96C8EEE7A8544DBBC60218776455843E5EC61D9DFD7DF3ACD6DF5E130A5D2F958F5E26F3B1A80029EBFD13F7EB24301750EB8608E7FC73882797E2342A37DF45486ACA0603D411592A13934799D203ECCC197C0F103180D5A0C1CCC3BFA3CF1DB199ED934B4A5B52CA9BA66FB9C97D85B06977C83533F16F6B7F3D6E5945B802362E7ED9C42E873017EA4E0C9E7AC96087AA7FD269D363A8BE8FD39A0234CA842ED209865164D39C4EF592E7223F3C3684EAE0A2AC757D3CBAFB2E873C52B09BAA4788190EDA773D946A5CC67D59C6EDF9EF20394D2F40DF35D20E5F67E1891C6E61798B1A8343E22B7FDF274A996F679A4EAA1505DA4EFE1C4D94A69EAECE3DA05D6CFB63294D7433D9885EE18EC94FF1F23DD6FD9F99BBA2D5BEF46E0A477CC434F86B2FD67FD55B06AA2B698A70FAA1894E925BCBB2E9F229A1858E789920A1AFB5FCDFBD4D3F904F87FD863372CB7C103BE365AD8848141AEE2534D42BCBF5A954F8BF62DF7C29F9ECAEAE7543B56E7188D440A0BB4760FD0ADC019796611E4A31152849818CCF0B146C9CCBCF4FBED7969BE81C49D92FB26A63249DB99EE6CBE935677AB40F50A267815FFBA5BFD7A72C5EBEE8DF1DDF713516F2E4550C9E74FB1C7E2CBA3ABCB91E315A1ABC8E9D48CED3971A0B73DFAE6955CF3CFDD925D86B4F3FD182327599379F9E13D7EA5E405C335B1D95B6DDE4DE69C492F98F51974BCB66C317439653A9014166C922E61150FF9902F9C22C29B7F15A27121BEB39835D835CB7135C3992E82C765A9131485893DC7C853937E1F0C9E0EB30E0502224195E318AB98C56E4211C926240BDB95A1C9345BF9AC10E2482950974895268C12DD92D0EC7E941A162A57106C0E2A3D36376488EB22694D4F68C0FD03FC8ABE4F5EDDF3E47A9E9F7612C2ADD9F847309DDBAD4DC71C3EB2838F29CAC79E8AAF3E88865C8C4FC5FDDDD5EE0CEAC09985CCCD5A5AA6D009BCE39AF3F8C8AF865F584AAF03FB8431F6BEBDA7EE1F1F0B3D672BC6840F3417F497CDE3CD3DC2A214C2C895F61A20A7CB184DC6DEC73002923A6B50B671329F2A16E611E0099B5F0438AFBDD2285D1AA097DD74C4E5D7CC6F75E4313C67E91C71AA49B5048780C0331BDB8917247BF059D9CCDF6FDC885C2950C6FB84BA8ABC463C2DD67629F8C28600081C10C75EE88DB112C432A516983A933A584A957646E2B5B4F9255055A0558574D46EE3E074CFF5FAEBFEAB61FFC994E5B9F46E16552590257B6C4FBF80597B11A705BA730FA495D31FCB0B3E568FA264A6C4512E9B598162AC242437DD47A319FE31A5D9622343B71286BE7E2CCC6709978044170B6B7C8F0F1D109B9D79F86C9A2B4E2758AA97569F47F242BC0233CC44E0E51A02EA805D15A30E193776174FA43787B621293CAA785B609AC5FB851A90647C6A35547AB985E5CCFF969EA452A047C5D7E6CC86404DE41FF4FDDD2B20474BEA70C843CE3A3B383E62F2BC234B4D4962275A3C73E530ED2CC8BF45CB2086FFAA5A299B5236A46066294D80B938A18D71B718B083E685173D10725DAEACCEAA6821A7C9362E5DA01DE8DA4DC54C4C7882E61056FCE7EDEE3A84E3414AA7055DAB55349D3DA50EE216C797C4E5B7D7A225D35E29B150F7499086442509ECC8240CE53A08032C97F6A07ACA7C599DDAE5E3B38E9D78FAF9FE804EA5AD23DE442794991D6633F3CE3D83B08F8FE5D862ECE02B8E710EDD552CE1CE465353A6A3FC014E60F0A8ACE5FD7E0A6E524FA7453E795B96C2DE5E2081460C3754DF5A782984D8CD5C7ED1200B45C45E58387EBA329B93F63143BE6D4CC911D305EAEB6685A07B1E4F982E17904B775F6255E8279FE7B5B445AA0523023313343478D9CA9B40E2048B2D1D637468C91D2EFFF0611164254646782C6D77197A1A7B2C9D5E0F0FD4E576E7782ED0C50ACBCD2192A5275808BEE000000000000000000000000000000090F16202A30353C", + ).unwrap(); + + let sig = + MLDSA87::sign_mu_deterministic(&sk, None, mu.as_slice().try_into().unwrap(), [0u8; 32]) + .unwrap(); + assert_eq!(&sig, expected_sig.as_slice()); } #[test] diff --git a/crypto/mldsa/tests/wycheproof.rs b/crypto/mldsa/tests/wycheproof.rs new file mode 100644 index 0000000..5a00b99 --- /dev/null +++ b/crypto/mldsa/tests/wycheproof.rs @@ -0,0 +1,1119 @@ +//! Test against the project wycheproof repo available at: +//! https://github.com/C2SP/wycheproof +//! Requires that the wycheproof repository is cloned and available for testing at "../wycheproof" +//! relative to the root of this git project. +//! +//! This test file exercises the following test sets: +//! +//! * mldsa_44_sign_noseed_test +//! * mldsa_44_sign_seed_test +//! * mldsa_44_verify_test +//! * mldsa_65_sign_noseed_test +//! * mldsa_65_sign_seed_test +//! * mldsa_65_verify_test +//! * mldsa_87_sign_noseed_test +//! * mldsa_87_sign_seed_test +//! * mldsa_87_verify_test + +#![allow(dead_code)] + +use bouncycastle_core::errors::SignatureError; +use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; +use bouncycastle_core::traits::{ + SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, +}; +use bouncycastle_hex as hex; +use bouncycastle_mldsa::{ + MLDSA44, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65, MLDSA65PrivateKey, MLDSA65PublicKey, + MLDSA87, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPublicKeyTrait, MLDSATrait, MuBuilder, +}; + +#[cfg(test)] +mod wycheproof { + use crate::{ + MLDSASignNoSeedTestCase, MLDSASignSeedTestCase, MLDSAVerifyTestCase, ParameterSet, + }; + use std::fs; + use std::path::Path; + use std::sync::Once; + + const TEST_DATA_PATH_RELATIVE: &str = "../../../wycheproof/testvectors_v1"; + const TEST_DATA_PATH: &str = "../wycheproof/testvectors_v1"; + + static TEST_DATA_CHECK: Once = Once::new(); + + fn test_for_presence_of_test_data() -> Result<(), ()> { + let found: u8; + if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + found = 1; + } else if Path::new(TEST_DATA_PATH).exists() { + found = 2; + } else { + found = 3; + }; + + // just print once + TEST_DATA_CHECK.call_once(|| match found { + 1 => println!("wycheproof found at: {:?}", TEST_DATA_PATH_RELATIVE), + 2 => println!("wycheproof found at: {:?}", TEST_DATA_PATH), + _ => println!("WARNING: wycheproof directory not found; tests will be skipped"), + }); + + if found == 1 || found == 2 { Ok(()) } else { Err(()) } + } + + #[test] + fn mldsa_44_sign_noseed_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string( + TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_44_sign_noseed_test.json", + ) + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_44_sign_noseed_test.json") + .unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSASignNoSeedTestCase::parse(contents, ParameterSet::Mldsa44); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa44(); + } + + println!("mldsa_44_sign_noseed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_44_sign_seed_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string( + TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_44_sign_seed_test.json", + ) + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_44_sign_seed_test.json") + .unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSASignSeedTestCase::parse(contents, ParameterSet::Mldsa44); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa44(); + } + + println!("mldsa_44_sign_seed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_44_verify_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_44_verify_test.json") + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_44_verify_test.json").unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSAVerifyTestCase::parse(contents, ParameterSet::Mldsa44); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa44(); + } + + println!("mldsa_44_verify_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_65_sign_noseed_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string( + TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_65_sign_noseed_test.json", + ) + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_65_sign_noseed_test.json") + .unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSASignNoSeedTestCase::parse(contents, ParameterSet::Mldsa65); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa65(); + } + + println!("mldsa_65_sign_noseed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_65_sign_seed_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string( + TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_65_sign_seed_test.json", + ) + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_65_sign_seed_test.json") + .unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSASignSeedTestCase::parse(contents, ParameterSet::Mldsa65); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa65(); + } + + println!("mldsa_65_sign_seed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_65_verify_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_65_verify_test.json") + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_65_verify_test.json").unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSAVerifyTestCase::parse(contents, ParameterSet::Mldsa65); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa65(); + } + + println!("mldsa_65_verify_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_87_sign_noseed_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string( + TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_87_sign_noseed_test.json", + ) + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_87_sign_noseed_test.json") + .unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSASignNoSeedTestCase::parse(contents, ParameterSet::Mldsa87); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa87(); + } + + println!("mldsa_87_sign_noseed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_87_sign_seed_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string( + TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_87_sign_seed_test.json", + ) + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_87_sign_seed_test.json") + .unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSASignSeedTestCase::parse(contents, ParameterSet::Mldsa87); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa87(); + } + + println!("mldsa_87_sign_seed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_87_verify_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_87_verify_test.json") + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_87_verify_test.json").unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSAVerifyTestCase::parse(contents, ParameterSet::Mldsa87); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa87(); + } + + println!("mldsa_87_verify_test: all {} test cases passed.", num_test_cases); + } +} + +/* Structs for holding test data */ + +#[derive(Clone, Debug, PartialEq)] +enum ParameterSet { + Mldsa44, + Mldsa65, + Mldsa87, +} + +#[derive(Clone)] +struct MLDSASignNoSeedTestCase { + parameter_set: ParameterSet, + // testGroup-level fields, copied onto every test case in the group + private_key: String, + public_key: String, + // test-level fields + tc_id: u32, + comment: String, + msg: Option, + mu: String, + ctx: Option, + sig: String, + result: String, +} + +impl MLDSASignNoSeedTestCase { + fn new(parameter_set: ParameterSet) -> Self { + Self { + parameter_set, + private_key: String::new(), + public_key: String::new(), + tc_id: 0, + comment: String::new(), + msg: None, + mu: String::new(), + ctx: None, + sig: String::new(), + result: String::new(), + } + } + + fn parse(data: String, parameter_set: ParameterSet) -> Vec { + let json: serde_json::Value = + serde_json::from_str(&data).expect("test data is not valid JSON"); + + let mut test_cases = Vec::::new(); + + let groups = json["testGroups"].as_array().expect("testGroups is not an array"); + for group in groups { + // The private/public key are defined once per group and shared by + // every test in that group. + let private_key = group["privateKey"].as_str().unwrap_or("").to_string(); + let public_key = group["publicKey"].as_str().unwrap_or("").to_string(); + + let tests = group["tests"].as_array().expect("tests is not an array"); + for test in tests { + test_cases.push(Self { + parameter_set: parameter_set.clone(), + private_key: private_key.clone(), + public_key: public_key.clone(), + tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, + comment: test["comment"].as_str().unwrap_or("").to_string(), + msg: match test["msg"].as_str() { + Some(msg) => Some(msg.to_string()), + None => None, + }, + mu: test["mu"].as_str().unwrap_or("").to_string(), + ctx: test["ctx"].as_str().map(|s| s.to_string()), + sig: test["sig"].as_str().unwrap_or("").to_string(), + result: test["result"].as_str().unwrap_or("").to_string(), + }); + } + } + + test_cases + } + + fn run_mldsa44(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa44); + + /* Load the keys */ + + let sk = match MLDSA44PrivateKey::from_bytes(&hex::decode(&self.private_key).unwrap()) { + Ok(sk) => sk, + Err(SignatureError::DecodingError(_)) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("Failed to decode private key: {}", self.comment); + } + } + _ => { + panic!("something else went wrong"); + } + }; + + let pk = MLDSA44PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()).unwrap(); + + /* Compute the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + // build mu + let mu: [u8; 64] = if self.msg.is_none() { + // we can't compute it, so just take the one provided + hex::decode(&self.mu).unwrap().as_slice().try_into().unwrap() + } else { + match MuBuilder::compute_mu( + &pk.compute_tr(), + &hex::decode(self.msg.clone().unwrap()).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ) { + Ok(mu) => mu, + Err(SignatureError::LengthError(_)) => { + if self.result == "invalid" { + /* good -- test passed */ + return; + } else { + panic!("failed to compute mu") + } + } + _ => panic!("failed to compute mu"), + } + }; + assert_eq!(mu, hex::decode(&self.mu).unwrap().as_slice()); + + // generate the signature using an all-zero signing nonce + let sig = MLDSA44::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); + + let res = MLDSA44::verify_mu_internal( + &pk, + &pk.A_hat(), + &mu, + &hex::decode(&self.sig).unwrap().try_into().unwrap(), + ); + + if self.result == "valid" { + assert!(res); + } else { + assert!(!res); + }; + } + + fn run_mldsa65(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa65); + + /* Load the keys */ + + let sk = match MLDSA65PrivateKey::from_bytes(&hex::decode(&self.private_key).unwrap()) { + Ok(sk) => sk, + Err(SignatureError::DecodingError(_)) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("Failed to decode private key: {}", self.comment); + } + } + _ => { + panic!("something else went wrong"); + } + }; + + let pk = MLDSA65PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()).unwrap(); + + /* Compute the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + // build mu + let mu: [u8; 64] = if self.msg.is_none() { + // we can't compute it, so just take the one provided + hex::decode(&self.mu).unwrap().as_slice().try_into().unwrap() + } else { + match MuBuilder::compute_mu( + &pk.compute_tr(), + &hex::decode(self.msg.clone().unwrap()).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ) { + Ok(mu) => mu, + Err(SignatureError::LengthError(_)) => { + if self.result == "invalid" { + /* good -- test passed */ + return; + } else { + panic!("failed to compute mu") + } + } + _ => panic!("failed to compute mu"), + } + }; + assert_eq!(mu, hex::decode(&self.mu).unwrap().as_slice()); + + // generate the signature using an all-zero signing nonce + let sig = MLDSA65::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); + + let res = MLDSA65::verify_mu_internal( + &pk, + &pk.A_hat(), + &mu, + &hex::decode(&self.sig).unwrap().try_into().unwrap(), + ); + + if self.result == "valid" { + assert!(res); + } else { + assert!(!res); + }; + } + + fn run_mldsa87(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa87); + + /* Load the keys */ + + let sk = match MLDSA87PrivateKey::from_bytes(&hex::decode(&self.private_key).unwrap()) { + Ok(sk) => sk, + Err(SignatureError::DecodingError(_)) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("Failed to decode private key: {}", self.comment); + } + } + _ => { + panic!("something else went wrong"); + } + }; + + let pk = MLDSA87PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()).unwrap(); + + /* Compute the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + // build mu + let mu: [u8; 64] = if self.msg.is_none() { + // we can't compute it, so just take the one provided + hex::decode(&self.mu).unwrap().as_slice().try_into().unwrap() + } else { + match MuBuilder::compute_mu( + &pk.compute_tr(), + &hex::decode(self.msg.clone().unwrap()).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ) { + Ok(mu) => mu, + Err(SignatureError::LengthError(_)) => { + if self.result == "invalid" { + /* good -- test passed */ + return; + } else { + panic!("failed to compute mu") + } + } + _ => panic!("failed to compute mu"), + } + }; + assert_eq!(mu, hex::decode(&self.mu).unwrap().as_slice()); + + // generate the signature using an all-zero signing nonce + let sig = MLDSA87::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); + + let res = MLDSA87::verify_mu_internal( + &pk, + &pk.A_hat(), + &mu, + &hex::decode(&self.sig).unwrap().try_into().unwrap(), + ); + + if self.result == "valid" { + assert!(res); + } else { + assert!(!res); + }; + } +} + +#[derive(Clone)] +struct MLDSASignSeedTestCase { + parameter_set: ParameterSet, + // testGroup-level fields, copied onto every test case in the group + private_seed: String, + public_key: String, + // test-level fields + tc_id: u32, + comment: String, + msg: Option, + mu: String, + ctx: Option, + sig: String, + result: String, +} + +impl MLDSASignSeedTestCase { + fn new(parameter_set: ParameterSet) -> Self { + Self { + parameter_set, + private_seed: String::new(), + public_key: String::new(), + tc_id: 0, + comment: String::new(), + msg: None, + mu: String::new(), + ctx: None, + sig: String::new(), + result: String::new(), + } + } + + fn parse(data: String, parameter_set: ParameterSet) -> Vec { + let json: serde_json::Value = + serde_json::from_str(&data).expect("test data is not valid JSON"); + + let mut test_cases = Vec::::new(); + + let groups = json["testGroups"].as_array().expect("testGroups is not an array"); + for group in groups { + // The private/public key are defined once per group and shared by + // every test in that group. + let private_seed = group["privateSeed"].as_str().unwrap_or("").to_string(); + let public_key = group["publicKey"].as_str().unwrap_or("").to_string(); + + let tests = group["tests"].as_array().expect("tests is not an array"); + for test in tests { + test_cases.push(Self { + parameter_set: parameter_set.clone(), + private_seed: private_seed.clone(), + public_key: public_key.clone(), + tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, + comment: test["comment"].as_str().unwrap_or("").to_string(), + msg: match test["msg"].as_str() { + Some(msg) => Some(msg.to_string()), + None => None, + }, + mu: test["mu"].as_str().unwrap_or("").to_string(), + ctx: test["ctx"].as_str().map(|s| s.to_string()), + sig: test["sig"].as_str().unwrap_or("").to_string(), + result: test["result"].as_str().unwrap_or("").to_string(), + }); + } + } + + test_cases + } + + fn run_mldsa44(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa44); + + /* Load the keys */ + + let mut seed = match KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.private_seed).unwrap(), + KeyType::Seed, + ) { + Ok(seed) => seed, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + }; + // allow an all-zero seed for testing + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => (), + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + } + + let (pk, sk) = match MLDSA44::keygen_from_seed(&seed) { + Ok((pk, sk)) => (pk, sk), + Err(e) => { + panic!("{:?}", e) + } + }; + + let loaded_pk = + MLDSA44PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()).unwrap(); + assert_eq!(loaded_pk, pk); + + /* Compute the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + // build mu + let mu: [u8; 64] = if self.msg.is_none() { + // we can't compute it, so just take the one provided + hex::decode(&self.mu).unwrap().as_slice().try_into().unwrap() + } else { + match MuBuilder::compute_mu( + &pk.compute_tr(), + &hex::decode(self.msg.clone().unwrap()).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ) { + Ok(mu) => mu, + Err(SignatureError::LengthError(_)) => { + if self.result == "invalid" { + /* good -- test passed */ + return; + } else { + panic!("failed to compute mu") + } + } + _ => panic!("failed to compute mu"), + } + }; + assert_eq!(mu, hex::decode(&self.mu).unwrap().as_slice()); + + // generate the signature using an all-zero signing nonce + let sig = MLDSA44::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); + + let res = MLDSA44::verify_mu_internal( + &pk, + &pk.A_hat(), + &mu, + &hex::decode(&self.sig).unwrap().try_into().unwrap(), + ); + + if self.result == "valid" { + assert!(res); + } else { + assert!(!res); + }; + } + + fn run_mldsa65(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa65); + + /* Load the keys */ + + let mut seed = match KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.private_seed).unwrap(), + KeyType::Seed, + ) { + Ok(seed) => seed, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + }; + // allow an all-zero seed for testing + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => (), + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + } + + let (pk, sk) = match MLDSA65::keygen_from_seed(&seed) { + Ok((pk, sk)) => (pk, sk), + Err(e) => { + panic!("{:?}", e) + } + }; + + let loaded_pk = + MLDSA65PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()).unwrap(); + assert_eq!(loaded_pk, pk); + + /* Compute the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + // build mu + let mu: [u8; 64] = if self.msg.is_none() { + // we can't compute it, so just take the one provided + hex::decode(&self.mu).unwrap().as_slice().try_into().unwrap() + } else { + match MuBuilder::compute_mu( + &pk.compute_tr(), + &hex::decode(self.msg.clone().unwrap()).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ) { + Ok(mu) => mu, + Err(SignatureError::LengthError(_)) => { + if self.result == "invalid" { + /* good -- test passed */ + return; + } else { + panic!("failed to compute mu") + } + } + _ => panic!("failed to compute mu"), + } + }; + assert_eq!(mu, hex::decode(&self.mu).unwrap().as_slice()); + + // generate the signature using an all-zero signing nonce + let sig = MLDSA65::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); + + let res = MLDSA65::verify_mu_internal( + &pk, + &pk.A_hat(), + &mu, + &hex::decode(&self.sig).unwrap().try_into().unwrap(), + ); + + if self.result == "valid" { + assert!(res); + } else { + assert!(!res); + }; + } + + fn run_mldsa87(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa87); + + /* Load the keys */ + + let mut seed = match KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.private_seed).unwrap(), + KeyType::Seed, + ) { + Ok(seed) => seed, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + }; + // allow an all-zero seed for testing + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => (), + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + } + + let (pk, sk) = match MLDSA87::keygen_from_seed(&seed) { + Ok((pk, sk)) => (pk, sk), + Err(e) => { + panic!("{:?}", e) + } + }; + + let loaded_pk = + MLDSA87PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()).unwrap(); + assert_eq!(loaded_pk, pk); + + /* Compute the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + // build mu + let mu: [u8; 64] = if self.msg.is_none() { + // we can't compute it, so just take the one provided + hex::decode(&self.mu).unwrap().as_slice().try_into().unwrap() + } else { + match MuBuilder::compute_mu( + &pk.compute_tr(), + &hex::decode(self.msg.clone().unwrap()).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ) { + Ok(mu) => mu, + Err(SignatureError::LengthError(_)) => { + if self.result == "invalid" { + /* good -- test passed */ + return; + } else { + panic!("failed to compute mu") + } + } + _ => panic!("failed to compute mu"), + } + }; + assert_eq!(mu, hex::decode(&self.mu).unwrap().as_slice()); + + // generate the signature using an all-zero signing nonce + let sig = MLDSA87::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); + + let res = MLDSA87::verify_mu_internal( + &pk, + &pk.A_hat(), + &mu, + &hex::decode(&self.sig).unwrap().try_into().unwrap(), + ); + + if self.result == "valid" { + assert!(res); + } else { + assert!(!res); + }; + } +} + +#[derive(Clone)] +struct MLDSAVerifyTestCase { + parameter_set: ParameterSet, + // testGroup-level fields, copied onto every test case in the group + public_key: String, + // test-level fields + tc_id: u32, + comment: String, + msg: String, + ctx: Option, + sig: String, + result: String, +} + +impl MLDSAVerifyTestCase { + fn new(parameter_set: ParameterSet) -> Self { + Self { + parameter_set, + public_key: String::new(), + tc_id: 0, + comment: String::new(), + msg: String::new(), + ctx: None, + sig: String::new(), + result: String::new(), + } + } + + fn parse(data: String, parameter_set: ParameterSet) -> Vec { + let json: serde_json::Value = + serde_json::from_str(&data).expect("test data is not valid JSON"); + + let mut test_cases = Vec::::new(); + + let groups = json["testGroups"].as_array().expect("testGroups is not an array"); + for group in groups { + // The private/public key are defined once per group and shared by + // every test in that group. + let public_key = group["publicKey"].as_str().unwrap_or("").to_string(); + + let tests = group["tests"].as_array().expect("tests is not an array"); + for test in tests { + test_cases.push(Self { + parameter_set: parameter_set.clone(), + public_key: public_key.clone(), + tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, + comment: test["comment"].as_str().unwrap_or("").to_string(), + msg: test["msg"].as_str().unwrap_or("").to_string(), + ctx: test["ctx"].as_str().map(|s| s.to_string()), + sig: test["sig"].as_str().unwrap_or("").to_string(), + result: test["result"].as_str().unwrap_or("").to_string(), + }); + } + } + + test_cases + } + + fn run_mldsa44(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa44); + + /* Load the key */ + + let pk = match MLDSA44PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()) { + Ok(pk) => pk, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + }; + + /* Verify the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + match MLDSA44::verify( + &pk, + &hex::decode(&self.msg).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + &hex::decode(&self.sig).unwrap(), + ) { + Ok(()) => { + if self.result != "valid" { + panic!("signature passed when it should have failed:"); + } + } + Err(e) => { + if self.result != "invalid" { + panic!("signature failed when it should have passed: {:?}", e); + } + } + } + } + + fn run_mldsa65(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa65); + + /* Load the key */ + + let pk = match MLDSA65PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()) { + Ok(pk) => pk, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + }; + + /* Verify the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + match MLDSA65::verify( + &pk, + &hex::decode(&self.msg).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + &hex::decode(&self.sig).unwrap(), + ) { + Ok(()) => { + if self.result != "valid" { + panic!("signature passed when it should have failed:"); + } + } + Err(e) => { + if self.result != "invalid" { + panic!("signature failed when it should have passed: {:?}", e); + } + } + } + } + + fn run_mldsa87(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa87); + + /* Load the key */ + + let pk = match MLDSA87PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()) { + Ok(pk) => pk, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + }; + + /* Verify the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + match MLDSA87::verify( + &pk, + &hex::decode(&self.msg).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + &hex::decode(&self.sig).unwrap(), + ) { + Ok(()) => { + if self.result != "valid" { + panic!("signature passed when it should have failed"); + } + } + Err(e) => { + if self.result != "invalid" { + panic!("signature failed when it should have passed: {:?}", e); + } + } + } + } +} diff --git a/crypto/mldsa_lowmemory/Cargo.toml b/crypto/mldsa_lowmemory/Cargo.toml index f9a0a81..83ccce7 100644 --- a/crypto/mldsa_lowmemory/Cargo.toml +++ b/crypto/mldsa_lowmemory/Cargo.toml @@ -15,6 +15,7 @@ bouncycastle-core-test-framework.workspace = true bouncycastle-hex.workspace = true bouncycastle-rng.workspace = true criterion.workspace = true +serde_json = "1.0" [[bench]] name = "mldsa_benches" diff --git a/crypto/mldsa_lowmemory/src/aux_functions.rs b/crypto/mldsa_lowmemory/src/aux_functions.rs index a8ac1a2..ce0f4ca 100644 --- a/crypto/mldsa_lowmemory/src/aux_functions.rs +++ b/crypto/mldsa_lowmemory/src/aux_functions.rs @@ -3,8 +3,7 @@ // use crate::matrix::{Matrix, Vector}; use crate::mldsa::{G, H, POLY_T0PACKED_LEN}; use crate::mldsa::{ - MLDSA44_GAMMA1, MLDSA44_GAMMA2, MLDSA65_GAMMA1, MLDSA65_GAMMA2, N, - POLY_T1PACKED_LEN, d, q, + MLDSA44_GAMMA1, MLDSA44_GAMMA2, MLDSA65_GAMMA1, MLDSA65_GAMMA2, N, POLY_T1PACKED_LEN, d, q, }; use crate::polynomial::Polynomial; use bouncycastle_core::traits::XOF; @@ -24,6 +23,7 @@ pub(crate) fn coeff_from_three_bytes(b: &[u8; 3]) -> Result { let z: i32 = ((b2_prime as i32) << 16) | ((b[1] as i32) << 8) | (b[0] as i32); + // mutants note: yeah, this could be z <= q and nothing changes because z == q is the same as z == 0 if z < q { Ok(z) } else { Err(()) } } @@ -64,7 +64,6 @@ pub(crate) fn simple_bit_pack_t1(w: &Polynomial) -> [u8; POLY_T1PACKED_LEN] { output } - /// As defined in Algorithm 17, this gives the length of a packed bitstring representing a polynomial /// whose coefficients have been rounded to \[-eta, eta], which is 32*bitlen(2*eta). pub const fn bitlen_eta(eta: usize) -> usize { @@ -166,7 +165,8 @@ pub(crate) fn bit_pack_t0(t0: &Polynomial) -> [u8; POLY_T0PACKED_LEN] { /// A variant of Algorithm 17 specific to packing z in the signature value in \[βˆ’π›Ύ1 + 1, 𝛾1]. pub(crate) fn bitpack_gamma1( z: &Polynomial, - out: &mut [u8; POLY_Z_PACKED_LEN]) { + out: &mut [u8; POLY_Z_PACKED_LEN], +) { let mut t: [u32; 4] = [0; 4]; match GAMMA1 { MLDSA44_GAMMA1 => { @@ -227,7 +227,6 @@ pub(crate) fn simple_bit_unpack_t1(v: &[u8; POLY_T1PACKED_LEN]) -> Polynomial { w } - /// A variant of Algorithm 19 BitUnpack specific to a=eta, b=eta /// Input: π‘Ž, 𝑏 ∈ β„• and a byte string 𝑣 of length 32 β‹… bitlen (π‘Ž + 𝑏). /// Output: A polynomial 𝑀 ∈ 𝑅 with coefficients in [𝑏 βˆ’ 2𝑐 + 1, 𝑏], where 𝑐 = bitlen (π‘Ž + 𝑏). @@ -344,15 +343,13 @@ pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { } /// Part of unpacking the sig value -pub(crate) fn unpack_c_tilde< - const LAMBDA_over_4: usize, ->(sig: &[u8]) -> &[u8; LAMBDA_over_4] { +pub(crate) fn unpack_c_tilde(sig: &[u8]) -> &[u8; LAMBDA_over_4] { sig[..LAMBDA_over_4].try_into().unwrap() } /// Part of unpacking the sig value pub(crate) fn unpack_z_row< const GAMMA1: i32, - const BETA: i32, + const GAMMA1_MINUS_BETA: i32, const LAMBDA_over_4: usize, const POLY_Z_PACKED_LEN: usize, const SIG_LEN: usize, @@ -360,16 +357,17 @@ pub(crate) fn unpack_z_row< idx: usize, sig: &[u8; SIG_LEN], ) -> Result { - // assert: idx < l + // assert: idx < l, but we don't have easy access to l // skip to the start of the z's let pos = LAMBDA_over_4; let z = bit_unpack_gamma1::( - &sig[pos + idx * POLY_Z_PACKED_LEN..pos + (idx + 1) * POLY_Z_PACKED_LEN]); + &sig[pos + idx * POLY_Z_PACKED_LEN..pos + (idx + 1) * POLY_Z_PACKED_LEN], + ); // Perform the norm check from // Alg 8; Line 13 (first half) return [[ ||𝐳||∞ < 𝛾1 βˆ’ 𝛽]] - if z.check_norm(GAMMA1 - BETA) { Err(()) } else { Ok(z) } + if z.check_norm::() { Err(()) } else { Ok(z) } } /// Part of unpacking the sig value pub(crate) fn unpack_h_row< @@ -391,7 +389,6 @@ pub(crate) fn unpack_h_row< // skip over the other stuff in the encoded sig value let pos = LAMBDA_over_4 + l * POLY_Z_PACKED_LEN; - // This inlines Algorithm 21 HintBitUnpack(𝑦) // 2: Index ← 0 @@ -399,12 +396,13 @@ pub(crate) fn unpack_h_row< // let mut idx = 0usize; // This row calc is a bit weird because technically it's supposed to be done at the end // of the previous loop - let idx = if row == 0 { 0 } else { sig[pos + OMEGA as usize + row-1] as usize }; + let idx = if row == 0 { 0 } else { sig[pos + OMEGA as usize + row - 1] as usize }; // 3: for 𝑖 from 0 to π‘˜ βˆ’ 1 do // β–· reconstruct 𝐑[𝑖] // for i in 0..k { // 4: if 𝑦[πœ” + 𝑖] < Index or 𝑦[πœ” + 𝑖] > πœ” then return βŠ₯ + // mutants note: don't have test vectors that exercise this condition if sig[pos + (OMEGA as usize) + row] < (idx as u8) || sig[pos + (OMEGA as usize) + row] > OMEGA as u8 { @@ -418,6 +416,7 @@ pub(crate) fn unpack_h_row< // 8: if Index > First then // 9: if 𝑦[Index βˆ’ 1] β‰₯ 𝑦[Index] then return βŠ₯ // β–· malformed input + // mutants note: don't have test vectors that exercise this condition if j > idx && sig[pos + j - 1] >= sig[pos + j] { return None; } @@ -429,6 +428,7 @@ pub(crate) fn unpack_h_row< } // β–· read any leftover bytes in the first πœ” bytes of 𝑦 for malformed (nonzero) bytes + // mutants note: if row == k - 1 { let idx = sig[pos + OMEGA as usize + row] as usize; for j in idx..OMEGA as usize { @@ -693,6 +693,10 @@ pub(crate) fn decompose(r: i32) -> (i32, i32) { } r1 = r - r0 * 2 * GAMMA2; + + // mutants note: the choice of (q - 1) is a bit arbitrary in that after doing the bit-shifting, + // this seems to work out mathematically equivalent if you do q/2, or (q+3)/2, but we'll leave it as (q-1)/2 + // since that's algorithmically correct, and just ignore the mutants results. r1 -= (((q - 1) / 2 - r1) >> 31) & q; (r0, r1) @@ -735,13 +739,8 @@ pub(crate) fn make_hint(z: i32, r: i32) -> i32 { // if r1 != v1 { 1 } else { 0 } // By the powers of someone much more clever than me, this is equivalent. - - if z <= GAMMA2 || z > q - GAMMA2 || (z == q - GAMMA2 && r == 0) - { - 0 - } else { - 1 - } + // mutants note: we do not have KATs that exercise all branches of this if + if z <= GAMMA2 || z > q - GAMMA2 || (z == q - GAMMA2 && r == 0) { 0 } else { 1 } } /// Algorithm 40 UseHint(β„Ž, π‘Ÿ) @@ -759,6 +758,8 @@ pub(super) fn use_hint(a: i32, hint: i32) -> i32 { match GAMMA2 { MLDSA44_GAMMA2 => { + // mutants note: this passes unit tests if it's a1 >= 0 + // we'll leave it like this because it matches the spec if a1 > 0 { if a0 == 43 { 0 } else { a0 + 1 } } else { @@ -767,11 +768,9 @@ pub(super) fn use_hint(a: i32, hint: i32) -> i32 { } // ML-DSA65 and 87 have the same GAMMA2 MLDSA65_GAMMA2 => { - if a1 > 0 { - (a0 + 1) & 15 - } else { - (a0 - 1) & 15 - } + // mutants note: this passes unit tests if it's a0 >= 0 + // we'll leave it like this because it matches the spec + if a1 > 0 { (a0 + 1) & 15 } else { (a0 - 1) & 15 } } _ => { panic!("Invalid GAMMA2 value") diff --git a/crypto/mldsa_lowmemory/src/hash_mldsa.rs b/crypto/mldsa_lowmemory/src/hash_mldsa.rs index 81a0b1f..33b9176 100644 --- a/crypto/mldsa_lowmemory/src/hash_mldsa.rs +++ b/crypto/mldsa_lowmemory/src/hash_mldsa.rs @@ -65,26 +65,23 @@ use crate::mldsa::{H, MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSATrait}; use crate::mldsa::{ - MLDSA44_BETA, MLDSA44_C_TILDE, MLDSA44_ETA, MLDSA44_FULL_SK_LEN, MLDSA44_GAMMA1, - MLDSA44_GAMMA1_MASK_LEN, MLDSA44_GAMMA2, MLDSA44_LAMBDA, MLDSA44_LAMBDA_over_4, MLDSA44_OMEGA, - MLDSA44_PK_LEN, MLDSA44_POLY_ETA_PACKED_LEN, MLDSA44_POLY_W1_PACKED_LEN, - MLDSA44_POLY_Z_PACKED_LEN, MLDSA44_S1_PACKED_LEN, MLDSA44_S2_PACKED_LEN, MLDSA44_SIG_LEN, - MLDSA44_SK_LEN, MLDSA44_TAU, MLDSA44_k, MLDSA44_l, + MLDSA44_BETA, MLDSA44_C_TILDE, MLDSA44_ETA, MLDSA44_GAMMA1, MLDSA44_GAMMA1_MINUS_BETA, MLDSA44_GAMMA2_MINUS_BETA, MLDSA44_GAMMA1_MASK_LEN, + MLDSA44_GAMMA2, MLDSA44_LAMBDA, MLDSA44_LAMBDA_over_4, MLDSA44_OMEGA, MLDSA44_PK_LEN, + MLDSA44_POLY_W1_PACKED_LEN, MLDSA44_POLY_Z_PACKED_LEN, + MLDSA44_SIG_LEN, MLDSA44_SK_LEN, MLDSA44_FULL_SK_LEN, MLDSA44_TAU, MLDSA44_S1_PACKED_LEN, MLDSA44_S2_PACKED_LEN, MLDSA44_k, MLDSA44_l, }; use crate::mldsa::{MLDSA44_T1_PACKED_LEN, MLDSA65_T1_PACKED_LEN, MLDSA87_T1_PACKED_LEN}; use crate::mldsa::{ - MLDSA65_BETA, MLDSA65_C_TILDE, MLDSA65_ETA, MLDSA65_FULL_SK_LEN, MLDSA65_GAMMA1, - MLDSA65_GAMMA1_MASK_LEN, MLDSA65_GAMMA2, MLDSA65_LAMBDA, MLDSA65_LAMBDA_over_4, MLDSA65_OMEGA, - MLDSA65_PK_LEN, MLDSA65_POLY_ETA_PACKED_LEN, MLDSA65_POLY_W1_PACKED_LEN, - MLDSA65_POLY_Z_PACKED_LEN, MLDSA65_S1_PACKED_LEN, MLDSA65_S2_PACKED_LEN, MLDSA65_SIG_LEN, - MLDSA65_SK_LEN, MLDSA65_TAU, MLDSA65_k, MLDSA65_l, + MLDSA65_BETA, MLDSA65_C_TILDE, MLDSA65_ETA, MLDSA65_GAMMA1, MLDSA65_GAMMA1_MINUS_BETA, MLDSA65_GAMMA2_MINUS_BETA, MLDSA65_GAMMA1_MASK_LEN, + MLDSA65_GAMMA2, MLDSA65_LAMBDA, MLDSA65_LAMBDA_over_4, MLDSA65_OMEGA, MLDSA65_PK_LEN, + MLDSA65_POLY_W1_PACKED_LEN, MLDSA65_POLY_Z_PACKED_LEN, + MLDSA65_SIG_LEN, MLDSA65_SK_LEN, MLDSA65_FULL_SK_LEN, MLDSA65_TAU, MLDSA65_S1_PACKED_LEN, MLDSA65_S2_PACKED_LEN, MLDSA65_k, MLDSA65_l, }; use crate::mldsa::{ - MLDSA87_BETA, MLDSA87_C_TILDE, MLDSA87_ETA, MLDSA87_FULL_SK_LEN, MLDSA87_GAMMA1, - MLDSA87_GAMMA1_MASK_LEN, MLDSA87_GAMMA2, MLDSA87_LAMBDA, MLDSA87_LAMBDA_over_4, MLDSA87_OMEGA, - MLDSA87_PK_LEN, MLDSA87_POLY_ETA_PACKED_LEN, MLDSA87_POLY_W1_PACKED_LEN, - MLDSA87_POLY_Z_PACKED_LEN, MLDSA87_S1_PACKED_LEN, MLDSA87_S2_PACKED_LEN, MLDSA87_SIG_LEN, - MLDSA87_SK_LEN, MLDSA87_TAU, MLDSA87_k, MLDSA87_l, + MLDSA87_BETA, MLDSA87_C_TILDE, MLDSA87_ETA, MLDSA87_GAMMA1, MLDSA87_GAMMA1_MINUS_BETA, MLDSA87_GAMMA2_MINUS_BETA, MLDSA87_GAMMA1_MASK_LEN, + MLDSA87_GAMMA2, MLDSA87_LAMBDA, MLDSA87_LAMBDA_over_4, MLDSA87_OMEGA, MLDSA87_PK_LEN, + MLDSA87_POLY_W1_PACKED_LEN, MLDSA87_POLY_Z_PACKED_LEN, + MLDSA87_SIG_LEN, MLDSA87_SK_LEN, MLDSA87_FULL_SK_LEN, MLDSA87_TAU, MLDSA87_S1_PACKED_LEN, MLDSA87_S2_PACKED_LEN, MLDSA87_k, MLDSA87_l, }; use crate::mldsa_keys::{MLDSAPrivateKeyInternalTrait, MLDSAPublicKeyInternalTrait}; use crate::{ @@ -151,8 +148,9 @@ pub type HashMLDSA44_with_SHA256 = HashMLDSA< MLDSA44_S1_PACKED_LEN, MLDSA44_S2_PACKED_LEN, MLDSA44_T1_PACKED_LEN, - MLDSA44_POLY_ETA_PACKED_LEN, MLDSA44_LAMBDA_over_4, + MLDSA44_GAMMA1_MINUS_BETA, + MLDSA44_GAMMA2_MINUS_BETA, MLDSA44_GAMMA1_MASK_LEN, >; @@ -188,8 +186,9 @@ pub type HashMLDSA65_with_SHA256 = HashMLDSA< MLDSA65_S1_PACKED_LEN, MLDSA65_S2_PACKED_LEN, MLDSA65_T1_PACKED_LEN, - MLDSA65_POLY_ETA_PACKED_LEN, MLDSA65_LAMBDA_over_4, + MLDSA65_GAMMA1_MINUS_BETA, + MLDSA65_GAMMA2_MINUS_BETA, MLDSA65_GAMMA1_MASK_LEN, >; @@ -225,8 +224,9 @@ pub type HashMLDSA87_with_SHA256 = HashMLDSA< MLDSA87_S1_PACKED_LEN, MLDSA87_S2_PACKED_LEN, MLDSA87_T1_PACKED_LEN, - MLDSA87_POLY_ETA_PACKED_LEN, MLDSA87_LAMBDA_over_4, + MLDSA87_GAMMA1_MINUS_BETA, + MLDSA87_GAMMA2_MINUS_BETA, MLDSA87_GAMMA1_MASK_LEN, >; @@ -262,8 +262,9 @@ pub type HashMLDSA44_with_SHA512 = HashMLDSA< MLDSA44_S1_PACKED_LEN, MLDSA44_S2_PACKED_LEN, MLDSA44_T1_PACKED_LEN, - MLDSA44_POLY_ETA_PACKED_LEN, MLDSA44_LAMBDA_over_4, + MLDSA44_GAMMA1_MINUS_BETA, + MLDSA44_GAMMA2_MINUS_BETA, MLDSA44_GAMMA1_MASK_LEN, >; @@ -299,8 +300,9 @@ pub type HashMLDSA65_with_SHA512 = HashMLDSA< MLDSA65_S1_PACKED_LEN, MLDSA65_S2_PACKED_LEN, MLDSA65_T1_PACKED_LEN, - MLDSA65_POLY_ETA_PACKED_LEN, MLDSA65_LAMBDA_over_4, + MLDSA65_GAMMA1_MINUS_BETA, + MLDSA65_GAMMA2_MINUS_BETA, MLDSA65_GAMMA1_MASK_LEN, >; @@ -336,8 +338,9 @@ pub type HashMLDSA87_with_SHA512 = HashMLDSA< MLDSA87_S1_PACKED_LEN, MLDSA87_S2_PACKED_LEN, MLDSA87_T1_PACKED_LEN, - MLDSA87_POLY_ETA_PACKED_LEN, MLDSA87_LAMBDA_over_4, + MLDSA87_GAMMA1_MINUS_BETA, + MLDSA87_GAMMA2_MINUS_BETA, MLDSA87_GAMMA1_MASK_LEN, >; @@ -397,8 +400,9 @@ pub struct HashMLDSA< const S1_PACKED_LEN: usize, const S2_PACKED_LEN: usize, const T1_PACKED_LEN: usize, - const POLY_ETA_PACKED_LEN: usize, const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, > { _phantom: PhantomData<(PK, SK)>, @@ -468,8 +472,9 @@ impl< const S1_PACKED_LEN: usize, const S2_PACKED_LEN: usize, const T1_PACKED_LEN: usize, - const POLY_ETA_PACKED_LEN: usize, const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, > HashMLDSA< @@ -497,8 +502,9 @@ impl< S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, - POLY_ETA_PACKED_LEN, LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, GAMMA1_MASK_LEN, > { @@ -526,8 +532,9 @@ impl< S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, - POLY_ETA_PACKED_LEN, LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, GAMMA1_MASK_LEN, >::keygen_internal(seed) } @@ -623,8 +630,9 @@ impl< S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, - POLY_ETA_PACKED_LEN, LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, GAMMA1_MASK_LEN, >::sign_mu_deterministic_out(sk, &mu, rnd, output)?; @@ -719,8 +727,9 @@ impl< const S1_PACKED_LEN: usize, const S2_PACKED_LEN: usize, const T1_PACKED_LEN: usize, - const POLY_ETA_PACKED_LEN: usize, const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, > Signature for HashMLDSA< @@ -748,8 +757,9 @@ impl< S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, - POLY_ETA_PACKED_LEN, LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, GAMMA1_MASK_LEN, > { @@ -777,8 +787,9 @@ impl< S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, - POLY_ETA_PACKED_LEN, LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, GAMMA1_MASK_LEN, >::keygen() } @@ -836,20 +847,13 @@ impl< )); } - if output.len() < SIG_LEN { - return Err(SignatureError::LengthError( - "Output buffer insufficient size to hold signature", - )); - } - let output_sized: &mut [u8; SIG_LEN] = output[..SIG_LEN].as_mut().try_into().unwrap(); - if self.sk.is_some() { if self.signer_rnd.is_none() { Self::sign_ph_out( &self.sk.unwrap(), &ph, Some(&self.ctx[..self.ctx_len]), - output_sized, + output, ) } else { Self::sign_ph_deterministic_out( @@ -857,7 +861,7 @@ impl< Some(&self.ctx[..self.ctx_len]), &ph, self.signer_rnd.unwrap(), - output_sized, + output, ) } } else if self.seed.is_some() { @@ -876,7 +880,7 @@ impl< Some(&self.ctx[..self.ctx_len]), &ph, rnd, - output_sized, + output, ) } else { unreachable!() @@ -963,8 +967,9 @@ impl< const S1_PACKED_LEN: usize, const S2_PACKED_LEN: usize, const T1_PACKED_LEN: usize, - const POLY_ETA_PACKED_LEN: usize, const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, > PHSignature for HashMLDSA< @@ -992,8 +997,9 @@ impl< S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, - POLY_ETA_PACKED_LEN, LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, GAMMA1_MASK_LEN, > { @@ -1003,7 +1009,7 @@ impl< ctx: Option<&[u8]>, ) -> Result<[u8; SIG_LEN], SignatureError> { let mut out = [0u8; SIG_LEN]; - Self::sign_out(sk, ph, ctx, &mut out)?; + Self::sign_ph_out(sk, ph, ctx, &mut out)?; Ok(out) } @@ -1018,12 +1024,6 @@ impl< ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN], ) -> Result { - if output.len() < SIG_LEN { - return Err(SignatureError::LengthError( - "Output buffer insufficient size to hold signature", - )); - } - let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN]; HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; Self::sign_ph_deterministic_out(sk, ctx, ph, rnd, output) @@ -1086,8 +1086,9 @@ impl< S1_PACKED_LEN, S2_PACKED_LEN, T1_PACKED_LEN, - POLY_ETA_PACKED_LEN, LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, GAMMA1_MASK_LEN, >::verify_mu_internal(pk, &mu, sig_sized) { diff --git a/crypto/mldsa_lowmemory/src/low_memory_helpers.rs b/crypto/mldsa_lowmemory/src/low_memory_helpers.rs index d947bbe..d9e52f1 100644 --- a/crypto/mldsa_lowmemory/src/low_memory_helpers.rs +++ b/crypto/mldsa_lowmemory/src/low_memory_helpers.rs @@ -2,10 +2,12 @@ //! and other intermediate values by never holding the whole thing in memory at once, but re-constructing //! what it needs in pieces, which generally means handling the matrices and vectors row-wise or entry-wise. -use bouncycastle_core::errors::SignatureError; -use crate::mldsa::{d}; -use crate::aux_functions::{bit_unpack_eta, bitlen_eta, expand_mask_poly, rej_ntt_poly, unpack_z_row}; +use crate::aux_functions::{ + bit_unpack_eta, bitlen_eta, expand_mask_poly, rej_ntt_poly, unpack_z_row, +}; +use crate::mldsa::d; use crate::polynomial::Polynomial; +use bouncycastle_core::errors::SignatureError; #[inline(always)] pub(crate) fn expandA_elem(rho: &[u8; 32], i: usize, j: usize) -> Polynomial { @@ -41,17 +43,18 @@ pub(crate) fn compute_w_row( rho: &[u8; 32], sig: &[u8; SIG_LEN], t1: &Polynomial, c: &Polynomial, - idx: usize) -> Result { + idx: usize, +) -> Result { // Algorithm 8: line 9: 𝐰′_approx ← NTTβˆ’1(𝐀_hat ∘ NTT(𝐳) βˆ’ NTT(𝑐) ∘ NTT(𝐭1 β‹… 2^𝑑)) // broken out for clarity: // NTTβˆ’1( @@ -60,13 +63,18 @@ pub(crate) fn compute_wp_approx_row< // ) // β–· 𝐰'_approx = 𝐀𝐳 βˆ’ 𝑐𝐭1 β‹… 2^𝑑 - let mut z_i = unpack_z_row::(0, sig)?; + let mut z_i = + unpack_z_row::( + 0, sig, + )?; z_i.ntt(); let mut Az_acc = rej_ntt_poly(rho, &[0u8, idx as u8]); Az_acc.multiply_ntt(&z_i); for col in 1..l { - z_i = unpack_z_row::(col, sig)?; + z_i = unpack_z_row::( + col, sig, + )?; z_i.ntt(); // [Optimization Note]: @@ -77,15 +85,15 @@ pub(crate) fn compute_wp_approx_row< Az_acc.add_ntt(&tmp); } - let ct1 = { - let mut t1_i = t1.clone(); + let ct1 = compute_ct1(t1.clone(), c.clone()); + fn compute_ct1(mut t1_i: Polynomial, mut c: Polynomial) -> Polynomial { t1_i.shift_left::(); t1_i.ntt(); - let mut c = c.clone(); c.ntt(); t1_i.multiply_ntt(&c); + t1_i - }; + } Az_acc.sub(&ct1); Az_acc.inv_ntt(); @@ -95,7 +103,9 @@ pub(crate) fn compute_wp_approx_row< } pub(crate) fn compute_z_component< - const GAMMA1: i32, const GAMMA1_MASK_LEN: usize, const BETA: i32, + const GAMMA1: i32, + const GAMMA1_MASK_LEN: usize, + const GAMMA1_MINUS_BETA: i32, >( s1: &Polynomial, rho_p_p: &[u8; 64], @@ -112,13 +122,10 @@ pub(crate) fn compute_z_component< let mut z = cs1; z.add_ntt(&y); - if z.check_norm(GAMMA1 - BETA) { Ok(None) } else { Ok(Some(z)) } + if z.check_norm::() { Ok(None) } else { Ok(Some(z)) } } -pub(crate) fn compute_w0cs2_component< - const GAMMA2: i32, - const BETA: i32, ->( +pub(crate) fn compute_w0cs2_component( s2: &Polynomial, w: &Polynomial, c_hat: &Polynomial, @@ -129,7 +136,6 @@ pub(crate) fn compute_w0cs2_component< let mut cs2 = s2_hat; // rename cs2.inv_ntt(); - // Note: this could be further optimized by using the optimization described in // https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf section 5.1: // "instead of computing (r1, r0) = Decomposeq (w βˆ’ cs2, Ξ±) @@ -139,12 +145,10 @@ pub(crate) fn compute_w0cs2_component< let mut w0cs2 = w.clone(); w0cs2.low_bits::(); w0cs2.sub(&cs2); - if w0cs2.check_norm(GAMMA2 - BETA) { None } else { Some(w0cs2) } + if w0cs2.check_norm::() { None } else { Some(w0cs2) } } -pub(crate) fn compute_ct0_component< - const GAMMA2: i32, ->( +pub(crate) fn compute_ct0_component( t0_row: &Polynomial, c_hat: &Polynomial, ) -> Option { @@ -154,7 +158,7 @@ pub(crate) fn compute_ct0_component< let mut ct0 = t0_hat; // rename ct0.inv_ntt(); - if ct0.check_norm(GAMMA2) { None } else { Some(ct0) } + if ct0.check_norm::() { None } else { Some(ct0) } } pub(crate) fn s_unpack(s_packed: &[u8], idx: usize) -> Polynomial { diff --git a/crypto/mldsa_lowmemory/src/mldsa.rs b/crypto/mldsa_lowmemory/src/mldsa.rs index 28f4386..a6efa48 100644 --- a/crypto/mldsa_lowmemory/src/mldsa.rs +++ b/crypto/mldsa_lowmemory/src/mldsa.rs @@ -309,26 +309,33 @@ //! is still larger). //! Contact us if you need such a thing implemented. -use core::marker::PhantomData; -use crate::aux_functions::{sample_in_ball, bitpack_gamma1, bitlen_eta, unpack_h_row, unpack_c_tilde}; -use crate::low_memory_helpers::{compute_ct0_component, compute_w0cs2_component, compute_w_row, compute_wp_approx_row, compute_z_component, s_unpack}; -use crate::mldsa_keys::{MLDSAPublicKeyTrait, MLDSAPublicKeyInternalTrait}; -use crate::mldsa_keys::{MLDSAPrivateKeyTrait, MLDSAPrivateKeyInternalTrait}; -use crate::{MLDSA44PublicKey, MLDSA44PrivateKey, MLDSA65PublicKey, MLDSA65PrivateKey, MLDSA87PublicKey, MLDSA87PrivateKey}; +use crate::aux_functions::{ + bitlen_eta, bitpack_gamma1, sample_in_ball, unpack_c_tilde, unpack_h_row, +}; +use crate::low_memory_helpers::{ + compute_ct0_component, compute_w_row, compute_w0cs2_component, compute_wp_approx_row, + compute_z_component, s_unpack, +}; +use crate::mldsa_keys::{MLDSAPrivateKeyInternalTrait, MLDSAPrivateKeyTrait}; +use crate::mldsa_keys::{MLDSAPublicKeyInternalTrait, MLDSAPublicKeyTrait}; +use crate::{ + MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, + MLDSA87PublicKey, +}; use bouncycastle_core::errors::SignatureError; -use bouncycastle_core::key_material::{KeyMaterial}; -use bouncycastle_core::traits::{RNG, SecurityStrength, XOF, Signature, Algorithm}; -use bouncycastle_rng::{HashDRBG_SHA512}; +use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, Signature, XOF}; +use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::{SHAKE128, SHAKE256}; - +use core::marker::PhantomData; // imports needed just for docs #[allow(unused_imports)] -use bouncycastle_core::traits::PHSignature; +use crate::hash_mldsa; #[allow(unused_imports)] use bouncycastle_core::key_material::KeyMaterial256; #[allow(unused_imports)] -use crate::hash_mldsa; +use bouncycastle_core::traits::PHSignature; /*** Constants ***/ /// @@ -351,16 +358,17 @@ pub const MLDSA_RND_LEN: usize = 32; pub const MLDSA_TR_LEN: usize = 64; /// Length of the \[u8] holding a ML-DSA mu value. pub const MLDSA_MU_LEN: usize = 64; +/// Length of the \[u8] holding an private key seed. +pub const MLDSA_SEED_LEN: usize = 32; pub(crate) const POLY_T0PACKED_LEN: usize = 416; pub(crate) const POLY_T1PACKED_LEN: usize = 320; - /* ML-DSA-44 params */ /// Length of the \[u8] holding a ML-DSA-44 public key. pub const MLDSA44_PK_LEN: usize = 1312; /// Length of the \[u8] holding a ML-DSA-44 private key, which in this implementation is just a 32-byte seed. -pub const MLDSA44_SK_LEN: usize = 32; +pub const MLDSA44_SK_LEN: usize = MLDSA_SEED_LEN; /// The length of the FIPS representation of the private key, which can be produced by [MLDSAPrivateKeyTrait::encode_full_sk] pub const MLDSA44_FULL_SK_LEN: usize = 2560; /// Length of the \[u8] holding a ML-DSA-44 signature value. @@ -368,7 +376,7 @@ pub const MLDSA44_SIG_LEN: usize = 2420; pub(crate) const MLDSA44_TAU: i32 = 39; pub(crate) const MLDSA44_LAMBDA: i32 = 128; pub(crate) const MLDSA44_GAMMA1: i32 = 1 << 17; -pub(crate) const MLDSA44_GAMMA2: i32 = (q - 1) / 88; +pub(crate) const MLDSA44_GAMMA2: i32 = (q - 1) / 88; // mutants note: because of the bitshifting, the "- 1" ends up not mattering pub(crate) const MLDSA44_k: usize = 4; pub(crate) const MLDSA44_l: usize = 4; pub(crate) const MLDSA44_ETA: usize = 2; @@ -379,23 +387,23 @@ pub(crate) const MLDSA44_OMEGA: i32 = 80; pub(crate) const MLDSA44_C_TILDE: usize = 32; pub(crate) const MLDSA44_POLY_Z_PACKED_LEN: usize = 576; pub(crate) const MLDSA44_POLY_W1_PACKED_LEN: usize = 192; -pub(crate) const MLDSA44_S1_PACKED_LEN: usize = bitlen_eta(MLDSA44_ETA) * MLDSA44_l; // 384 bytes -pub(crate) const MLDSA44_S2_PACKED_LEN: usize = bitlen_eta(MLDSA44_ETA) * MLDSA44_k; // 384 bytes -pub(crate) const MLDSA44_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA44_k; // 768 bytes -pub(crate) const MLDSA44_POLY_ETA_PACKED_LEN: usize = 32*3; -pub(crate) const MLDSA44_LAMBDA_over_4: usize = 128/4; +pub(crate) const MLDSA44_S1_PACKED_LEN: usize = bitlen_eta(MLDSA44_ETA) * MLDSA44_l; // 384 bytes +pub(crate) const MLDSA44_S2_PACKED_LEN: usize = bitlen_eta(MLDSA44_ETA) * MLDSA44_k; // 384 bytes +pub(crate) const MLDSA44_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA44_k; // 768 bytes +pub(crate) const MLDSA44_LAMBDA_over_4: usize = 128 / 4; +pub(crate) const MLDSA44_GAMMA1_MINUS_BETA: i32 = MLDSA44_GAMMA1 - MLDSA44_BETA; // mutants note: there is a test vector for this in the regular implementation, but we don't know the sk seed for it, so can't test it here. +pub(crate) const MLDSA44_GAMMA2_MINUS_BETA: i32 = MLDSA44_GAMMA2 - MLDSA44_BETA; // mutants note: there is a test vector for this in the regular implementation, but we don't know the sk seed for it, so can't test it here. // Alg 32 // 1: 𝑐 ← 1 + bitlen (𝛾1 βˆ’ 1) -pub(crate) const MLDSA44_GAMMA1_MASK_LEN: usize = 576; // 32*(1 + bitlen (𝛾1 βˆ’ 1) ) - +pub(crate) const MLDSA44_GAMMA1_MASK_LEN: usize = 576; // 32*(1 + bitlen (𝛾1 βˆ’ 1) ) /* ML-DSA-65 params */ /// Length of the \[u8] holding a ML-DSA-65 public key. pub const MLDSA65_PK_LEN: usize = 1952; /// Length of the \[u8] holding a ML-DSA-65 private key, which in this implementation is just a 32-byte seed. -pub const MLDSA65_SK_LEN: usize = 32; +pub const MLDSA65_SK_LEN: usize = MLDSA_SEED_LEN; /// The length of the FIPS representation of the private key, which can be produced by [MLDSAPrivateKeyTrait::encode_full_sk] pub const MLDSA65_FULL_SK_LEN: usize = 4032; /// Length of the \[u8] holding a ML-DSA-65 signature value. @@ -403,7 +411,7 @@ pub const MLDSA65_SIG_LEN: usize = 3309; pub(crate) const MLDSA65_TAU: i32 = 49; pub(crate) const MLDSA65_LAMBDA: i32 = 192; pub(crate) const MLDSA65_GAMMA1: i32 = 1 << 19; -pub(crate) const MLDSA65_GAMMA2: i32 = (q - 1) / 32; +pub(crate) const MLDSA65_GAMMA2: i32 = (q - 1) / 32; // mutants note: because of the bitshifting, the "- 1" ends up not mattering pub(crate) const MLDSA65_k: usize = 6; pub(crate) const MLDSA65_l: usize = 5; pub(crate) const MLDSA65_ETA: usize = 4; @@ -414,24 +422,23 @@ pub(crate) const MLDSA65_OMEGA: i32 = 55; pub(crate) const MLDSA65_C_TILDE: usize = 48; pub(crate) const MLDSA65_POLY_Z_PACKED_LEN: usize = 640; pub(crate) const MLDSA65_POLY_W1_PACKED_LEN: usize = 128; -pub(crate) const MLDSA65_S1_PACKED_LEN: usize = bitlen_eta(MLDSA65_ETA) * MLDSA65_l; // 640 bytes -pub(crate) const MLDSA65_S2_PACKED_LEN: usize = bitlen_eta(MLDSA65_ETA) * MLDSA65_k; // 768 bytes -pub(crate) const MLDSA65_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA65_k; // 1152 bytes -pub(crate) const MLDSA65_POLY_ETA_PACKED_LEN: usize = 32*4; -pub(crate) const MLDSA65_LAMBDA_over_4: usize = 192/4; +pub(crate) const MLDSA65_S1_PACKED_LEN: usize = bitlen_eta(MLDSA65_ETA) * MLDSA65_l; // 640 bytes +pub(crate) const MLDSA65_S2_PACKED_LEN: usize = bitlen_eta(MLDSA65_ETA) * MLDSA65_k; // 768 bytes +pub(crate) const MLDSA65_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA65_k; // 1152 bytes +pub(crate) const MLDSA65_LAMBDA_over_4: usize = 192 / 4; +pub(crate) const MLDSA65_GAMMA1_MINUS_BETA: i32 = MLDSA65_GAMMA1 - MLDSA65_BETA; // mutants note: there is a test vector for this in the regular implementation, but we don't know the sk seed for it, so can't test it here. +pub(crate) const MLDSA65_GAMMA2_MINUS_BETA: i32 = MLDSA65_GAMMA2 - MLDSA65_BETA; // mutants note: there is a test vector for this in the regular implementation, but we don't know the sk seed for it, so can't test it here. // Alg 32 // 1: 𝑐 ← 1 + bitlen (𝛾1 βˆ’ 1) pub(crate) const MLDSA65_GAMMA1_MASK_LEN: usize = 640; - - /* ML-DSA-87 params */ /// Length of the \[u8] holding a ML-DSA-87 public key. pub const MLDSA87_PK_LEN: usize = 2592; /// Length of the \[u8] holding a ML-DSA-87 private key, which in this implementation is just a 32-byte seed. -pub const MLDSA87_SK_LEN: usize = 32; +pub const MLDSA87_SK_LEN: usize = MLDSA_SEED_LEN; /// The length of the FIPS representation of the private key, which can be produced by [MLDSAPrivateKeyTrait::encode_full_sk] pub const MLDSA87_FULL_SK_LEN: usize = 4896; /// Length of the \[u8] holding a ML-DSA-87 signature value. @@ -439,7 +446,7 @@ pub const MLDSA87_SIG_LEN: usize = 4627; pub(crate) const MLDSA87_TAU: i32 = 60; pub(crate) const MLDSA87_LAMBDA: i32 = 256; pub(crate) const MLDSA87_GAMMA1: i32 = 1 << 19; -pub(crate) const MLDSA87_GAMMA2: i32 = (q - 1) / 32; +pub(crate) const MLDSA87_GAMMA2: i32 = (q - 1) / 32; // mutants note: because of the bitshifting, the "- 1" ends up not mattering pub(crate) const MLDSA87_k: usize = 8; pub(crate) const MLDSA87_l: usize = 7; pub(crate) const MLDSA87_ETA: usize = 2; @@ -450,23 +457,21 @@ pub(crate) const MLDSA87_OMEGA: i32 = 75; pub(crate) const MLDSA87_C_TILDE: usize = 64; pub(crate) const MLDSA87_POLY_Z_PACKED_LEN: usize = 640; pub(crate) const MLDSA87_POLY_W1_PACKED_LEN: usize = 128; -pub(crate) const MLDSA87_S1_PACKED_LEN: usize = bitlen_eta(MLDSA87_ETA) * MLDSA87_l; // 672 bytes -pub(crate) const MLDSA87_S2_PACKED_LEN: usize = bitlen_eta(MLDSA87_ETA) * MLDSA87_k; // 768 bytes -pub(crate) const MLDSA87_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA87_k; // 1024 bytes -pub(crate) const MLDSA87_POLY_ETA_PACKED_LEN: usize = 32*3; -pub(crate) const MLDSA87_LAMBDA_over_4: usize = 256/4; +pub(crate) const MLDSA87_S1_PACKED_LEN: usize = bitlen_eta(MLDSA87_ETA) * MLDSA87_l; // 672 bytes +pub(crate) const MLDSA87_S2_PACKED_LEN: usize = bitlen_eta(MLDSA87_ETA) * MLDSA87_k; // 768 bytes +pub(crate) const MLDSA87_T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * MLDSA87_k; // 1024 bytes +pub(crate) const MLDSA87_LAMBDA_over_4: usize = 256 / 4; +pub(crate) const MLDSA87_GAMMA1_MINUS_BETA: i32 = MLDSA87_GAMMA1 - MLDSA87_BETA; // mutants note: there is a test vector for this in the regular implementation, but we don't know the sk seed for it, so can't test it here. +pub(crate) const MLDSA87_GAMMA2_MINUS_BETA: i32 = MLDSA87_GAMMA2 - MLDSA87_BETA; // mutants note: there is a test vector for this in the regular implementation, but we don't know the sk seed for it, so can't test it here. // Alg 32 // 1: 𝑐 ← 1 + bitlen (𝛾1 βˆ’ 1) pub(crate) const MLDSA87_GAMMA1_MASK_LEN: usize = 640; - - // Typedefs just to make the algorithms look more like the FIPS 204 sample code. pub(crate) type H = SHAKE256; pub(crate) type G = SHAKE128; - /*** Pub Types ***/ /// The ML-DSA-44 algorithm. @@ -492,8 +497,9 @@ pub type MLDSA44 = MLDSA< MLDSA44_S1_PACKED_LEN, MLDSA44_S2_PACKED_LEN, MLDSA44_T1_PACKED_LEN, - MLDSA44_POLY_ETA_PACKED_LEN, MLDSA44_LAMBDA_over_4, + MLDSA44_GAMMA1_MINUS_BETA, + MLDSA44_GAMMA2_MINUS_BETA, MLDSA44_GAMMA1_MASK_LEN, >; @@ -525,8 +531,9 @@ pub type MLDSA65 = MLDSA< MLDSA65_S1_PACKED_LEN, MLDSA65_S2_PACKED_LEN, MLDSA65_T1_PACKED_LEN, - MLDSA65_POLY_ETA_PACKED_LEN, MLDSA65_LAMBDA_over_4, + MLDSA65_GAMMA1_MINUS_BETA, + MLDSA65_GAMMA2_MINUS_BETA, MLDSA65_GAMMA1_MASK_LEN, >; @@ -558,8 +565,9 @@ pub type MLDSA87 = MLDSA< MLDSA87_S1_PACKED_LEN, MLDSA87_S2_PACKED_LEN, MLDSA87_T1_PACKED_LEN, - MLDSA87_POLY_ETA_PACKED_LEN, MLDSA87_LAMBDA_over_4, + MLDSA87_GAMMA1_MINUS_BETA, + MLDSA87_GAMMA2_MINUS_BETA, MLDSA87_GAMMA1_MASK_LEN, >; @@ -576,9 +584,28 @@ pub struct MLDSA< const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + 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, @@ -594,8 +621,9 @@ pub struct MLDSA< const S1_PACKED_LEN: usize, const S2_PACKED_LEN: usize, const T1_PACKED_LEN: usize, - const POLY_ETA_PACKED_LEN: usize, const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, > { _phantom: PhantomData<(PK, SK)>, @@ -620,9 +648,28 @@ impl< const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + 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, @@ -638,41 +685,41 @@ impl< const S1_PACKED_LEN: usize, const S2_PACKED_LEN: usize, const T1_PACKED_LEN: usize, - const POLY_ETA_PACKED_LEN: usize, const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> 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, - POLY_ETA_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MASK_LEN, > + 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, + > { /// Should still be ok in FIPS mode - pub fn keygen_from_os_rng() -> Result< - (PK, SK), - SignatureError, - > { + pub fn keygen_from_os_rng() -> Result<(PK, SK), SignatureError> { let mut seed = KeyMaterial::<32>::new(); HashDRBG_SHA512::new_from_os().fill_keymaterial_out(&mut seed)?; Self::keygen_internal(&seed) @@ -684,12 +731,7 @@ impl< /// the appropriate [SecurityStrength] for the requested ML-DSA parameter set. /// If you happen to have your seed in a larger KeyMaterial, you'll have to copy it using /// [KeyMaterial::from_key]. - pub(crate) fn keygen_internal( - seed: &KeyMaterial<32>, - ) -> Result< - (PK, SK), - SignatureError, - > { + pub(crate) fn keygen_internal(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError> { let sk = SK::from_keymaterial(seed)?; let pk = sk.derive_pk(); let pk = PK::new(pk.rho, pk.t1_packed); // stupid conversion, but it gets around these overly-generified rust types @@ -702,9 +744,28 @@ impl< const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + 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, @@ -720,35 +781,55 @@ impl< const S1_PACKED_LEN: usize, const S2_PACKED_LEN: usize, const T1_PACKED_LEN: usize, - const POLY_ETA_PACKED_LEN: usize, const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> MLDSATrait 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, - POLY_ETA_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MASK_LEN, -> { +> + MLDSATrait< + PK_LEN, + SK_LEN, + FULL_SK_LEN, + SIG_LEN, + PK, + SK, + LAMBDA, + GAMMA2, + k, + l, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + eta, + > + 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, + > +{ /*** Key Generation and PK / SK consistency checks ***/ /// Imports a secret key from a seed. @@ -764,10 +845,7 @@ impl< fn keygen_from_seed_and_encoded( seed: &KeyMaterial<32>, encoded_sk: &[u8; SK_LEN], - ) -> Result< - (PK, SK), - SignatureError, - > { + ) -> Result<(PK, SK), SignatureError> { let (pk, sk) = Self::keygen_internal(seed)?; let sk_from_bytes = SK::sk_decode(encoded_sk); @@ -787,10 +865,7 @@ impl< /// (in which case a keygen_from_seed is run and then the pk's compared). /// /// Returns either `()` or [SignatureError::ConsistencyCheckFailed]. - fn keypair_consistency_check( - pk: &PK, - sk: &SK, - ) -> Result<(), SignatureError> { + fn keypair_consistency_check(pk: &PK, sk: &SK) -> Result<(), SignatureError> { // This is maybe a computationally heavy way to compare them, but it works let derived_pk = sk.derive_pk(); if derived_pk.compute_tr() == pk.compute_tr() { @@ -858,10 +933,7 @@ impl< /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both /// FIPS 204 itself, as well as subsequent FAQ documents. /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG. - fn sign_mu( - sk: &SK, - mu: &[u8; 64], - ) -> Result<[u8; SIG_LEN], SignatureError> { + fn sign_mu(sk: &SK, mu: &[u8; 64]) -> Result<[u8; SIG_LEN], SignatureError> { let mut out: [u8; SIG_LEN] = [0u8; SIG_LEN]; Self::sign_mu_out(sk, mu, &mut out)?; Ok(out) @@ -883,7 +955,11 @@ impl< Self::sign_mu_deterministic_out(sk, mu, rnd, output) } - fn sign_mu_deterministic(sk: &SK, mu: &[u8; 64], rnd: [u8; 32]) -> Result<[u8; SIG_LEN], SignatureError> { + fn sign_mu_deterministic( + sk: &SK, + mu: &[u8; 64], + rnd: [u8; 32], + ) -> Result<[u8; SIG_LEN], SignatureError> { let mut out = [0u8; SIG_LEN]; let bytes_written = Self::sign_mu_deterministic_out(sk, mu, rnd, &mut out)?; debug_assert_eq!(bytes_written, SIG_LEN); @@ -898,6 +974,11 @@ impl< rnd: [u8; 32], output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + + // This function is a mash-up of keyGen (Algorithm 6) and sign (Algorithm 7), + // with a special emphasis on deriving values only as we need them, which in particular + // means that we'll process matrices and vectors row or component-wise. // I have tried to keep this as clean as possible for correspondence with the FIPS, // but I have moved things around so that I can use unnamed scopes to limit how many @@ -929,6 +1010,7 @@ impl< h.absorb(mu); let mut rho_p_p = [0u8; 64]; h.squeeze_out(&mut rho_p_p); + rho_p_p }; @@ -942,6 +1024,7 @@ impl< loop { // FIPS 204 s. 6.2 allows: // "Implementations may limit the number of iterations in this loop to not exceed a finite maximum value." + // mutants note: there is no test for this because we don't know of a KAT that will exceed this limit. if kappa > 1000 * k as u16 { return Err(SignatureError::GenericError( "Rejection sampling loop exceeded max iterations, try again with a different signing nonce.", @@ -949,11 +1032,17 @@ impl< } // 11-15: derive c_tilde without materializing y_hat or w as full vectors. - let sig_val_c_tilde = { // scope for hash + let sig_val_c_tilde = { + // scope for hash let mut hash = H::new(); hash.absorb(mu); for row in 0..k { - let mut w = compute_w_row::(&sk.rho(), &rho_p_p, kappa, row); + let mut w = compute_w_row::( + &sk.rho(), + &rho_p_p, + kappa, + row, + ); w.high_bits::(); hash.absorb(&w.w1_encode::()); } @@ -978,14 +1067,17 @@ impl< // 18-23 (z path): compute and encode each z polynomial directly into the caller buffer. let mut rejected = false; for col in 0..l { - let z = match compute_z_component::( + let z = match compute_z_component::( // [Optimization Note]: // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form. // weirdly, in perf testing, this actually caused memory usage to go by a small amount; // maybe because re-computing the intermediates adds more to the widest point of the alg? // &sk.compute_s1_row(col), &s_unpack::(&s1_packed, col), - &rho_p_p, &c_hat, kappa, col, + &rho_p_p, + &c_hat, + kappa, + col, )? { Some(z) => z, None => { @@ -998,6 +1090,7 @@ impl< } if rejected { + // mutants note: we don't have a test vector that exercises this kappa += l as u16; continue; } @@ -1005,26 +1098,30 @@ impl< // 19-28 (hint path): recompute rows as needed and write the packed hint directly. let mut hint_count = 0usize; for row in 0..k { - let mut w = compute_w_row::(&sk.rho(), &rho_p_p, kappa, row); - let mut tmp = - match compute_w0cs2_component::( - // [Optimization Note]: - // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form. - // &sk.compute_s2_row(row), - &s_unpack::(&s2_packed, row), - &w, &c_hat) { - Some(tmp) => tmp, - None => { - rejected = true; - break; - } - }; + let mut w = + compute_w_row::(&sk.rho(), &rho_p_p, kappa, row); + let mut tmp = match compute_w0cs2_component::( + // [Optimization Note]: + // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form. + // &sk.compute_s2_row(row), + &s_unpack::(&s2_packed, row), + &w, + &c_hat, + ) { + Some(tmp) => tmp, + None => { + rejected = true; + break; + } + }; let ct0 = match compute_ct0_component::( // [Optimization Note]: // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form. // &sk.compute_t0_row(row), &c_hat) { - &sk.compute_t0_row(row, &s1_packed, &s2_packed), &c_hat) { + &sk.compute_t0_row(row, &s1_packed, &s2_packed), + &c_hat, + ) { Some(ct0) => ct0, None => { rejected = true; @@ -1038,6 +1135,9 @@ impl< w.high_bits::(); let (hint_row, weight) = tmp.make_hint_row::(&w); let next_hint_count = hint_count + weight as usize; + + // mutants note: don't have a test vector that exercises this condition, + // not even in bc-test-data if next_hint_count > OMEGA as usize { rejected = true; break; @@ -1064,19 +1164,27 @@ impl< Ok(SIG_LEN) } - fn sign_mu_deterministic_from_seed(seed: &KeyMaterial<32>, mu: &[u8; 64], rnd: [u8; 32]) -> Result<[u8; SIG_LEN], SignatureError> { + fn sign_mu_deterministic_from_seed( + seed: &KeyMaterial<32>, + mu: &[u8; 64], + rnd: [u8; 32], + ) -> Result<[u8; SIG_LEN], SignatureError> { let mut out = [0u8; SIG_LEN]; SK::from_keymaterial(&seed)?; Self::sign_mu_deterministic_out(&SK::from_keymaterial(&seed)?, mu, rnd, &mut out)?; Ok(out) } - fn sign_mu_deterministic_from_seed_out(seed: &KeyMaterial<32>, mu: &[u8; 64], rnd: [u8; 32], output: &mut [u8; SIG_LEN]) -> Result { + fn sign_mu_deterministic_from_seed_out( + seed: &KeyMaterial<32>, + mu: &[u8; 64], + rnd: [u8; 32], + output: &mut [u8; SIG_LEN], + ) -> Result { SK::from_keymaterial(&seed)?; Self::sign_mu_deterministic_out(&SK::from_keymaterial(&seed)?, mu, rnd, output) } - /// To be used for deterministic signing in conjunction with the [MLDSA44::sign_init], [MLDSA44::sign_update], and [MLDSA44::sign_final] flow. /// Can be set anywhere after [MLDSA44::sign_init] and before [MLDSA44::sign_final] fn set_signer_rnd(&mut self, rnd: [u8; 32]) { @@ -1085,28 +1193,26 @@ impl< /// Alternative initialization of the streaming signer where you have your private key /// as a seed and you want to delay its expansion as late as possible for memory-usage reasons. - fn sign_init_from_seed(seed: &KeyMaterial<32>, ctx: Option<&[u8]>) -> Result { + fn sign_init_from_seed( + seed: &KeyMaterial<32>, + ctx: Option<&[u8]>, + ) -> Result { let (_pk, sk) = Self::keygen_from_seed(seed)?; - Ok( - Self { - _phantom: PhantomData, - mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?, - signer_rnd: None, - sk: None, - seed: Some(seed.clone()), - pk: None } - ) + Ok(Self { + _phantom: PhantomData, + mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?, + signer_rnd: None, + sk: None, + seed: Some(seed.clone()), + pk: None, + }) } /// Algorithm 8 ML-DSA.Verify_internal(π‘π‘˜, 𝑀′, 𝜎) /// Internal function to verify a signature 𝜎 for a formatted message 𝑀′ . /// Input: Public key π‘π‘˜ ∈ 𝔹32+32π‘˜(bitlen (π‘žβˆ’1)βˆ’π‘‘) and message 𝑀′ ∈ {0, 1}βˆ— . /// Input: Signature 𝜎 ∈ π”Ήπœ†/4+β„“β‹…32β‹…(1+bitlen (𝛾1βˆ’1))+πœ”+π‘˜. - fn verify_mu_internal( - pk: &PK, - mu: &[u8; 64], - sig: &[u8; SIG_LEN], - ) -> bool { + fn verify_mu_internal(pk: &PK, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> bool { // 1: (𝜌, 𝐭1) ← pkDecode(π‘π‘˜) // Already done -- the pk struct is already decoded @@ -1132,24 +1238,33 @@ impl< for row in 0..k { let mut wp_approx = match { // 9: 𝐰′_approx ← NTTβˆ’1(𝐀_hat ∘ NTT(𝐳) βˆ’ NTT(𝑐) ∘ NTT(𝐭1 β‹… 2^𝑑)) - compute_wp_approx_row::( - pk.rho(), - sig, - &pk.unpack_t1_row(row), - &c, - row) + compute_wp_approx_row::< + GAMMA1, + GAMMA1_MINUS_BETA, + l, + POLY_Z_PACKED_LEN, + LAMBDA_over_4, + SIG_LEN, + >(pk.rho(), sig, &pk.unpack_t1_row(row), &c, row) } { Ok(wp_approx) => wp_approx, // means the norm check on z failed Err(_) => return false, }; - let h_i - = match unpack_h_row:: - (row, &sig) { - Some(h_i) => h_i, - // means there were more than OMEGA bits set in the hint - None => return false, + let h_i = match unpack_h_row::< + GAMMA1, + k, + l, + OMEGA, + LAMBDA_over_4, + POLY_Z_PACKED_LEN, + SIG_LEN, + >(row, &sig) + { + Some(h_i) => h_i, + // means there were more than OMEGA bits set in the hint + None => return false, }; // 10: 𝐰1β€² ← UseHint(𝐑, 𝐰'_approx) @@ -1175,9 +1290,28 @@ pub trait MLDSATrait< const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + 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 LAMBDA: i32, const GAMMA2: i32, const k: usize, @@ -1185,8 +1319,9 @@ pub trait MLDSATrait< const S1_PACKED_LEN: usize, const S2_PACKED_LEN: usize, const T1_PACKED_LEN: usize, - const ETA: usize -> : Sized { + const ETA: usize, +>: Sized +{ /// Imports a secret key from a seed. fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError>; /// Imports a secret key from both a seed and an encoded_sk. @@ -1198,10 +1333,7 @@ pub trait MLDSATrait< fn keygen_from_seed_and_encoded( seed: &KeyMaterial<32>, encoded_sk: &[u8; SK_LEN], - ) -> Result< - (PK, SK), - SignatureError, - >; + ) -> Result<(PK, SK), SignatureError>; /// Given a public key and a secret key, check that the public key matches the secret key. /// This is a sanity check that the public key was generated correctly from the secret key. /// @@ -1210,10 +1342,7 @@ pub trait MLDSATrait< /// (in which case a keygen_from_seed is run and then the pk's compared). /// /// Returns either `()` or [SignatureError::ConsistencyCheckFailed]. - fn keypair_consistency_check( - pk: &PK, - sk: &SK, - ) -> Result<(), SignatureError>; + fn keypair_consistency_check(pk: &PK, sk: &SK) -> Result<(), SignatureError>; /// This provides the first half of the "External Mu" interface to ML-DSA which is described /// in, and allowed under, NIST's FAQ that accompanies FIPS 204. /// @@ -1267,10 +1396,7 @@ pub trait MLDSATrait< /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both /// FIPS 204 itself, as well as subsequent FAQ documents. /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG. - fn sign_mu( - sk: &SK, - mu: &[u8; 64], - ) -> Result<[u8; SIG_LEN], SignatureError>; + fn sign_mu(sk: &SK, mu: &[u8; 64]) -> Result<[u8; SIG_LEN], SignatureError>; /// Performs an ML-DSA signature using the provided external message representative `mu`. /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both /// FIPS 204 itself, as well as subsequent FAQ documents. @@ -1357,16 +1483,15 @@ pub trait MLDSATrait< /// Can be set anywhere after [MLDSA44::sign_init] and before [MLDSA44::sign_final] fn set_signer_rnd(&mut self, rnd: [u8; 32]); /// An alternate way to start the streaming signing mode by providing a private key seed instead of an expanded private key - fn sign_init_from_seed(seed: &KeyMaterial<32>, ctx: Option<&[u8]>) -> Result; + fn sign_init_from_seed( + seed: &KeyMaterial<32>, + ctx: Option<&[u8]>, + ) -> Result; /// Algorithm 8 ML-DSA.Verify_internal(π‘π‘˜, 𝑀′, 𝜎) /// Internal function to verify a signature 𝜎 for a formatted message 𝑀′ . /// Input: Public key π‘π‘˜ ∈ 𝔹32+32π‘˜(bitlen (π‘žβˆ’1)βˆ’π‘‘) and message 𝑀′ ∈ {0, 1}βˆ— . /// Input: Signature 𝜎 ∈ π”Ήπœ†/4+β„“β‹…32β‹…(1+bitlen (𝛾1βˆ’1))+πœ”+π‘˜. - fn verify_mu_internal( - pk: &PK, - mu: &[u8; 64], - sig: &[u8; SIG_LEN], - ) -> bool; + fn verify_mu_internal(pk: &PK, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> bool; } impl< @@ -1374,9 +1499,28 @@ impl< const SK_LEN: usize, const FULL_SK_LEN: usize, const SIG_LEN: usize, - PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, + 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, @@ -1392,36 +1536,39 @@ impl< const S1_PACKED_LEN: usize, const S2_PACKED_LEN: usize, const T1_PACKED_LEN: usize, - const POLY_ETA_PACKED_LEN: usize, const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> Signature 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, - POLY_ETA_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MASK_LEN, -> { - +> Signature + 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 keygen() -> Result<(PK, SK), SignatureError> { Self::keygen_from_os_rng() } @@ -1433,7 +1580,12 @@ impl< Ok(out) } - fn sign_out(sk: &SK, msg: &[u8], ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN]) -> Result { + fn sign_out( + sk: &SK, + msg: &[u8], + ctx: Option<&[u8]>, + output: &mut [u8; SIG_LEN], + ) -> Result { let mu = MuBuilder::compute_mu(&sk.tr(), msg, ctx)?; let bytes_written = Self::sign_mu_out(sk, &mu, output)?; @@ -1441,15 +1593,14 @@ impl< } fn sign_init(sk: &SK, ctx: Option<&[u8]>) -> Result { - Ok( - Self { - _phantom: PhantomData, - mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?, - signer_rnd: None, - sk: Some(sk.clone()), - seed: None, - pk: None } - ) + Ok(Self { + _phantom: PhantomData, + mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?, + signer_rnd: None, + sk: Some(sk.clone()), + seed: None, + pk: None, + }) } fn sign_update(&mut self, msg_chunk: &[u8]) { @@ -1466,17 +1617,21 @@ impl< let mu = self.mu_builder.do_final(); if self.sk.is_none() && self.seed.is_none() { - return Err(SignatureError::GenericError("Somehow you managed to construct a streaming signer without a private key, impressive!")) + return Err(SignatureError::GenericError( + "Somehow you managed to construct a streaming signer without a private key, impressive!", + )); } - if output.len() < SIG_LEN { return Err(SignatureError::LengthError("Output buffer insufficient size to hold signature")) } - let output_sized: &mut [u8; SIG_LEN] = output[..SIG_LEN].as_mut().try_into().unwrap(); - if self.sk.is_some() { if self.signer_rnd.is_none() { - Self::sign_mu_out(&self.sk.unwrap(), &mu, output_sized) + Self::sign_mu_out(&self.sk.unwrap(), &mu, output) } else { - Self::sign_mu_deterministic_out(&self.sk.unwrap(), &mu, self.signer_rnd.unwrap(), output_sized) + Self::sign_mu_deterministic_out( + &self.sk.unwrap(), + &mu, + self.signer_rnd.unwrap(), + output, + ) } } else if self.seed.is_some() { let rnd = if self.signer_rnd.is_some() { @@ -1486,15 +1641,19 @@ impl< HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; rnd }; - Self::sign_mu_deterministic_from_seed_out(&self.seed.unwrap(), &mu, rnd, output_sized) - } else { unreachable!() } + Self::sign_mu_deterministic_from_seed_out(&self.seed.unwrap(), &mu, rnd, output) + } else { + unreachable!() + } } fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)?; - if sig.len() < SIG_LEN { return Err(SignatureError::LengthError("Signature value is not the correct length.")) } - if Self::verify_mu_internal(pk, &mu, &sig[..SIG_LEN].try_into().unwrap()) { + if sig.len() != SIG_LEN { + return Err(SignatureError::LengthError("Signature value is not the correct length.")); + } + if Self::verify_mu_internal(pk, &mu, &sig.try_into().unwrap()) { Ok(()) } else { Err(SignatureError::SignatureVerificationFailed) @@ -1502,15 +1661,14 @@ impl< } fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result { - Ok( - Self { - _phantom: Default::default(), - mu_builder: MuBuilder::do_init(&pk.compute_tr(), ctx)?, - signer_rnd: None, - sk: None, - seed: None, - pk: Some(pk.clone()) } - ) + Ok(Self { + _phantom: Default::default(), + mu_builder: MuBuilder::do_init(&pk.compute_tr(), ctx)?, + signer_rnd: None, + sk: None, + seed: None, + pk: Some(pk.clone()), + }) } fn verify_update(&mut self, msg_chunk: &[u8]) { @@ -1520,10 +1678,16 @@ impl< fn verify_final(self, sig: &[u8]) -> Result<(), SignatureError> { let mu = self.mu_builder.do_final(); - assert!(self.pk.is_some(), "Somehow you managed to construct a streaming verifier without a public key, impressive!"); + assert!( + self.pk.is_some(), + "Somehow you managed to construct a streaming verifier without a public key, impressive!" + ); - if sig.len() < SIG_LEN { return Err(SignatureError::LengthError("Signature value is not the correct length.")) } - if Self::verify_mu_internal(&self.pk.unwrap(), &mu, &sig[..SIG_LEN].try_into().unwrap()) { + if sig.len() != SIG_LEN { + return Err(SignatureError::LengthError("Signature value is not the correct length.")); + } + + if Self::verify_mu_internal(&self.pk.unwrap(), &mu, &sig.try_into().unwrap()) { Ok(()) } else { Err(SignatureError::SignatureVerificationFailed) @@ -1531,7 +1695,6 @@ impl< } } - /// Implements parts of Algorithm 2 and Line 6 of Algorithm 7 of FIPS 204. /// Provides a stateful version of [MLDSATrait::compute_mu_from_pk] and [MLDSATrait::compute_mu_from_tr] /// that supports streaming @@ -1548,7 +1711,11 @@ pub struct MuBuilder { impl MuBuilder { /// Algorithm 7 /// 6: πœ‡ ← H(BytesToBits(π‘‘π‘Ÿ)||𝑀′, 64) - pub fn compute_mu(tr: &[u8; 64],msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; 64], SignatureError> { + pub fn compute_mu( + tr: &[u8; 64], + msg: &[u8], + ctx: Option<&[u8]>, + ) -> Result<[u8; 64], SignatureError> { let mut mu_builder = MuBuilder::do_init(&tr, ctx)?; mu_builder.do_update(msg); let mu = mu_builder.do_final(); @@ -1559,7 +1726,10 @@ impl MuBuilder { /// This function requires the public key hash `tr`, which can be computed from the public key /// using [MLDSAPublicKeyTrait::compute_tr]. pub fn do_init(tr: &[u8; 64], ctx: Option<&[u8]>) -> Result { - let ctx = match ctx { Some(ctx) => ctx, None => &[] }; + let ctx = match ctx { + Some(ctx) => ctx, + None => &[], + }; // Algorithm 2 // 1: if |𝑐𝑑π‘₯| > 255 then diff --git a/crypto/mldsa_lowmemory/src/mldsa_keys.rs b/crypto/mldsa_lowmemory/src/mldsa_keys.rs index 66b70aa..81985bd 100644 --- a/crypto/mldsa_lowmemory/src/mldsa_keys.rs +++ b/crypto/mldsa_lowmemory/src/mldsa_keys.rs @@ -1,14 +1,30 @@ -use crate::aux_functions::{bit_pack_eta, bit_pack_t0, bitlen_eta, power_2_round, rej_bounded_poly, simple_bit_pack_t1, simple_bit_unpack_t1}; +use crate::aux_functions::{ + bit_pack_eta, bit_pack_t0, bitlen_eta, power_2_round, rej_bounded_poly, simple_bit_pack_t1, + simple_bit_unpack_t1, +}; +use crate::low_memory_helpers::{expandA_elem, s_unpack}; use crate::mldsa::{H, N, POLY_T0PACKED_LEN}; +use crate::mldsa::{ + MLDSA44_ETA, MLDSA44_FULL_SK_LEN, MLDSA44_GAMMA2, MLDSA44_LAMBDA, MLDSA44_PK_LEN, + MLDSA44_S1_PACKED_LEN, MLDSA44_S2_PACKED_LEN, MLDSA44_SK_LEN, MLDSA44_k, MLDSA44_l, +}; +use crate::mldsa::{ + MLDSA44_T1_PACKED_LEN, MLDSA65_T1_PACKED_LEN, MLDSA87_T1_PACKED_LEN, POLY_T1PACKED_LEN, +}; +use crate::mldsa::{ + MLDSA65_ETA, MLDSA65_FULL_SK_LEN, MLDSA65_GAMMA2, MLDSA65_LAMBDA, MLDSA65_PK_LEN, + MLDSA65_S1_PACKED_LEN, MLDSA65_S2_PACKED_LEN, MLDSA65_SK_LEN, MLDSA65_k, MLDSA65_l, +}; +use crate::mldsa::{ + MLDSA87_ETA, MLDSA87_FULL_SK_LEN, MLDSA87_GAMMA2, MLDSA87_LAMBDA, MLDSA87_PK_LEN, + MLDSA87_S1_PACKED_LEN, MLDSA87_S2_PACKED_LEN, MLDSA87_SK_LEN, MLDSA87_k, MLDSA87_l, +}; use crate::{ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME}; -use crate::mldsa::{MLDSA44_LAMBDA, MLDSA44_GAMMA2, MLDSA44_ETA, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_FULL_SK_LEN, MLDSA44_k, MLDSA44_l, MLDSA44_S1_PACKED_LEN, MLDSA44_S2_PACKED_LEN}; -use crate::mldsa::{MLDSA65_LAMBDA, MLDSA65_GAMMA2, MLDSA65_ETA, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_FULL_SK_LEN, MLDSA65_k, MLDSA65_l, MLDSA65_S1_PACKED_LEN, MLDSA65_S2_PACKED_LEN}; -use crate::mldsa::{MLDSA87_LAMBDA, MLDSA87_GAMMA2, MLDSA87_ETA, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_FULL_SK_LEN, MLDSA87_k, MLDSA87_l, MLDSA87_S1_PACKED_LEN, MLDSA87_S2_PACKED_LEN}; -use crate::mldsa::{POLY_T1PACKED_LEN, MLDSA44_T1_PACKED_LEN, MLDSA65_T1_PACKED_LEN, MLDSA87_T1_PACKED_LEN}; -use crate::low_memory_helpers::{expandA_elem, s_unpack}; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Secret, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, XOF}; +use bouncycastle_core::traits::{ + Secret, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, XOF, +}; use core::fmt; use core::fmt::{Debug, Display, Formatter}; @@ -17,21 +33,56 @@ use core::fmt::{Debug, Display, Formatter}; use crate::mldsa::MLDSATrait; use crate::polynomial::Polynomial; - /* Pub Types */ /// ML-DSA-44 Public Key pub type MLDSA44PublicKey = MLDSAPublicKey; /// ML-DSA-44 Private Key -pub type MLDSA44PrivateKey = MLDSASeedPrivateKey; +pub type MLDSA44PrivateKey = MLDSASeedPrivateKey< + MLDSA44_LAMBDA, + MLDSA44_GAMMA2, + MLDSA44_k, + MLDSA44_l, + MLDSA44_ETA, + MLDSA44_S1_PACKED_LEN, + MLDSA44_S2_PACKED_LEN, + MLDSA44_T1_PACKED_LEN, + MLDSA44_PK_LEN, + MLDSA44_SK_LEN, + MLDSA44_FULL_SK_LEN, +>; /// ML-DSA-65 Public Key pub type MLDSA65PublicKey = MLDSAPublicKey; /// ML-DSA-65 Private Key -pub type MLDSA65PrivateKey = MLDSASeedPrivateKey; +pub type MLDSA65PrivateKey = MLDSASeedPrivateKey< + MLDSA65_LAMBDA, + MLDSA65_GAMMA2, + MLDSA65_k, + MLDSA65_l, + MLDSA65_ETA, + MLDSA65_S1_PACKED_LEN, + MLDSA65_S2_PACKED_LEN, + MLDSA65_T1_PACKED_LEN, + MLDSA65_PK_LEN, + MLDSA65_SK_LEN, + MLDSA65_FULL_SK_LEN, +>; /// ML-DSA-87 Public Key pub type MLDSA87PublicKey = MLDSAPublicKey; /// ML-DSA-87 Private Key -pub type MLDSA87PrivateKey = MLDSASeedPrivateKey; +pub type MLDSA87PrivateKey = MLDSASeedPrivateKey< + MLDSA87_LAMBDA, + MLDSA87_GAMMA2, + MLDSA87_k, + MLDSA87_l, + MLDSA87_ETA, + MLDSA87_S1_PACKED_LEN, + MLDSA87_S2_PACKED_LEN, + MLDSA87_T1_PACKED_LEN, + MLDSA87_PK_LEN, + MLDSA87_SK_LEN, + MLDSA87_FULL_SK_LEN, +>; /// An ML-DSA public key. #[derive(Clone)] @@ -41,7 +92,9 @@ pub struct MLDSAPublicKey : SignaturePublicKey { +pub trait MLDSAPublicKeyTrait: + SignaturePublicKey +{ /// Algorithm 23 pkDecode(π‘π‘˜) /// Reverses the procedure pkEncode. /// Input: Public key π‘π‘˜ ∈ 𝔹32+32π‘˜(bitlen (π‘žβˆ’1)βˆ’π‘‘). @@ -57,7 +110,12 @@ pub trait MLDSAPublicKeyTrait [u8; 64]; } -pub(crate) trait MLDSAPublicKeyInternalTrait { +pub(crate) trait MLDSAPublicKeyInternalTrait< + const k: usize, + const T1_PACKED_LEN: usize, + const PK_LEN: usize, +> +{ /// Not exposing a constructor publicly because you should have to get an instance either by /// running a keygen, or by decoding an existing key. fn new(rho: [u8; 32], t1_packed: [u8; T1_PACKED_LEN]) -> Self; @@ -69,12 +127,11 @@ pub(crate) trait MLDSAPublicKeyInternalTrait Polynomial; } -impl MLDSAPublicKeyTrait for MLDSAPublicKey { +impl + MLDSAPublicKeyTrait for MLDSAPublicKey +{ fn pk_decode(pk: &[u8; PK_LEN]) -> Self { - Self { - rho: pk[..32].try_into().unwrap(), - t1_packed: pk[32..].try_into().unwrap() - } + Self { rho: pk[..32].try_into().unwrap(), t1_packed: pk[32..].try_into().unwrap() } } fn compute_tr(&self) -> [u8; 64] { @@ -85,19 +142,30 @@ impl MLDSAPubli } } -impl MLDSAPublicKeyInternalTrait for MLDSAPublicKey { +impl + MLDSAPublicKeyInternalTrait + for MLDSAPublicKey +{ fn new(rho: [u8; 32], t1_packed: [u8; T1_PACKED_LEN]) -> Self { Self { rho, t1_packed } } - fn rho(&self) -> &[u8; 32] { &self.rho } + fn rho(&self) -> &[u8; 32] { + &self.rho + } fn unpack_t1_row(&self, row: usize) -> Polynomial { - simple_bit_unpack_t1(&self.t1_packed[row * POLY_T1PACKED_LEN .. (row + 1) * POLY_T1PACKED_LEN].try_into().unwrap()) + simple_bit_unpack_t1( + &self.t1_packed[row * POLY_T1PACKED_LEN..(row + 1) * POLY_T1PACKED_LEN] + .try_into() + .unwrap(), + ) } } -impl SignaturePublicKey for MLDSAPublicKey { +impl SignaturePublicKey + for MLDSAPublicKey +{ /// Algorithm 22 pkEncode(𝜌, 𝐭1) /// Encodes a public key for ML-DSA into a byte string. /// Input:𝜌 ∈ 𝔹32, 𝐭1 ∈ π‘…π‘˜ with coefficients in [0, 2bitlen (π‘žβˆ’1)βˆ’π‘‘ βˆ’ 1]. @@ -122,15 +190,24 @@ impl Signature } fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != PK_LEN { return Err(SignatureError::DecodingError("Provided key bytes are the incorrect length")) } + if bytes.len() != PK_LEN { + return Err(SignatureError::DecodingError( + "Provided key bytes are the incorrect length", + )); + } let sized_bytes: [u8; PK_LEN] = bytes[..PK_LEN].try_into().unwrap(); Ok(Self::pk_decode(&sized_bytes)) } } -impl Eq for MLDSAPublicKey { } +impl Eq + for MLDSAPublicKey +{ +} -impl PartialEq for MLDSAPublicKey { +impl PartialEq + for MLDSAPublicKey +{ fn eq(&self, other: &Self) -> bool { let self_encoded = self.encode(); let other_encoded = other.encode(); @@ -138,7 +215,9 @@ impl PartialEq } } -impl fmt::Debug for MLDSAPublicKey { +impl fmt::Debug + for MLDSAPublicKey +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let alg = match k { 4 => ML_DSA_44_NAME, @@ -150,7 +229,9 @@ impl fmt::Debug } } -impl Display for MLDSAPublicKey { +impl Display + for MLDSAPublicKey +{ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let alg = match k { 4 => ML_DSA_44_NAME, @@ -162,8 +243,6 @@ impl Display fo } } - - /// General trait for all ML-DSA private keys types. pub trait MLDSAPrivateKeyTrait< const k: usize, @@ -174,44 +253,33 @@ pub trait MLDSAPrivateKeyTrait< const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, -> : SignaturePrivateKey { +>: SignaturePrivateKey +{ /// New from KeyMaterial. Can throw a SignatureError if the KeyMaterial does not contain sufficient entropy. fn from_keymaterial(seed: &KeyMaterial<32>) -> Result; /// Get a ref to the seed, if there is one stored with this private key - fn seed(&self) -> &KeyMaterial<32>; + fn seed(&self) -> Option<&KeyMaterial<32>>; /// Get a copy of the key hash `tr`. /// This is computationally intensive as it requires fully re-computing the public key (and then discarding it). /// It is highly recommended that if you already have a copy of the public key, get `tr` from that, /// or else compute tr once and store it. fn tr(&self) -> [u8; 64]; - /// Returns the full public key, and has the side-effect of setting the public key hash tr in this MLDSASeedSK object. fn derive_pk(&self) -> MLDSAPublicKey; - /// Algorithm 24 skEncode(𝜌, 𝐾, π‘‘π‘Ÿ, 𝐬1, 𝐬2, 𝐭0) - /// Encodes a secret key for ML-DSA into a byte string. - /// Input: 𝜌 ∈ 𝔹32, 𝐾 ∈ 𝔹32, π‘‘π‘Ÿ ∈ 𝔹64 , 𝐬1 ∈ 𝑅ℓ with coefficients in [βˆ’πœ‚, πœ‚], 𝐬2 ∈ π‘…π‘˜ with - /// coefficients in [βˆ’πœ‚, πœ‚], 𝐭0 ∈ π‘…π‘˜ with coefficients in [βˆ’2π‘‘βˆ’1 + 1, 2π‘‘βˆ’1]. - /// Output: Private key π‘ π‘˜ ∈ 𝔹32+32+64+32β‹…((π‘˜+β„“)β‹…bitlen (2πœ‚)+π‘‘π‘˜). - fn sk_encode_out(&self, out: &mut [u8; SK_LEN]) -> usize; /// This produces the full private key in the encoding specified in FIPS 204 Algorithm 24 skEncode() /// so that it is compatible with other implementations. /// /// Note that since this encoding does not include the seed, this is a one-way operation; /// after exporting in this encoding, it will be impossible to re-import it into a [MLDSASeedPrivateKey]. - fn encode_full_sk(&self) -> [u8; FULL_SK_LEN] { - let mut out = [0; FULL_SK_LEN]; - self.encode_full_sk_out(&mut out); - - out - } + fn encode_full_sk(&self) -> [u8; FULL_SK_LEN]; /// This produces the full private key in the encoding specified in FIPS 204 Algorithm 24 skEncode() /// so that it is compatible with other implementations. /// /// Note that since this encoding does not include the seed, this is a one-way operation; /// after exporting in this encoding, it will be impossible to re-import it into a [MLDSASeedPrivateKey]. - fn encode_full_sk_out(&self, out: &mut [u8; FULL_SK_LEN]); + fn encode_full_sk_out(&self, out: &mut [u8; FULL_SK_LEN]) -> usize; /// Algorithm 25 skDecode(π‘ π‘˜) /// Reverses the procedure skEncode. /// Input: Private key π‘ π‘˜ ∈ 𝔹32+32+64+32β‹…((β„“+π‘˜)β‹…bitlen (2πœ‚)+π‘‘π‘˜). @@ -244,7 +312,6 @@ pub struct MLDSASeedPrivateKey< K: [u8; 32], } - impl< const LAMBDA: i32, const GAMMA2: i32, @@ -257,7 +324,21 @@ impl< const SK_LEN: usize, const PK_LEN: usize, const FULL_SK_LEN: usize, -> Drop for MLDSASeedPrivateKey { +> Drop + for MLDSASeedPrivateKey< + LAMBDA, + GAMMA2, + k, + l, + eta, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > +{ fn drop(&mut self) { // seed is a KeyMaterialSized which will zeroize itself self.rho.fill(0u8); @@ -278,7 +359,22 @@ impl< const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, -> Secret for MLDSASeedPrivateKey {} +> Secret + for MLDSASeedPrivateKey< + LAMBDA, + GAMMA2, + k, + l, + eta, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > +{ +} impl< const LAMBDA: i32, @@ -292,7 +388,21 @@ impl< const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, -> Debug for MLDSASeedPrivateKey { +> Debug + for MLDSASeedPrivateKey< + LAMBDA, + GAMMA2, + k, + l, + eta, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > +{ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let alg = match k { 4 => ML_DSA_44_NAME, @@ -300,12 +410,7 @@ impl< 8 => ML_DSA_87_NAME, _ => panic!("Unsupported key length"), }; - write!( - f, - "MLDSASeedPrivateKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", - alg, - self.tr(), - ) + write!(f, "MLDSASeedPrivateKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", alg, self.tr(),) } } @@ -321,7 +426,21 @@ impl< const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, -> Display for MLDSASeedPrivateKey { +> Display + for MLDSASeedPrivateKey< + LAMBDA, + GAMMA2, + k, + l, + eta, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > +{ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let alg = match k { 4 => ML_DSA_44_NAME, @@ -329,12 +448,7 @@ impl< 8 => ML_DSA_87_NAME, _ => panic!("Unsupported key length"), }; - write!( - f, - "MLDSASeedPrivateKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", - alg, - self.tr(), - ) + write!(f, "MLDSASeedPrivateKey {{ alg: {}, pub_key_hash (tr): {:x?} }}", alg, self.tr(),) } } @@ -350,13 +464,27 @@ impl< const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, -> MLDSASeedPrivateKey { +> + MLDSASeedPrivateKey< + LAMBDA, + GAMMA2, + k, + l, + eta, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > +{ /// Create a new MLDSASeedPrivateKey from a 32-byte KeyMaterial. /// Seed SecurityStrength must match algorithm security strength: 128-bit (ML-DSA-44), 192-bit (ML-DSA-65), or 256-bit (ML-DSA-87), /// otherwise it throws a SignatureError::KeyGenError("SecurityStrength". pub fn new(seed: &KeyMaterial<32>) -> Result { if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::BytesFullEntropy) - || seed.key_len() != 32 + || seed.key_len() != 32 { return Err(SignatureError::KeyGenError( "Seed must be 32 bytes and KeyType::Seed or KeyType::BytesFullEntropy.", @@ -368,7 +496,7 @@ impl< } let (rho, rho_prime, K) = Self::compute_rhos_and_K(&seed); - Ok(Self { seed: seed.clone(), rho, rho_prime, K}) + Ok(Self { seed: seed.clone(), rho, rho_prime, K }) } fn compute_rhos_and_K(seed: &KeyMaterial<32>) -> ([u8; 32], [u8; 64], [u8; 32]) { @@ -393,12 +521,7 @@ impl< (rho, rho_prime, K) } - fn compute_t_row( - &self, - idx: usize, - s1_packed: &[u8], - s2_packed: &[u8], - ) -> Polynomial { + fn compute_t_row(&self, idx: usize, s1_packed: &[u8], s2_packed: &[u8]) -> Polynomial { debug_assert!(idx < k); // [Optimization Note]: @@ -417,13 +540,6 @@ impl< // s1 = self.compute_s1_row(col); let mut s1_hat = s_unpack::(s1_packed, col); s1_hat.ntt(); - // let tmp = polynomial::multiply_ntt( - // // [Optimization Note]: - // // this is reconstructing a row of the public matrix A_hat, - // // which nobody is proposing to keep in memory. - // &rej_ntt_poly(&self.rho, &[col as u8, idx as u8]), - // &s1_hat, - // ); let mut A_elem = expandA_elem(&self.rho, idx, col); A_elem.multiply_ntt(&s1_hat); t_hat_i.add_ntt(&A_elem); @@ -456,7 +572,21 @@ impl< const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, -> SignaturePrivateKey for MLDSASeedPrivateKey { +> SignaturePrivateKey + for MLDSASeedPrivateKey< + LAMBDA, + GAMMA2, + k, + l, + eta, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > +{ /// Encodes the private key seed. fn encode(&self) -> [u8; SK_LEN] { debug_assert_eq!(SK_LEN, /* seed */ 32); @@ -465,6 +595,8 @@ impl< } fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { + out.fill(0); + out.copy_from_slice(self.seed.ref_to_bytes()); debug_assert_eq!(self.seed.ref_to_bytes().len(), SK_LEN); @@ -497,13 +629,38 @@ impl< const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, -> MLDSAPrivateKeyTrait -for MLDSASeedPrivateKey { +> + MLDSAPrivateKeyTrait< + k, + l, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > + for MLDSASeedPrivateKey< + LAMBDA, + GAMMA2, + k, + l, + eta, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > +{ fn from_keymaterial(seed: &KeyMaterial<32>) -> Result { Self::new(seed) } - fn seed(&self) -> &KeyMaterial<32> { &self.seed } + fn seed(&self) -> Option<&KeyMaterial<32>> { + Some(&self.seed) + } fn tr(&self) -> [u8; 64] { let pk: MLDSAPublicKey = self.derive_pk(); @@ -520,26 +677,20 @@ for MLDSASeedPrivateKey::new(self.rho.clone(), t1_packed) } - fn sk_encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { - out.copy_from_slice(self.seed.ref_to_bytes()); - - SK_LEN - } fn encode_full_sk(&self) -> [u8; FULL_SK_LEN] { let mut out = [0; FULL_SK_LEN]; - self.encode_full_sk_out(&mut out); + _ = self.encode_full_sk_out(&mut out); out } - fn encode_full_sk_out(&self, out: &mut [u8; FULL_SK_LEN]) { + fn encode_full_sk_out(&self, out: &mut [u8; FULL_SK_LEN]) -> usize { out.fill(0); // Algorithm 24 skEncode(𝜌, 𝐾, π‘‘π‘Ÿ, 𝐬1, 𝐬2, 𝐭0) @@ -556,29 +707,30 @@ for MLDSASeedPrivateKey Self { Self::from_bytes(sk).unwrap() } @@ -594,7 +746,7 @@ pub(crate) trait MLDSAPrivateKeyInternalTrait< const S2_PACKED_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, -> : Sized +>: Sized { fn rho(&self) -> &[u8; 32]; fn K(&self) -> &[u8; 32]; @@ -624,8 +776,32 @@ impl< const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, -> MLDSAPrivateKeyInternalTrait -for MLDSASeedPrivateKey { +> + MLDSAPrivateKeyInternalTrait< + LAMBDA, + GAMMA2, + k, + l, + eta, + S1_PACKED_LEN, + S2_PACKED_LEN, + PK_LEN, + SK_LEN, + > + for MLDSASeedPrivateKey< + LAMBDA, + GAMMA2, + k, + l, + eta, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > +{ fn rho(&self) -> &[u8; 32] { &self.rho } @@ -634,10 +810,7 @@ for MLDSASeedPrivateKey Polynomial { + fn compute_s1_row(&self, idx: usize) -> Polynomial { debug_assert!(idx < l); rej_bounded_poly::(&self.rho_prime, &(idx as u16).to_le_bytes()) } @@ -646,15 +819,15 @@ for MLDSASeedPrivateKey(&s1_i, &mut s1_packed[idx * bitlen_eta(eta)..(idx + 1) * bitlen_eta(eta)]); + bit_pack_eta::( + &s1_i, + &mut s1_packed[idx * bitlen_eta(eta)..(idx + 1) * bitlen_eta(eta)], + ); } s1_packed } - fn compute_s2_row( - &self, - idx: usize, - ) -> Polynomial { + fn compute_s2_row(&self, idx: usize) -> Polynomial { debug_assert!(idx < k); rej_bounded_poly::(&self.rho_prime, &((idx + l) as u16).to_le_bytes()) } @@ -663,17 +836,15 @@ for MLDSASeedPrivateKey(&s2_i, &mut s2_packed[idx * bitlen_eta(eta)..(idx + 1) * bitlen_eta(eta)]); + bit_pack_eta::( + &s2_i, + &mut s2_packed[idx * bitlen_eta(eta)..(idx + 1) * bitlen_eta(eta)], + ); } s2_packed } - fn compute_t0_row( - &self, - idx: usize, - s1_packed: &[u8], - s2_packed: &[u8], - ) -> Polynomial { + fn compute_t0_row(&self, idx: usize, s1_packed: &[u8], s2_packed: &[u8]) -> Polynomial { let mut t0 = self.compute_t_row(idx, s1_packed, s2_packed); for j in 0..N { (_, t0[j]) = power_2_round(t0[j]); @@ -682,12 +853,7 @@ for MLDSASeedPrivateKey Polynomial { + fn compute_t1_row(&self, idx: usize, s1_packed: &[u8], s2_packed: &[u8]) -> Polynomial { let mut t1 = self.compute_t_row(idx, s1_packed, s2_packed); for j in 0..N { (t1[j], _) = power_2_round(t1[j]); @@ -696,4 +862,3 @@ for MLDSASeedPrivateKey for Polynomial { @@ -32,7 +34,7 @@ impl IndexMut for Polynomial { impl Polynomial { /// Create a new polynomial with all coefficients set to zero. pub const fn new() -> Self { - Self{ coeffs: [0i32; N] } + Self { coeffs: [0i32; N] } } pub(crate) fn conditional_add_q(&mut self) { @@ -80,20 +82,24 @@ impl Polynomial { } } - pub(crate) fn check_norm(&self, bound: i32) -> bool { + pub(crate) fn check_norm(&self) -> bool { // Fine that this is not constant-time (returns true early) because it is used in a rejection loop. // IE the early quit here leads to rejection and continuing to the top of the rejection loop, or failing the signature validation. // So the i32 that we just checked in a non-constant-time manner is about to get thrown away. - if bound > (q - 1) / 8 { - return true; - } + + // Note: this formulation of the check_norm function usually requires this bounds check + // if bound > (q - 1) / 8 { + // return true; + // } + // but since BOUND is a constant here, we'll just do a debug_assert to make sure the value is what we expect. + debug_assert!(BOUND <= (q - 1) / 8); let mut t: i32; for x in self.coeffs.iter() { t = *x >> 31; t = *x - (t & (2 * *x)); - if t >= bound { + if t >= BOUND { return true; } } @@ -128,29 +134,28 @@ impl Polynomial { // then it's free to optimize all of the computation into CPU registers and skip, in this case, // several hundred physical memory writes. // So while it looks odd to use a scope variable in a low-memory implementation, it's way faster - // and I'm not convinced that it uses any more physical memory. + // and I'm not convinced that it uses any more physical memory. let mut r = [0u8; POLY_W1_PACKED_LEN]; match POLY_W1_PACKED_LEN { MLDSA44_POLY_W1_PACKED_LEN => { - for i in 0..N/4 { - r[3 * i] = - ((self[4 * i]) as u8) | ((self[4 * i + 1] << 6) as u8); - r[3 * i + 1] = - ((self[4 * i + 1] >> 2) as u8) | ((self[4 * i + 2] << 4) as u8); - r[3 * i + 2] = - ((self[4 * i + 2] >> 4) as u8) | ((self[4 * i + 3] << 2) as u8); + for i in 0..N / 4 { + r[3 * i] = ((self[4 * i]) as u8) | ((self[4 * i + 1] << 6) as u8); + r[3 * i + 1] = ((self[4 * i + 1] >> 2) as u8) | ((self[4 * i + 2] << 4) as u8); + r[3 * i + 2] = ((self[4 * i + 2] >> 4) as u8) | ((self[4 * i + 3] << 2) as u8); } - }, + } // ML-DSA65 and 87 share a POLY_W1_PACKED_LEN value MLDSA65_POLY_W1_PACKED_LEN => { - for i in 0..N/2 { + for i in 0..N / 2 { r[i] = ((self[2 * i]) | (self[2 * i + 1] << 4)) as u8; } - }, - _ => { unreachable!() } + } + _ => { + unreachable!() + } } - + r } @@ -234,11 +239,7 @@ impl Polynomial { } } - - pub(crate) fn use_hint( - &mut self, - h: &Polynomial, - ) { + pub(crate) fn use_hint(&mut self, h: &Polynomial) { for i in 0..N { self[i] = use_hint::(self[i], h[i]); } @@ -270,7 +271,7 @@ impl Display for Polynomial { /// of expressions of the form c = a * b (mod q). /// The output is not necessarily less than q in absolute value, but it is less than 2q in absolute value pub(crate) fn montgomery_reduce(a: i64) -> i32 { - debug_assert!(a > - ((q as i64) <<31) && a < ((q as i64) <<31)); + debug_assert!(a > -((q as i64) << 31) && a < ((q as i64) << 31)); // 2: 𝑑 ← ((π‘Ž mod 2^32) β‹… QINV) mod 2^32 let t: i32 = (a as i32).wrapping_mul(q_inv); @@ -279,7 +280,6 @@ pub(crate) fn montgomery_reduce(a: i64) -> i32 { ((a - ((t as i64) * (q as i64))) >> 32) as i32 } - pub(crate) fn conditional_add_q(a: i32) -> i32 { a + ((a >> 31) & q) } @@ -287,16 +287,16 @@ pub(crate) fn conditional_add_q(a: i32) -> i32 { #[test] /// These are the results it's giving; I'm not sure if these are "correct" or not. fn test_conditional_add_q() { - assert_eq!(conditional_add_q(-q -1), -1); + assert_eq!(conditional_add_q(-q - 1), -1); assert_eq!(conditional_add_q(-q), 0); - assert_eq!(conditional_add_q(-q -2), -2); - assert_eq!(conditional_add_q(-q +1), 1); - assert_eq!(conditional_add_q(-1), q-1); + assert_eq!(conditional_add_q(-q - 2), -2); + assert_eq!(conditional_add_q(-q + 1), 1); + assert_eq!(conditional_add_q(-1), q - 1); assert_eq!(conditional_add_q(0), 0); assert_eq!(conditional_add_q(1), 1); - assert_eq!(conditional_add_q(q -1), q-1); + assert_eq!(conditional_add_q(q - 1), q - 1); assert_eq!(conditional_add_q(q), q); - assert_eq!(conditional_add_q(q +1), q+1); + assert_eq!(conditional_add_q(q + 1), q + 1); } /// Constants for NTT @@ -327,4 +327,4 @@ const ZETAS: [i32; 256] = [ 472078, -426683, 1723600, -1803090, 1910376, -1667432, -1104333, -260646, -3833893, -2939036, -2235985, -420899, -2286327, 183443, -976891, 1612842, -3545687, -554416, 3919660, -48306, -1362209, 3937738, 1400424, -846154, 1976782, -]; \ No newline at end of file +]; diff --git a/crypto/mldsa_lowmemory/tests/bc_test_data.rs b/crypto/mldsa_lowmemory/tests/bc_test_data.rs index d9be448..9d98971 100644 --- a/crypto/mldsa_lowmemory/tests/bc_test_data.rs +++ b/crypto/mldsa_lowmemory/tests/bc_test_data.rs @@ -8,629 +8,876 @@ use bouncycastle_sha3::SHAKE256; #[allow(unused_imports)] #[allow(dead_code)] - #[cfg(test)] -// mod bc_test_data { -// use std::{fs}; -// use bouncycastle_core::errors::SignatureError; -// use bouncycastle_hex as hex; -// use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial256, KeyType}; -// use bouncycastle_core::traits::{Hash, PHSignature, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey}; -// use bouncycastle_mldsa_lowmemory::{HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA512, HashMLDSA87_with_SHA512, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyTrait, MLDSATrait, MLDSA44, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA65, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA87, MLDSA87_PK_LEN, MLDSA87_SK_LEN}; -// use bouncycastle_sha2::SHA512; -// use crate::BustedMuBuilder; -// -// const TEST_DATA_PATH: &str = "../../../bc-test-data/pqc/crypto/mldsa"; -// -// #[test] -// #[allow(non_snake_case)] -// fn ML_DSA_keyGen() { -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-keyGen.txt").unwrap(); -// let test_cases = KeyGenTestCase::parse(contents); -// -// for test_case in test_cases { -// test_case.run(); -// } -// } -// -// #[derive(Clone)] -// struct KeyGenTestCase { -// vs_id: u32, -// algorithm: String, -// mode: String, -// revision: String, -// is_sample: bool, -// tg_id: u32, -// test_type: String, -// parameter_set: String, -// tc_id: u32, -// seed: String, -// pk: String, -// sk: String, -// } -// -// impl KeyGenTestCase { -// fn new() -> Self { -// Self{ vs_id: 0, algorithm: String::new(), mode: String::new(), revision: String::new(), is_sample: false, tg_id: 0, test_type: String::new(), parameter_set: String::new(), tc_id: 0, seed: String::new(), pk: String::new(), sk: String::new()} -// } -// -// fn is_full(&self) -> bool { -// !self.algorithm.is_empty() -// } -// -// fn parse(data: String) -> Vec { -// let mut test_cases = Vec::::new(); -// let mut test_case = KeyGenTestCase::new(); -// for line in data.lines() { -// let (tag, value) = match line.split_once(" = ") { -// Some(pair) => pair, -// None => { -// if test_case.is_full() { test_cases.push(test_case.clone()); } -// continue; -// } -// }; -// -// match tag { -// "vsId" => test_case.vs_id = value.parse().unwrap(), -// "algorithm" => test_case.algorithm = value.to_string(), -// "mode" => test_case.mode = value.to_string(), -// "revision" => test_case.revision = value.to_string(), -// "isSample" => test_case.is_sample = value.parse().unwrap(), -// "tgId" => test_case.tg_id = value.parse().unwrap(), -// "testType" => test_case.test_type = value.to_string(), -// "parameterSet" => test_case.parameter_set = value.to_string(), -// "tcId" => test_case.tc_id = value.parse().unwrap(), -// "seed" => test_case.seed = value.to_string(), -// "pk" => test_case.pk = value.to_string(), -// "sk" => test_case.sk = value.to_string(), -// val => panic!("Invalid tag: {}", val), -// } -// } -// -// test_cases -// } -// -// fn run(&self) { -// assert_eq!(self.mode, "keyGen"); -// -// let mut seed = KeyMaterial256::from_bytes_as_type( -// &hex::decode(&self.seed).unwrap(), -// KeyType::Seed, -// ).unwrap(); -// // for the purposes of the test cases, accept an all-zero seed -// seed.allow_hazardous_operations(); -// seed.set_key_type(KeyType::Seed).unwrap(); -// seed.set_security_strength(SecurityStrength::_256bit).unwrap(); -// seed.drop_hazardous_operations(); -// -// match self.parameter_set.as_str() { -// "ML-DSA-44" => { -// let (pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLDSA44_PK_LEN] = hex::decode(&self.pk).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLDSA44_SK_LEN] = hex::decode(&self.seed).unwrap().try_into().unwrap(); -// assert_eq!(sk.encode(), sk_sized); -// }, -// "ML-DSA-65" => { -// let (pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLDSA65_PK_LEN] = hex::decode(&self.pk).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLDSA65_SK_LEN] = hex::decode(&self.seed).unwrap().try_into().unwrap(); -// assert_eq!(sk.encode(), sk_sized); -// }, -// "ML-DSA-87" => { -// let (pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLDSA87_PK_LEN] = hex::decode(&self.pk).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLDSA87_SK_LEN] = hex::decode(&self.seed).unwrap().try_into().unwrap(); -// assert_eq!(sk.encode(), sk_sized); -// }, -// val => panic!("Invalid parameter set: {}", val), -// -// } -// } -// } -// -// // commented out because the bc test vectors are missing private key seeds, so I can't use them. -// // #[test] -// #[allow(non_snake_case)] -// fn ML_DSA_sigGen() { -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-sigGen.txt").unwrap(); -// let test_cases = SigGenTestCase::parse(contents); -// -// let num_tests = test_cases.len(); -// for test_case in test_cases { -// test_case.run(); -// } -// -// println!("SUCCESS! ML-DSA-sigGen test cases passed: {}!", num_tests); -// } -// -// #[derive(Clone)] -// struct SigGenTestCase { -// vs_id: u32, -// algorithm: String, -// mode: String, -// revision: String, -// is_sample: bool, -// tg_id: u32, -// test_type: String, -// parameter_set: String, -// deterministic: bool, -// tc_id: u32, -// sk: String, -// message: String, -// rnd: String, -// signature: String, -// } -// -// impl SigGenTestCase { -// fn new() -> Self { -// Self{ vs_id: 0, algorithm: String::new(), mode: String::new(), revision: String::new(), is_sample: false, tg_id: 0, test_type: String::new(), parameter_set: String::new(), deterministic: false, tc_id: 0, sk: String::new(), message: String::new(), rnd: String::new(), signature: String::new()} -// } -// -// fn is_full(&self) -> bool { -// !self.algorithm.is_empty() -// } -// -// fn parse(data: String) -> Vec { -// let mut test_cases = Vec::::new(); -// let mut test_case = SigGenTestCase::new(); -// for line in data.lines() { -// let (tag, value) = match line.split_once(" = ") { -// Some(pair) => pair, -// None => { -// if test_case.is_full(){ test_cases.push(test_case.clone()); } -// continue; -// } -// }; -// -// match tag { -// "vsId" => test_case.vs_id = value.parse().unwrap(), -// "algorithm" => test_case.algorithm = value.to_string(), -// "mode" => test_case.mode = value.to_string(), -// "revision" => test_case.revision = value.to_string(), -// "isSample" => test_case.is_sample = value.parse().unwrap(), -// "tgId" => test_case.tg_id = value.parse().unwrap(), -// "testType" => test_case.test_type = value.to_string(), -// "parameterSet" => test_case.parameter_set = value.to_string(), -// "deterministic" => test_case.deterministic = value.parse().unwrap(), -// "tcId" => test_case.tc_id = value.parse().unwrap(), -// "sk" => test_case.sk = value.to_string(), -// "message" => test_case.message = value.to_string(), -// "rnd" => test_case.rnd = value.to_string(), -// "signature" => test_case.signature = value.to_string(), -// val => panic!("Invalid tag: {}", val), -// } -// } -// -// test_cases -// } -// -// fn run(&self) { -// assert_eq!(self.mode, "sigGen"); -// -// let rnd = if self.deterministic{ [0u8; 32] } else { hex::decode(&self.rnd).unwrap().as_slice().try_into().unwrap() }; -// -// match self.parameter_set.as_str() { -// "ML-DSA-44" => { -// let sk = MLDSA44PrivateKey::from_bytes(&hex::decode(&self.sk).unwrap()).unwrap(); -// -// // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() -// // so need to manually compute mu -// // let mu = MLDSA44::compute_mu_from_tr( -// // &hex::decode(&self.message).unwrap(), -// // None, -// // sk.tr(), -// // ).unwrap(); -// let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); -// mb.do_update(&hex::decode(&self.message).unwrap()); -// let mu = mb.do_final(); -// -// let sig = MLDSA44::sign_mu_deterministic(&sk, &mu, rnd).unwrap(); -// assert_eq!(&sig, &*hex::decode(&self.signature).unwrap(), "ML-DSA-sigGen params: {}, vsId: {}, tgId: {}, tcId: {}", self.parameter_set, self.vs_id, self.tg_id, self.tc_id); -// }, -// "ML-DSA-65" => { -// let sk = MLDSA65PrivateKey::from_bytes(&hex::decode(&self.sk).unwrap()).unwrap(); -// -// // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() -// // so need to manually compute mu -// // let mu = MLDSA65::compute_mu_from_tr( -// // &hex::decode(&self.message).unwrap(), -// // None, -// // sk.tr(), -// // ).unwrap(); -// let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); -// mb.do_update(&hex::decode(&self.message).unwrap()); -// let mu = mb.do_final(); -// -// let sig = MLDSA65::sign_mu_deterministic(&sk, &mu, rnd).unwrap(); -// assert_eq!(&sig, &*hex::decode(&self.signature).unwrap()); -// }, -// "ML-DSA-87" => { -// let sk = MLDSA87PrivateKey::from_bytes(&hex::decode(&self.sk).unwrap()).unwrap(); -// -// // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() -// // so need to manually compute mu -// // let mu = MLDSA87::compute_mu_from_tr( -// // &hex::decode(&self.message).unwrap(), -// // None, -// // sk.tr(), -// // ).unwrap(); -// let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); -// mb.do_update(&hex::decode(&self.message).unwrap()); -// let mu = mb.do_final(); -// -// let sig = MLDSA87::sign_mu_deterministic(&sk, &mu, rnd).unwrap(); -// assert_eq!(&sig, &*hex::decode(&self.signature).unwrap()); -// }, -// val => panic!("Invalid parameter set: {}", val), -// -// } -// } -// } -// -// // This is also against the non-compliant mu that doesn't have a ctx, which I don't have an easy way to test -// // #[test] -// #[allow(non_snake_case)] -// fn ML_DSA_sigVer() { -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-sigVer.txt").unwrap(); -// let test_cases = SigVerTestCase::parse(contents); -// -// for test_case in test_cases { -// test_case.run(); -// } -// } -// -// #[derive(Clone)] -// struct SigVerTestCase { -// vs_id: u32, -// algorithm: String, -// mode: String, -// revision: String, -// is_sample: bool, -// tg_id: u32, -// test_type: String, -// parameter_set: String, -// pk: String, -// tc_id: u32, -// message: String, -// signature: String, -// test_passed: bool, -// } -// -// impl SigVerTestCase { -// fn new() -> Self { -// Self{ vs_id: 0, algorithm: String::new(), mode: String::new(), revision: String::new(), is_sample: false, tg_id: 0, test_type: String::new(), parameter_set: String::new(), tc_id: 0, pk: String::new(), message: String::new(), signature: String::new(), test_passed: false} -// } -// -// fn is_full(&self) -> bool { -// !self.algorithm.is_empty() -// } -// -// fn parse(data: String) -> Vec { -// let mut test_cases = Vec::::new(); -// let mut test_case = SigVerTestCase::new(); -// for line in data.lines() { -// let (tag, value) = match line.split_once(" = ") { -// Some(pair) => pair, -// None => { -// if test_case.is_full() { test_cases.push(test_case.clone()); } -// continue; -// } -// }; -// -// match tag { -// "vsId" => test_case.vs_id = value.parse().unwrap(), -// "algorithm" => test_case.algorithm = value.to_string(), -// "mode" => test_case.mode = value.to_string(), -// "revision" => test_case.revision = value.to_string(), -// "isSample" => test_case.is_sample = value.parse().unwrap(), -// "tgId" => test_case.tg_id = value.parse().unwrap(), -// "testType" => test_case.test_type = value.to_string(), -// "parameterSet" => test_case.parameter_set = value.to_string(), -// "pk" => test_case.pk = value.to_string(), -// "tcId" => test_case.tc_id = value.parse().unwrap(), -// "message" => test_case.message = value.to_string(), -// "signature" => test_case.signature = value.to_string(), -// "testPassed" => test_case.test_passed = value.parse().unwrap(), -// val => panic!("Invalid tag: {}", val), -// } -// } -// -// test_cases -// } -// -// fn run(&self) { -// assert_eq!(self.mode, "sigVer"); -// -// match self.parameter_set.as_str() { -// "ML-DSA-44" => { -// let pk = MLDSA44PublicKey::from_bytes(&hex::decode(&self.pk).unwrap()).unwrap(); -// -// match MLDSA44::verify(&pk, &hex::decode(&self.message).unwrap(), None, &hex::decode(&self.signature).unwrap()) { -// Ok(()) => if !self.test_passed { panic!("Verification succeeded when it shouldn't have!") }, -// Err(SignatureError::SignatureVerificationFailed) => { if self.test_passed { panic!("Verification failed when it shouldn't have! vsId: {}, tgId: {}, tcId: {}", self.vs_id, self.tg_id, self.tc_id) } }, -// _ => panic!("An unexpected error occurred") -// } -// }, -// "ML-DSA-65" => { -// let pk = MLDSA65PublicKey::from_bytes(&hex::decode(&self.pk).unwrap()).unwrap(); -// -// match MLDSA65::verify(&pk, &hex::decode(&self.message).unwrap(), None, &hex::decode(&self.signature).unwrap()) { -// Ok(()) => if self.test_passed { /* good */ } else { panic!("Verification succeeded when it shouldn't have!") }, -// Err(SignatureError::SignatureVerificationFailed) => { if !self.test_passed {} else { panic!("Verification failed when it should have!") } }, -// _ => panic!("An unexpected error occurred") -// } -// }, -// "ML-DSA-87" => { -// let pk = MLDSA87PublicKey::from_bytes(&hex::decode(&self.pk).unwrap()).unwrap(); -// -// match MLDSA87::verify(&pk, &hex::decode(&self.message).unwrap(), None, &hex::decode(&self.signature).unwrap()) { -// Ok(()) => if self.test_passed { /* good */ } else { panic!("Verification succeeded when it shouldn't have!") }, -// Err(SignatureError::SignatureVerificationFailed) => { if !self.test_passed {} else { panic!("Verification failed when it should have!") } }, -// _ => panic!("An unexpected error occurred") -// } -// }, -// val => panic!("Invalid parameter set: {}", val), -// -// } -// } -// } -// -// // not working, not totally sure why -// // #[test] -// #[allow(non_snake_case)] -// fn ML_DSA_rsp() { -// // MLDsa44 -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa44.rsp").unwrap(); -// let test_cases = MldsaRspTestCase::::parse(contents); -// for test_case in test_cases { -// test_case.run("MLDsa44"); -// } -// -// // MLDsa65 -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa65.rsp").unwrap(); -// let test_cases = MldsaRspTestCase::::parse(contents); -// for test_case in test_cases { -// test_case.run("MLDsa65"); -// } -// -// -// // MLDsa87 -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa87.rsp").unwrap(); -// let test_cases = MldsaRspTestCase::::parse(contents); -// for test_case in test_cases { -// test_case.run("MLDsa87"); -// } -// -// -// // MLDsa44 -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa44sha512.rsp").unwrap(); -// let test_cases = MldsaRspTestCase::::parse(contents); -// for test_case in test_cases { -// test_case.run("MLDsa44"); -// } -// -// // MLDsa65 -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa65sha512.rsp").unwrap(); -// let test_cases = MldsaRspTestCase::::parse(contents); -// for test_case in test_cases { -// test_case.run("MlDsa65"); -// } -// -// -// // MLDsa87 -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa87sha512.rsp").unwrap(); -// let test_cases = MldsaRspTestCase::::parse(contents); -// for test_case in test_cases { -// test_case.run("MlDsa87"); -// } -// } -// -// #[derive(Clone)] -// struct MldsaRspTestCase { -// count: u32, -// seed: String, -// mlen: u32, -// msg: String, -// pk: String, -// sk: String, -// smlen: u32, -// sm: String, -// message_hash: String, -// message_prime: String, -// context: String, -// } -// -// impl MldsaRspTestCase { -// fn new() -> Self { -// Self{ count: 0, seed: String::new(), mlen: 0, msg: String::new(), pk: String::new(), sk: String::new(), smlen: 0, sm: String::new(), message_hash: String::new(), message_prime: String::new(), context: String::new()} -// } -// -// fn is_full(&self) -> bool { -// !self.seed.is_empty() -// } -// -// fn parse(data: String) -> Vec> { -// let mut test_cases = Vec::<>::new(); -// let mut test_case = MldsaRspTestCase::new(); -// for line in data.lines() { -// let (tag, value) = match line.split_once(" = ") { -// Some(pair) => pair, -// None => { -// if test_case.is_full() { test_cases.push(test_case.clone()); } -// continue; -// } -// }; -// -// match tag { -// "count" => test_case.count = value.parse().unwrap(), -// "seed" => test_case.seed = value.to_string(), -// "mlen" => test_case.mlen = value.parse().unwrap(), -// "msg" => test_case.msg = value.to_string(), -// "pk" => test_case.pk = value.to_string(), -// "sk" => test_case.sk = value.to_string(), -// "smlen" => test_case.smlen = value.parse().unwrap(), -// "sm" => test_case.sm = value.to_string(), -// "message_hash" => test_case.message_hash = value.to_string(), -// "message_prime" => test_case.message_prime = value.to_string(), -// "context" => { -// test_case.context = value.to_string(); -// if test_case.context == "zero_length" || test_case.context == "none" { -// test_case.context = String::new(); -// } -// }, -// val => panic!("Invalid tag: {}", val), -// } -// } -// -// test_cases -// } -// -// fn run(&self, parameter_set: &str) { -// match parameter_set { -// "MLDsa44" => { -// let mut seed = KeyMaterial256::from_bytes_as_type( -// &hex::decode(&self.seed).unwrap(), -// KeyType::Seed, -// ).unwrap(); -// // for the purposes of the test cases, accept an all-zero seed -// seed.allow_hazardous_operations(); -// seed.set_key_type(KeyType::Seed).unwrap(); -// seed.set_security_strength(SecurityStrength::_256bit).unwrap(); -// seed.drop_hazardous_operations(); -// -// -// let (pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLDSA44_PK_LEN] = hex::decode(&self.pk).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLDSA44_SK_LEN] = hex::decode(&self.sk).unwrap().try_into().unwrap(); -// assert_eq!(sk.encode(), sk_sized); -// -// if IS_HASH_MLDSA { -// // we're only testing SHA512 -// let ph: [u8; 64] = SHA512::new().hash(&hex::decode(&self.msg).unwrap()).as_slice().try_into().unwrap(); -// assert_eq!(ph, &*hex::decode(&self.message_hash).unwrap()); -// -// let sig = HashMLDSA44_with_SHA512::sign_ph_deterministic(&sk, Some(&*hex::decode(&self.context).unwrap()), &ph, [0u8; 32]).unwrap(); -// assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); -// -// HashMLDSA44_with_SHA512::verify(&pk, -// &*hex::decode(&self.msg).unwrap(), -// Some(&*hex::decode(&self.context).unwrap()), -// &sig).expect(&format!("paramSet: {}, is_hash: {}, count: {}", parameter_set, IS_HASH_MLDSA, self.count)); -// } else { -// // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() -// // so need to manually compute mu -// let mu = MLDSA65::compute_mu_from_tr( -// &sk.tr(), -// &hex::decode(&self.msg).unwrap(), -// Some(&hex::decode(&self.context).unwrap()), -// ).unwrap(); -// -// let sig = MLDSA44::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); -// assert_eq!(sig, &*hex::decode(&self.sm).unwrap(), "paramSet: {}, count: {}", parameter_set, self.count); -// -// MLDSA44::verify(&pk, &hex::decode(&self.msg).unwrap(), Some(&hex::decode(&self.context).unwrap()), &sig).unwrap(); -// } -// }, -// "MlDsa65" | "MLDsa65" => { -// let mut seed = KeyMaterial256::from_bytes_as_type( -// &hex::decode(&self.seed).unwrap(), -// KeyType::Seed, -// ).unwrap(); -// // for the purposes of the test cases, accept an all-zero seed -// seed.allow_hazardous_operations(); -// seed.set_key_type(KeyType::Seed).unwrap(); -// seed.set_security_strength(SecurityStrength::_256bit).unwrap(); -// seed.drop_hazardous_operations(); -// -// let (pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLDSA65_PK_LEN] = hex::decode(&self.pk).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLDSA65_SK_LEN] = hex::decode(&self.sk).unwrap().try_into().unwrap(); -// assert_eq!(sk.encode(), sk_sized); -// -// if IS_HASH_MLDSA { -// // we're only testing SHA512 -// let ph: [u8; 64] = SHA512::new().hash(&hex::decode(&self.msg).unwrap()).as_slice().try_into().unwrap(); -// assert_eq!(ph, &*hex::decode(&self.message_hash).unwrap()); -// -// let sig = HashMLDSA65_with_SHA512::sign_ph_deterministic(&sk, Some(&*hex::decode(&self.context).unwrap()), &ph, [0u8; 32]).unwrap(); -// assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); -// -// HashMLDSA65_with_SHA512::verify(&pk, -// &*hex::decode(&self.message_hash).unwrap(), -// Some(&*hex::decode(&self.context).unwrap()), -// &sig).expect(&format!("paramSet: {}, isHash: {}, count: {}", parameter_set, IS_HASH_MLDSA, self.count)); -// } else { -// // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() -// // so need to manually compute mu -// let mu = MLDSA65::compute_mu_from_tr( -// &sk.tr(), -// &hex::decode(&self.msg).unwrap(), -// Some(&hex::decode(&self.context).unwrap()), -// ).unwrap(); -// -// let sig = MLDSA65::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); -// assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); -// -// MLDSA65::verify(&pk, &hex::decode(&self.msg).unwrap(), Some(&hex::decode(&self.context).unwrap()), &sig).unwrap(); -// } -// }, -// "MLDsa87" => { -// let mut seed = KeyMaterial256::from_bytes_as_type( -// &hex::decode(&self.seed).unwrap(), -// KeyType::Seed, -// ).unwrap(); -// // for the purposes of the test cases, accept an all-zero seed -// seed.allow_hazardous_operations(); -// seed.set_key_type(KeyType::Seed).unwrap(); -// seed.set_security_strength(SecurityStrength::_256bit).unwrap(); -// seed.drop_hazardous_operations(); -// -// let (pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLDSA87_PK_LEN] = hex::decode(&self.pk).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLDSA87_SK_LEN] = hex::decode(&self.sk).unwrap().try_into().unwrap(); -// assert_eq!(sk.encode(), sk_sized); -// -// -// if IS_HASH_MLDSA { -// // we're only testing SHA512 -// let ph: [u8; 64] = SHA512::new().hash(&hex::decode(&self.msg).unwrap()).as_slice().try_into().unwrap(); -// assert_eq!(ph, &*hex::decode(&self.message_hash).unwrap()); -// -// let sig = HashMLDSA87_with_SHA512::sign_ph_deterministic(&sk, Some(&*hex::decode(&self.context).unwrap()), &ph, [0u8; 32]).unwrap(); -// assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); -// -// HashMLDSA87_with_SHA512::verify(&pk, -// &*hex::decode(&self.message_hash).unwrap(), -// Some(&*hex::decode(&self.context).unwrap()), -// &sig).unwrap(); -// } else { -// // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() -// // so need to manually compute mu -// let mu = MLDSA65::compute_mu_from_tr( -// &sk.tr(), -// &hex::decode(&self.msg).unwrap(), -// Some(&hex::decode(&self.context).unwrap()), -// ).unwrap(); -// -// let sig = MLDSA87::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); -// assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); -// -// MLDSA87::verify(&pk, &hex::decode(&self.msg).unwrap(), Some(&hex::decode(&self.context).unwrap()), &sig).unwrap(); -// } -// }, -// val => panic!("Invalid parameter set: {}", val), -// } -// } -// } -// } +mod bc_test_data { + #![allow(unused)] + + #[allow(unused)] + #[allow(dead_code)] + use crate::BustedMuBuilder; + use bouncycastle_core::errors::SignatureError; + use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{ + Hash, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + }; + use bouncycastle_hex as hex; + use bouncycastle_mldsa_lowmemory::{ + HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA512, HashMLDSA87_with_SHA512, MLDSA44, + MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65, + MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87, + MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyTrait, + MLDSATrait, + }; + use bouncycastle_sha2::SHA512; + use std::fs; + use std::path::Path; + use std::process::exit; + use std::sync::Once; + + const TEST_DATA_PATH_RELATIVE: &str = "../../../bc-test-data/pqc/crypto/mldsa"; + const TEST_DATA_PATH: &str = "../../../bc-test-data/pqc/crypto/mldsa"; + + static TEST_DATA_CHECK: Once = Once::new(); + + fn test_for_presence_of_test_data() { + TEST_DATA_CHECK.call_once(|| { + if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + println!("bc-test-data found at: {:?}", TEST_DATA_PATH_RELATIVE); + } else if !Path::new(TEST_DATA_PATH).exists() { + println!("bc-test-data found at: {:?}", TEST_DATA_PATH); + } else { + println!("bc-test-data directory not found"); + exit(0); + } + }); + } + + #[test] + #[allow(non_snake_case)] + fn ML_DSA_keyGen() { + test_for_presence_of_test_data(); + + let contents = + fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-keyGen.txt").unwrap(); + let test_cases = KeyGenTestCase::parse(contents); + + for test_case in test_cases { + test_case.run(); + } + } + + #[derive(Clone)] + struct KeyGenTestCase { + vs_id: u32, + algorithm: String, + mode: String, + revision: String, + is_sample: bool, + tg_id: u32, + test_type: String, + parameter_set: String, + tc_id: u32, + seed: String, + pk: String, + sk: String, + } + + impl KeyGenTestCase { + fn new() -> Self { + Self { + vs_id: 0, + algorithm: String::new(), + mode: String::new(), + revision: String::new(), + is_sample: false, + tg_id: 0, + test_type: String::new(), + parameter_set: String::new(), + tc_id: 0, + seed: String::new(), + pk: String::new(), + sk: String::new(), + } + } + + fn is_full(&self) -> bool { + !self.algorithm.is_empty() + } + + fn parse(data: String) -> Vec { + let mut test_cases = Vec::::new(); + let mut test_case = KeyGenTestCase::new(); + for line in data.lines() { + let (tag, value) = match line.split_once(" = ") { + Some(pair) => pair, + None => { + if test_case.is_full() { + test_cases.push(test_case.clone()); + } + continue; + } + }; + + match tag { + "vsId" => test_case.vs_id = value.parse().unwrap(), + "algorithm" => test_case.algorithm = value.to_string(), + "mode" => test_case.mode = value.to_string(), + "revision" => test_case.revision = value.to_string(), + "isSample" => test_case.is_sample = value.parse().unwrap(), + "tgId" => test_case.tg_id = value.parse().unwrap(), + "testType" => test_case.test_type = value.to_string(), + "parameterSet" => test_case.parameter_set = value.to_string(), + "tcId" => test_case.tc_id = value.parse().unwrap(), + "seed" => test_case.seed = value.to_string(), + "pk" => test_case.pk = value.to_string(), + "sk" => test_case.sk = value.to_string(), + val => panic!("Invalid tag: {}", val), + } + } + + test_cases + } + + fn run(&self) { + assert_eq!(self.mode, "keyGen"); + + let mut seed = KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) + .unwrap(); + // for the purposes of the test cases, accept an all-zero seed + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit).unwrap(); + seed.drop_hazardous_operations(); + + match self.parameter_set.as_str() { + "ML-DSA-44" => { + let (pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLDSA44_PK_LEN] = + hex::decode(&self.pk).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLDSA44_SK_LEN] = + hex::decode(&self.seed).unwrap().try_into().unwrap(); + assert_eq!(sk.encode(), sk_sized); + } + "ML-DSA-65" => { + let (pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLDSA65_PK_LEN] = + hex::decode(&self.pk).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLDSA65_SK_LEN] = + hex::decode(&self.seed).unwrap().try_into().unwrap(); + assert_eq!(sk.encode(), sk_sized); + } + "ML-DSA-87" => { + let (pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLDSA87_PK_LEN] = + hex::decode(&self.pk).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLDSA87_SK_LEN] = + hex::decode(&self.seed).unwrap().try_into().unwrap(); + assert_eq!(sk.encode(), sk_sized); + } + val => panic!("Invalid parameter set: {}", val), + } + } + } + + // this seems buggy and I'm not sure why. Possibly because the bc-test-data was written against Round 3 Dilithium and not ML-DSA. + // todo -- debug + // #[test] + #[allow(non_snake_case)] + fn ML_DSA_sigGen() { + test_for_presence_of_test_data(); + + let contents = + fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-sigGen.txt").unwrap(); + let test_cases = SigGenTestCase::parse(contents); + + let num_tests = test_cases.len(); + for test_case in test_cases { + test_case.run(); + } + println!("SUCCESS! ML-DSA-sigGen test cases passed: {}!", num_tests); + } + + #[derive(Clone)] + struct SigGenTestCase { + vs_id: u32, + algorithm: String, + mode: String, + revision: String, + is_sample: bool, + tg_id: u32, + test_type: String, + parameter_set: String, + deterministic: bool, + tc_id: u32, + sk: String, + message: String, + rnd: String, + signature: String, + } + + impl SigGenTestCase { + fn new() -> Self { + Self { + vs_id: 0, + algorithm: String::new(), + mode: String::new(), + revision: String::new(), + is_sample: false, + tg_id: 0, + test_type: String::new(), + parameter_set: String::new(), + deterministic: false, + tc_id: 0, + sk: String::new(), + message: String::new(), + rnd: String::new(), + signature: String::new(), + } + } + + fn is_full(&self) -> bool { + !self.algorithm.is_empty() + } + + fn parse(data: String) -> Vec { + let mut test_cases = Vec::::new(); + let mut test_case = SigGenTestCase::new(); + for line in data.lines() { + let (tag, value) = match line.split_once(" = ") { + Some(pair) => pair, + None => { + if test_case.is_full() { + test_cases.push(test_case.clone()); + } + continue; + } + }; + + match tag { + "vsId" => test_case.vs_id = value.parse().unwrap(), + "algorithm" => test_case.algorithm = value.to_string(), + "mode" => test_case.mode = value.to_string(), + "revision" => test_case.revision = value.to_string(), + "isSample" => test_case.is_sample = value.parse().unwrap(), + "tgId" => test_case.tg_id = value.parse().unwrap(), + "testType" => test_case.test_type = value.to_string(), + "parameterSet" => test_case.parameter_set = value.to_string(), + "deterministic" => test_case.deterministic = value.parse().unwrap(), + "tcId" => test_case.tc_id = value.parse().unwrap(), + "sk" => test_case.sk = value.to_string(), + "message" => test_case.message = value.to_string(), + "rnd" => test_case.rnd = value.to_string(), + "signature" => test_case.signature = value.to_string(), + val => panic!("Invalid tag: {}", val), + } + } + + test_cases + } + + fn run(&self) { + assert_eq!(self.mode, "sigGen"); + + let rnd = if self.deterministic { + [0u8; 32] + } else { + hex::decode(&self.rnd).unwrap().as_slice().try_into().unwrap() + }; + + match self.parameter_set.as_str() { + "ML-DSA-44" => { + let sk = + MLDSA44PrivateKey::from_bytes(&hex::decode(&self.sk).unwrap()).unwrap(); + + // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() + // so need to manually compute mu + // let mu = MLDSA44::compute_mu_from_tr( + // &hex::decode(&self.message).unwrap(), + // None, + // sk.tr(), + // ).unwrap(); + let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); + mb.do_update(&hex::decode(&self.message).unwrap()); + let mu = mb.do_final(); + + let sig = MLDSA44::sign_mu_deterministic(&sk, &mu, rnd).unwrap(); + assert_eq!( + &sig, + &*hex::decode(&self.signature).unwrap(), + "ML-DSA-sigGen params: {}, vsId: {}, tgId: {}, tcId: {}", + self.parameter_set, + self.vs_id, + self.tg_id, + self.tc_id + ); + } + "ML-DSA-65" => { + let sk = + MLDSA65PrivateKey::from_bytes(&hex::decode(&self.sk).unwrap()).unwrap(); + + // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() + // so need to manually compute mu + // let mu = MLDSA65::compute_mu_from_tr( + // &hex::decode(&self.message).unwrap(), + // None, + // sk.tr(), + // ).unwrap(); + let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); + mb.do_update(&hex::decode(&self.message).unwrap()); + let mu = mb.do_final(); + + let sig = MLDSA65::sign_mu_deterministic(&sk, &mu, rnd).unwrap(); + assert_eq!(&sig, &*hex::decode(&self.signature).unwrap()); + } + "ML-DSA-87" => { + let sk = + MLDSA87PrivateKey::from_bytes(&hex::decode(&self.sk).unwrap()).unwrap(); + + // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() + // so need to manually compute mu + // let mu = MLDSA87::compute_mu_from_tr( + // &hex::decode(&self.message).unwrap(), + // None, + // sk.tr(), + // ).unwrap(); + let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); + mb.do_update(&hex::decode(&self.message).unwrap()); + let mu = mb.do_final(); + + let sig = MLDSA87::sign_mu_deterministic(&sk, &mu, rnd).unwrap(); + assert_eq!(&sig, &*hex::decode(&self.signature).unwrap()); + } + val => panic!("Invalid parameter set: {}", val), + } + } + } + + // this seems buggy and I'm not sure why. Possibly because the bc-test-data was written against Round 3 Dilithium and not ML-DSA. + // todo -- debug + // #[test] + #[allow(non_snake_case)] + fn ML_DSA_sigVer() { + test_for_presence_of_test_data(); + + let contents = + fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-sigVer.txt").unwrap(); + let test_cases = SigVerTestCase::parse(contents); + + for test_case in test_cases { + test_case.run(); + } + } + + #[derive(Clone)] + struct SigVerTestCase { + vs_id: u32, + algorithm: String, + mode: String, + revision: String, + is_sample: bool, + tg_id: u32, + test_type: String, + parameter_set: String, + pk: String, + tc_id: u32, + message: String, + signature: String, + test_passed: bool, + } + impl SigVerTestCase { + fn new() -> Self { + Self { + vs_id: 0, + algorithm: String::new(), + mode: String::new(), + revision: String::new(), + is_sample: false, + tg_id: 0, + test_type: String::new(), + parameter_set: String::new(), + tc_id: 0, + pk: String::new(), + message: String::new(), + signature: String::new(), + test_passed: false, + } + } + + fn is_full(&self) -> bool { + !self.algorithm.is_empty() + } + + fn parse(data: String) -> Vec { + let mut test_cases = Vec::::new(); + let mut test_case = SigVerTestCase::new(); + for line in data.lines() { + let (tag, value) = match line.split_once(" = ") { + Some(pair) => pair, + None => { + if test_case.is_full() { + test_cases.push(test_case.clone()); + } + continue; + } + }; + + match tag { + "vsId" => test_case.vs_id = value.parse().unwrap(), + "algorithm" => test_case.algorithm = value.to_string(), + "mode" => test_case.mode = value.to_string(), + "revision" => test_case.revision = value.to_string(), + "isSample" => test_case.is_sample = value.parse().unwrap(), + "tgId" => test_case.tg_id = value.parse().unwrap(), + "testType" => test_case.test_type = value.to_string(), + "parameterSet" => test_case.parameter_set = value.to_string(), + "pk" => test_case.pk = value.to_string(), + "tcId" => test_case.tc_id = value.parse().unwrap(), + "message" => test_case.message = value.to_string(), + "signature" => test_case.signature = value.to_string(), + "testPassed" => test_case.test_passed = value.parse().unwrap(), + val => panic!("Invalid tag: {}", val), + } + } + + test_cases + } + + fn run(&self) { + assert_eq!(self.mode, "sigVer"); + + match self.parameter_set.as_str() { + "ML-DSA-44" => { + let pk = MLDSA44PublicKey::from_bytes(&hex::decode(&self.pk).unwrap()).unwrap(); + + match MLDSA44::verify( + &pk, + &hex::decode(&self.message).unwrap(), + None, + &hex::decode(&self.signature).unwrap(), + ) { + Ok(()) => { + if !self.test_passed { + panic!("Verification succeeded when it shouldn't have!") + } + } + Err(SignatureError::SignatureVerificationFailed) => { + if self.test_passed { + panic!( + "Verification failed when it shouldn't have! vsId: {}, tgId: {}, tcId: {}", + self.vs_id, self.tg_id, self.tc_id + ) + } + } + _ => panic!("An unexpected error occurred"), + } + } + "ML-DSA-65" => { + let pk = MLDSA65PublicKey::from_bytes(&hex::decode(&self.pk).unwrap()).unwrap(); + + match MLDSA65::verify( + &pk, + &hex::decode(&self.message).unwrap(), + None, + &hex::decode(&self.signature).unwrap(), + ) { + Ok(()) => { + if self.test_passed { /* good */ + } else { + panic!("Verification succeeded when it shouldn't have!") + } + } + Err(SignatureError::SignatureVerificationFailed) => { + if !self.test_passed { + } else { + panic!("Verification failed when it should have!") + } + } + _ => panic!("An unexpected error occurred"), + } + } + "ML-DSA-87" => { + let pk = MLDSA87PublicKey::from_bytes(&hex::decode(&self.pk).unwrap()).unwrap(); + + match MLDSA87::verify( + &pk, + &hex::decode(&self.message).unwrap(), + None, + &hex::decode(&self.signature).unwrap(), + ) { + Ok(()) => { + if self.test_passed { /* good */ + } else { + panic!("Verification succeeded when it shouldn't have!") + } + } + Err(SignatureError::SignatureVerificationFailed) => { + if !self.test_passed { + } else { + panic!("Verification failed when it should have!") + } + } + _ => panic!("An unexpected error occurred"), + } + } + val => panic!("Invalid parameter set: {}", val), + } + } + } + + // not working, not totally sure why + // #[test] + #[allow(non_snake_case)] + fn ML_DSA_rsp() { + test_for_presence_of_test_data(); + + // MLDsa44 + let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa44.rsp").unwrap(); + let test_cases = MldsaRspTestCase::::parse(contents); + for test_case in test_cases { + test_case.run("MLDsa44"); + } + + // MLDsa65 + let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa65.rsp").unwrap(); + let test_cases = MldsaRspTestCase::::parse(contents); + for test_case in test_cases { + test_case.run("MLDsa65"); + } + + // MLDsa87 + let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa87.rsp").unwrap(); + let test_cases = MldsaRspTestCase::::parse(contents); + for test_case in test_cases { + test_case.run("MLDsa87"); + } + + // MLDsa44 + let contents = + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa44sha512.rsp").unwrap(); + let test_cases = MldsaRspTestCase::::parse(contents); + for test_case in test_cases { + test_case.run("MLDsa44"); + } + + // MLDsa65 + let contents = + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa65sha512.rsp").unwrap(); + let test_cases = MldsaRspTestCase::::parse(contents); + for test_case in test_cases { + test_case.run("MlDsa65"); + } + + // MLDsa87 + let contents = + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa87sha512.rsp").unwrap(); + let test_cases = MldsaRspTestCase::::parse(contents); + for test_case in test_cases { + test_case.run("MlDsa87"); + } + } + + #[derive(Clone)] + struct MldsaRspTestCase { + count: u32, + seed: String, + mlen: u32, + msg: String, + pk: String, + sk: String, + smlen: u32, + sm: String, + message_hash: String, + message_prime: String, + context: String, + } + + impl MldsaRspTestCase { + fn new() -> Self { + Self { + count: 0, + seed: String::new(), + mlen: 0, + msg: String::new(), + pk: String::new(), + sk: String::new(), + smlen: 0, + sm: String::new(), + message_hash: String::new(), + message_prime: String::new(), + context: String::new(), + } + } + + fn is_full(&self) -> bool { + !self.seed.is_empty() + } + + fn parse(data: String) -> Vec> { + let mut test_cases = Vec::new(); + let mut test_case = MldsaRspTestCase::new(); + for line in data.lines() { + let (tag, value) = match line.split_once(" = ") { + Some(pair) => pair, + None => { + if test_case.is_full() { + test_cases.push(test_case.clone()); + } + continue; + } + }; + + match tag { + "count" => test_case.count = value.parse().unwrap(), + "seed" => test_case.seed = value.to_string(), + "mlen" => test_case.mlen = value.parse().unwrap(), + "msg" => test_case.msg = value.to_string(), + "pk" => test_case.pk = value.to_string(), + "sk" => test_case.sk = value.to_string(), + "smlen" => test_case.smlen = value.parse().unwrap(), + "sm" => test_case.sm = value.to_string(), + "message_hash" => test_case.message_hash = value.to_string(), + "message_prime" => test_case.message_prime = value.to_string(), + "context" => { + test_case.context = value.to_string(); + if test_case.context == "zero_length" || test_case.context == "none" { + test_case.context = String::new(); + } + } + val => panic!("Invalid tag: {}", val), + } + } + + test_cases + } + + fn run(&self, parameter_set: &str) { + match parameter_set { + "MLDsa44" => { + let mut seed = KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) + .unwrap(); + // for the purposes of the test cases, accept an all-zero seed + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit).unwrap(); + seed.drop_hazardous_operations(); + + let (pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLDSA44_PK_LEN] = + hex::decode(&self.pk).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLDSA44_SK_LEN] = + hex::decode(&self.sk).unwrap().try_into().unwrap(); + assert_eq!(sk.encode(), sk_sized); + + if IS_HASH_MLDSA { + // we're only testing SHA512 + let ph: [u8; 64] = SHA512::new() + .hash(&hex::decode(&self.msg).unwrap()) + .as_slice() + .try_into() + .unwrap(); + assert_eq!(ph, &*hex::decode(&self.message_hash).unwrap()); + + let sig = HashMLDSA44_with_SHA512::sign_ph_deterministic( + &sk, + Some(&*hex::decode(&self.context).unwrap()), + &ph, + [0u8; 32], + ) + .unwrap(); + assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); + + HashMLDSA44_with_SHA512::verify( + &pk, + &*hex::decode(&self.msg).unwrap(), + Some(&*hex::decode(&self.context).unwrap()), + &sig, + ) + .expect(&format!( + "paramSet: {}, is_hash: {}, count: {}", + parameter_set, IS_HASH_MLDSA, self.count + )); + } else { + // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() + // so need to manually compute mu + let mu = MLDSA65::compute_mu_from_tr( + &sk.tr(), + &hex::decode(&self.msg).unwrap(), + Some(&hex::decode(&self.context).unwrap()), + ) + .unwrap(); + + let sig = MLDSA44::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); + assert_eq!( + sig, + &*hex::decode(&self.sm).unwrap(), + "paramSet: {}, count: {}", + parameter_set, + self.count + ); + + MLDSA44::verify( + &pk, + &hex::decode(&self.msg).unwrap(), + Some(&hex::decode(&self.context).unwrap()), + &sig, + ) + .unwrap(); + } + } + "MlDsa65" | "MLDsa65" => { + let mut seed = KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) + .unwrap(); + // for the purposes of the test cases, accept an all-zero seed + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit).unwrap(); + seed.drop_hazardous_operations(); + + let (pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLDSA65_PK_LEN] = + hex::decode(&self.pk).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLDSA65_SK_LEN] = + hex::decode(&self.sk).unwrap().try_into().unwrap(); + assert_eq!(sk.encode(), sk_sized); + + if IS_HASH_MLDSA { + // we're only testing SHA512 + let ph: [u8; 64] = SHA512::new() + .hash(&hex::decode(&self.msg).unwrap()) + .as_slice() + .try_into() + .unwrap(); + assert_eq!(ph, &*hex::decode(&self.message_hash).unwrap()); + + let sig = HashMLDSA65_with_SHA512::sign_ph_deterministic( + &sk, + Some(&*hex::decode(&self.context).unwrap()), + &ph, + [0u8; 32], + ) + .unwrap(); + assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); + + HashMLDSA65_with_SHA512::verify( + &pk, + &*hex::decode(&self.message_hash).unwrap(), + Some(&*hex::decode(&self.context).unwrap()), + &sig, + ) + .expect(&format!( + "paramSet: {}, isHash: {}, count: {}", + parameter_set, IS_HASH_MLDSA, self.count + )); + } else { + // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() + // so need to manually compute mu + let mu = MLDSA65::compute_mu_from_tr( + &sk.tr(), + &hex::decode(&self.msg).unwrap(), + Some(&hex::decode(&self.context).unwrap()), + ) + .unwrap(); + + let sig = MLDSA65::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); + + MLDSA65::verify( + &pk, + &hex::decode(&self.msg).unwrap(), + Some(&hex::decode(&self.context).unwrap()), + &sig, + ) + .unwrap(); + } + } + "MLDsa87" => { + let mut seed = KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) + .unwrap(); + // for the purposes of the test cases, accept an all-zero seed + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit).unwrap(); + seed.drop_hazardous_operations(); + + let (pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLDSA87_PK_LEN] = + hex::decode(&self.pk).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLDSA87_SK_LEN] = + hex::decode(&self.sk).unwrap().try_into().unwrap(); + assert_eq!(sk.encode(), sk_sized); + + if IS_HASH_MLDSA { + // we're only testing SHA512 + let ph: [u8; 64] = SHA512::new() + .hash(&hex::decode(&self.msg).unwrap()) + .as_slice() + .try_into() + .unwrap(); + assert_eq!(ph, &*hex::decode(&self.message_hash).unwrap()); + + let sig = HashMLDSA87_with_SHA512::sign_ph_deterministic( + &sk, + Some(&*hex::decode(&self.context).unwrap()), + &ph, + [0u8; 32], + ) + .unwrap(); + assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); + + HashMLDSA87_with_SHA512::verify( + &pk, + &*hex::decode(&self.message_hash).unwrap(), + Some(&*hex::decode(&self.context).unwrap()), + &sig, + ) + .unwrap(); + } else { + // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() + // so need to manually compute mu + let mu = MLDSA65::compute_mu_from_tr( + &sk.tr(), + &hex::decode(&self.msg).unwrap(), + Some(&hex::decode(&self.context).unwrap()), + ) + .unwrap(); + + let sig = MLDSA87::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, &*hex::decode(&self.sm).unwrap()); + + MLDSA87::verify( + &pk, + &hex::decode(&self.msg).unwrap(), + Some(&hex::decode(&self.context).unwrap()), + &sig, + ) + .unwrap(); + } + } + val => panic!("Invalid parameter set: {}", val), + } + } + } +} /// This builds a "busted" mu where the ctx is absent (not 0-length, but actually not there) /// just for the sake of compatibility with the bc-test-data tests @@ -650,7 +897,7 @@ impl BustedMuBuilder { } /// This function requires the public key hash `tr`, which can be computed from the public key using [MLDSAPublicKey::compute_tr]. - pub fn do_init(tr: &[u8; 64], /*ctx: Option<&[u8]>*/) -> Result { + pub fn do_init(tr: &[u8; 64] /*ctx: Option<&[u8]>*/) -> Result { // let ctx = match ctx { // Some(ctx) => ctx, // None => &[] @@ -693,4 +940,4 @@ impl BustedMuBuilder { mu } -} \ No newline at end of file +} diff --git a/crypto/mldsa_lowmemory/tests/hash_mldsa_tests.rs b/crypto/mldsa_lowmemory/tests/hash_mldsa_tests.rs index d208e9c..1380296 100644 --- a/crypto/mldsa_lowmemory/tests/hash_mldsa_tests.rs +++ b/crypto/mldsa_lowmemory/tests/hash_mldsa_tests.rs @@ -1,21 +1,23 @@ -use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -use bouncycastle_core::traits::{Hash, Signature}; -use bouncycastle_mldsa_lowmemory::{HashMLDSA44_with_SHA512, MLDSA44_SIG_LEN}; -use bouncycastle_sha2::SHA512; +use bouncycastle_core::traits::Signature; use bouncycastle_hex as hex; #[cfg(test)] mod hash_mldsa_tests { use super::*; + use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; - use bouncycastle_core::traits::{Hash}; + use bouncycastle_core::traits::{Hash, PHSignature}; use bouncycastle_core_test_framework::signature::TestFrameworkSignature; - use bouncycastle_mldsa_lowmemory::{HashMLDSA44_with_SHA256, HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA256, HashMLDSA65_with_SHA512, HashMLDSA87_with_SHA256, HashMLDSA87_with_SHA512, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSATrait, MLDSA44, MLDSA65, MLDSA87}; - use bouncycastle_mldsa_lowmemory::{MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_SIG_LEN}; - use bouncycastle_mldsa_lowmemory::{MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_SIG_LEN}; - use bouncycastle_mldsa_lowmemory::{MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_SIG_LEN}; - use bouncycastle_sha2::SHA512; - + use bouncycastle_mldsa_lowmemory::{ + HashMLDSA44_with_SHA256, HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA256, + HashMLDSA65_with_SHA512, HashMLDSA87_with_SHA256, HashMLDSA87_with_SHA512, MLDSA44, + MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87, + MLDSA87PrivateKey, MLDSA87PublicKey, MLDSATrait, + }; + use bouncycastle_mldsa_lowmemory::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; + use bouncycastle_mldsa_lowmemory::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; + use bouncycastle_mldsa_lowmemory::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; + use bouncycastle_sha2::{SHA256, SHA512}; #[test] fn core_framework_signature() { @@ -27,14 +29,14 @@ mod hash_mldsa_tests { tf.test_signature::(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::(false); + tf.test_ph_signature::(false); + tf.test_ph_signature::(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::(false); + tf.test_ph_signature::(false); + tf.test_ph_signature::(false); } #[test] @@ -43,13 +45,18 @@ mod hash_mldsa_tests { // bc-java only supports HashML-DSA with SHA512, not with SHA256, so can't cross-test that. let seed = KeyMaterial256::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f").unwrap(), + &hex::decode("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f") + .unwrap(), KeyType::Seed, - ).unwrap(); - let rnd: [u8; 32] = hex::decode("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f").unwrap()[..32].try_into().unwrap(); + ) + .unwrap(); + let rnd: [u8; 32] = + hex::decode("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f") + .unwrap()[..32] + .try_into() + .unwrap(); let msg = b"The quick brown fox"; - // HashML-DSA-44_with_SHA512 let expected_sig = hex::decode("8fbf0813a2bbe17e6a8bae1bbabc8704c59fe8910b8125426b6983eb50bb26c8b6249722fdea7c26d731d7ca34ff100be306d6e7d11367e521e783eaf799cd8c235e45c663abf632aad1543c5faf13220af0eb06c7a0e7f0d1a6385dbc7fd10e58ed905850c9f9692ee8ca6642dcaa2bb1c6fea12bcbdc59d5a19c78ad1ec952dd4f22e651b2a42035b63cf5b510ab95cf0c9a9fd77389d3fae9b42b123199c84a881ff30d7955c9841f5319d93a2c531d4d26bc6341f07c42acda0f5ec4cf70932dee570292699128d23f13ebc7d79bea2ff7ca352369e8b765e4e2fbcb2476f67b8cc8c84690be164e08c34be160806435993be3dce5455338f14eb9f3918fd70b3753d374cdd84c350654d626881a0757a20244b86e7b5eba61a517e75f60e8658795133079e72b8bd4ce9fce5c6af2a94988bb3141b38e8498d9f01a5cea3f2e24f5f4b6f64e2105010d9efe12693241149f115ca2a4086c456a9c852ade47f07f0a78eaad4ed4a67a18ffb12f9f9eaa151b5973010f021c7f11a79df404b637fa4a777b3ef7dc724f191baac9dcf1a5e376978c146c944c1f8f510412c05c872551e625b50426dc0433f89b89e67e6a6bcac4c1ab86c2da13cc0c52911319889cbecfde58c5af586ff0b802aebc18b13014f5d189af1fe335a8fc3b37d90cbfaa89d7f6db2d9960787a49c7c632e339c75d3e618d55971885d4b45f58db4c9a0fd50ababadde1ad2423178e0aa26e6f3d16f6b6f03f5dcb2e2eb54ca4aac44fabc92f6b4eea194174e15f5c26801cbb8519e04fc8bfbc8ddb63a3cfbe4ba2b92c7a38f3c64a1702ee785ccb745d3a6f5853521796526c1dfc2b0bfb774a2b1812524e6ab5f15137e22dcf70136274cb0181cb277303478d9a5153f56e9624ea9d2f838a9bc054e080973a86e174c72fa4bb78c01598ed3f5115939fa172537d8799ada93af028b437048b0ae1b412fda490b3a5a292552927cf3ac540b1c67a97c2a7a94a6217a7a3fb7526c00a0d2a13e64aed1449c4029c4f9ef7b7c783929c37713c7cec1d55d1371dbe6ed00782e143e2ffb74cef8bec56c18e37e707e1a7e1fb04cc0243f0002de7644e8780f215910754985ce1cf6b4e16c0656e2b9fe55fd4fa4340a4de5b01624afbc819902b90a17f0b8d55841f2d3b41e43bc2727b3584ab49db5548169c5e207ace157469cc2d712e885e67735afbee9d5874b9bbac6a2d88cf8f957537c137e44b105202942ecd3cfdd792b2657f025d48c4ea172052c7f33ef8f44e808b8888ca755414eb191a1c4cfed2ec6ab9dcf8aa1451b1640b09f0022349091d19665fa3ca2d5f6ab9d883c0f03fabfe9565c7fc2a536ea73758fde6490f4de2e138f39a628175f2860e8694bdb9c2045d218c78b29243ec2b40e5bebbe2688985e337b528df5549f4adc5a36dd04f7045bcc436676cc6c8b58b0e0205b7e1bea512749102883e4a65600dbc0744b03f2445950eeb536cdd8a88cb90d069c4205e4a0df830170c73779245729d896d14730dccce05a2f1cab706e9929cc1ace014727d793b1f1f8b572bc7a760b15b325c5fa4b1511f253567caebaafe7acc0cc400e470cce9ed5121caba5371038906d8ee1643f336146ac6c743c2cc36912195da57aa1e557ee4040997583dfa77e0bbad48ff901ccf4f28b32b350f2383812a5bb59211f8a90aefda3eef487de26746303676d5727a4ee39dd5a2d8e0072fcd4dab6e0af099aee6b379283272c3e56b5a55b5b399832482ce311a3a629ea2e01cf4c236ca4bc807898fbce977521fb75ff02699f81a26bb69c7a69d46edbe4575ca2f11c361b269b918f7826c61496b815390efa51b92bc70b83c3fb1f311be5b23d7cf6fcf2d4877c3e7d439c4bac5aef81348688f97fd34b32c3cb798feb38197c6754527a75cdb38e28647de8fec0d77cf3786cb5d339f6569ca879d941d88c8cc1194443c40c0ea86d5d4cad5f7db683effde3339bfd63ad5cfb1caba26521e3c9c6d93d9c58e38431e40eab5f7cb2158c8f48e771f551e940a8607af3fd44aad01bdb9a04418aa03aefeceeef5bffed53cb37919d280f8f8d73965b02ca4515d26d33ae3afc97c779b72656ef34399e6508bdc9017bd17d17ed675db7294fee98bf8fed1d84154949dfad1dba8168ee1f6d8828f80ad5a8c11aceffde97886fe2440f26b74436a8534f5ac3de9fb61f3bd6c7ec5c761aaf0036be004a9d5d952b8719afd5cc6da5081632e1a10398fc7d7edabe522e75ea774819b1f2f558c46c276eb6419504a4f9d1226544ebc4dccfa76cf26ad90661e9f78d563472e78cbed3833655983e9458aa71dcdb44fbe13295606bfe7a02715044589652c641585e3950086e40e30885934b92e302ced1a94e95fffa9402afe1f359569a394019d5265862dce4b828b657e43591d199b3500394f871155debc78922305c366350868bd81b06608a44ae383aacb8c0761bbf8bc7a1ee1b9bc7f5a9173544f9987c9b15706a50a193c84dea3317b71e04369a52c32cc3d0eafa918eededa4dd321b1ba99a668c436f16f7f2f1a1ffe847f86a6a1c39b857c118b848593265042eb4a1ba8a50303ad7034d2ab4960bdde975dbc3fa632777b8ff5c541af07e63ee05defa4aed3fda7a69a67191617f92dac21e511db12fa95a5fe1ca37f184e02f58b835faa8ceadd8bfbd938626a7565007a5e022b97debe1732835560e74bfd58c0eb0624fb36703d5aa05a71256cc432bc3850f7b982048c3329f717317e9a755440d1e6d3934dab952e23a993d15fad17534bc848060b51a15e670766c6bd3649957bf89e8fa34950fb1870089a5a9e82af440cd2571f2edaf68d4c1ff4a82c30d7e0b1ee60483fbfc3eeff73c97c7ec9d07444d05624cebbe408f2d2fe6cb43c17d64f135b113538035d0ab73e9822b804b607e88ae999a035ee22d7fda883c817ee5a027208bc22046585f24451f76dfc6e9da9e62085de03a323de7b7ba09cfe6bf1e3b1643dda9d1b798edc54741084595af65b36b9a323a90edefbd37e9038b68991846cb5ecc442785aa7fe6993cf3cda097c3417d234aeac8540e12f810a07fd78548708a72092ff1c4b59f9f8c4023e89a344ded87915b65cfb5547a57cca97c33c861b04125550648434e960c144dc7cefb12459b314da4d6cfdab29e2f4354dbe9ca93970964816c366924c84fd1e7f592cdd8fb37264d359d508bff7b2fd342d80375f87fd76bdc5932517aebe6aed1a7e27632e980b63ec70af947130ab190de8bb309ad1528a51a5142215181b252b2f345f6a72aaabcaea1114152f344c5764656a6c89a2a7b7b9badce5050a2661738f99b5babdbec4cccfdd35677b84b9c8d9deedf4f9fc00000000000000000000000000000000000000000e21303c").unwrap(); @@ -61,7 +68,6 @@ mod hash_mldsa_tests { let sig = HashMLDSA44_with_SHA512::sign_ph_deterministic(&sk, None, &ph, rnd).unwrap(); assert_eq!(&sig, expected_sig.as_slice()); - // HashML-DSA-65_with_SHA512 let expected_sig = hex::decode("cb99a9fd2063ddda7114cf99b577b7d9a6e7540ca225d84e5b04c28e30a4a09c6ec470a596a1efa809a42250487d908676b8a50df1a032c1c4c5124e989ce795a44faf0bd5e46627b1272d99b065d38c60148892bdc93159c828cc7e60996fd1825993fb001a901d7cbfb5a24b05446ce0f4ba5629434224646d10dcf4a55c851a22c690ebc443fea0fe2a7858d5e175b1e0a9115b3431ebeb78ab670f6f79c0f94d60fc2658b24a9ef06f51434aa4dc1b0797cf905dea13d5a1519e6483dd60dd33eee62a2898d3179d1b459cf316cc7ab2a0be94ec676f1b7e35b5ee123bf17fa39aa188210d2906991beeaa5e63516debd90504dadf4673de1ebc67a2b7e399b30485a4ebe37c9e7dce4c885076d3741c7841b319fb6b5fc228392935b0d20fe1ec8a4441ea53e47940816f8ead499d73702be1ed6d4f99c5f48d82acf60d1b08c4fc7d66c0cbfa93c4880977e0fde301dcc038b0cd354f6ba7b14191ed925f25cc1168ae1b48849b6f24e2911e8a4046bfa1b3e2be62369a730096c79d9f460e79adbafe4e9c723123df5d872cad3fe1553df2f6f49ee7f5278b75c445a3aa4fb16836faa8f6e766d457f803ffb11d32ca9de876ad0b8733a16cbd91d319489b738c9af693266c115af3be2afc29e2c6f669a133aa7b22aa6a84a7adbfe0dac48871b3f41ad20f78f003761b729eeb8a6c05cba15df2491b882e9699025cf2483151a6fc6e0839fc13c4529b3a5a67c0fbaedbbed2aaa764d5b936a94e3e281bb465b2b2f837b396c96c75bd3e58b8b344c13094eeb3105eddb41b7d1e8fd0f05a127e4d306c6d011a8d7da26438ba50bcaa11cd7136a738c1562c4a5681b3ae5870c21838f0fd1b79d20528726adf64fe3c85bd92632e720cc6c9fbdf37c6d293ffaf7ad2e01284d66ef48b6c25b9503f61ba967031bdb462528eb6b02566385a7f51bd0f92404c43c2eeb325c76190e121ab57d308f6749ac612c138664f198a477a1eb59d2cdd0ab217a2f3e79120d9c09936a8671ffe35ab87130909f84a9beaf8733b86498736be36052a6852ef2320226369120c20c1bc4cb676a9a31dbe346601d5166db023829fe000e2be7d7f9ee29ea4de25ad3fb66dc2cce9669d2c7aeb2bedbf24c117fd40a563172246fee9ac79bde567d09032d25b54ad017c367dff2f69c55115f142724bcee8da2095efc81611e5f357aa5b5a46513e86ed1e28266a9d110433fc4f69b7e965abb6d781d69936b1eefac7a5efec7478d3b1b3e3181bb3f415311510d6284349f63586bfbeb5e0ffa6d89d518a2a12997d0622387fcb267844a9c7281f04a34379038d1b8db1050058c8b57072b4c6bcc582a734730529e5e301dff85bfb90d7e6a6214c3414caaff452b11adacf7dad839516ad2d6dc2b7e8c9273ad0229b004a303c2771e744cb227f7dd999c08094cc70849152557ec3e90797710cf88996db05970f457c7c32a219033ffa71d8f11a422d2bd6a71becf07b9694149bbbcc1bc773c94f170d7702086099c53d0a24b8780d586cee313df06d5cd4ba04925a6f85390bf8a9e93f1251a84e34802cea764e7b8c3e50ee500cc77ca4e265cd2e7514db01a502e054cd407c5369008390d239c62e42dec5f628da94e71a97dfc86e3f916fe66d13a8c72102ffd03171293a6be503768eb418c4e6e66b7836244354bca0f7076683364130219c19ad843397c307f0c05c05fa521226bda6ea6768a3f473e5304964feaaa78c2b6a50bc666a4ed0ed655702239ce85fb86b7a2d8520b0257260faf830b681b86b11cc0cf43d63d77f6d2c8c84f4fb55a6ba70a32e389d167b4ecd07bf909707d3e72da5411e0136320e0460b70e0a39469260ea51370b4cec27f82ad86940725584b7a2a370cc9af96ad607bb1855f9631bd796b34cfc1971c23174d54375535422f5965170e84b78e89d4bd9094e9996e018cd8872ade216ff5c4679174cc24d9ad112e5e2be28f9792a1a5716969dacadc1ede4911a8cb7f6fd9fa35d68974d0481c736a265f1c74397cec2eb1235d725b0241875d6e413632496ab62ced9e922c955a7d09e9dbaba4ca6642a56ed1617088298d497257c5be1f7ea55d72560c919785d24395873efbfab048506fd88489aff60c1846419a43e77cd3689d2913b9845c2d1249440aa143e0043036efb56a8877e1218ff693d4e95f8c86dc0b38af1d41c651dc681680bfd3742127bebfe32acadd8cda346b40374fd7aefa84072e52b51832b79ee5aba35562c161f53234093472b65785ab638fd56c0219deb1c19c97feac2e833f5105538fc81acdffe51dee05ac4fbc583be22c62d45aad7263736981a9fc5472f30465bb4ae1f677bfe53342a573befe34baf9b8b5e3b64e3cdc994f0b7cba4ec6342ffb0ebfeadf01eecd63bd6496b534747f0379505626f1068e997fd61effcb3574e372b030dbba4a14e47e28b38e4f8ff910bc589756255863400f1bc638ec227eb05d49f9227f59c463c2162ee107ba3f6151cb09220287b90d27f9843b74db9d22848d197d4124aa20cb2ccc673344646ed87b1fd9e16d674b6d7ddb8f7921f5de5c045b9c13362442c0eeab3ea35573a720c68fd640ccc7a7b3e630a9b7c799686e051666976581e69fe79c4275e308fc7a60f69021e2c501811269acc05fbaf82f12a0535da36134aad9def3a8523c6f6edccfa3523c02f63bf590deea39fd30b3bdbd2deb9a4a076b19262781a5f549943e09f7995b594980f838b1ffd801c0a3b69f5a007975f72cc01d64291e85b3159623f210819f44f773375a2434b3b479b9004fdb8d6100481f64a2386de41677da5b1e7aeef840fda0e0f9a01dfdbf52cb18f60d459029bd536c22da9af2777dfa5093c032c4a62fff604fb66e9d0d3a249e8a6596d47b7d658492b3dd7fa14d507a5dd08927bd118e889e5ccc940e484233d245aa2cf67394e4fd15e5e129d3955d4ac6e230c974414cad06ad301da92fc6453abbef3eb14647e088b9c6ac74c3517f9d6ffd24eeb62d3f591aa34f828c1ba902efdbe322160326e4d46eef78726cae9c6e68643e2dfa259ef0e761f5142e06bdd02c5f516d101fc4171391ec338c427ab89c757a3847a928f1cfffe40a9fa6276c7d08b27f9910cc02f14f60d0fcca7cf518e7f0a6f546892b38592744fd4c670e6e68912b0ff2de7e5a907bf67f33a75f16ace28927eb375e5c694a712c122ccefd72b3d755bef017fb7f1ce17e366a8993a1b8d0aa14c3eaffeb78e778a192d3d80e37dcc4344bf4de73c6ef7523f53fb04232ac97aa64ab76b115405adfeeeb5fbf97f448dae8ce4dd26e68f8aaafb9cdce8d38522976fd18c80f7e3f12dc459f6c8c43dfa3b1ce0cc4d7b67236e63a20ec156117e0893b8aaed7d9b5c9d3751589e38591533c944049bcac38a7585d4dd3016ce70ac1f0d5a4d656e611d87f5ba65839aedabfb1820ce60e2e02c33ba16e02dc21ec349c1da32a54a460baea2a73f43cef1d8877d9320098dd543f786bef664ba8216d263dca871760926a405a25caf226b85a95382386fa5a2144723c3aa2ceee4a69d4f3f8473facea0fdcb38c5f8a83ac087301c4e95de8023351a258870f6fe4be5f9e713fd84ec28fdfb1737016affe39b29daeeb8ddedb5a1025885a7c77e54c4c556944bc6fa0dcfe1147d8be50c262970e645254e38ffe1b4a2c8583cb4769b313e37b86ba2c572cbb374d61ff294c62c9320f28af2e2ac62ea6d3a564f0ba53e79d4f870306cc8c524ecc2f13cf9eab683a483f65aa99249ab8437ed759b664cf71123ab2774892680c8aa0948e0f4a320a45583d672a4d70b7ac3e2fd42302a6f572fd5d5873f80f68b6da6a47d0e3361cbe30071345406e992439911a20df8c82875992ca4a45f8934d3e45ad202341a58520c12ad83e22da9161588688aef5483c9f15c9d8a0a5c7c4dfbc9fc0369d6727fc4ac6e532dcbb8bec20a552c4acfaae9db33fff348041ea2887c0e8d5c9b798032ada7a2c0bfe814e2535fabf01bceac486106f16ea2df69d2eaacb7d4b02e50c3352c821eddb503075278ee4eaf779d6c3cfd2b77c388b97c66abe59b507a111de824211cb57c1ce7a8fa25309061cd3847ee67508bb6354686057b3518a3a33de389919398a6f5609779a5cf709355fcd6be7ed78c5c83951489ea7cf4fbb7c3ec210abca5263736dc462b600132f206bcb3009c6c261769745b7a9d5b691ffe858bae747c710217de7ba66431526bd6e63fb9d6f36e828d88b32fe9212ede700e41d94040a9312961a4071907c2161cf9e5b15b426aa25074ee0363563e1e215a3c2b44173e27704ec8e788ea6b1a9ec4465ac6d74b78d6564cb67ba378f419a0d65214a488ad2120be6b72c58054580cafc3ab2b6224b23af3c248920bf601285f2b9cce1793904b84e8070315c4479eebc003b4f3cbd37c1f0895614d22950695c864ddd8ea4ca20d472f44860a991969d797f5d502bac7d8797639313afa2244f43874c3758cc4f7231500d8bf1052f7e0d370650f946beaba47dc78ed963019f7ed67e93b69993d0947b159d4dd0eb88526242a3542749dbaeef1fa1361db6e7a9193b0c2d0e3e5e6f6fb232580b2b8c111acc3fc233b8d90e500000000000000000000000000000000090c181e2227").unwrap(); @@ -73,7 +79,6 @@ mod hash_mldsa_tests { let sig = HashMLDSA65_with_SHA512::sign_ph_deterministic(&sk, None, &ph, rnd).unwrap(); assert_eq!(&sig, expected_sig.as_slice()); - // HashML-DSA-87_with_SHA512 let expected_sig = hex::decode("54b340f2f8318713194c4a7fa5381a4bc09af874ce020573c2da381b156b84643209a5506c2d31049cc71db7552acfd32185fa5755b4bcfbc8570c148a15e487a8be1ebe7f7cf735689cd49ace98831144dae634d50ffeab8fca9963077ff25f534f23af50d86b9131f4293fe638452389d1df255bb9a9e011cb6987f45f4a51d85d907b839378d2fa1d4ac23f6736eb2941d9faca5c174025cfb88ca43c24ca0d774391fd02784a7e0b1064f9e9f5b736ad0c9b9b3410c15a2c24dd178a7ff04d52b4985a7fb2c6375002546667cbc79c759b61f54087c3cf49d1c5014f66d4d6b1bbe7b30a5dc26bd4d667bf5f970452806bd43c8e195aa7219f073776c4a05fd886197158fc694a5be8a057d57ce499175672000d677c3c20f4b814cf3f340335127dc5c7d9359eebacca82386ec1ea0c4b8735151437ef7f33b9f276b5d95344305d336798936a1edab987ff50569d9410aa245192d1d71732b2e8b90ec275ef7c293bb982bb73cf1b46dbb585daa4c89feca3b197c8cd29eec186b1a724f16deebf498e6a22deed4b47c980bdace1a05fe7f9c42fdaea8caf11a9d4a84949a4a3be6c7fe6e8c0cd721fe238558bb158c5a40203024cfef40f27afb216ba61dbd3af379b86bf675f6cecc6cf5acde4e76b63b62b6760802ba7497689bf24580659b17c15195bb9b8f55547dfdc04171eae5356817800880bd47de17396171af06408b73f87c8f9ead1124c13899618518eeba5eba9ac0fc39b615611d25ccb1a11e83226d42bc3bbb0eff64cf6e666227c2a3d551edf1e04565ea33e5b8c8c9885e153dcdcb3608e90ff2251700bcd2111bf5e292c29f69dc80a4dbd47b9e0368ecdf0925dba0d27c9b6f82aa6f7ff7e8385d3d0017b5ca65e039c2a8913bece73baf10a413be5de60729b29614d1c35928b91831ff2c1e6d24be82b56e446584c2a2a18ced134676eb887a9bf0026934147e4f70b9d30ea95b432301e2280981a6a8628c9d52ca5f91f5e2e20b58ee09abc035e079b508b565b7de0d221c80c022f83e5835747e2b5de4d5070af101657e253f3e26caa1238462a151149e66ad8d435fde6fe0d08bf5cf9ef9ecbe280ff5715f03401994cf37d25aef7e100db66c86ea84bf738145e2e9a13317bf7649371d6c21f79ea599e86e2f25879e512b5b7aa9f6e84e85eb20aeedfafd605d69a674c662e921a54c28110fd4b457f8058aa1c781379a6720e420a7ecd949c2027ce629808c9ab26efc951e3a1fd3ff3241303376191d3d9e1409ac56f6871e13a012f65d80d4f566133919ce3372c47e5be464cc70dabe9fa201fcdc59f8e8c311f8f43d40bbee23d563da37f82b1dd7feafd112020d5ffdd3301e440722d5bda145048a52d10b43418cdef434901e3fcbb2e931352c2a675bea9bbc5d6eddf96a0ebc7d71648f211bcbe02ef26a22f489428dfb38473be6ffc7cb2703c3e4aaa9c27f6e0b933638ff432cb91146180e9368aa2cce8416768a3fe8984ae3910f14888c64cf3f1e1222904831aec32b6d3b2d2244218452c44441e20d27f342b9cd60d603f0fbb7879752cc4553e6dc9176515b90bc0d933d6d4da2c405e5d4475798acb57e419c25f2d7af10557ed48e1f493ee55303bbf5824ba7447f4f41e2767da3d288d2beb067181cdf1bb98664608776134c351561e761d222c8906df68a04de76b99358d76b538db244cba491000c6dd61373f3ce6d62fc4f4b158ccf9f6260ea02e19b9697d946d21264846e4cf7d78f5fc0e9de819ef654c3e1d820ccdf45cd0a600806813d5ffdbd6f0c64fd1928df7d92f21b20a6593a71d418a0a480a7beca0abfc3cb732cb93e3a0ed51a06b5cfa782601ee6174a35ff188a7af57426fe0129a1039d76c9b6d523873272cc7cd90f042948bdaa4f2b690be247564938f185c51ae63a6e54e618f6787164fc09275d4b7b201d06de3bd4b57c4909c0c901f39742a6f5301d4c5a2d8e314b5009082bc292a9b39f61c45c88160f93ef2988ba9d7a80d5f52360e331cc828100e17af7ed4aa3491298355bae804e3af7418299bab44f09b171562ae098b0d507b0bfede06f75fe6daab51f6dcb9ccbd38da9b123dc6cc5b775399696bea6c5768e875d341eb173aa6d63d85f150c5039d843234c67c3cdf1c101b3b019711adb42acd8ed03ae6c7aa44c35a845263877023a3b7b4679c4797f43e4980f27e18e30e0cb088b8e9586aea971fdaf6bfb9ddef3ae7598932cd4b15bf9be8c0c3778f7aa609c86d583f7facdaaeb41404a07d3bbe225792e76c2ef86dd68ba3b945d7b849cdf87c2ccef534597464901fefa1441723d601ade276380b97d2426fcd19fc90c09aac5852599b955f1084c79ade067bbf764b4d2ae597e8b87a600e79f84796a12f61047568612ea279baf9e3feffa3d16de35ac00aa010c551ebf7c27f8484b1cfcc47959ad20ae180c92cc121f0d298bbac47ae482d4fab23087df6fac3b8e026c39faf921ba1e53d6a776832a4b96da9875a682d626399ded7f577e1e88011b9e14a2ed73d3d7db2ac67844e3017613a237991fabd91b7df66948e2f8bf1324dcf45ac3fc2983ab749c62ea5bd1db3caf8e7958686a1c7b7cbeaa88d4bacbbba0ea21b74c9d47fdc61df30fe021adc5129d3b82a7c81778deb908b3f2bed5519c124ec1395a222ba7e54dac2df1656c318f66546485d1037c34ae70ee64f678ca6a1f1930e235c5a14658e98dba421e0c3ab342d6f2045c35d70bb712ed9a7890f9471c43c376ce8effe0f4f263ab307973557c38c2b6dcc137c9f7b42048f78acad85f0d5ee9efc570139475628bc6a02c4ff431bfd4c9d9e7307ddf6bf348ca778618fde825f1f8bf286e70a2bb6d784602a83400cbae753b9fa35f59bf9770a26dd8c04cf46a7e5e9435ff26ab3719f8ca685ad321de859d3a56819d47ceec489e9bddaa85ac0fd75b831b96b374b9effe1425c4f6ca49a66858b5563033bb91a383f91a85220c088209614975bf509a91a58d36fd027425d01f2516cf87a50f7928981b1367f29cbf8ca313e4a7ce130bbcfe95775ff3b410c0c4ccb0b2e6ff154235ecb28694c16ea728ffc0a7bec0b84cf01b0605f1d32be08c70a794e73dad1c5a1498e8ba9bdb020ad7beb959a0c70b93e3c02d8d068611b2465e096f9b08acc49b90bf697b197f1dd00b0452a06ffecc35627e031143e9beee4b292a130f0e526e82eba33be227e813c3ad47acfba9399418d2910ece7e14db125b5f1e7c4431599024ffa3e82d020702d3f65338b9df5fb03c0795b892a8e0fb494bca5f0127bafa303532524baad8031259511d863ebd8d58afe810b37a612b99c4514bb5d0d22615fa0ca833804c7bf06605720f3f18ff3d257678a4ed048f6c5c21897a9c810192d80a24696a0a99a88675352b934d6d4ef884ef5702927d499525744ce004c56e5d231f5bd1520e679cbad48741cab40707bbd651cb65940f635b83c94a76c462418e6059919fdad24fd55c32680848f97ba89ca383506ce8b5b1d0165af06e0d7b51694aa5607f1dd78cdcaaf87f700019fba62ba17f6609b993a44c839210fb266ea437bf88c69cf7dc3159b93dd30e995249094b7460670892f2da18d47e34a01b212277f5cfd8aaa07f090c3390ab7a56425c322cb130fb7c5bd7dfd602ad7c586a21ab0bc03a89ec49ce08f1cf920cf25d07561996673ea5f261e122616753d0a49fb7416c4dd8070f5a95b9c0fe6d11abe1f8aaec1c620ac3033ed6d1e5c29f773184f9f10790bae64d1f6a21bff248c6773acb075827e7193fe9d40584b6533afab5cb1f4c8a7d6c4adce7a2e634ef907020f06a13be3c8586fd7e65a745c345b3efede788b8c04623872461652f0b04239c19b5968c23e1a3e41529b7c58690bb9d749e6d462ccef13baaceeaf6013f9538e82f47553b01011b3e599b8ebabd150c1f5776193d18dac011d47e7da75c2c2e28e96105d20ae7d2c77f0eb19cfae34a8d18580bec72823d2f50122f06de21c6f21859fa90abb2a4f6bd07b377bd40c0e93a84b16f4d0b469d5d0168b3b632c3006ff6432331cb4da52b2f69e5304905b1903a6d1cc2305296107148eed143d9ae5069757cd91541b83658e82370ea6ec36b9811e6c35081e11c1380e59315f9ad94cc25087c071429c0e92fac8a2c1a53dd52ee18ebd281deef9048225572c765de65e50d6a3c9f4be09bc8617c9b7666c0322b73d81c333e50f8ba470ea4c7ab0b2c2aa972eb67a68a0e7fde93e75b09d81558fb61adea9d7f74a3532baf1b099d78e35402e95968f325331118c69c9176a9e90b11977b2ee7b4036c702e92894aae27da2c2074191e791db8fae4f85245fff7375519ba86627cbb38489d2afce34b0a2f76e52c2dc828b139b79ff9e5dff83e14b294a839fc8f688a60d2893eee9c9425d2fd49912476d05222800b936e3458b971c870f4c6cfec89ca70e91e5000a8fcc84f763cadbb56103af4bdea1276bccfbb228818f76667f85f63bc10b22105b2067c52ab52fa513bad6fbb0b1462f11b8cff7231942765f2e4eb225a73af6ed4873166badee6e8a2cb6ceba09691a0b8c0da29946b82fd07e4f484e66fa89b3912283a9c877d4b40e6c9e9ca256447c9d2d5c6b40790694706d6ad88f0da66ffab7cade6322989e8197c3d3bdac3ccdf16a4f59fd02f0ad3ffe3bdc77f8d83d21491808de9880111a91bee85225ec89f5d0f585c82d0c5dbe448a7fb1ab79d4a993aedae36a02abf5970970971eaf674761d299be56177f17c3889db7a4ad539df3d4f0a4f5a76212809ad3e18124e24799a55e6c28ceda011120bee72b695a17268913320a40201fef32e554e8c90d78eedeb90b44769fd54ffd1acf58135bbd5d927f82a99a747cf1503e9702a0fa72ddabf25e1f67948456947bd98aaf04136aa815901d94103c0377f4d4dfa1e9c3075d2e63f0f0f9167068c1345786f3c159ecdf694c1509da518e26bb22dccf6c14b4bc13b75ab00f71aca9b09b25247497c4dd29af1484290bca60864f7bebcc69627f4a3467e29751a01ff0a4e7c1fbc363b20ecec477061a9cf326823aed9adc396f4f12d15cc806f6e14b5ceca6b0dd3729c60e91ca7b34fc9368aaab98111fb8b33817e843090b5c8418cd9617f2e61f238f0258ddffb13413092cb67dfbb04188e8770ad09753deffa9e83f2ba3d72efcd5b8c8cb1ff8910eb53fbdd96ef6342f358f5c3d08f5c863123f6de0454e8c358abba2bcff97c563ecc59175f8314ffbc082ca893d5383dceacbda304450c7c9553f5aa0f1e0a41b8a50b1f03d689904f446cf56f2ca1202bd24f153c06efd63558407318d12eb933a0d68dbf9164e1a487161ed78c58eab5649ad84fb3b42494e797fdabd19857b786177deeb3b84896f0f31f4a71041bbdb39c2723ea6a82fb61882c4770cba23f3d009220f2c15fdd6ea0fa2d473f41c7d5527d498e131435b11844d3f285da458c86d80eda63609a6fd83b0ed004f7f553e3a2787922c881139e2f40819f4a1f29dffb7b42bccb38238b17fc63c18d2931c2c6bc45a418eedd10c3799ad5a993a54647b8537752fbc456e00330fca1dcf249c4614cd16715521a98503f80e4e97b36015fef9bdf38424a9ce2ffd9441c8cad34757da186f2ba4ebb39c664e33a6fc9df721da3b94e2bc9ff9cab1dbfe5aa07a7c7c7a715b8bd97997578428e9c89a0c8569049f942f54a0a63a000b412849a5897d2fa8b79c479bdf17e9ee8869c8e62c952c91b9c52acfc20a78993d9f93ab523975beecc1d52c01dd18b88821719012979c21818143adf4e89b60a4f21631e0023c6c25c9236d9ccaea6c44eae4507bdcffdb83c719005293e5f0d4232f3d3497b63b6f5464d78378a65b5cf677a4dd2a3f9c072c101f0207ecb99ddad33c333ec425d3f4065b3c02e9b441dfe7b15c2647f5ef6074b0c67ddde60c6d82e9f7d5a60fc429fb77b433ec71305e9939ae60ff395f2a05a413ae2cc5d6bd78c6ff2cb38334e0dacdfa36b247cd21bfbae48c8e9ac6d3012067c9721bcb946acb2bfc8004f08b5c69f7ef86bb2fb982e2a0ffc1d408c85819500eac5d02d13832b5edaa3bcc9477d4749eb7929cc66e8a47d5378b268ec26832906be7ceadddefa1c449fadda6b371950dc66343e27c74b62fa265cf7a2e9c81b19cce51a1d0b72c03dac45b825c2569841885460ccccbde6e9fe88bdb2acacbed6f18e7e77c1680f8ab792f2d1b08d67e36c194947e22f6862bd24003d1e139f0fcc2a7784ae420e3e03943e3de2d6d6627ee3d6ccfa4c14155ff8428a2ef31b420df2988f8c77072cf53ae91b3ca0f7f42c14b8a88b2e2ba94a55834dd2da785d6b2095664e4cbf1b94796aac2c0e3a1ba3c070804336848a0b5e558a50812b0b1fc0207769ca4db0a8296a9adbabbbddaf417193236698390b0f9fe0d192d404953ade7ebf2415998dadde9ea153b3e757ef52989ced5fb00000000000000000000000000000000050b151f2930363b").unwrap(); @@ -85,57 +90,147 @@ mod hash_mldsa_tests { let sig = HashMLDSA87_with_SHA512::sign_ph_deterministic(&sk, None, &ph, rnd).unwrap(); assert_eq!(&sig, expected_sig.as_slice()); } -} - -#[test] -fn test_streaming_api() { - // I don't have a KAT, so I'll test against the regular implementation - - let msg = b"The quick brown fox jumped over the lazy dog."; - - // ML-DSA-44 - - let seed = KeyMaterial256::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), - KeyType::Seed, - ).unwrap(); - - let rnd = [1u8; 32]; - - let ctx: Option<&[u8]> = Some(b"testing streaming API"); - - // BEGIN expected values - let (expected_pk, expected_sk) = HashMLDSA44_with_SHA512::keygen_from_seed(&seed).unwrap(); - let expected_ph: [u8; 64] = SHA512::new().hash(msg).try_into().unwrap(); - let mut expected_sig = [0u8; MLDSA44_SIG_LEN]; - let bytes_written = HashMLDSA44_with_SHA512::sign_ph_deterministic_out( - &expected_sk, ctx, &expected_ph, rnd, &mut expected_sig).unwrap(); - assert_eq!(bytes_written, MLDSA44_SIG_LEN); - HashMLDSA44_with_SHA512::verify(&expected_pk, msg, ctx, &expected_sig).unwrap(); - // END expected values + #[test] + fn test_streaming_api() { + // I don't have a KAT, so I'll test against the regular implementation - // test the streaming API from sk - - let mut s = HashMLDSA44_with_SHA512::sign_init(&expected_sk, ctx).unwrap(); - s.set_signer_rnd(rnd); - s.sign_update(msg); - let sig = s.sign_final().unwrap(); - assert_eq!(&sig, &expected_sig); - - - // test the streaming API from seed - - let mut s = HashMLDSA44_with_SHA512::sign_init_from_seed(&seed, ctx).unwrap(); - s.set_signer_rnd(rnd); - s.sign_update(msg); - let sig = s.sign_final().unwrap(); - assert_eq!(&sig, &expected_sig); + let msg = b"The quick brown fox jumped over the lazy dog."; + // ML-DSA-44 - // test the streaming verifier + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), + KeyType::Seed, + ) + .unwrap(); + + let rnd = [1u8; 32]; + + let ctx: Option<&[u8]> = Some(b"testing streaming API"); + + // BEGIN expected values + let (expected_pk, expected_sk) = HashMLDSA44_with_SHA512::keygen_from_seed(&seed).unwrap(); + let expected_ph: [u8; 64] = SHA512::new().hash(msg).try_into().unwrap(); + let mut expected_sig = [0u8; MLDSA44_SIG_LEN]; + let bytes_written = HashMLDSA44_with_SHA512::sign_ph_deterministic_out( + &expected_sk, ctx, &expected_ph, rnd, &mut expected_sig, + ) + .unwrap(); + assert_eq!(bytes_written, MLDSA44_SIG_LEN); + HashMLDSA44_with_SHA512::verify(&expected_pk, msg, ctx, &expected_sig).unwrap(); + // END expected values + + // test the streaming API from sk + + let mut s = HashMLDSA44_with_SHA512::sign_init(&expected_sk, ctx).unwrap(); + s.set_signer_rnd(rnd); + s.sign_update(msg); + let sig = s.sign_final().unwrap(); + assert_eq!(&sig, &expected_sig); + + // test the streaming API from seed + + let mut s = HashMLDSA44_with_SHA512::sign_init_from_seed(&seed, ctx).unwrap(); + s.set_signer_rnd(rnd); + s.sign_update(msg); + let sig = s.sign_final().unwrap(); + assert_eq!(&sig, &expected_sig); + + // test the streaming verifier + + let mut v = HashMLDSA44_with_SHA512::verify_init(&expected_pk, ctx).unwrap(); + v.verify_update(msg); + v.verify_final(&expected_sig).unwrap(); + } - let mut v = HashMLDSA44_with_SHA512::verify_init(&expected_pk, ctx).unwrap(); - v.verify_update(msg); - v.verify_final(&expected_sig).unwrap(); -} \ No newline at end of file + #[test] + fn test_boundary_conditions() { + let msg = b"The quick brown fox jumped over the lazy dog"; + + // ctx too long + // this is common to all parameter sets, so I'll just test MLDSA44 + let (_pk, sk) = HashMLDSA44_with_SHA256::keygen().unwrap(); + + // ctx with len 255 works + HashMLDSA44_with_SHA256::sign_init(&sk, Some(&[1u8; 255])).unwrap(); + + // ctx with len 256 is too long + let too_long_ctx = [1u8; 256]; + match HashMLDSA44_with_SHA256::sign_init(&sk, Some(&too_long_ctx)) { + Err(SignatureError::LengthError(_)) => { /* good */ } + _ => panic!("Expected error for ctx too long"), + } + + // test various things that are shorter / longer than required + + // sig too long / too short + + // MLDSA44 + let (pk, sk) = HashMLDSA44_with_SHA256::keygen().unwrap(); + let sig = HashMLDSA44_with_SHA256::sign(&sk, msg, None).unwrap(); + // too short + match HashMLDSA44_with_SHA256::verify(&pk, msg, None, &sig[..MLDSA44_SIG_LEN - 1]) { + Err(SignatureError::LengthError(_)) => { /* good */ } + _ => panic!("Expected error for sig too short"), + } + + // sig too long + let mut sig_too_long = [0u8; MLDSA44_SIG_LEN + 2]; + sig_too_long[..MLDSA44_SIG_LEN].copy_from_slice(&sig); + sig_too_long[MLDSA44_SIG_LEN..].copy_from_slice(&[1u8, 0u8]); + match HashMLDSA44_with_SHA256::verify(&pk, msg, None, &sig_too_long) { + Err(SignatureError::LengthError(_)) => { /* good */ } + _ => panic!("Expected error for sig too short"), + } + + // sign_ph_deterministic ctx just right at 255 + let sig = HashMLDSA44_with_SHA512::sign_ph_deterministic( + &sk, + /*ctx*/ Some(&[1u8; 255]), + /*ph*/ &[2u8; 64], + [3u8; 32], + ) + .unwrap(); + HashMLDSA44_with_SHA512::verify_ph(&pk, &[2u8; 64], Some(&[1u8; 255]), &sig).unwrap(); + + // sign_ph_deterministic ctx too long + match HashMLDSA44_with_SHA512::sign_ph_deterministic( + &sk, + /*ctx*/ Some(&[1u8; 256]), + /*ph*/ &[2u8; 64], + [3u8; 32], + ) { + Err(SignatureError::LengthError(_)) => { /* good */ } + _ => panic!("Expected error"), + } + + // sign_ph_deterministic ctx just right at 255 + let sig = HashMLDSA44_with_SHA512::sign_ph_deterministic( + &sk, + Some(&[1u8; 255]), + &[2u8; 64], + [3u8; 32], + ) + .unwrap(); + HashMLDSA44_with_SHA512::verify_ph(&pk, &[2u8; 64], Some(&[1u8; 255]), &sig).unwrap(); + + // sign_ph_deterministic ctx too long + match HashMLDSA44_with_SHA512::sign_ph_deterministic( + &sk, + Some(&[2u8; 256]), + &[2u8; 64], + [3u8; 32], + ) { + Err(SignatureError::LengthError(_)) => { /* good */ } + _ => panic!("Expected error"), + } + + // verify_ph ctx too long + match HashMLDSA44_with_SHA512::verify_ph(&pk, &[2u8; 64], Some(&[2u8; 256]), &sig) { + Err(SignatureError::LengthError(_)) => { /* good */ } + _ => panic!("Expected error"), + } + } +} diff --git a/crypto/mldsa_lowmemory/tests/mldsa_key_tests.rs b/crypto/mldsa_lowmemory/tests/mldsa_key_tests.rs index 4117319..4919fbb 100644 --- a/crypto/mldsa_lowmemory/tests/mldsa_key_tests.rs +++ b/crypto/mldsa_lowmemory/tests/mldsa_key_tests.rs @@ -3,15 +3,22 @@ mod mldsa_key_tests { #![allow(dead_code)] #![allow(unused_imports)] - use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; - use bouncycastle_core::traits::{Signature, SignaturePrivateKey, SignaturePublicKey}; - use bouncycastle_core_test_framework::signature::{TestFrameworkSignatureKeys}; - use bouncycastle_mldsa_lowmemory::{MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyTrait, MLDSAPublicKeyTrait, MLDSATrait, MLDSA44, MLDSA65, MLDSA87}; - use bouncycastle_mldsa_lowmemory::{MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_SIG_LEN}; - use bouncycastle_mldsa_lowmemory::{MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_SIG_LEN}; - use bouncycastle_mldsa_lowmemory::{MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_SIG_LEN}; + use bouncycastle_core::errors::SignatureError; + use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{ + SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + }; + use bouncycastle_core_test_framework::signature::TestFrameworkSignatureKeys; use bouncycastle_hex as hex; - + use bouncycastle_mldsa_lowmemory::mldsa::{MLDSA_SEED_LEN, MLDSA44_FULL_SK_LEN}; + use bouncycastle_mldsa_lowmemory::{ + MLDSA44, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65, MLDSA65PrivateKey, MLDSA65PublicKey, + MLDSA87, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyTrait, MLDSAPublicKeyTrait, + MLDSATrait, + }; + use bouncycastle_mldsa_lowmemory::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; + use bouncycastle_mldsa_lowmemory::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; + use bouncycastle_mldsa_lowmemory::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; #[test] fn core_framework_tests() { @@ -25,10 +32,16 @@ mod mldsa_key_tests { #[test] fn encode_decode() { let seed = KeyMaterial256::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), KeyType::Seed, ).unwrap(); + let expected_sk_bytes: [u8; MLDSA44_FULL_SK_LEN] = hex::decode("d7b2b47254aae0db45e7930d4a98d2c97d8f1397d1789dafa17024b316e9bec939ce0f7f77f8db5644dcda366bfe4734bd95f435ff9a613aa54aa41c2c694c04329a07b1fabb48f52a309f11a1898f848e2322ffe623ec810db3bee33685854a88269da320d5120bfcfe89a18e30f7114d83aa404a646b6c997389860d12522ee0006e2384819186619b260d118664d4a62822184482402898146148a6614c4248a19208c2382951244808a125c2083108c47120140914836c18a78084106ec9c07022b56408b0610c070498124451886959004622932041062e42b64c01164914284c41a85180460a5116515a0820022244dc9849d13251e13065d3c08592a85112a1640039220946621cc70cd9086dd0062652408580443091062c50c80924c5841a966d4a982c99066da4443220a7645a326e11b57020926124138e04852c0a4872c8a051d3082a99208058242024074e59148810a46460c06de0b28d1b1909203422c024410943710a212061a2015222521b80809a340013934dd3322922170a9892691a14512027219cc02062a2814818691a854d8344695b2041031242cb184601a90d0c023183b0215a224ac89205d9906904306a4b064ad2b2011c404081423252327254a6405a18100c321292c2805212625c82280bb46c03428d53100c14010ee1365288842491020a63462620062911c228d0204802b36ca236095a8648cbb4618b4662c440821a890910024d24b24520122524c90588288cc9c04d5948220a276ec134644c90605b445082864943880443b28c603080a2882d84a46d8ca629d0c68442064689885100a98d01498de4380da4068dd3947142b26c1a84611ba32842b42808a0711ac531e0a04c013765242862142890091061d940221b3360090292d02481200408491844a3222d5c8844149808a446610195640b390a0c9450ca406ad2b220c0380182308e13b908918084148829c0189112350da02422e20406d9c2850428121cc989180272d24029c20812d8062a9994719bb8682384291a2289144511dc82445096450c4484c0b2049aa60543862c44326e88442120a84c9a3070e3b82d63268803254903438c48a809ca147253344e1243081ba704593022d99480e234228142129c302a9434266104452426281346094a326d11280918b82562281113410d41b21190844c8b1212a2c688c9c030220606d2188e848630904452128831d9207113c52843060e033060cca6845826524c88011ef72562c85ffa43acfa49217f2b172d7bbc14620e6d980a71aabbdf0c45e9a206ecb1423fee15decc17601300149d9223cd6e6c6e1fa8e41fc7c64938ab68905fd3dcda50d87082e7d0d71d1bc9b2b84c85523ca8fe6cad294adf83be15b108ff721d0cc87bc3dd3a7590184b0e845663a91fc9e1c3c53a61d867420b04f092355753bc65a06368fd41295fd09924132c6f91f67964c142674a725c343914c4cecf58c074bcaf4558c97bf7911e07aa6d0938f2ee2bb3c1a8c595d635e84342fdea01dc24b211ad2fc281cf77e59110c7abc54bf0c86d480b9be276471dc9d603cee98cfdab3e9fcfb703793560549ea4450fa7b33fb9169c44b4d25fb9c457f49791cd3da03eac96095813c105132ccda4e63e49228cd23d8a1f37856f142d93b90db09f82af89258c63aab8047a80c036c9357ea2046f8dc6354f0c5295f342bb417d3cfeb0b1fd33622c29e14cbbd92e1363c65ebd4504b7512329b9670e32e1b2c67a54e7f1a55f8b9f9ea04e8ca3a705e62a3c5e637374afb7aeb6ddea612cde28f01a202d7aa4e34722d27dd3f9b89894d019fd5d4d7119efe3723bba104cb8bb0981e074de3afe200daaaead826cc45f244dbf431afab34efbdf782474d2fd57118f646214934ed99cba3b003e8d67a3836f6f19fc41910ce5163ee3ae99eb84d514eb761e63684ea56f9791d2dd4aac6e6168b948c817f75a222acb0e8cdc03cc4afe8f67157e1a363b7faeff9f172b98913677c5a1dd085e9ee4c22052c1af58193116673dcd3bfc5f34b855dcc6c77885649e9e71f43d4aea0f4b72ca7eda0578ba13d31a658d2d060a9a66ff69ed1be7997a2fb1d2723d38f9bfabe18f8e7b3cda906e4e9b5e942c8eaeb296070ebfd364947a940cc978bed66b37749e6d5dcd7be8c494440e2b84cecfefb98c0bedfb3c41e3359d2cd7197fbe720c48aa6c6b6465c1ee63e3569c2adc744491370b7f7826fe0b77a1d19d64101d032b918106b42d2ef73747e5601fe4ba50f23ede521f031a817d15294a43722e8378784b6db0cf1ba9e8ae911d9201b9ce9cc3019c6f5c27cb98da26144b64225a7c932b30f761e78a2d59a1d8b83ec6344a2f6dd47e765706d00bf4a79a6a926c3ba91d812c8f2c797ab1796709e5d16856778293529f0286d015c3b5399619642a333e9e593d6e3f5353994208e9e6a332851d7f652522a928b917e27e2d6d42137dfe2ebfa6fb1c67b26c0254528685f7ebdbe315a68eaa2da769e8a9f42d3e60007c71330926b2c0012d83ead4e4fd1ed872ccd1972201d2b027f3545ac2d30cd78bc1d740feccbc6fc2a0446c6e30eac51f5a69098aa2d447f2085b4e4e4b92ccc26921d2de478518cd090ce267aea2d27ada57fd88b4976d89fb843cdccf49a76ca2679e6801bfa7fb031896fb50629704b9923936bb5dd385311121cadfb11995e59b73034cf67ed03ab813867648d025828087e949a9afd16b95d72d99b1edca257aac132ffb7a0709aed5a9c0ff05fb0f2bbf28409eed7b5f5801be964ced019e1cb7851d3851f10290674e19ffb008b301c4acf641a2bb14216e1d69cabf52b5ef227496b0f30799a855d117fad3744a6fa33503ea798b52ddd7ee5426609dbfcd3f0c13b164d6c051f7ed4a119719a712e388d328402081ff1354b554d2c237afed3b151c4ba8e9f4bdeb8499a3066e26bbc69e8af089dec71731d1dc529eab17ef7374734c0fe475494c83836bdd34a03b9bc89914716061bfb98ec6e61c3ed4438edcaf25243c647086b9ea7018b0d9a8a0b00cecb00abde2498d69c2336101a772cbe4f571523f51bd05882cdf358b849cc140aa1faf22423a12851ce0e33fd48975a4959fa5c5fe418c93908191ab6e741b77bfe02cbd698ee795c466d615619e6441382c6eac01834ee9ab73cea80bbe235c78da91bd79b6f82f899785d68700d393e675c2224d6b7a1ad21320495679adaed70167b50866713a53109db7b6f7d81304ecdfd83b319b1ef248306b45ad29e7ddcc863dac56048b5d69ea175011f7614c00a86a863cde1872a8932878b9ac7e1ac5bda4997b72064f0cd75f4c814e034de11acb9013cf7ea926b4e7eaace070c7ba2188efad2e431e1223d45dd05c4d8403c2e45cee6413ecbe7527e873e455c4e610a61839aacc0bd56d2483e78f298b66a478eb2f558cbafca86be847baeb02c5b216c8cd88fea4df249b09e670a20703abac24b0a91abc4a5646601442ba10becfd30993880051d07f56a05a9379e7a8e6befee3f22faa106398f7706006e42e9be1ef89d25c272f11a95095c587d713732284de9dbd3c7217b0689e21d8eb0ff69668").unwrap() + .try_into().unwrap(); + let expected_pk_bytes: [u8; MLDSA44_PK_LEN] = hex::decode("d7b2b47254aae0db45e7930d4a98d2c97d8f1397d1789dafa17024b316e9bec94fc9946d42f19b79a7413bbaa33e7149cb42ed5115693ac041facb988adeb5fe0e1d8631184995b592c397d2294e2e14f90aa414ba3826899ac43f4cccacbc26e9a832b95118d5cb433cbef9660b00138e0817f61e762ca274c36ad554eb22aac1162e4ab01acba1e38c4efd8f80b65b333d0f72e55dfe71ce9c1ebb9889e7c56106c0fd73803a2aecfeafded7aa3cb2ceda54d12bd8cd36a78cf975943b47abd25e880ac452e5742ed1e8d1a82afa86e590c758c15ae4d2840d92bca1a5090f40496597fca7d8b9513f1a1bda6e950aaa98de467507d4a4f5a4f0599216582c3572f62eda8905ab3581670c4a02777a33e0ca7295fd8f4ff6d1a0a3a7683d65f5f5f7fc60da023e826c5f92144c02f7d1ba1075987553ea9367fcd76d990b7fa99cd45afdb8836d43e459f5187df058479709a01ea6835935fa70460990cd3dc1ba401ba94bab1dde41ac67ab3319dcaca06048d4c4eef27ee13a9c17d0538f430f2d642dc2415660de78877d8d8abc72523978c042e4285f4319846c44126242976844c10e556ba215b5a719e59d0c6b2a96d39859071fdcc2cde7524a7bedae54e85b318e854e8fe2b2f3edfac9719128270aafd1e5044c3a4fdafd9ff31f90784b8e8e4596144a0daf586511d3d9962b9ea95af197b4e5fc60f2b1ed15de3a5bef5f89bdc79d91051d9b2816e74fa54531efdc1cbe74d448857f476bcd58f21c0b653b3b76a4e076a6559a302718555cc63f74859aabab925f023861ca8cd0f7badb2871f67d55326d7451135ad45f4a1ba69118fbb2c8a30eec9392ef3f977066c9add5c710cc647b1514d217d958c7017c3e90fd20c04e674b90486e9370a31a001d32f473979e4906749e7e477fa0b74508f8a5f2378312b83c25bd388ca0b0fff7478baf42b71667edaac97c46b129643e586e5b055a0c211946d4f36e675bed5860fa042a315d9826164d6a9237c35a5fbf495490a5bd4df248b95c4aae7784b605673166ac4245b5b4b082a09e9323e62f2078c5b76783446defd736ad3a3702d49b089844900a61833397bc4419b30d7a97a0b387c1911474c4d41b53e32a977acb6f0ea75db65bb39e59e701e76957def6f2d44559c31a77122b5204e3b5c219f1688b14ed0bc0b801b3e6e82dcd43e9c0e9f41744cd9815bd1bc8820d8bb123f04facd1b1b685dd5a2b1b8dbbf3ed933670f095a180b4f192d08b10b8fabbdfcc2b24518e32eea0a5e0c904ca844780083f3b0cd2d0b8b6af67bc355b9494025dc7b0a78fa80e3a2dbfeb51328851d6078198e9493651ae787ec0251f922ba30e9f51df62a6d72784cf3dd205393176dfa324a512bd94970a36dd34a514a86791f0eb36f0145b09ab64651b4a0313b299611a2a1c48891627598768a3114060ba4443486df51522a1ce88b30985c216f8e6ed178dd567b304a0d4cafba882a28342f17a9aa26ae58db630083d2c358fdf566c3f5d62a428567bc9ea8ce95caa0f35474b0bfa8f339a250ab4dfcf2083be8eefbc1055e18fe15370eecb260566d83ff06b211aaec43ca29b54ccd00f8815a2465ef0b46515cc7e41f3124f09efff739309ab58b29a1459a00bce5038e938c9678f72eb0e4ee5fdaae66d9f8573fc97fc42b4959f4bf8b61d78433e86b0335d6e9191c4d8bf487b3905c108cfd6ac24b0ceb7dcb7cf51f84d0ed687b95eaeb1c533c06f0d97023d92a70825837b59ba6cb7d4e56b0a87c203862ae8f315ba5925e8edefa679369a2202766151f16a965f9f81ece76cc070b55869e4db9784cf05c830b3242c8312").unwrap() + .try_into().unwrap(); + let (pk1, sk1) = MLDSA44::keygen_from_seed(&seed).unwrap(); let pk1_bytes = pk1.encode(); let sk1_bytes = sk1.encode(); @@ -39,18 +52,97 @@ mod mldsa_key_tests { let sk2_bytes = sk2.encode(); assert_eq!(sk1_bytes, sk2_bytes); + + // encode_out + let mut pk_bytes = [0u8; MLDSA44_PK_LEN]; + let bytes_written = pk1.encode_out(&mut pk_bytes); + assert_eq!(bytes_written, MLDSA44_PK_LEN); + assert_eq!(pk_bytes, expected_pk_bytes); + + let mut sk_bytes = [0u8; MLDSA44_SK_LEN]; + let bytes_written = sk1.encode_out(&mut sk_bytes); + assert_eq!(bytes_written, MLDSA_SEED_LEN); + assert_eq!(sk_bytes.as_slice(), seed.ref_to_bytes()); + + // encode_full_sk + assert_eq!(sk1.encode_full_sk(), expected_sk_bytes); + + // encode_full_sk_out + let mut sk_bytes = [0u8; MLDSA44_FULL_SK_LEN]; + let bytes_written = sk1.encode_full_sk_out(&mut sk_bytes); + assert_eq!(bytes_written, MLDSA44_FULL_SK_LEN); + assert_eq!(sk_bytes, expected_sk_bytes); } #[test] fn seed() { let seed = KeyMaterial256::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), KeyType::Seed, ).unwrap(); + // todo change mlkem to also hold the whole keymaterial? + let (_pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); - assert_eq!(sk.seed(), &seed); + assert_eq!(sk.seed(), Some(&seed)); + + // it'll reject a keyen with a seed too weak, and preserve the seed otherwise + let mut seed128 = seed.clone(); + seed128.allow_hazardous_operations(); + seed128.set_security_strength(SecurityStrength::_128bit).unwrap(); + seed128.drop_hazardous_operations(); + + let mut seed192 = seed.clone(); + seed192.allow_hazardous_operations(); + seed192.set_security_strength(SecurityStrength::_192bit).unwrap(); + seed192.drop_hazardous_operations(); + + let mut seed256 = seed.clone(); + seed256.allow_hazardous_operations(); + seed256.set_security_strength(SecurityStrength::_256bit).unwrap(); + seed256.drop_hazardous_operations(); + + // MLDSA44 + let (_pk, sk) = MLDSA44::keygen_from_seed(&seed128).unwrap(); + assert_eq!(sk.seed(), Some(&seed128)); + + let (_pk, sk) = MLDSA44::keygen_from_seed(&seed192).unwrap(); + assert_eq!(sk.seed(), Some(&seed192)); + + let (_pk, sk) = MLDSA44::keygen_from_seed(&seed256).unwrap(); + assert_eq!(sk.seed(), Some(&seed256)); + + // MLDSA65 + match MLDSA65::keygen_from_seed(&seed128) { + Err(SignatureError::KeyGenError(_)) => { /* good */ } + _ => { + panic!("unexpected error") + } + } + + let (_pk, sk) = MLDSA65::keygen_from_seed(&seed192).unwrap(); + assert_eq!(sk.seed(), Some(&seed192)); + + let (_pk, sk) = MLDSA65::keygen_from_seed(&seed256).unwrap(); + assert_eq!(sk.seed(), Some(&seed256)); + + // MLDSA87 + match MLDSA87::keygen_from_seed(&seed128) { + Err(SignatureError::KeyGenError(_)) => { /* good */ } + _ => { + panic!("unexpected error") + } + } + match MLDSA87::keygen_from_seed(&seed192) { + Err(SignatureError::KeyGenError(_)) => { /* good */ } + _ => { + panic!("unexpected error") + } + } + let (_pk, sk) = MLDSA87::keygen_from_seed(&seed256).unwrap(); + assert_eq!(sk.seed(), Some(&seed256)); } #[test] @@ -178,4 +270,4 @@ mod mldsa_key_tests { let sk_str = format!("{:?}", sk87); assert!(sk_str.contains("MLDSASeedPrivateKey { alg: ML-DSA-87, pub_key_hash (tr):")); } -} \ No newline at end of file +} diff --git a/crypto/mldsa_lowmemory/tests/mldsa_tests.rs b/crypto/mldsa_lowmemory/tests/mldsa_tests.rs index 78d3b43..b3eb8c3 100644 --- a/crypto/mldsa_lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa_lowmemory/tests/mldsa_tests.rs @@ -1,19 +1,23 @@ /// This performs tests using the public interfaces of the crate. #[cfg(test)] mod mldsa_tests { + use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; use bouncycastle_core::errors::SignatureError; - use bouncycastle_core::key_material::{KeyMaterial256, KeyType, KeyMaterialTrait}; - use bouncycastle_core::traits::{Signature, SignaturePrivateKey, SignaturePublicKey, RNG}; + use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{ + RNG, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; - use bouncycastle_mldsa_lowmemory::{MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MuBuilder, Polynomial, MLDSA44, MLDSA65, MLDSA87, MLDSA_TR_LEN}; - use bouncycastle_mldsa_lowmemory::{MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_SIG_LEN}; - use bouncycastle_mldsa_lowmemory::{MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_SIG_LEN}; - use bouncycastle_mldsa_lowmemory::{MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_SIG_LEN}; - use bouncycastle_mldsa_lowmemory::{MLDSATrait, MLDSAPublicKeyTrait, MLDSAPrivateKeyTrait}; - use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; - + use bouncycastle_mldsa_lowmemory::{ + MLDSA_TR_LEN, MLDSA44, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65, MLDSA65PrivateKey, + MLDSA65PublicKey, MLDSA87, MLDSA87PrivateKey, MLDSA87PublicKey, MuBuilder, + }; + use bouncycastle_mldsa_lowmemory::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; + use bouncycastle_mldsa_lowmemory::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; + use bouncycastle_mldsa_lowmemory::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; + use bouncycastle_mldsa_lowmemory::{MLDSAPrivateKeyTrait, MLDSAPublicKeyTrait, MLDSATrait}; #[test] fn test_framework_signature() { let tf = TestFrameworkSignature::new(false, true); @@ -42,10 +46,11 @@ mod mldsa_tests { fn rfc9881_keygen() { // note: same seed for MLDSA44, MLDSA65, MLDSA87 let seed = KeyMaterial256::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), KeyType::Seed, - ).unwrap(); - + ) + .unwrap(); /* MLDSA44 */ let expected_pk_bytes: [u8; MLDSA44_PK_LEN] = hex::decode("d7b2b47254aae0db45e7930d4a98d2c97d8f1397d1789dafa17024b316e9bec94fc9946d42f19b79a7413bbaa33e7149cb42ed5115693ac041facb988adeb5fe0e1d8631184995b592c397d2294e2e14f90aa414ba3826899ac43f4cccacbc26e9a832b95118d5cb433cbef9660b00138e0817f61e762ca274c36ad554eb22aac1162e4ab01acba1e38c4efd8f80b65b333d0f72e55dfe71ce9c1ebb9889e7c56106c0fd73803a2aecfeafded7aa3cb2ceda54d12bd8cd36a78cf975943b47abd25e880ac452e5742ed1e8d1a82afa86e590c758c15ae4d2840d92bca1a5090f40496597fca7d8b9513f1a1bda6e950aaa98de467507d4a4f5a4f0599216582c3572f62eda8905ab3581670c4a02777a33e0ca7295fd8f4ff6d1a0a3a7683d65f5f5f7fc60da023e826c5f92144c02f7d1ba1075987553ea9367fcd76d990b7fa99cd45afdb8836d43e459f5187df058479709a01ea6835935fa70460990cd3dc1ba401ba94bab1dde41ac67ab3319dcaca06048d4c4eef27ee13a9c17d0538f430f2d642dc2415660de78877d8d8abc72523978c042e4285f4319846c44126242976844c10e556ba215b5a719e59d0c6b2a96d39859071fdcc2cde7524a7bedae54e85b318e854e8fe2b2f3edfac9719128270aafd1e5044c3a4fdafd9ff31f90784b8e8e4596144a0daf586511d3d9962b9ea95af197b4e5fc60f2b1ed15de3a5bef5f89bdc79d91051d9b2816e74fa54531efdc1cbe74d448857f476bcd58f21c0b653b3b76a4e076a6559a302718555cc63f74859aabab925f023861ca8cd0f7badb2871f67d55326d7451135ad45f4a1ba69118fbb2c8a30eec9392ef3f977066c9add5c710cc647b1514d217d958c7017c3e90fd20c04e674b90486e9370a31a001d32f473979e4906749e7e477fa0b74508f8a5f2378312b83c25bd388ca0b0fff7478baf42b71667edaac97c46b129643e586e5b055a0c211946d4f36e675bed5860fa042a315d9826164d6a9237c35a5fbf495490a5bd4df248b95c4aae7784b605673166ac4245b5b4b082a09e9323e62f2078c5b76783446defd736ad3a3702d49b089844900a61833397bc4419b30d7a97a0b387c1911474c4d41b53e32a977acb6f0ea75db65bb39e59e701e76957def6f2d44559c31a77122b5204e3b5c219f1688b14ed0bc0b801b3e6e82dcd43e9c0e9f41744cd9815bd1bc8820d8bb123f04facd1b1b685dd5a2b1b8dbbf3ed933670f095a180b4f192d08b10b8fabbdfcc2b24518e32eea0a5e0c904ca844780083f3b0cd2d0b8b6af67bc355b9494025dc7b0a78fa80e3a2dbfeb51328851d6078198e9493651ae787ec0251f922ba30e9f51df62a6d72784cf3dd205393176dfa324a512bd94970a36dd34a514a86791f0eb36f0145b09ab64651b4a0313b299611a2a1c48891627598768a3114060ba4443486df51522a1ce88b30985c216f8e6ed178dd567b304a0d4cafba882a28342f17a9aa26ae58db630083d2c358fdf566c3f5d62a428567bc9ea8ce95caa0f35474b0bfa8f339a250ab4dfcf2083be8eefbc1055e18fe15370eecb260566d83ff06b211aaec43ca29b54ccd00f8815a2465ef0b46515cc7e41f3124f09efff739309ab58b29a1459a00bce5038e938c9678f72eb0e4ee5fdaae66d9f8573fc97fc42b4959f4bf8b61d78433e86b0335d6e9191c4d8bf487b3905c108cfd6ac24b0ceb7dcb7cf51f84d0ed687b95eaeb1c533c06f0d97023d92a70825837b59ba6cb7d4e56b0a87c203862ae8f315ba5925e8edefa679369a2202766151f16a965f9f81ece76cc070b55869e4db9784cf05c830b3242c8312").unwrap() @@ -60,7 +65,11 @@ mod mldsa_tests { // run keygen from seed let (derived_pk, derived_sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); let sk_bytes = derived_sk.encode(); - assert_eq!(&sk_bytes, &*hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap()); + assert_eq!( + &sk_bytes, + &*hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + ); assert_eq!(derived_pk.encode(), expected_pk_bytes.as_slice()); // also test the `impl Eq` @@ -74,15 +83,13 @@ mod mldsa_tests { let mut wrong_sk_bytes = sk_bytes.clone(); wrong_sk_bytes[4..8].copy_from_slice(&[0u8, 0u8, 0u8, 0u8]); match MLDSA44::keygen_from_seed_and_encoded(&seed, &wrong_sk_bytes) { - Err(SignatureError::KeyGenError(_)) => {/* good */ }, + Err(SignatureError::KeyGenError(_)) => { /* good */ } _ => panic!("sk_from_seed_and_encoded should fail with InvalidSignature"), } // check that it outputs to the full encoding correctly assert_eq!(derived_sk.encode_full_sk(), &*hex::decode("d7b2b47254aae0db45e7930d4a98d2c97d8f1397d1789dafa17024b316e9bec939ce0f7f77f8db5644dcda366bfe4734bd95f435ff9a613aa54aa41c2c694c04329a07b1fabb48f52a309f11a1898f848e2322ffe623ec810db3bee33685854a88269da320d5120bfcfe89a18e30f7114d83aa404a646b6c997389860d12522ee0006e2384819186619b260d118664d4a62822184482402898146148a6614c4248a19208c2382951244808a125c2083108c47120140914836c18a78084106ec9c07022b56408b0610c070498124451886959004622932041062e42b64c01164914284c41a85180460a5116515a0820022244dc9849d13251e13065d3c08592a85112a1640039220946621cc70cd9086dd0062652408580443091062c50c80924c5841a966d4a982c99066da4443220a7645a326e11b57020926124138e04852c0a4872c8a051d3082a99208058242024074e59148810a46460c06de0b28d1b1909203422c024410943710a212061a2015222521b80809a340013934dd3322922170a9892691a14512027219cc02062a2814818691a854d8344695b2041031242cb184601a90d0c023183b0215a224ac89205d9906904306a4b064ad2b2011c404081423252327254a6405a18100c321292c2805212625c82280bb46c03428d53100c14010ee1365288842491020a63462620062911c228d0204802b36ca236095a8648cbb4618b4662c440821a890910024d24b24520122524c90588288cc9c04d5948220a276ec134644c90605b445082864943880443b28c603080a2882d84a46d8ca629d0c68442064689885100a98d01498de4380da4068dd3947142b26c1a84611ba32842b42808a0711ac531e0a04c013765242862142890091061d940221b3360090292d02481200408491844a3222d5c8844149808a446610195640b390a0c9450ca406ad2b220c0380182308e13b908918084148829c0189112350da02422e20406d9c2850428121cc989180272d24029c20812d8062a9994719bb8682384291a2289144511dc82445096450c4484c0b2049aa60543862c44326e88442120a84c9a3070e3b82d63268803254903438c48a809ca147253344e1243081ba704593022d99480e234228142129c302a9434266104452426281346094a326d11280918b82562281113410d41b21190844c8b1212a2c688c9c030220606d2188e848630904452128831d9207113c52843060e033060cca6845826524c88011ef72562c85ffa43acfa49217f2b172d7bbc14620e6d980a71aabbdf0c45e9a206ecb1423fee15decc17601300149d9223cd6e6c6e1fa8e41fc7c64938ab68905fd3dcda50d87082e7d0d71d1bc9b2b84c85523ca8fe6cad294adf83be15b108ff721d0cc87bc3dd3a7590184b0e845663a91fc9e1c3c53a61d867420b04f092355753bc65a06368fd41295fd09924132c6f91f67964c142674a725c343914c4cecf58c074bcaf4558c97bf7911e07aa6d0938f2ee2bb3c1a8c595d635e84342fdea01dc24b211ad2fc281cf77e59110c7abc54bf0c86d480b9be276471dc9d603cee98cfdab3e9fcfb703793560549ea4450fa7b33fb9169c44b4d25fb9c457f49791cd3da03eac96095813c105132ccda4e63e49228cd23d8a1f37856f142d93b90db09f82af89258c63aab8047a80c036c9357ea2046f8dc6354f0c5295f342bb417d3cfeb0b1fd33622c29e14cbbd92e1363c65ebd4504b7512329b9670e32e1b2c67a54e7f1a55f8b9f9ea04e8ca3a705e62a3c5e637374afb7aeb6ddea612cde28f01a202d7aa4e34722d27dd3f9b89894d019fd5d4d7119efe3723bba104cb8bb0981e074de3afe200daaaead826cc45f244dbf431afab34efbdf782474d2fd57118f646214934ed99cba3b003e8d67a3836f6f19fc41910ce5163ee3ae99eb84d514eb761e63684ea56f9791d2dd4aac6e6168b948c817f75a222acb0e8cdc03cc4afe8f67157e1a363b7faeff9f172b98913677c5a1dd085e9ee4c22052c1af58193116673dcd3bfc5f34b855dcc6c77885649e9e71f43d4aea0f4b72ca7eda0578ba13d31a658d2d060a9a66ff69ed1be7997a2fb1d2723d38f9bfabe18f8e7b3cda906e4e9b5e942c8eaeb296070ebfd364947a940cc978bed66b37749e6d5dcd7be8c494440e2b84cecfefb98c0bedfb3c41e3359d2cd7197fbe720c48aa6c6b6465c1ee63e3569c2adc744491370b7f7826fe0b77a1d19d64101d032b918106b42d2ef73747e5601fe4ba50f23ede521f031a817d15294a43722e8378784b6db0cf1ba9e8ae911d9201b9ce9cc3019c6f5c27cb98da26144b64225a7c932b30f761e78a2d59a1d8b83ec6344a2f6dd47e765706d00bf4a79a6a926c3ba91d812c8f2c797ab1796709e5d16856778293529f0286d015c3b5399619642a333e9e593d6e3f5353994208e9e6a332851d7f652522a928b917e27e2d6d42137dfe2ebfa6fb1c67b26c0254528685f7ebdbe315a68eaa2da769e8a9f42d3e60007c71330926b2c0012d83ead4e4fd1ed872ccd1972201d2b027f3545ac2d30cd78bc1d740feccbc6fc2a0446c6e30eac51f5a69098aa2d447f2085b4e4e4b92ccc26921d2de478518cd090ce267aea2d27ada57fd88b4976d89fb843cdccf49a76ca2679e6801bfa7fb031896fb50629704b9923936bb5dd385311121cadfb11995e59b73034cf67ed03ab813867648d025828087e949a9afd16b95d72d99b1edca257aac132ffb7a0709aed5a9c0ff05fb0f2bbf28409eed7b5f5801be964ced019e1cb7851d3851f10290674e19ffb008b301c4acf641a2bb14216e1d69cabf52b5ef227496b0f30799a855d117fad3744a6fa33503ea798b52ddd7ee5426609dbfcd3f0c13b164d6c051f7ed4a119719a712e388d328402081ff1354b554d2c237afed3b151c4ba8e9f4bdeb8499a3066e26bbc69e8af089dec71731d1dc529eab17ef7374734c0fe475494c83836bdd34a03b9bc89914716061bfb98ec6e61c3ed4438edcaf25243c647086b9ea7018b0d9a8a0b00cecb00abde2498d69c2336101a772cbe4f571523f51bd05882cdf358b849cc140aa1faf22423a12851ce0e33fd48975a4959fa5c5fe418c93908191ab6e741b77bfe02cbd698ee795c466d615619e6441382c6eac01834ee9ab73cea80bbe235c78da91bd79b6f82f899785d68700d393e675c2224d6b7a1ad21320495679adaed70167b50866713a53109db7b6f7d81304ecdfd83b319b1ef248306b45ad29e7ddcc863dac56048b5d69ea175011f7614c00a86a863cde1872a8932878b9ac7e1ac5bda4997b72064f0cd75f4c814e034de11acb9013cf7ea926b4e7eaace070c7ba2188efad2e431e1223d45dd05c4d8403c2e45cee6413ecbe7527e873e455c4e610a61839aacc0bd56d2483e78f298b66a478eb2f558cbafca86be847baeb02c5b216c8cd88fea4df249b09e670a20703abac24b0a91abc4a5646601442ba10becfd30993880051d07f56a05a9379e7a8e6befee3f22faa106398f7706006e42e9be1ef89d25c272f11a95095c587d713732284de9dbd3c7217b0689e21d8eb0ff69668").unwrap()); - - /* MLDSA65 */ let expected_pk_bytes: [u8; MLDSA65_PK_LEN] = hex::decode("48683d91978e31eb3dddb8b0473482d2b88a5f625949fd8f58a561e696bd4c27d05b38dbb2edf01e664efd81be1ea893688ce68aa2d51c5958f8bbc6eb4e89ee67d2c0320954d57212cac7229ff1d6eaf03928bd51511f8d88d847736c7de2730d5978e5410713160978867711bf5539a0bfc4c350c2be572baf0ee2e2fb16ccfea08028d99ac49aebb75937ddce111cdab62fff3cea8ba2233d1e56fbc5c5a1e726de63fadd2af016b119177fa3d971a2d9277173fce55b67745af0b7c21d597dbeb93e6a32f341c49a5a8be9e825088d1f2aa45155d6c8ae15367e4eb003b8fdf7851071949739f9fff09023eaf45104d2a84a45906eed4671a44dc28d27987bb55df69e9e8561f61a80a72699503865fed9b7ee72a8e17a19c408144f4b29afef7031c3a6d8571610b42c9f421245a88f197e16812b031159b65b9687e5b3e934c5225ae98a79ba73d2b399d73510effad19e53b8450f0ba8fce1012fd98d260a74aaaa13fae249a006b1c34f5ba0b882f26378222fb36f2283c243f0ffeb5f1bb414a0a70d55e3d40a56b6cbc88ae1f03b7b2882d98deea28e145c9dedfd8eaf1cef2ed94a8b050f8964f46d1ea0d0c2a43e0dda6182adbf4f6ed175b6742257859bf22f3a417ecf1f9d89317b5e539d587af16b9e1313e04514ffa64ba8b3ff2b8321f8811cb3fb022c8f644e70a4b80a2fbfee604abb7379091ea8e6c5c74dfc0283666b40c0793870028204a136bf5da9568eb798d349038bdb0c11e03445e7847cb5069c75cf28ac601c7799d958210ddbcb226e51afef9f1de47b073873d6d3f97456bede085082e74a298b2cd48f4b3093155f366c8fa601c6af858dfa32c08491b2a29887f90335949a5d6edaa679882a3a95d6bf6d970a221f4b9d3d8cbf384af81aac95e2b3294e04789ac83727a5dc04559f96af41d8a053516feeeebc52746eb6ab2819e09108710d835f011fa63065872ad334d5cdffb2b2310507e92fc993ae317da97f4f309cdaf0f67ed99d90215576083849f953b246d7fedb3fdb67679850a5ad404e64147fb7cf4f6aeddd05afb4b834968d1fe88014960dce5d942236526e12a478d69e5fbe6970310b308c06845018cfc7b2ab430a13a6b1ac7bb02cccbb3d911ac2f11068613fbe029bfdce02cf5cd38950ed72c83944edfbc75615af87f864c051f3c55456c5412863a40c06d1dab562bdff0571b8d3c3917bbd300880bba5e998239b95fa91b7d6416d4f398b3adbcd30983ed3592b4d9ef7d4236fd00f50d98aa53a235ac4172720f77d96172672980cfe8ff7a5a702783edc2ba31b2259015a112fc7f468a9c2f9464039002d30ef678b4cb798bc116216bf7a9a7c18ba03b7b58fd07515d3115049d3614be7a07e744300750df1d2c58753389059eafc3d785ccdd31c07648bedc03a5c3b8ad46d064d59c13d57374729fc4e295362e2a5191204530428bc1522afa28ff5fe1655e304ca5bc8c27ad0e0c6a39dd4df28956c14b38cc93682cefe402bbd5e82d29c464e44eb5d37b48fc568dfe0cc6e8e16baea05e5135590f19294e73e8367b0216dbb815030b9de55913f08039c42351c59e5515dd5af8e089a15e625e8f6dee639386c46497d7a263288774de581a7de9629b41b4424141f978fb8331208efdec3c6e0de39bc57063f3dcd6c470373c08891ea29cbc7cc6d6483b8889083ace86aa7b51b1c2cfe6e2ad18d97ce36fbc56ea42fae97e6a7ac114864478c366df1ebb1e7b11a9098504fd5975bdf1f49dc70002b63c1739a9d263fbad4073f6a9f6c2b8af4b4c332a103a0cffa5deeb2d062ca3c215fd360026be7c5164f4a4424ef74948804d66f46487732c8202c795478647b4ea71d627c086024cca354a41f0877b38f19b3774ad2095c8da53b069e21c76ae2d2007e16719ed40080d334f7da52e9f5a5990439caf083a95b833f02ad10a08c1a6d0f260c007285bd4a2f47703a5aef465287d253b18ac22514316210ff566814b10f87a293d6f199d3c3959990d0c1268b4f50d5f9fcefbbf237bd0c28b80182d6659741f14f10bfbb21bba12ab620aa2396f56c0686b4ea9017990224216b2fe8ad76c4a9148eef9a86a3635a6aa77bc1dcfb6fba59a77dfda9b7530dc0ca8648c8d973738e01bab8f08b4905e84aa4641bd602410cd97520265f2f231f2b35e15eb2fa04d2bd94d5a77abaf1e0e161010a990087f5b46ea988b2bc0512fda0fa923dadd6c45c5301d09483673265b5ab2e10f4ba520f6bbad564a5c3d5e27bdb080f7d20e13296a3181954c39c649c943ebe17df5c1f7aae0a8fe126c477585a5d4d648a0d008b6af5e8cd31be69a9296d4f3fd25ed86f221e4b93f65f5929967533624b9235750c30707550b58536d109a7131c5a5bbe4a5715567c12534aec7660761eebb9fae2891c774589b80e566ad557ddef7367196b7227ea9870ef09ddfec79d6b9319a6879b5205d76bf7aba5acf33afb59d17fc54e68383d6be5a08e9b66da53dcde008bb294b8582bd132cdcc49959fdbc21e52721880c8ad0352c79f03a43bbd84c4cdfdc6c529005e1e7cd9a349a7168a35569ba5dea818968d5a91466bd6e64e20bf62417198afc4e81c28dd77ed4028232398b52fbde86bc84f475b9016710ce2aabc11a06b4dbac901ec16cf365ca3f2d53813948a693a0f93e79c46ca5d5a6dca3d28ca50ad18bd13fca55059dd9b185f79f9c47196a4e81b2104bc460a051e02f2e8444f").unwrap() .try_into().unwrap(); @@ -93,12 +100,14 @@ mod mldsa_tests { assert_eq!(pk_bytes.len(), expected_pk_bytes.len()); assert_eq!(pk_bytes, expected_pk_bytes.as_slice()); - // run keygen from seed let (derived_pk, derived_sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); let sk_bytes = derived_sk.encode(); - assert_eq!(&sk_bytes, &*hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap()); - + assert_eq!( + &sk_bytes, + &*hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + ); assert_eq!(derived_pk.encode(), expected_pk_bytes.as_slice()); // also test the `impl Eq` @@ -112,15 +121,13 @@ mod mldsa_tests { let mut wrong_sk_bytes = sk_bytes.clone(); wrong_sk_bytes[4..8].copy_from_slice(&[0u8, 0u8, 0u8, 0u8]); match MLDSA65::keygen_from_seed_and_encoded(&seed, &wrong_sk_bytes) { - Err(SignatureError::KeyGenError(_)) => {/* good */ }, + Err(SignatureError::KeyGenError(_)) => { /* good */ } _ => panic!("sk_from_seed_and_encoded should fail with InvalidSignature"), } // check that it outputs to the full encoding correctly assert_eq!(derived_sk.encode_full_sk(), &*hex::decode("48683d91978e31eb3dddb8b0473482d2b88a5f625949fd8f58a561e696bd4c27d853fa69b8199023e8cd678dd9fabf9047646ffd0cb3cc7f795805a71e70d2371b0563e3cd3346149c8c9ebcf23b0a4e5a900eea9c6562790a7c63e38663daa2dddb6e480dc405a1e701948b74841ef5cc1c3f2bf327972e9510510cd5375ecc0855717711872221862381000424778061475007501717035504515125471838046175722244108868608646012747567180870666864332444122043638667502823634244322057364106455547722755681433614625508206437685468754353751068718333805475052580752818843811087260202008588301836113828212061711578768788878643754601657155084718866072732880664741856762180318276641578245025646643113504364780126673143011660655864718368863503847861101202356116137860785321240075478823043666116604255418285605367785638434430632610770731784272141116530385276867460150823735320766107504681248066603032652312445408800318088767217307182472151278011654474866172233380866064468352158420368011802118183317735453488100448653674370577258833460384232856810060426042584560235682051838638432421224245645858677145728504788717180618836086864156508116502646700608266227383172407257300727288620667588682607064020330343663155464245345667187345658370225084685628807036708462371710065717584778708655537822351446772856730322870014332061715845526632502651334777380355164313473510662751757402468881706743468186017652453330872104343401032287635155265081307745444168154183636411204026873043677712808846355453006245810458365124842780345166635843785601465115742321436685224777313450178362420550006484471234408800604735405783336308210615225207248851348637067622588571265673476816464684258708122705500838320023208066345336003346857247063554003577122752307142536874374570056643224482852072183330205337334077278055253063525040673346131807280717248377634573185851602333443625164338160858773462428830070365853755007552315037021324630437086806361503030043586357080211066473463522620330438021085287578321078867480856347436734284058466841437005510873426447721127384736526472577144704178644260247118740812216605847178137067680817058185585471363421075580163583585184403847110338742628247741365544270734635777500662562684202124683864616646031225388845400845734464754472560546166846630880638271563287183840652247681160662130330186802801384630505657238758365723230688046122606651675570532413227673517080153001628460134887701118815571315464311704732882856368234555041862765631111687505104254414427852211171788153685157447166255365583630250285576875327137103723705714761713651841242366444664143520521085157033363860258426628148110546268173038756433216588568663632813406254012040886547886171657623726234867030115115632050753502122108426531435567111525720106853630150557586058784314313278788087384788637881813873426178388524667733506021151464238232680135440783475385535752832335187601152134325773333655188615816168241842212230841448151201103024777242544366067717707603014525403500183873237735265086357113734481605277456553730085837785035121115480628850180268138652053468013207241803213005723864076427114101838525510632607104865176833828572762354518735083132886376661426311675033112553764176031433177212234418a82e4f5c9ea0faf99eb04d78a7332711117c33f18eca21f8743376ada5219804a7ed9a5557fcd67a3550b3a4b8c588629c021475fa3d56d5d6cfbb1a09bda8d14de622ddff16d8bc99b14278a8af1d76bed157672dd9c32316f97e8daadef8d9da69586725567fb96b59990d4bf0bc9c195b90b74295f5675b24257c2710c175b0153f2911328c2eb7abb9ad46e70a8b53c39ea642cee4b3cb42620e863ce8b650ce8adcd923721a1687023c673a8cbb6b03d51cd197e8c346ebadce93950f88cee201db9e320843e29f300d9a19500d70a4caf272c69e4eef69fbb8a55efd7ca2bed990d2d3b582848f9c45c2abc54cfc47d34f06c0ffa56fcd762ab9cba9146d7725218963b240d72b6d22c93171fbd47788b76e72042def0878d23df631a1a1e5a6027686de5b4a10e91069c8f2ba0259b04d6409da96567ca52da497026e583a0ecefc1f01e6b988e21f9767a2b7e1672deb9a1e2a3fcc863aa91517c334620601b4fe79730e934935f4b6fbc4e32695145c2b5f6a127fecc0a277451ebc3fd523444f9ee7c9c34534f356db544fc31c1bfde5f65c77ea2f7c2eae4c55ebaf104271c566fd4ebac71c7a62c74952817ae675504d9599b1b762b6aca168a83248c9d9adb0ceb1556e5759490bbc0c7900795ad72123038b662f64f106a9993681a25d59af7bc97a235be9284c5bc45a6c90cb1c2999c663d96b478e2307f85548957d65740e2673e9ebd1352829038f462b8fd3b5681da55c0252523853525ea0ad647e71ac2c5a8893e603ac97e56c04ceb2f26f5c5b4b6d94ab811380fd00f2208fe86535086aebfd35c29120624c04fbb6113929d9c556350253766c209fdba83c95fccd342a28099355d00bc863f4eef596eb0b42ebcc7c79491cceae205ea0b8059fbb8a5726c5949d2b15e7e29c51fc9b02ee1a4fc357b5f1bef9c4add46a2a920c2fbf08a37eb1514bfa15110a4392a74c6f13c50c5cffd97531098d7cd23b60eb35c4a428b46c55386e1010c4ba7f70e4c7ecb7575f3063a71e84dfdcf09a58b2cdb0f99f27ed378610d25cbad7bfa6ba0d59189cfe88eab9b46d7e6db0307eabe4198e99bd71f779ab66581e0912fc7b1d2585245e9a12687a975cd5e8e1dcc045d5f891c4c685db07cf81e77389b363eb6bdfe39b27ff84c97eefee162e3b451fe6914719cb6436d855960ff915d7cea6adeafdfc1c05786c49f923a474ffdfc3153a06e6ed0b0ad220d72524434d5273c0aab6dde4e91476d581a2695a60de6d9f44d77aa08266e938eeb4a9597c9b64986059e49262a4eab2454e14015ad0536c42733a5d77d7995c2a20446009ebfe5632c80c08ed2b97af35066489f597eb1b1f11f04f60e0c9040159c44ab3e60e0a15229d191228bed17bbc3ac939b3c67cee135f352c27216c9c31f72a3e87040c5f619306eb0b6cca2a9ce7b22a1694d00ca9c05e315126457f26ce84f9617241860782f864b473d84017491902b1bdc8cdc5800dd46127fb80a71c095b473a562529b3b1e7e437e158a5f6666e9974d005b062c2309e6dce98f9b658c6e3f9a216d58c8c9142bd1c8c85a9da872ebbfad3fea9d9aba2b68c0e8f19c6ff5f00584d45daf9d6c9d69ed04b8da8d687258b77807927612c530446fea7697ae3f926698929bc6a5a8cf3e2024c0f0c5ee57b5869bf981881caf9e3665fc7f7efc678929f87a56eaa42ea4d1ff6691822dd79a47096b776d1d8f01456e5873b0738406c382c573ae9cde2d9e7f231b6cc5c676e7cf43963373013a58075381ff0949be084546d72e4f8a3e5fe4aa5091add234e2afe0030b1b663ae9d2d32410986b9402aaaf2465b74a5e2d0bc38e3a92bbddd8a1fed7b948c23cce6f8c08fe356835ba65b0f984068616ef48138efd89bf357a54d2ebbf376cbdcc69c5f1f61c64d2794bc06ccb9abdf66e25085d8c830e2ae3b0fe0f07a7af8b9320bf342970997d67d7c12593a8fbfade635aac53083a7022c47d5f77a52b57b598da9392ae6d86afc46fc06455181b9c75a646dc21f81e4bf213753de737fd2a140027920add35a223f9f5f4465ceb60c03ed0455a333a5cc83adbf43f1f42c2ccb8328c21c7ab7faed2b21cfade2da55223aaab2af9b41c7332341746341b39aa2f43815650f5480511424cfa6901779c4d18b638cc0287aaaf31680338d20b17c7449fdc6a278a8d96a82ee4c4eca40125e2d65290071c7aef1be6a991598fb9d59512523bcd4b38c566b8e80a73ae333e134414327ef1d83c47c49dfe7936df1338a5e247787868fc84fdcb95ac89c185c4bb5fd57b2338ac42b41c10a823df39624f36b15a2f067584e06ca2e08ccaff1618fe01dd06df3512e0b724dec8506da24215acacc2c51b82ad8d302002fb41068b1da4f8bb147987b3516bad5dbddf01318fd3fa9bc43702ac498c719d95f2e841b622a5e4848a3c5c262959992ea7a7d72ca8a368028f497dfad93355cbb1bb9786d14ff2cf590317848f95856427110dda36f5192a816ce9c8816cc7bbfc804efc40085a3850b89f1e7fe5656dba410f906a97c32336c1ae7e81737a83e087354e428da8538d948dbf5dfacb59dd2b5fd3bc803f4ba432c9a739df2cfa9ed9484320f97edff1a48c6b86b3002cfb772dd5e562bc4c3d683ed964b6199fa0514b0790d958095b7b85c6be875fbb559e1930146ccea63a388a194fe09c3dea03be52de27e901017afe809af630a7382bf5c4cd4d1b8f41579fb4348ede4ca05f4cd3f139a31b2544e516dbe4086b9bb4b2bed47e2d230982dd5192429d377b7c0745cc068e2f5a4aa04c7ff87209ed1259976a0fc9b25e9e851d4e3502c02c85d6dff029e211d01ebf0e9e7188d568f8437d813b0f122f2fb17603b693ed9c38f17cfd50b815e6d9dfc0ed2ccf19f6399274a1420f235a59d8bf724345e14e45d9e4be8934dfc3fa92678db61d7118bf53cb8a2225b335f7eae50e3f941237628db76d8ea38f77a72af3a26c81fe43523b335535a5d1db7c38f341082bb5734d089e8ae309cfda3a0bcb5cd5b097113c8edf9616aa4f6e6631b9125276fb3f680a34341c3db668dc6cad45fc93b2708ca2af75ccce734fd191c50089dad53982fddae02531ff93e1f21ff395fc0a12874edf06b6f9647e95a7324586c71dfd91d901d621858190fecd00ccd110bbac59f96cb884c3c93994748a56f41283bfc41fb89052153a894588c3cb9017f3d66326c985637e575acb812346342654025d602de3ba940c19ac1a633dffda977b529b8013e19c1d6d0680f4dae62c924450ae66aab82f21473061dab3d62b247f907e3551939ad3f5465e9d08a82bfea17eea1b6b2b923757477f993000b2f43b70f28aaab1fe9a26ad1fd3361616c0b0e242fe76604b7033a1f30e97e28f526ca3c880fe2b8d9d1b0c9ff188b31cb9d97425acab9b216d98a6ae355e583da71e8864ee3d16b0759796190ef545c1e62bfef92af6ca147b13244d6c892fc8ef223ab3f43f924c2f466097ee8").unwrap()); - - /* MLDSA87 */ let expected_pk_bytes: [u8; MLDSA87_PK_LEN] = hex::decode("9792bcec2f2430686a82fccf3c2f5ff665e771d7ab41b90258cfa7e90ec97124a73b323b9ba21ab64d767c433f5a521effe18f86e46a188952c4467e048b729e7fc4d115e7e48da1896d5fe119b10dcddef62cb307954074b42336e52836de61da941f8d37ea68ac8106fabe19070679af6008537120f70793b8ea9cc0e6e7b7b4c9a5c7421c60f24451ba1e933db1a2ee16c79559f21b3d1b8305850aa42afbb13f1f4d5b9f4835f9d87dfceb162d0ef4a7fdc4cba1743cd1c87bb4967da16cc8764b6569df8ee5bdcbffe9a4e05748e6fdf225af9e4eeb7773b62e8f85f9b56b548945551844fbd89806a4ac369bed2d256100f688a6ad5e0a709826dc4449e91e23c5506e642361ef5a313712f79bc4b3186861ca85a4bab17e7f943d1b8a333aa3ae7ce16b440d6018f9e04daf5725c7f1a93fad1a5a27b67895bd249aa91685de20af32c8b7e268c7f96877d0c85001135a4f0a8f1b8264fa6ebe5a349d8aecad1a16299ccf2fd9c7b85bace2ced3aa1276ba61ee78ed7e5ca5b67cdd458a9354030e6abbbabf56a0a2316fec9dba83b51d42fd3167f1e0f90855d5c66509b210265dc1e54ec44b43ba7cf9aef118b44d80912ce75166a6651e116cebe49229a7062c09931f71abd2293f76f7efc3215ba97800037e58e470bdbbb43c1b0439eaf79c54d93b44aac9efe9fbe151874cfb2a64cbee28cc4c0fe7775e5d870f1c02e5b2e3c5004c995f24c9b779cb753a277d0e71fd425eb6bc2ca56ce129db51f70740f31e63976b50c7312e9797d78c5b1ac24a5fa347cc916e0a83f5c3b675cd30b81e3fa10b93444e07397571cce98b28da51db9056bc728c5b0b1181e2fbd387b4c79ab1a5fefece37167af772ddad14eb4c3982da5a59d0e9eb173ec6315091170027a3ab5ef6aa129cb8585727b9358a28501d713a72f3f1db31714286f9b6408013af06045d75592fc0b7dd47c73ed9c75b11e9d7c69f7cadfc3280a9062c5273c43be1c34f87448864cea7b5c97d6d32f59bd5f25384653bb5c4faa45bea8b89402843e645b6b9269e2bd988ddacb033328ffb060450f7df080053e6969b251e875ecec32cfc592840d69ab69a75e06b379c535d95266b082f4f09c93162b33b0d9f7307a4eaaa52104437fed66f8ee3eabbd45d67b25a8133f496468b52baffdbfad93eef1a9818b5e42ec722788a3d8d3529fc777d2ba570801dfae01ec88302837c1fb9e0355727645ee1046c3f915f6ae82dad4fb6b0356a46518ffc834155c3b4fe6dafa6cc8a5ccf53c73a0849d8d44f7dcf72754e70e1b7dfb447bb4ef49d1a718f6171bbce200950e0ce926106b151a3e871d5ce49731bd6650a9b0ca972da1c5f136d44820ea6383c08f3b384cf2338e789c513f618cc5694a6f0cee104511e1ed7c5f23a1ebfd8a0db8424553240156dbf622831b0c643d1c551b6f3f7a98d29b85c2de05a65fa615eee16495bd90737672115b53e91c5d90028cf3f1a93953a153de53b44084e9ccff6b736693926daefebb2d77aa5ad689b92f31686669df16d1715cc58f7a2cfb72dd1a51e92f825993a74022be7e9eb6054654457094d14928f20215e7b222ac56b51adbec8d8bdb6983979a7e3a21b44b5d1518ca97d0b5195f51ed6a24350c89747e1edea51b448e3e9147054ce927873c90db394d86888e07dff177593d6f79e152302204aeb03be2386af3e24078bd028b1689f5e147c9f452c8ceb02ec59cc9db63a03576ceeafe98239023897da0236630a53c0de7f435a19869792fab36e7b9e635760f09069e6432e700035ac2a02879fff0a1e1bec522047193d94eb5df1efd53eea1144ca78940852f5ec9727904b366ede4f5e2d331fad5fc282ea2c47e923142771c3dd75a87357487def99e5f18e9d9ed623c175d02888c51f82c07a80d54716b3c3c2bdbe2e9f0a9bbaaebeb4d52936876406f5c00e8e4bbd0a5ec05797e6207c5ab6c88f1a688421bd05a114f4d7de2ac241fa0e8bedff47f762ddcbeaa91004f8d31e85095c81054994ad3826e344ba96040810fc0b2ad1de48cfade002c62e5a49a0731ab38344bc1636df16bf607d56855e56d684003c718e4bad9e5a099979fcddeeb1c4a7776cd37a3417cb0e184e29ef9bc0e87475ba663be09e00ab562eb7c0f7165f969a9b42414198ccf1bff2a2c8d689a414ece7662927665689e94db961ebaec5615cbc1a7895c6851ac961432ff1118d4607d32ef9dc732d51333be4b4d0e30ddea784eca8be47e741be9c19631dc470a52ef4dc13a4f3633fd434d787c170977b417df598e1d0dde506bb71d6f0bc17ec70e3b03cdc1965cb36993f633b0472e50d0923ac6c66fdf1d3e6459cc121f0f5f94d09e9dbcf5d690e23233838a0bacb7c638d1b2650a4308cd171b6855126d1da672a6ed85a8d78c286fb56f4ab3d21497528045c63262c8a42af2f9802c53b7bb8be28e78fe0b5ce45fbb7a1af1a3b28a8d94b7890e3c882e39bc98e9f0ad76025bf0dd2f00298e7141a226b3d7cee414f604d1e0ba54d11d5fe58bccea6ad77ad2e8c1caacf32459014b7b91001b1efa8ad172a523fb8e365b577121bf9fd88a2c60c21e821d7b6acb47a5a995e40caced5c223b8fe6de5e18e9d2e5893aefebb7aae7ff1a146260e2f110e939528213a0025a38ec79aabc861b25ebc509a4674c132aaacb7e0146f14efd11cfcaf4caa4f775a716ce325e0a435a4d349d720bcf137450afc45046fc1a1f83a9d329777a7084e4aadae7122ce97005930528eb3c7f7f1129b372887a371155a3ba201a25cbf1dcb64e7cdee092c3141fb5550fe3d0dd82e870e578b2b46500818113b8f6569773c677385b69a42b77dcba7acffd95fd4452e23aaa1d37e1da2151ea658d40a3596b27ac9f8129dc6cf0643772624b59f4f461230df471ca26087c3942d5c6687df6082835935a3f87cb762b0c3b1d0dda4a6533965bef1b7b8292e254c014d090fed857c44c1839c694c0a64e3fad90a11f534722b6ee1574f2e149d55d744de4887024e08511431c062750e16c74ab9f3242f2db3ffb12a8d6107faa229d6f6373b07f36d3932b3bdb04c19dd64eadd7f93c3c564c358a1c81dcf1c9c31e5b06568f97544c17dc15698c5cb38983a9afc42783faa773a52c9d8260690be9e3156aa5bc1509dea3f69587695cd6ff172ba83e6a6d8a7d6bbebbbcda3672731983f89bc5831dc37c3f3c5c56facc697f3cb20bd5dbadbd702e54844ac2f626901fe159db93dfd4773d8fe73562b846c1fc856d1802762840ebc72d7988bde75cbca70d319d32ce0cc0253bb2ad455723ee0c7f4736ce6e6665c5aca32a481c53839bc259167b013d0423395eeb9aaaee3206149a7d550d67fc5fdfe4a8a5c35d2510b664379ab8f72855a2af47abce2a632048eaf89e5cb4a88debc53a595103acce4f1cff18acff07afe1eb5716aa1e40b63134c3a3ae9579fa87f515be093c2d29db6d6b65c93661e00636b592704d093cc6716c2342eb1853d48c85c63ac8a2854462c7b77e7e3bd1eac5bca28ffaa00b5d349f8a547ad875b96a8c2b2910c9301309a3f9138a5693111f55b3c009ca947c39dfc82d98eb1caa4a9cbe885f786fa86e55be062222f8ba90a974073326b31212aece0a34a60").unwrap() .try_into().unwrap(); @@ -131,12 +138,14 @@ mod mldsa_tests { assert_eq!(pk_bytes.len(), expected_pk_bytes.len()); assert_eq!(pk_bytes, expected_pk_bytes.as_slice()); - // run keygen from seed let (derived_pk, derived_sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); let sk_bytes = derived_sk.encode(); - assert_eq!(&sk_bytes, &*hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap()); - + assert_eq!( + &sk_bytes, + &*hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + ); assert_eq!(derived_pk.encode(), expected_pk_bytes.as_slice()); // also test the `impl Eq` @@ -150,27 +159,28 @@ mod mldsa_tests { let mut wrong_sk_bytes = sk_bytes.clone(); wrong_sk_bytes[4..8].copy_from_slice(&[0u8, 0u8, 0u8, 0u8]); match MLDSA87::keygen_from_seed_and_encoded(&seed, &wrong_sk_bytes) { - Err(SignatureError::KeyGenError(_)) => {/* good */ }, + Err(SignatureError::KeyGenError(_)) => { /* good */ } _ => panic!("sk_from_seed_and_encoded should fail with InvalidSignature"), } // check that it outputs to the full encoding correctly assert_eq!(derived_sk.encode_full_sk(), &*hex::decode("9792bcec2f2430686a82fccf3c2f5ff665e771d7ab41b90258cfa7e90ec97124d8e9ee4e90a16c602f5ec9bc38517dc30e329d5ab27673bd85f4c9b0300f776389886750b57c24db3fc012e61ede59753337374fa7124991549af243496d0637cb3be05a5948235bf79875f896d8fe0cab30c84948db4d6315aaaf160ac6243664220148161109112c94028922452c62b84500452a08967090126e149370d446108444515896910ca92982b241c90871c428680496894840859b226d1c28645912419cb891840489449005cb3462a086904026922099291305695c3468a4328e19269259461009a44923424d1236615810650128901a334c998631d3a249098225431428c0388103154d5b2886088748233152942225c3c04da49821984020d14286cb40705bb0719c962cc112065346090c450214466e91b42154b08ce446429a208c0121251341055a402213c90ca0184052c230cb342c4bc8681ba4604984846330294aa0695b8004d2380a14264ce2b2448ba211244649c414520b427103b210922880012488e308110a052819c481002022dc446842122244002ac9266a0c8731e0c04499148418360d11374222188c63b2910c9808a1a0010892440413245c987182847184325251b0319c422e1aa82802089101c0890bc7058a246522c2644c88915c826813a56450208621040291944448223022394a02988409a28819192944488c22950ca104720487701221104206841b498589064ad33608dbc04058b6510ca7098c24619990648cc2905b249010a34903256143328a119844c8b22004384110472c19c6441c252c048830d946699b20001b46825aa4805ba0491890250026800bc2315a407254c620c1b03124b14d10952814000aa0c84d54a28823988160900402162c13214091868d08c291911426d0b40c09c66051442e04112600291193c20863a431220028c114080c402c4114069c20725422068b084da148691030411c284944188ecc9648d942501b06490c458823040d20302e23852c14073040b6854cc02044862019006c540248cc886c59063249148404c750134928e40609d3c610c8284c23394452a464cca8494438320a898400342c22858d1031090932651c898c40402921850009a16d84c064e2022d48044012098ee0422e93440812106a01840592308acb348ea2262e5c86110b3508181000023426242389d1840024466013b249242846180271a03890891444d3962da31840232721c0185043c80441428d5c264144a26d48120e4032250b14820a482ecb828803a3601b25268cb82024b08598042108a72c833864543289010401234984029569d1a44d13a40c91460d6194809038455cc65001172053c6289b1810411268901221c01084421692538229812649d8a4059a2624240329e04026d20248112468119989981485c9200d50128c1c0810021000009528c1289059b485d314650a406e11296518c24c213465d830691030521931668c188ac8c0084c9830a3a62041162218052e22252a64b8250ab30163208409800919280e021101a39411e3986c58202109411060362208067112c2855aa085c0c6845c3806cbb669148484532282a1a640cc8600c42622a0a808983472d4204143c4904816681b16521a370250204248488a201141e2006cc0c20c14064911314d19060a8946091b816544c800820670001672cc24508a42899c969064287092b268982662619440c11689d842641a214e62906421c8248b286d5c4292a0c64d0c8580cc884dd4428d42348a0b0451c32686242581123506a04404c894815bb4311c08065c240803276a20c225e1809019b46da3460c4b186050c62c1b922d111504a2000421482ed81606d2108a83a22508310d093851d948490b164c2332251919024a4409d1b2210b832c23258593168544a0441b83500222724b04809b146521936018130ad9460d224561c8b440a1422d02b8090014449bb6110b978c40104a82146ada90051c028e0c1972a3b48d24305011870964c628e4189298b46c61165140460e1c3248da205188368a23b1218290281a1532e2186192048e13b690131368c984684c406d0b330081464dd2380c049681a4885002908522b004d3a471d28010ca964051a641a48428e008520b308cd2380a0c2951c38209ca2091d83692a3a628924222a216011a348637d9a659169881ec21cf4811869d1d7f139f0537e96f1184585405fd17808af1e06239d3b34e5aca8bf1369677b447ac718ac47d850c4d77b0be31dc9f508e3978f24274ab0185f727abdff59f4490371bf04610e364e64ec875ef9d20dc94077e1e166327a879b8ab516160b2a3f77437b9b3cc7d17aeaddc84db62746a35ac096f782f62a7f01aa6d6693deec90b23c66985a02307e0a1cae598a67324dba0f52f22432275e93257065c3b7e5e1cfe1dfd4d0df086df21243414a2d27e20230a829be4eb4c82c16d35f78b0e5e198332e00074bb64612fab17d4c8971cb68e5edab0369f1157b3469abd8384e2d9553f1b78e786e1ee9d0b98d39f83ccecf37d1ebd3a9d63aec766164a10171a4fd8c63daf182c421258c5f529aa55cb7ebae2e1652315e1f71e8a74131410d03247ede11d34db91f6f08aa2478fd789679c04949f71bc0171e07e3a8bb5753dbbdaa411a6350ab46eefbf86fc551c29efe4cdd7661d5cf6c3db22d0cedde599854459d97f20df7455bdf356a198d0f7eb6d34111fc940b25c0543b788edda9d26810eac3d6cc9c51327c2cf83e887d4089e19695e11add837f6f440cc360f93f32fee8a9663712c6bbd38c84ab7b54823ec363eb7e42eb59fc1fce60fbd55307b3ec85fd9daf3206d7b4b3917f1c8b7a92e3c67d89880fdf2e47f5a0c994595db170af41babf5a25b4dc1c42dd6a9db271e764de2fb015a49a850c7919be47006a336e2e325fde53ac599554d0a7de4ef45ec40c39d6baff311beee75d89e02ad31f4be4bd20ae9194f5edddaa6650776116e9f270f77714ad7a8e89acef74b7ff7d8dbec27f8020a985247e2cdacef4894a4d68ba37ca912d6be73501c995181e5b77723350b3631da3700e13fd366e131bf06b36eb6b0345093209f0a7beffae1fdd875b00687c1163c353d7d2ac90937b34e978e92f821adc9662202ece89a17e7bb65ae17d83b90dbbe6a501a4e1345bee4e5a5b53af2e5ba3d1ef3f4e05adf0b3a4cf2e530360fee64929902b571f6fd2e305652a4cb010f79f815e18f2bbb8cc89fa6fc76f77c89e293cf175a0b195800fe72d2ccdd7d75e5bd90bc6ac435d6a440ef852e9a1c8c53de03bf193365d735aaf29c5162a617e364e7f944168d0fb48fef40558f454297cc3dd508662cf23fb88e1954aa45d1c5e115bcc36f05b3e098d555220f40be2629b34507b8464c54c27b5dec78da8f22650514797af86a2512bcb7e2923379ef6d73c137006c1b38f51e37f93585e29041a3e4e3af46007ce13b8b5f7b17d5d65d7d5668e427bcbe7ec1d7c408c054a48c1ae797bf99acbc8d2607522935fd665ea7822d930f23eabff783bb23697569e204b943141e00c08810956be0525365dbab54ed48cb76964ccdf5cbd3aee7282d4a0000d2784d7b8fab16b2f7f0d5225732b1efbc4eb1cfedeb43fde79b69ecc0fbeaa1e6b40728673bd4b2e98a0d4a8f02f853950730f28d35eb12fcc79768b8e18e4bda0e58a331a2f71d7ccc2d451b32b1c65c312acf47ee513b21954c41c00c873872ee94cf14f46037425361f4bdb54821f711460cebae8c07508a9219f88fa6bedaa678eed501944a16ae6f7b5bb7a2e1e357e70d7b98461a2c71cb0fa762d6ad9824081d37f292fd4be8b84c36110dc744360201beebe0bd6c9d05e869256d2ff3f99517b7efd2a33774056cb5671675a8b492e9f5f2620eb8ef9381d3d1df19938b7b5ffaac59bc8110fa87ba8d7a3d0165f8e41dd0f804f11b9ded0f352a597835d06307a8e0c6ef4d21904339e1cf458923a3e89e025d945347366c02f3dd6368d4e47e85d3d2a9705bd57961852e5a579f93b1c514c539f49ea1163a2a493b0efcb47f4748f6a99e10bf7078282e4ace18136e2a8b3ee0a380dcd3b3ef3e65e1b8157289d62467ad488ba0392b2e90a1ededcbdc931dc17298ccef76645c7d330a05c2ce40f89b85468f357a217751e154631304ec4e04bb45b3678909c74af51ce370364d8f4f7eb1e61e00287429c9961de8322ca9a2629b1309d800e92bc1dc5055dcc797f33866eb0cfd8d490250d48ffca8022f49290e2d5376162fbaa982d16453c825b35f6515635ea92bea72367baa54de3f9eaea69542a81a4127f71cbaa257f324fefef14f08fbd65a049cd2fb362594a8e23ff1a2617db5b158f6f01cf50ab0ed95c6e709841164108b06e1b40ab0ab11c408301d3d9d8ea69e968a9600b3d17f38011ce28074e2c2e10bf6197c602d8d0ce7d3a3ef2d89623bc9f12ea338791e9266bb8ce02b124c6c7929baea693244098454a080eb7523e13bb1b7c5b6775fabababbe9075fe5687aa451397bb9cfccd051243e9bf5aef24062d335de5fce24e9ddbde1191052d80c36df9f8434872f277ed4f5a1ce8ebd3b960824a4e4f1001b04cb685f9bee4d0ddb0c571598ac2021a6606fd23345c6fbb84f0ce05fe52734521b7b07c6388d3a3b99318bf0131504aa9dfbaf548f9d32a9cd4c6893524b11330a2d3aad3ed2a58966ebb0134465d543fd7797af549f568eaebe957f64fec854674902b97558756986946ea3ab7a251cbbea11a687bd43f5d0bd89cd2caba61d5218374990ee8b92219ed25dca011c68a9757c013bd837b2dd734e3751f64fcb4b23dcd6bc57ea567f5716e17367244751e2303b22a953e772756956cdcc013ffd2c32490754422a572529d4c92f1ebb19f1dad4d036f2fdf31ca9101bdf81aea948aedcf217aa8fccd7a0771aa2753e1a823bf41c95377a2ffa61b2265138153ce86d2c87dd07a4b32d27f5f2872641431ce9a18a502aaefd9afc5b0d13cd46c357e38e69e1ee945add1992932a5b1e5c5629c9f48f7661853da00787c9d78fb925553bf07a50dd5b9d935853420e4d1a71ae62ff90ca193cdd6c2f4bed263415aaf9a35094bc2a22e2a663c7645001cd190b7bc17c75feadf8e87ce5c24b763b6584ed32e71b0268142ea3ed6898157bf923bebf0192d1bf5ee30a7d351634a60b504dde38a2e114f7ae9bf176d4a18ba2895a7bb4b47444a9ba8dbb4c124cd41bbb32f4bcb1de48c4abb510607a001b5a000bba43618b6c19e43517b45b42405928b67c713881858bad3a42511c2716ff9cd332034b672b52ff16610805cdbe7544a8a84b66e1c745a73c1b6bcda5b77b951f36c0f7a5372de9e5d1f9bbcde8843c6909002dda4875e67571af0bec581856c32c09c240e664e761e57cd0d8dc8a71cb918a5762d111285cd8b5613ddbd0ca08ac0342b2bdee38f96fa754bb2b087179c113c93986a810356eb94540b93cb9dec4aa9290ff12ec1aa2e656c9be3d590753c366c601406c061bc22033a1fd1f4e1111d039b8813b983cb506c3ea7ff3057983e8bf01682fbb00f43005313c82c1392918a6165a13338ffe11a992c1fb3d1032aa679a418c8ba4f8a0bc199e10cf6bd77a14fdd6a06093514348e3a8974434ae8a3676369c6be2cf90e672b343fce04ac6b22e0cf47568bc45d70a68e68c649a4830ae218590c1a437e7a23a54efe44f67086eb697b9fa57835f0b8f70f0a929226efb336c0e21833a028218cd63732c80aa477e62d141dba81854f70da68daff4a84cb6de779254e8a97e73565374af4092af05cbd6654afc3fd72f0ae232695cb6668eafecc4069bd90bb528b83efa2fbcdbd93b289929621ed74d808738fc103eeb105510851fc9319f171ea0ced0b97b5b9fb5ef985186bc52098f9eb476f67b7cc7665d47587975cb45a50fc64100719bf76345f0fdf1e09efe9fb800dc114e46be0879a195cc06870e23d2631dae71c3994481c8761c40d07c5bfca95e718b7b22585af03ed34175a46d57af3518e32a7fc1aa4482732a81a87f724f8d2e780b3a39d451a380f75c2d680cc7213eab1d4a59d394ae3810a1c90818d52f93fb203e2d8b1b5fa8f60b2d585d9135d648846f138b86953242d2bb1f2ecdf389b4de7651817b8e4e64b333f1aac523a93f2748a9c38ffbc29ced457b6f9781b08a67a1975d031ccd71545c0037434056c2434d13e6c4beebf46fc12222c0b2eccd6159d5aea8e554d7a09652b06bf7ca699a7199e716d05dd553041a8f2b303d236a9babaafb9fa528f28a2ca2aa780b940383c099aa65a0074b83fd1f0bc5b7b5e46c25e54838b3cbcfc95f87f1d471b3ba894434fa58952fdcb77f161372693306dba4e8f216d1c8e5caff0fe8360a51c60763644169fdc6a8267f2e3f909a61b2a678bce6ae90403a836b1a7b7e8cd8b54c37087a9e14446d95e6908d2eedbfcc653e02fdf771f701a79b9e5a26ed0a947842070f3b5701742211219e761762c37f0d0a1d1b9750fee577e1208115c66ac07ec091e6a3fc4aa6a253bcba868edd3154dcaf5162f615e85490a6ca342f34c43ac61a3ea6bfeefd850e190eb1d8da4d28b5eceeb1678c02433ecd5d48b2536404257e8ca7bef5855f2b813ed2f4c409445a3317c9be1a35ae2fb4d2b87921b904bf2c14db514cee045251cfc276374db15c99dea15acde197c6eb524988e39b63287beb8676865aaa3bad1b43b8cab15cbf27a498759e3203abf369e97242f0b0154149f14ac233cdb73a22b7fb8f09325bf2ace83bb6b5db8a121a2b682149a69131ccce52229840b113fc7b0bcc58405bfe87f1f95ffc2e96fc5596567e94364dfaa6d9d5a6eb99ae4ddf424").unwrap()); - } #[test] fn keygen_error_cases() { /* - Testing this condition: - if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::BytesFullEntropy) - || seed.key_len() != 32 - */ + Testing this condition: + if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::BytesFullEntropy) + || seed.key_len() != 32 + */ // success case KeyType: seed let mut seed = KeyMaterial256::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), KeyType::Seed, - ).unwrap(); + ) + .unwrap(); /* MLDSA44 */ let expected_pk_bytes: [u8; MLDSA44_PK_LEN] = hex::decode("d7b2b47254aae0db45e7930d4a98d2c97d8f1397d1789dafa17024b316e9bec94fc9946d42f19b79a7413bbaa33e7149cb42ed5115693ac041facb988adeb5fe0e1d8631184995b592c397d2294e2e14f90aa414ba3826899ac43f4cccacbc26e9a832b95118d5cb433cbef9660b00138e0817f61e762ca274c36ad554eb22aac1162e4ab01acba1e38c4efd8f80b65b333d0f72e55dfe71ce9c1ebb9889e7c56106c0fd73803a2aecfeafded7aa3cb2ceda54d12bd8cd36a78cf975943b47abd25e880ac452e5742ed1e8d1a82afa86e590c758c15ae4d2840d92bca1a5090f40496597fca7d8b9513f1a1bda6e950aaa98de467507d4a4f5a4f0599216582c3572f62eda8905ab3581670c4a02777a33e0ca7295fd8f4ff6d1a0a3a7683d65f5f5f7fc60da023e826c5f92144c02f7d1ba1075987553ea9367fcd76d990b7fa99cd45afdb8836d43e459f5187df058479709a01ea6835935fa70460990cd3dc1ba401ba94bab1dde41ac67ab3319dcaca06048d4c4eef27ee13a9c17d0538f430f2d642dc2415660de78877d8d8abc72523978c042e4285f4319846c44126242976844c10e556ba215b5a719e59d0c6b2a96d39859071fdcc2cde7524a7bedae54e85b318e854e8fe2b2f3edfac9719128270aafd1e5044c3a4fdafd9ff31f90784b8e8e4596144a0daf586511d3d9962b9ea95af197b4e5fc60f2b1ed15de3a5bef5f89bdc79d91051d9b2816e74fa54531efdc1cbe74d448857f476bcd58f21c0b653b3b76a4e076a6559a302718555cc63f74859aabab925f023861ca8cd0f7badb2871f67d55326d7451135ad45f4a1ba69118fbb2c8a30eec9392ef3f977066c9add5c710cc647b1514d217d958c7017c3e90fd20c04e674b90486e9370a31a001d32f473979e4906749e7e477fa0b74508f8a5f2378312b83c25bd388ca0b0fff7478baf42b71667edaac97c46b129643e586e5b055a0c211946d4f36e675bed5860fa042a315d9826164d6a9237c35a5fbf495490a5bd4df248b95c4aae7784b605673166ac4245b5b4b082a09e9323e62f2078c5b76783446defd736ad3a3702d49b089844900a61833397bc4419b30d7a97a0b387c1911474c4d41b53e32a977acb6f0ea75db65bb39e59e701e76957def6f2d44559c31a77122b5204e3b5c219f1688b14ed0bc0b801b3e6e82dcd43e9c0e9f41744cd9815bd1bc8820d8bb123f04facd1b1b685dd5a2b1b8dbbf3ed933670f095a180b4f192d08b10b8fabbdfcc2b24518e32eea0a5e0c904ca844780083f3b0cd2d0b8b6af67bc355b9494025dc7b0a78fa80e3a2dbfeb51328851d6078198e9493651ae787ec0251f922ba30e9f51df62a6d72784cf3dd205393176dfa324a512bd94970a36dd34a514a86791f0eb36f0145b09ab64651b4a0313b299611a2a1c48891627598768a3114060ba4443486df51522a1ce88b30985c216f8e6ed178dd567b304a0d4cafba882a28342f17a9aa26ae58db630083d2c358fdf566c3f5d62a428567bc9ea8ce95caa0f35474b0bfa8f339a250ab4dfcf2083be8eefbc1055e18fe15370eecb260566d83ff06b211aaec43ca29b54ccd00f8815a2465ef0b46515cc7e41f3124f09efff739309ab58b29a1459a00bce5038e938c9678f72eb0e4ee5fdaae66d9f8573fc97fc42b4959f4bf8b61d78433e86b0335d6e9191c4d8bf487b3905c108cfd6ac24b0ceb7dcb7cf51f84d0ed687b95eaeb1c533c06f0d97023d92a70825837b59ba6cb7d4e56b0a87c203862ae8f315ba5925e8edefa679369a2202766151f16a965f9f81ece76cc070b55869e4db9784cf05c830b3242c8312").unwrap() .try_into().unwrap(); @@ -182,15 +192,16 @@ mod mldsa_tests { seed.set_key_type(KeyType::BytesFullEntropy).unwrap(); _ = MLDSA44::keygen_from_seed(&seed).unwrap(); - // Failure case: key type != Seed || BytesFullEntropy let mac_seed = KeyMaterial256::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), KeyType::MACKey, - ).unwrap(); + ) + .unwrap(); match MLDSA44::keygen_from_seed(&mac_seed) { - Err(SignatureError::KeyGenError(_)) => { /* good */ }, + Err(SignatureError::KeyGenError(_)) => { /* good */ } _ => panic!("expected KeyGenError"), } @@ -198,11 +209,12 @@ mod mldsa_tests { let seed = KeyMaterial256::from_bytes_as_type( &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718").unwrap(), KeyType::Seed, - ).unwrap(); + ) + .unwrap(); assert_eq!(seed.key_len(), 25); match MLDSA44::keygen_from_seed(&seed) { - Err(SignatureError::KeyGenError(_)) => { /* good */ }, + Err(SignatureError::KeyGenError(_)) => { /* good */ } _ => panic!("expected KeyGenError"), } } @@ -216,22 +228,36 @@ mod mldsa_tests { let sk = MLDSA44PrivateKey::from_bytes(&hex::decode(MLDSA44_KAT1.sk).unwrap()).unwrap(); let rnd = if !MLDSA44_KAT1.deterministic { - let mut rnd = [0u8; 32]; - bouncycastle_rng::DefaultRNG::default().next_bytes_out(&mut rnd).unwrap(); - rnd - } else { [0u8; 32] }; + let mut rnd = [0u8; 32]; + bouncycastle_rng::DefaultRNG::default().next_bytes_out(&mut rnd).unwrap(); + rnd + } else { + [0u8; 32] + }; - let mu = MLDSA44::compute_mu_from_sk(&sk,&hex::decode(MLDSA44_KAT1.message).unwrap(), Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap())).unwrap(); + let mu = MLDSA44::compute_mu_from_sk( + &sk, + &hex::decode(MLDSA44_KAT1.message).unwrap(), + Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap()), + ) + .unwrap(); let sig = MLDSA44::sign_mu_deterministic(&sk, &mu, rnd).unwrap(); assert_eq!(&sig, &*hex::decode(MLDSA44_KAT1.signature).unwrap()); - MLDSA44::verify(&sk.derive_pk(), &hex::decode(MLDSA44_KAT1.message).unwrap(), Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap()), &sig).unwrap(); + MLDSA44::verify( + &sk.derive_pk(), + &hex::decode(MLDSA44_KAT1.message).unwrap(), + Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap()), + &sig, + ) + .unwrap(); // test the streaming API on the same value let mut s = MLDSA44::sign_init(&sk, Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap())).unwrap(); s.set_signer_rnd(rnd); s.sign_update(&hex::decode(MLDSA44_KAT1.message).unwrap()); let sig = s.sign_final().unwrap(); - let decoded_sig: &[u8; MLDSA44_SIG_LEN] = &hex::decode(MLDSA44_KAT1.signature).unwrap().try_into().unwrap(); + let decoded_sig: &[u8; MLDSA44_SIG_LEN] = + &hex::decode(MLDSA44_KAT1.signature).unwrap().try_into().unwrap(); assert_eq!(&sig, decoded_sig); // Then with the message broken into chunks @@ -241,9 +267,8 @@ mod mldsa_tests { s.sign_update(msg_chunk); } let sig_val = s.sign_final().unwrap(); - MLDSA44::verify(&sk.derive_pk(), DUMMY_SEED_1024, Some(b"streaming API chunked"), &sig_val).unwrap(); - - + MLDSA44::verify(&sk.derive_pk(), DUMMY_SEED_1024, Some(b"streaming API chunked"), &sig_val) + .unwrap(); // ML-DSA-65 @@ -252,25 +277,36 @@ mod mldsa_tests { let mut rnd = [0u8; 32]; bouncycastle_rng::DefaultRNG::default().next_bytes_out(&mut rnd).unwrap(); rnd - } else { [0u8; 32] }; + } else { + [0u8; 32] + }; - let mu = MLDSA65::compute_mu_from_sk(&sk, &hex::decode(MLDSA65_KAT1.message).unwrap(), Some(&hex::decode(MLDSA65_KAT1.ctx).unwrap())).unwrap(); + let mu = MLDSA65::compute_mu_from_sk( + &sk, + &hex::decode(MLDSA65_KAT1.message).unwrap(), + Some(&hex::decode(MLDSA65_KAT1.ctx).unwrap()), + ) + .unwrap(); let sig = MLDSA65::sign_mu_deterministic(&sk, &mu, rnd).unwrap(); assert_eq!(&sig, &*hex::decode(MLDSA65_KAT1.signature).unwrap()); MLDSA65::verify( - &sk.derive_pk(), &*hex::decode(MLDSA65_KAT1.message).unwrap(), Some(&hex::decode(MLDSA65_KAT1.ctx).unwrap()), &sig).unwrap(); + &sk.derive_pk(), + &*hex::decode(MLDSA65_KAT1.message).unwrap(), + Some(&hex::decode(MLDSA65_KAT1.ctx).unwrap()), + &sig, + ) + .unwrap(); // test the streaming API on the same value let mut s = MLDSA65::sign_init(&sk, Some(&hex::decode(MLDSA65_KAT1.ctx).unwrap())).unwrap(); s.set_signer_rnd(rnd); s.sign_update(&hex::decode(MLDSA65_KAT1.message).unwrap()); let sig = s.sign_final().unwrap(); - let decoded_sig: &[u8; MLDSA65_SIG_LEN] = &hex::decode(MLDSA65_KAT1.signature).unwrap().try_into().unwrap(); + let decoded_sig: &[u8; MLDSA65_SIG_LEN] = + &hex::decode(MLDSA65_KAT1.signature).unwrap().try_into().unwrap(); assert_eq!(&sig, decoded_sig); - - // ML-DSA-87 let sk = MLDSA87PrivateKey::from_bytes(&hex::decode(MLDSA87_KAT1.sk).unwrap()).unwrap(); @@ -278,39 +314,57 @@ mod mldsa_tests { let mut rnd = [0u8; 32]; bouncycastle_rng::DefaultRNG::default().next_bytes_out(&mut rnd).unwrap(); rnd - } else { [0u8; 32] }; + } else { + [0u8; 32] + }; - let mu = MLDSA87::compute_mu_from_sk(&sk, &hex::decode(MLDSA87_KAT1.message).unwrap(), Some(&hex::decode(MLDSA87_KAT1.ctx).unwrap())).unwrap(); + let mu = MLDSA87::compute_mu_from_sk( + &sk, + &hex::decode(MLDSA87_KAT1.message).unwrap(), + Some(&hex::decode(MLDSA87_KAT1.ctx).unwrap()), + ) + .unwrap(); let sig = MLDSA87::sign_mu_deterministic(&sk, &mu, rnd).unwrap(); assert_eq!(&sig, &*hex::decode(MLDSA87_KAT1.signature).unwrap()); - MLDSA87::verify(&sk.derive_pk(), &*hex::decode(MLDSA87_KAT1.message).unwrap(), Some(&hex::decode(MLDSA87_KAT1.ctx).unwrap()), &sig).unwrap(); + MLDSA87::verify( + &sk.derive_pk(), + &*hex::decode(MLDSA87_KAT1.message).unwrap(), + Some(&hex::decode(MLDSA87_KAT1.ctx).unwrap()), + &sig, + ) + .unwrap(); // test the streaming API on the same value let mut s = MLDSA87::sign_init(&sk, Some(&hex::decode(MLDSA87_KAT1.ctx).unwrap())).unwrap(); s.set_signer_rnd(rnd); s.sign_update(&hex::decode(MLDSA87_KAT1.message).unwrap()); let sig = s.sign_final().unwrap(); - let decoded_sig: &[u8; MLDSA87_SIG_LEN] = &hex::decode(MLDSA87_KAT1.signature).unwrap().try_into().unwrap(); + let decoded_sig: &[u8; MLDSA87_SIG_LEN] = + &hex::decode(MLDSA87_KAT1.signature).unwrap().try_into().unwrap(); assert_eq!(&sig, decoded_sig); } #[test] - fn test_sign_mu_deterministic_from_seed_out() { + fn test_sign_mu_deterministic_from_seed() { // I don't have a KAT, so I'll test against the regular implementation // ML-DSA-44 let seed = KeyMaterial256::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), KeyType::Seed, - ).unwrap(); + ) + .unwrap(); let rnd = if !MLDSA44_KAT1.deterministic { let mut rnd = [0u8; 32]; bouncycastle_rng::DefaultRNG::default().next_bytes_out(&mut rnd).unwrap(); rnd - } else { [0u8; 32] }; + } else { + [0u8; 32] + }; let tr: [u8; MLDSA_TR_LEN]; { @@ -320,38 +374,114 @@ mod mldsa_tests { // BEGIN expected values let (_, expected_sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); - let expected_mu = MLDSA44::compute_mu_from_sk(&expected_sk, - &hex::decode(MLDSA44_KAT1.message).unwrap(), - Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap())).unwrap(); + let expected_mu = MLDSA44::compute_mu_from_sk( + &expected_sk, + &hex::decode(MLDSA44_KAT1.message).unwrap(), + Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap()), + ) + .unwrap(); let mut expected_sig = [0u8; MLDSA44_SIG_LEN]; - let bytes_written = MLDSA44::sign_mu_deterministic_out(&expected_sk, &expected_mu, rnd, &mut expected_sig).unwrap(); + let bytes_written = + MLDSA44::sign_mu_deterministic_out(&expected_sk, &expected_mu, rnd, &mut expected_sig) + .unwrap(); assert_eq!(bytes_written, MLDSA44_SIG_LEN); // END expected values - let mu = MLDSA44::compute_mu_from_tr(&tr, &hex::decode(MLDSA44_KAT1.message).unwrap(), Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap())).unwrap(); + let mu = MLDSA44::compute_mu_from_tr( + &tr, + &hex::decode(MLDSA44_KAT1.message).unwrap(), + Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap()), + ) + .unwrap(); assert_eq!(&expected_mu, &mu); + + let sig = MLDSA44::sign_mu_deterministic_from_seed(&seed, &mu, rnd).unwrap(); + assert_eq!(&sig, &expected_sig); + let mut sig = [0u8; MLDSA44_SIG_LEN]; - let bytes_written = MLDSA44::sign_mu_deterministic_from_seed_out(&seed, &mu, rnd, &mut sig).unwrap(); + let bytes_written = + MLDSA44::sign_mu_deterministic_from_seed_out(&seed, &mu, rnd, &mut sig).unwrap(); assert_eq!(bytes_written, MLDSA44_SIG_LEN); assert_eq!(&sig, &expected_sig); let (pk, _) = MLDSA44::keygen_from_seed(&seed).unwrap(); - MLDSA44::verify(&pk, &hex::decode(MLDSA44_KAT1.message).unwrap(), Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap()), &sig).unwrap(); + MLDSA44::verify( + &pk, + &hex::decode(MLDSA44_KAT1.message).unwrap(), + Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap()), + &sig, + ) + .unwrap(); + + // test invalid seed types + let wrong_len_seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d").unwrap(), + KeyType::Seed, + ) + .unwrap(); + assert_eq!(wrong_len_seed.key_len(), 30); + match MLDSA44::sign_mu_deterministic_from_seed_out(&wrong_len_seed, &mu, rnd, &mut sig) { + Err(SignatureError::KeyGenError(_)) => { /* good */ } + _ => panic!("Expected KeyGenError"), + }; + + let wrong_type_seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), + KeyType::SymmetricCipherKey, + ) + .unwrap(); + assert_eq!(wrong_type_seed.key_type(), KeyType::SymmetricCipherKey); + + match MLDSA44::sign_mu_deterministic_from_seed_out(&wrong_type_seed, &mu, rnd, &mut sig) { + Err(SignatureError::KeyGenError(_)) => { /* good */ } + _ => panic!("Expected KeyGenError"), + }; + + // success case: seed SecurityStrength is exactly right + let mut low_security_seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), + KeyType::Seed, + ) + .unwrap(); + low_security_seed.allow_hazardous_operations(); + low_security_seed.set_security_strength(SecurityStrength::_192bit).unwrap(); + low_security_seed.drop_hazardous_operations(); + // a 128bit secure seed should be rejected by MLDSA87 + MLDSA65::sign_mu_deterministic_from_seed(&low_security_seed, &mu, rnd).unwrap(); + + // seed SecurityStrength is too low + let mut low_security_seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), + KeyType::Seed, + ) + .unwrap(); + low_security_seed.allow_hazardous_operations(); + low_security_seed.set_security_strength(SecurityStrength::_128bit).unwrap(); + low_security_seed.drop_hazardous_operations(); + // a 128bit secure seed should be rejected by MLDSA87 + match MLDSA87::sign_mu_deterministic_from_seed(&low_security_seed, &mu, rnd) { + Err(SignatureError::KeyGenError(_)) => { /* good */ } + _ => panic!("Expected KeyGenError"), + }; // test the streaming API on the same value - let mut s = MLDSA44::sign_init_from_seed(&seed, Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap())).unwrap(); + let mut s = + MLDSA44::sign_init_from_seed(&seed, Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap())) + .unwrap(); s.set_signer_rnd(rnd); s.sign_update(&hex::decode(MLDSA44_KAT1.message).unwrap()); let sig = s.sign_final().unwrap(); assert_eq!(&sig, &expected_sig); - - // while we're at it, test the streaming verifier cause I'm not sure where else this is being tested. - let mut v = MLDSA44::verify_init(&pk, Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap())).unwrap(); + let mut v = + MLDSA44::verify_init(&pk, Some(&hex::decode(MLDSA44_KAT1.ctx).unwrap())).unwrap(); v.verify_update(&hex::decode(MLDSA44_KAT1.message).unwrap()); v.verify_final(&expected_sig).unwrap(); } @@ -363,59 +493,116 @@ mod mldsa_tests { // ctx too long // this is common to all parameter sets, so I'll just test MLDSA44 let (_pk, sk) = MLDSA44::keygen().unwrap(); + + // ctx with len 255 works + MLDSA44::sign_init(&sk, Some(&[1u8; 255])).unwrap(); + + // ctx with len 256 is too long let too_long_ctx = [1u8; 256]; match MLDSA44::sign_init(&sk, Some(&too_long_ctx)) { - Err(SignatureError::LengthError(_)) => { /* good */ }, + Err(SignatureError::LengthError(_)) => { /* good */ } _ => panic!("Expected error for ctx too long"), } // test various things that are shorter / longer than required - // sig too long / too short // MLDSA44 let (pk, sk) = MLDSA44::keygen().unwrap(); let sig = MLDSA44::sign(&sk, msg, None).unwrap(); // too short - match MLDSA44::verify(&pk, msg, None, &sig[..MLDSA44_SIG_LEN-1]) { - Err(SignatureError::LengthError(_)) => { /* good */ }, + match MLDSA44::verify(&pk, msg, None, &sig[..MLDSA44_SIG_LEN - 1]) { + Err(SignatureError::LengthError(_)) => { /* good */ } _ => panic!("Expected error for sig too short"), } // too long - let mut sig_too_long= vec![1u8; MLDSA44_SIG_LEN + 2]; + let mut sig_too_long = [0u8; MLDSA44_SIG_LEN + 2]; sig_too_long[..MLDSA44_SIG_LEN].copy_from_slice(&sig); - MLDSA44::verify(&pk, msg, None, &sig_too_long).unwrap(); + sig_too_long[MLDSA44_SIG_LEN..].copy_from_slice(&[1u8, 0u8]); + match MLDSA44::verify(&pk, msg, None, &sig_too_long) { + Err(SignatureError::LengthError(_)) => { /* good */ } + _ => panic!("Expected error for sig too long"), + } // MLDSA65 let (pk, sk) = MLDSA65::keygen().unwrap(); let sig = MLDSA65::sign(&sk, msg, None).unwrap(); // too short - match MLDSA65::verify(&pk, msg, None, &sig[..MLDSA65_SIG_LEN-1]) { - Err(SignatureError::LengthError(_)) => { /* good */ }, + match MLDSA65::verify(&pk, msg, None, &sig[..MLDSA65_SIG_LEN - 1]) { + Err(SignatureError::LengthError(_)) => { /* good */ } _ => panic!("Expected error for sig too short"), } // too long - let mut sig_too_long= vec![1u8; MLDSA65_SIG_LEN + 2]; + let mut sig_too_long = [0u8; MLDSA65_SIG_LEN + 2]; sig_too_long[..MLDSA65_SIG_LEN].copy_from_slice(&sig); - MLDSA65::verify(&pk, msg, None, &sig_too_long).unwrap(); + sig_too_long[MLDSA65_SIG_LEN..].copy_from_slice(&[1u8, 0u8]); + match MLDSA65::verify(&pk, msg, None, &sig_too_long) { + Err(SignatureError::LengthError(_)) => { /* good */ } + _ => panic!("Expected error for sig too short"), + } // MLDSA87 let (pk, sk) = MLDSA87::keygen().unwrap(); let sig = MLDSA87::sign(&sk, msg, None).unwrap(); // too short - match MLDSA87::verify(&pk, msg, None, &sig[..MLDSA44_SIG_LEN-1]) { - Err(SignatureError::LengthError(_)) => { /* good */ }, + match MLDSA87::verify(&pk, msg, None, &sig[..MLDSA44_SIG_LEN - 1]) { + Err(SignatureError::LengthError(_)) => { /* good */ } _ => panic!("Expected error for sig too short"), } // too long - let mut sig_too_long= vec![1u8; MLDSA87_SIG_LEN + 2]; + let mut sig_too_long = [0u8; MLDSA87_SIG_LEN + 2]; sig_too_long[..MLDSA87_SIG_LEN].copy_from_slice(&sig); - MLDSA87::verify(&pk, msg, None, &sig_too_long).unwrap(); + sig_too_long[MLDSA87_SIG_LEN..].copy_from_slice(&[1u8, 0u8]); + match MLDSA87::verify(&pk, msg, None, &sig_too_long) { + Err(SignatureError::LengthError(_)) => { /* good */ } + _ => panic!("Expected error for sig too long"), + } + + /* GAMMA boundary conditions */ + + // test to pin MLDSA65_GAMMA1_MINUS_BETA (produces the wrong sig val if MLDSA65_GAMMA1_MINUS_BETA is wrong) + // this test doesn't work in the lowmemory version because I don't have the seed for this KAT's sk + // let sk = MLDSA65PrivateKey::from_bytes(&hex::decode("FAC3F0A1C095D7F14FE867233429E7FBECC3E56C1C47C5776F60FCC952271C16D701337CC4DB701037240B2CC96700E76D912695D01ECFD144A1A4480C9A7AFC6AEBFF85B31A51CEDCF03C089DB78D57D5736998094CED527BF62306D163DD076746390725A575F658812850340A6A22F21D1A4488F81CC9C51E34C7195E24E62743418082738460173216564147714141447322747378303064332088852786387656485685067602315841385530303073748288551114488671126472324211001632768375217888105730287103413366631348063456705643028124418424362865147136784028750605280522354664402012156867463840124312702746274317185820204146418146260428165381878680661671606625835681411564078617545272781118153404548772767065605505661113346550212253001708208871437608544687461742554212252531618480788375428626465682802665176276452816778253827888713776811675325120157277407836344676441366411518042352072878407260148473360521722835364503831153320866470060388717774746280482741018426182467855676081768764502460378420511274573338543580057135466786576641657141853703461273673864078756846882878275266371317370221447843022470612874036187668341702635480511522823511802187750128755208466178350262551186287764073638143070751153170178714610652685824431071335605871487821771381233638082075233653821363404721684841385534088177538652001225820118825132746376784420308558673853184746535462708561415062307838047410305386755301311878544252655175427735642725125852462520300512237334101417850348822685720422134232331416163464745560268223741265100778127023844048536253761851061180163560244303674215200615686636064370316373872671703546376007034507065834571177568604078030182100382824024244355146222000521887855141800051638076135301876078282783854476883578855568168226642036114282711266780675104628153263815046834464683021517741886573830273710441308840745348777606127672844722386630210200025302263135713023513144334831232011681183146135820156511536562848603567353564216431366041187872531665824337234157375170402400366431125740874338582777401587182726007834342527487856427375026483665335500311348218318404831768517434724867376286526385537375022315084547851162348038628580233345112714217535531172243054028425076334167574124642505645643703357747258152772101731762154367415443402180013310507803185561248014235117864462560741807827475862248680132084577068144204516541344387456176453454137741684473663420305220270013380651567340128517050186661163071768085126283043061688144133578020758648635074223303518423535803274805136544782015667213011858810228662670086040427500742786645441425845543080676585818187373073177826648377838253361800836061614077137087700227656343025125400007421502577613446081061602370815457845047316447020371536762643681172462742533741043757221578526022521413633247403254078850774373623685012472806652753134016260818410320635707047651107738122602141422051241873175027656625672307441218576154467471554748806141478270237022035541450228312172183426588573284554185388468111581188733140148403753651886533044825265188582527046117408104630522280766840883618814145722050535737005141474643706767817456415644868337004352380220517858504287578807218203860374826463502187671017880147716606130536501355309DEDE7F9572F3915A1C1B014B2FB3D7591DB0BED20443D420E8D4D3458381AF3506F93C6CB3561449E50A9E3349DC9B8CAD8AB1ECBF946CF6ECBBBC11A8B32B295D1E3CB23DF662E8A8625EE91ACCC74EE9AB059FB12A46AAEC343FF1B21BED35E135F46D05EE11877126A9961C897BFEA0D552E03BCB313AD1BEF5B21FFF09005417B0F531120D037D1106C4FFF3692C607108563B272D054D98299D1FEC0646536152FD6F915FBBD8ED1E864904149F919DA295CB79F4EE6A9123E82FC5DF0BC379743B0A650D8D26799B02C0A6F278B323BB7F58811E75CDDD6BA27200DD7BD5B94F6AF7AC76B791F12A1C464E2E112A591A12E3EE569C7B0BBF726C634737A7388D676BF36DA496E3A6DD4744D83D5379642261B61D6A08A164BE30360BEC7E05473A66CCDE29DE5F68006932739DD876E29954419750B6C2E282432E0E900F30C6DEFA5042216C398189F1072F3738DEA06AB44F7611BD995430B2204FCA4474A2A8DFAFC44607F9AABB80D5FF235BA93A74BCE8297D45A35322116117A5BD9C960A18758040EC670D8F0B5F75515DFAD91A60FE8F26A5048F9A955210B6CFFC7F6C963531E9B6EB0DAC6D744E63A22AC9E2935FCBD8FFDC6D1D5621266A338FFD3E28257C5F04B209DD1013C2EA6FBE2D197BBBC9B8F1A6DB84E361F1E1D6235566A289861E193CF51604CE29DB66C546C20CD3D769E128BE0C10C7E8EA640F23778F426DE99A675726CB837EBF6F2EDFD886E7522FEA3E9AE793E34B702493C6575828021B8A4E1DAB64DE98DB52126120B8B192AA8977143E11E7819A2C116598CA301670C51569432DC1C86270F72E474CC35B4A7621F0EF883BD9B187DF4EBF2BECB7487BE331A45E9CB43E5DB9225B21724D60CFFBE5A3817765B5DB2E111AE6445BFEDD6631A1FE8048E2008E5DF450E0FE622319217567AB202BE5F6E9D8859CADEB2210784D9FB1778BA039A98A2C47435EBA76EF170B9ACB2D3884D64831E910D2CE1E8346881490FEABFC5882C7CF9D6101916E4021B9CB723189345049B5048400307E0236F177329EE6CF9A67848A4D7C98349A94A68400EA141BDA866F5071F3F4A7B8B96372C5442B43A003962DA6A5F07B9A572E7EB8A057BE06C7AF19D4EA728B7BF7F0AA493C413190CEE7D9AA90033378A2FEAFB9F1F360A904BFFA62FE1161240FCCAD33473A93FDAD26DAADBBA4CD05BD1F5897C40FD60BBEA8ED0404347131B2251D4CE06A65BB5EC31BFF5D03CFEAFFD7D06D09CFE8D88706A0AED09DFBCD273C65E1797D65136325A78E56CC4836B39EBD5A964229D2E9A163FE0E12BF0355D6265D01B7F94874E579119AA7E8C7EA69C09A2FA46069FA64CF0CD2ADDE12ADCE6B422676C0F05951B5C85404E25455505730986415B693604F557E09A6C6F48F5C193BF7DDC0ADFFC91CCBE0D3F7577F9AF85BC039CF2787ACE3010A413E3A0613C323B00C5A42776789382EF804BA918646A27ADFF1C3BDB8D86D4D1237ADF4190FDA2D87510ED68CF9234A766EFAFF779EF9FC4F8337E147FB5C83227E97F67D497F675DA66D55A88E9611459D98473DBDDC0730B2F8C6FA606A22E26679E68BCA03A50DF3731992A97063A4A6AFFF8C268290422A9D094B85BD6851177BE2BC04FFC64CD96E866731B552DD3C779D49C7781BEB8DA3F85B942A9780E47675B11E5DD77ED1F343775E876A737CFA3CBDF5CBBD7614A3EABE4DB919BC3E13ADE651F45133A04DC5DE0DDBC0BE01DDD615BFE6666769CC663F4941131A80DE835C851EBC67DFA48024C07FFB50F5C7E0A5686D1AA9EA22E45F6DE2A07DFEF28E90B00C24074897A76FFBAAEDE6BBB9A0DC76D35E2163169C426B1F5221D0A33F06C25072A2B164521736C441D4547EBE8AC4C408D30EA34ABF595BCC4CDF6157E86D8F8C5950E9EF27290EA077D812941A1DBBB4C6D06D52583DF55547F657346373CA37BFE9CDD6010CB5B0033F643EE0D6C9052699FC8E2739B69E1896BA14C7BDAAA7305F5F424350979F60868CB7E9884C818444C0FC0481216D06AF22076B726DB17A496CD45E9BA344C9F49AAE12E2771DDC9AD40908A00F8A19B85F120DCB1BE238E77E15ABB6E24E4E8BC55A68DFA7D473ACF7519EB85F714C0F49908BF2A6F70AAFF77A3DF2500992E304E98F6641505B1A5DF02C66118492354EA720A7078827A1B769B308440864FDE478F312DD497F7C507AEEB9AE85933607F20E4FD1906FB6FD074B8935D0038A2793489136E8305131FB95296A42CCB66F4F90F7214642929DC6E6843CE5B83E58E3F22CA8A5586A1D93527D75691B1E485953FE1D3E56E1C3071965B8E8EBBFE0F3FAB5F24FC245D2AFC423AD67DBD22E69ED2619E42CA0AF8F30C06D969CD7C69AD0783E17D018DEF90E75198EFADE2AE031FAC1905D5911D198635A1972D9CD8C1E99F049FBD96E6F88A25773B9F6048B6C136B4110DA4F587EA5C27700BBB6800854C9A4D1375F9777E09B1E53AFAF73EECC5E7711F7B19E7026408DAA10E93B8D916ACB0A4AD57848C2B9F767C48709382A2CC57B9DF56264B8EA91A529B4ACC7BE8DD3F3DF30276CD459CAF32F65118B6914E22507C2D5E032038BC272442FF16A80224EE35A4CA90B2564EA78B3856858D985644B683F62FF5AFC04F4F6FAED8434B6CF4EAF116FE231FB7931968D7BBA18BA347628304EB13EC8BF83FAE853E208FD276EA60B2DA751B058D82EDEEB4099BE90A581B52415B3E043B39D570C6E3996F42733A1459A5E3667009FF1124C106D2ACA45107D242ED4FF486F1991A01B5E471D6D360C19D3D064B578147A82DEA65FE0F8B7897B7347038522A2EBEB7ED217E43C33D5B7ADDD9942B43F9D5B8D87416C425F3E6CA3BCD3D995051C02EA4EE84BCB3B877F27FB16993522A91539D3FF43925671FB256573017302DDCA5A562FFCEC07A1204560252C78C2D797DD4EBF300922D2891AF7D220D32C3AF68F20CC096F6D5F9038E5AE7BAD090F99F9AE7FF3A615EC4A7F1F759A52428F94DBD496A81B0B71278EE4127AA72599C97618BF1D053C471E85D888C2971D816E5147BB07AB2192FA473641495CE64596D543D37DF340181EC609E8EA8F72676C2DCDA357C19051A8642D99FA00F962D28B4830E81C8D0CD2DF26ABE514803C8192608E29B9ACB3B150A664C474C6E064F1BCAAD0A08344E911FBF8425A0893D71325012113B8F2FD74FC0E2966AAFD72F280D18D8D02F779B0B2C61DD5E4385B5B82242AC327C3DDB6AB8F288E8DDA24F1E5812A111860058DEA190208928F1AD208974AA2EDD53BAFF400888AA9D74C0071C65C30AFE4A02B99876621A0542BBCE337ECFC06F0A236C3E9C672B48E61EA70CDF82421C14B7F0D25474ED2B04DA1C570598B14CFF438BBC44AB3C931F47D65DD0E767DCD7DDA522CC4CB07EB78962A50D281BEC69B54A77D26C902D3759560DAB544404BE0AA3F80D3B84EC4731DFFD1579533004D2D1EBA09148AA84C93B6A2049AD019A0BDAB9F99389FA4").unwrap()).unwrap(); + // // because this is from the bc-test-data with busted mu values, need to fix the (busted) mu + // let mu = hex::decode("dd6438e69fdbe810789fb22940fb451c28ac570c8e195f4aebc22db848cabe970a76c92011702d672478fc09f2bdc10b0b7c2b1752a9d99ea7ac3a6381684540").unwrap(); + // let expected_sig = hex::decode( + // "C3263802CBF2818D55BAAE9C2BE8C1EE0123802BDAB7C87CF9E6CF06E2A8823E750FFC8FB917F9052275A2CC68C2ADF4CFC78D0144FD3E96694C116CE1672BBCA6A3E1F6BBE8A0C34E5C1874DE1798CFC7766FE1BA906A1C6EF40EAB1A914705C3B0F14ED8D212B81D400281F68644B2EF72330DA2BD16C792D8160896177E920EB8E198AB5A38F5FFC5088663D0D0E0EB30C342F41B886FF209C8939CA424C2C24380E4F5AD805242CD25F4AC445AB883CD5826FA4135F4D549B12662C687B0FAFF068F8C18077B463E910E7A17B3BA9A76418C79FC53CADC75E48E977EAEA1FE748EDF0A1C0CDF9D037812B1BE505BD39E4EDA489CD56369A1B3F793554D7DD815AB1A7DE536018F0E0D73B95C8EBA07626D92B0365F6F71FAEA07E1E80FEEA666EB57865D6D13E46B801FECE841A2179BA8DA1FC1C1AA53743F187F426DFA34510E11AF1D9570FAC5DF77C9BF7ACE64BB130DB2192959091DDFEED580C722ACE89F84729540EEE8CFF6D8E4AF91C1B0EBB42A65F47B80F4A300431D7DEC90BE739E31C9906D7403B82C196266D4FCC6659C5AA549D0ADC3AAAD1F9DB7C80E17839957DF682C10B1938DD9A292C65E0146566B1EE24529C925691BAC109CB308C4303C3513FF5B62275E84C8C94DD6CFBF2E0F9F0B735FEFC44095FA2E01009C37C33BBDF082B305AD479C743076EC2EFAC0176F15A0EFBFA504894A3F8C6B7ACB096182B71131F46B1EC7343C9DE78F85CC0352F97C188667898ACB4EB4173D715FEA3B69DCB1B47904C8AB51EB4915357995B5F69F979776C6CBF114BCB0A154D82D17A9251ED545FDBE7E04AB06B99662D18673F08D933CA0B84FBEBE17F783D4039078465C378E9AEF18423C7B86C411DF9B4A77A2BC8804C4E5A5F9BC5777166C221F60107D2107AB55BB53E354267F8329B71A77BFF920EA94D9240B436E8CBA65940C0734D6928F57257A16154BFA5D8C9459979DEF567EE0965239C0DBDF122723636F2C5C5CAC6C607BDB9FDE8B8557A687E3A68970BFFAD695BADA74CE3C681B81AC8CE3747B844B7530A606F6320ABE91F16E79EFFBCB8446319D888D957252836BE6D88C018EED2F5C46369FE88CAEE624905E71C41252B70F115F781E0D4D851D2820718DA03FA1E9F9490AA06D74E3D15A44CFC98E57AB49A63285451C2A43D5ACA6733952814C05391431052DD9FE5D4AFAFE7027E5E5C8E9247FD5B574B438C4E541D6F408964BFEACC54FF873C6976F4383AE2517D4860E3D775280DE63D2EB01709282170E33A2F5FBD80937B0D23188C978BA370B5BB154FE0F2FA0361A2F1E46FBAFE6494FEAFF26097EBD2E307301C8649FAF82B57788B62E6C69DE6F6B3ECCDF17C10CBA41C3D1A8025F5BE98D65F2B37AC2527BA60E99B89FDA9028845F6EE6E20C2C8E2E8D8627C4D5859CD141920133D4D4B3AC8B91C1B14B6F6B8DE35E9674F312B938DADB90C335931081DB90640883512125BD793467C78AB84208146EAE0D62004E51031377BBDE89AA0AF0609E9AE315EA8A043C368167073422018900F9C653535A96FF73DA466377CF462314F361E08ABA6CACAB9AA666633FC6602D645CEEF305829B90AFC8BBDA9F8E40EFFD62FC31A1492914E22DE711DC3102D64AF46D13ED766CF36E51629409F69EC4DF98CA97EC93DA4BA215C567C73104FDF6863229E2A2C9A3CA11DD3C8D505CF88944428F9D500029B770FDEB16388F63C50FCAD1953E2AC759730E7CAA930362274B6A3E0ABE1211613F3C36FEFD09454401B6110D95201BD022A3B6C68DD054AE0B0C547AFF5C7421EBF0879098112286C962A6164C799A7A20CCF3D4189B2AA240F895C54CB2F9E6C2C1E2BA9BEB185CCBD82CEC60A49D8CBBD3C26AC0BC5C43E2D5672E21F19ACFF857217A243D4C90600D3737EE78638AD6239C3AF4E79FCE030DDB0527EE428ADA0C1C9EB867593092CD1A5B77347FEF4541B71AB33932F8F1BE31AA475B9F4BBC07F218377E436E348F47607D9140E6114A05D2A072409C390EBC79231CB6DC2E3A698776F9C32B44C10F156AF450A8DAD6DEC0D294392DF6E17DDD312BB22997CF1D8A6EC55DFE401EBA486CF9C8C0E6E713D48C0CA8CD26EAF88DC0B34F86F35B6617C45173131779ED98E880BBA3520835F29604B6F32AB1DA8034F6F559E81050BBCD7833B0D71E90101339386AE3BC653CA50F37BBBEB64377987BD892CA51280499B6EB051345EF9BF1F6EE766142491F96E27787ED8991338F60D2A4BAABF43ACE7A6072C6BEB6D0FFF79A3B956DD5CF6348F6E02104E1F9F33B2AEDFFA6B833A00CB21BAD6AD04047DD845180131506080272925960DA888E393B43EA5770530347EAF9E9812E87A317D1BA66C61CACD55A8FFD6355BB39E596D33F4E8355A6B51C4E70697D4C5BE9FFE64FB11EA714D81B20677DBA268E8A9A5F61E8E268DDE6F6FC93F3FAD688312885681E0F67474689680532DB88E178AAF2FDD19E37B091020FF01008BF0FA8017075C2F02B5E7DCACFD1F337A24815C6D9A9E1678EE2EE1721A61ED774A7DE98051F799D4F3A5407C1F725B618AD3B0C17FD13D762005C143A9AD78B13DF4B376D924BD9AE37502446D2263046BC7CE97CB6371CBA72FE0224FE71D501884E9AA820F92E1CCC3F7F4D57214F0842C96B85BFEFC3767B81AD76F778B00EB2BAE3C99D75EAF295901B9DB273F4288FA4EA74650B90D114B081B1D061D97EF211A84726756396F36BCACC45A8EF3F57E2331EE4FBCEED55C48605B4A3D842067933F8AD6D72C1793B17515977EEB8404CB55388EA3CB43F504A5325870473C14287DE22D55015391D973A9556B93B0943C64F5CE8DCD365C1F1DB7B56B2FC91BF5552E5233C28755E366BCF303B33CE2FE608A5BF6348EAB272E4FC6B77F13367E12085E008C8CE17095727D83266CC321ACCC9C8B58F2D149C205A4DC6D3A599CBA0E3FC4EE042C3461445A536FB9AC09D2FEA87C7E0E8BE39B2D0894EA1E203CAECC5F79F13ACAA917401E75E8F36CBA4D34EB639725FE41E64912F48A70D1BA47309D23E40DBCFF986568B8536B5F464C5B169D5E0F90ADE7F46004098E5880340DB738A7B9C849A71D01B8F8F9FEA8B3B331210553C469A5EE257B4EB02EC8932787C54086C5DE6A82096F0AB32B716BBCBCAF26BAAA792931D946FA1D59EE8A9924484E38B02F89D12971903C675B9FE420DACB1C0172351A3A44376E6AF6F25B261173E98B04DD2E49BD795A17E9C3E1D7DD1EECAEC4B8A5AE4B266E4DC0618E41AB8F82A4F652D2A07814DDEBA856B6000EDAA8A8A0542466A83243AD8128B1C468215EB58CC436BBE6B7A8D8CB12FA78131E1887B3253BD623055D72B13E175AB7DB00E8D7680360990C1D9EAD06F04F51F739734DEDCB8AA5ED3BCFEFB49D5C89ED291C4AF1EC09BDFD24029B7A219067DAF493C53809BAAAF830C8D4A3D643AA1B0CFABE58D0B1A5E9C0357AC421459FF0B0766CF82E08F95BF8B6C65D81BFC342A07BA51C914D7F1FFFAE1E069BEA449EC9BCF898BC29CAF4FDDDE6846361A4393F6C6987C766037526A9FE42008FB59DEE597547B8F61C62085BCA0686B6237C384107ADF094C91A230D783F55D39F1E57128A92F0FF0CD92D6B454DF4A3AFA2E1E4DF2C6C9E3CA3B304E27A8D7F44C75EEF09F475ECE74B07A80DBE1B4F2F309756BCFEC0C2D78DEA4CDF3BC864FADBEAA1B9475BC7EA4ED80D26B14B61EDE93AA2A777C8403DAA7BEC36C5503A2114A97CCA8B04565F17A4F0DFAA4985A5FCBB4398323233ABCC728CB2E0D1E2CCD36645E5915985D39ADAE871AA5B1CF3B001FDC7D9F0BD82FCE569A5C6BBD216E09C11CC6AAE067686E95BB28C441F189AFA83A9E117D944927C4A76FED756A35E5EE0B0DF4159187CFFE5F6EC19F8DAB04791E04FA0A3DF55EC95B96D7AEE099867987986D3FF1422DFDA40100C226F8A24468393F21E11D3B583DD8A5FEED01832ACD1E24E2D5A8CE16C24629B9964F8B4E0B2B0F30601FA688711065190665ACEA6A971DF8FB708C056D68540A78FA750B43E0D54086502D4B19171F61B51AC075F7F09260B95F59BF343E1210BCD39C04B13267122B6CD78D2578EA3534EDF243D0FD520A212EBA25407E2B8119068B2CD1D1BA672E5CC73BE9676C9E2D906D44DEBA9573E7FC909E69BD9CCAEBE6600E30D3588CB8EE2E5CE95C65F3FD24E4D527CE764FEF539639155086FE564B607A1082A74CDE886527F8D821E42AE88281A1CFF38EB5159955C1326C2D425D1C09205128E0386CA5C71B911896543799C3B1ADE8391E4370519DB17122A1A5EB6338208984E7347FC174D6DC4D59AF64767115A344A11E2049CDA0AE0BE6D7AE0592FC3468DB58E29CD3010E3EB17F736EE0E6062BF1F345A804F1733C73FE30C70B6FB625D00E529A400C069074235969485142D37F3D963D8492EA4026867ADE6108D982E20B188504A6724E6448992A3F7962AC2F01222BBE0715069FDE7D9DD93A6AC8AD321163FDBF9429EB2DD38F44BCA086AEA91642D4C85DFCF322F0B822B6EF73C570B564DD721FC3E73ED50A7FC88644C4340951C20B1E2C4A65B0BEC3C5E10E4C5F769CABD7E571748485A3BFD0D2204748DAE3E5187A7D97A7000000000000000000000000000000030D151D2328", + // ).unwrap(); + // + // let sig = MLDSA65::sign_mu_deterministic(&sk, mu.as_slice().try_into().unwrap(), [0u8; 32]) + // .unwrap(); + // assert_eq!(&sig, expected_sig.as_slice()); + + // test to pin MLDSA44_GAMMA2_MINUS_BETA (produces the wrong sig val if MLDSA44_GAMMA2_MINUS_BETA is wrong) + // this can't be tested here because I don't have the seed for this KAT's sk + // let sk = MLDSA44PrivateKey::from_bytes(&hex::decode("1B8664E94372A180CAD8C3D93F57FC16F452EF524D2DACEF0A50C0DDED23FBC052EA7330489B17CA2FE4234FB0F9CCA63B6185963852374EE10116AB38AAE2A922693A91188E5F5057AB18485D98B87EB21CCFDF5E9F4B5C5787F5043988EF6790A47D70515D93C48C28A855111D73F245898C877BA4E6A646057579A111F59C1BC629D82652D388901B2961C2C270A3B68054329299362691889122320418918820132D03C28D8C144200B825E0A88198A210031328D4884D1126690AC38D1B1510089051C8142D02B390C8440564040800290CA4480EC4424D1A960409A58010472D42346911A4401481884830411B122589808981222E63164D11C165DB064C0A430412374AD19271400846409271001748C9124894064952C06D1C316AC0484C19B69103A70950100208460458084C49224EE4484559048D514265D99220E2A41162C231D01620C31800D4A45008390A22A67018B36882049103440642364E09B3484A8448DA382E5CA810E01810182466D446681B489102384D8B18510397259A400A00436992C40C129285C9C05060486450988884386220808D919268233401A0B8405B0662E4C42119B74099B265E2400860462AA0B40D1A3248CC06318304489BC6114A484503A4010CB68D11860111984CA21410CC2666E0A02C94B01183182C904805A4320860B29122B66142886DC9222D24C171A42211C9A08814854023034582A25183B6711BC88DD0480292387220B041238790C8081203C0099436918BA2244A422959042D48260194322860928C00A844D21264A0A6240C9870C4A6042134209CA4099B128A1B3249249651A022281B44640AB36C029969C4C2710B396D0B964509B6209CC87150142A80B620CB28121A93684B382E01350A88160520983181322104426E83A6481014495C304DCB1826000390A4386103314E02B3008A14440AC78111C5704018021426650BC68C53C4708098481B9044C242680C124E2049284A3671C9B08911A77049982403C0300A83654B426D99345110B82C021092999429A0124E98184D50242061908981282661B28108A6651B22450A342C9C984C4B382DC4086292188D41C46CE2C491D902419C1231C2120CA040440CB12D22062DA3901102286C580066801228C9083101872554244902C62CCCB28502168CD8422E24416D1C20924A0660892826D2329092922C03451258B2099B2481183804243390C2B44C5AB485C814610A188503852988409203A580C9C0E30E0791B8E694262282FDD2FA69AD5CA94D015922BB63BC690FD546BC1ACFA0E79B02E7D1C1E92F419DA68C30CB12C736E216FC72C394883FBF40CFF538A43D08CE8A390DC7B78542199A54EEFE8FCD49225596FC97B27CF8BDDB57209E48FB628909CF889D38855CFE04AB04486378D17DC5780E81168B5E1DF462FC2D43860031B59D80AF95AFF0278210B9D96023E8A88FF0E4403D710895C830049AF4B8BCB57C5972D067A579C8C0021E5C870E1EE10629518191E283E796B58700061BA656B9385F54AA6917EEF52E60669B7B4F4FD36063DFB7822CDB4F19D9DA4E2F41E4BAE8C5274E7A9EBDB80BC9555687827A10224D9044DB2E45A89F582A716B4D3B131AF7DBCEA95DB88D84C73281D4CA7961F752FFA520BA3FDCC245331849768CE4D4B1FC330DCE474F31558764395F97FFC847BE9147DC22A3021F666A4327FF7FBEF007683A84C9FD99F5088703784AFFB3D86F216405BB9218DF05BB4CFA100145C9358EDE1A0F4C69AD23F7BA56FE8755F711067D7D43CF9FE0A93D642383DB3A0086CF7FCCA3FC59379D416DACBCEB7B49DFF554748EE36DFBCC07D15270A0388DA88360E6CA476DCFF56DE0B7B359609E58A728EE4B3ABCC296E42CB76034C7E13BD0B5208EE8CDF451F6CCCE87E9A21C3DCB28B8B9805CA7115065757B3C6D2A39B28B8610A3F30092C2E9F43A744372FFDF6B5F69DA6589860C520882FBEC130B28E1788572252A5995EB00B4B008931220B27488FEF5D04BC28C1FFA9DB5D0F1ED49CC4D775EB3DEC26ACD92BBB8A33A195A0C2F5AB7386BEA3AC9A9EE90C762E005DEF59348B0B1A6A373CCCD0BCD4125E0602EAA2BE057603020C2DC849513333C019A22210B55BCDAD54279BFEC5F998C1D14F7590B31B403564ED2E54AD007EDA4F2DE06AB0315E4C3384BE2C5392BB9FF1D41C770A767E4947FF7B609CFEAFF54DC672E866BDB861B81CAD3CC646DC325A5E1CD37C45FD0E29D6C2117CDF4C8B3082249C4F8D3E736A1CC0628D5FCB322711D78B881E7AABF4FE8967C5D584296DE520B818966A9490C82063D0C3C743208B25EB69BAEF6CC65E3CFF0C0C02997F328D2F1D307D421A87EE8A2737C8D5E7F63141DE2E03F5BB1EA20C829C4A00407F2E500BA2877DA65D64B29E42F097164F09F69C9587E4F1A1A88F619B6D19F210C01D35443F21C7C987658DCFF4163C8646F7671D04ACCE322E32513737B35944D2E70D62924F922A35AE86347843CA971FDFE616FF0DF4C5F77C699B14A555E0D41FF39BA7BD156751E73EC64A25E80E93F59217A5AC8B2162CBA9416E54F04447A99F8073D881BDA47B7D8CAEC135C0D09A028DD2A3EA74BAEFDCECA0D08B8FA701B9ED25B59F39BAB7ECA16A9ACD0446827829EBD9393E8DEC95C2395704F8CAED2155CBE604B047F94A650A981BAA93F070C3142011BF4F1561DFFC4D2F2B7144AC4CCC8DB17FBC3811C421961B156DF1FDE2893FE87902385B0FB5538E996FC40CC9B5995297DB905C262E25145A3C35A6B6F2014CC9DDAF4EE05DED72B3D436E4A6711BBA6B802D0CBC4ED44AB923644CF9E4BA4F24EFE1427E44D0A3FA98C1AA92F37E56440BA282AE0A77831650AE866C86BB2AA49EDDA7AF27B13A612F6A185265D93097679A7ED5A3A38B4B40F16CF5B11B99E2F5B529B940A4583D38B46D388167B34458CAF13682B6070D4FC37700E0222B04B9C1CCFE8FA0BB2B193E743E93C38DFB5029561287C782D9A0BAEE67705C1DF3AB2D8A456EB7C3F5E5D117108C3C15C3F4D2D382A6F69A5716621D004E5F510C4226E4B6BF76C36E649DA5D6EAE2690B1ADA69D8570DFADDE392CE5D9AF4AC94085903C52B0CAA61856150DACA070342754BC3C71AF32F8EB6D5CDF10F0B20471971569E7F1490A32E6F4DAF129A6CAE4DBC977C6B0D18102B9F18226ADF28F168B24D307753BA24574808444E488D812E9E97827261801AB2DD2F62EA4F4C552E54735A018D7ACCB666F6FBA8BF9EA8280876591B8E73D6303B2F1E585BB86F4DA1769503E144D3BD503BA460802D9287CB7B2238C2FBF3490DCDE4D35F02A63463A4F488632070345CA7C19604FED48A7102562965BFD87F2776357B3D1B17AE98CEEC0159FBBE604AEE9D92570E526CF6A5806D1BFD3092007D3DE29B9860DA1DE62184B4F576A8E515BAB346E2A2A4DB1338F4490400CA5A3EBE9AC3A8173C1991839F3A62DF7B903F777B0E5608C7396719639C8BBE3CF8CB371A43F7C436796259728ED11C3AA32BA97FB0B9C708785FD00345543EB8CF195A442A42F6AFBBBCF597CA8230B019C283777830E70C19DCF018C7C0561A7F2FE").unwrap()).unwrap(); + // // because this is from the bc-test-data with busted mu values, need to fix the (busted) mu + // let mu = hex::decode("35fd0d24022864a6f16159a654290be0c26688439c30c979d14b9553299429cd7c01f9f9835121149c254f2615805f64a32feb0607d87e9417f2900e0d86e351").unwrap(); + // let expected_sig = hex::decode( + // "20E8368CE256DCE0BD48E92EB0C35821EBE95B1388A861DAF45ACD482BBD95AA3FFBCB71B01E95FEA987EF538F300EDB7B8211698B5190AC5214C681EA39C78CA5EAA17AE994CDAEA07C186342E50A85A5FF292E19922286A96C42B4A998420C9CF0743F36ABF758E695166F04E0FADF9922C48188BB0F5AF0FCF2072C409AB2128ACA60E2780EB6DC1F12A979D85AE7484DA9F13534FD77C57554DC1BE883F1D1551E1FD750B03708D3FBAC87677100EABFDEAFD6DCAD15D1B7B7DC57928916F2EB68C8DC169274401E67A1CDD8E6A0F7122D6733A4FA86AC099446522C9E0ED60E244530A5DEE67E2A07E802145B783C59B9D873BB21A40477A307A84E2BF35EB1A66C42E18B8227B95623B1BB21590AEFF8D36C4B0EF6168D667037DC52B88C6F2FC5C1B1C232E2516B213B5F54A757880AF90ED73FE076A1C0613FF93415EC517632B7D10E2C1B7AB67B4DD50EF4E04C8910E088B8800DD50D04E276171C45C2B755BDE1BED2E740FA6D28D52130C6A6EB8BCFC3B3FFB815B8F82C1DCBF1320AE1B8627B2A893407159B8A1C684051E37A47E61776B9A80A53D3F41473A719B0D60CF8C25546C1EA4878362DA2C29F94617FEDDEC531A1D8E59BE133C033C0C85E3A476F7D0A27216E269A410A9EB7ED154A974C434C06024472D896D6EE24472D915C0C6544DFF73A0AE32C1365042906C54545EFC28EE2B272AE7A3D9F04EE7F975B1B1C04587CB42130421E026E58E45F27FA3ED02D8121F48C82587A968D0DC14C3C84E10F853D486DF6B3D997D5958BBB2A97F57CCC7702BB29EA359BD62E2ACF5A77D16E3A9B0B82BB94B3F6E75871D44D873E0A5BCF7BBF4131A0A9551FC1F504D25CAD2998FC213CD999A8F031667856E67E3FBC74D400C14F7C826FB676FB9735DDFCEDEC534E920386FD7CA810028DEDFE6902C65AC329753AE2AB7D8F63C154FDFF0FEEEB4E6C9026C4508C5A7973B1DC6746FB75E819E976DED75E6473D4DB7E6F018F587EAEA112080CEB7CD91CFC001763CACFCAD115D80246935D97DCD5820D3BD28FCB3582AB2AC40500C7636CC6A69A5542ED845103B21E0FBD05914C734F7048974298776CD8C5E8F2B018469668660ADD8211E3878F5A4F3E40A9BB27B846276F505718973E6AC5B15C0E1AC01D316F93CEF58D5E360909BC374276B4345011BED44C5F2159D5CDE2E0CFB4CDFECB51D676A46055D6C6A7F733E05D5D1B13B5F6F49FD435BADFA5965AB32275F8434C05E0DFF1DF18E5BD575F42F35B24A6E7C5A8A3BD4132AE96D67F2938738904D6C7C6DF612DFA2EB64FE9505EE47CD708D69222D24B5E109C36D4B7044C2BBC9E3554693915119737BAE15D7BA6B65C43E73D4552442D893ED78E1C08BF91231E5F1780079CE01A9C86FA8EC38A12EDEA62243F188AD182A988E7364BF1DEBD958121179346E0C4D164739C35D4D51D5E84587A5636E8FBBE116AC3F39AD92A5A9458C98CC56396D37519C7D229CCF732C254C8AB46101FB79038F06DD00EF23DE921FEF86CC17A7F6A17EDACA42B4910FC8119D1B5FC2DEE2D365BCDB7DCBB1281EAD990230915F8CAFB11E3C9D00E723DB001723B1DD7CF94568417EDEC07398A67FF7B27FF28F778DFF71C349871097BAD91D16688300834E5113573B982AC37FA2C75EF807FC7E616A92C386E6E49759B428556CACFD9E73A91F8EBB1DEE8863DA836EFB161714C68B3CE44034E0823A86C7B5AEF4B7DB7708E04DE158A94D1835573F7E973FF19DCFA95764B0263DFAC46DF55F39C3C98602BFCA12953A300F4702587EC845C65F40C46298008B1AFFC5CC4436516B74B9C740E05601CCF67F3E913F4E53DFFF4776E33EAD531B0DEE7C43692B99D6B4EFE9354675712BAA24235B62B64AC5D3EA1D1E7CBD1B14F67B45BC07F78E4F12E322C4060CA3A6D8F5323FE4B6239F5B01D4381D31F0610209EE83E3E726121E543525F10468006C0FBFF6F6E93E62ECE69E2A98F4E4CEABAC0DE867CE60EDA11C25DF160F367BAF6E54987F678F43847465D5AD1D9947CFA888CBA92B45D1A367890ABAC3FBD50F3E7ED4AF97C1F29C927C339C973A1009ECBB9F6954D4EB8FB30601C5052E7608F6EE48CE00B246E9F47A3E5991F250BF7506BD577C41F14E41570DF2C50E8087BB912E84C5F4E8C51A9870D027029809133FDB22F311E9A71F61CB71F6981D71F45AA80BC922777E655B63E6C14C8706BAA424B9C8C7CFFCC38DEAD82FD95D916E71AA3825B95088698A5C03B18B766299E4CCF71C4A959724308BC2AEDC7AF7C630B92A8B5CACD810A504A261DA7A527EBE24D23985809A5BE891C48926458E0B2890E5FA9695D5F48AF59CF21C37D150433BA777724AA7BDEDB170156BB3A6D9E56FBADA473B279996DEC61E80408636BF546F967FB0AF8ED9E5081F0A4A8B532E40D0C4BC55AC7A4F37F603CB6426B5D36B0B34D231C9AA83666B9C63DF5F497A67B0FB040D106C01BE00D0B1A3E4291F2156D1E637A3A9A766794D683AE106B639B5213337F8A1FBD924F7B4D9566A7C0A009B8E1925DC2FBC1FB3385A42FBB868388BBE6EF55942F9D5ED2C30992FFE514EEFA7F145E150F94C001E8D08904FC19401E871379B79AC79F39A7EF9E738BECA86C9078A6FCB62CD1D84E23708AD98FD2E759E7C841C40A2241DDF864D75B400CB5C6B03502D016C491615A05316F9586414694A67DE2C4D509025B1671FAEFF5B5CD262FBAB6234673E7D7D062016CE7D4B54C813A1AA10E6C1859C8EEEFDB81A138B2378EFB821DA5BAD135FE4E65363AF136F8E6C6D1640D4F8FF491B0FABF3E4D5668E931E39B5928E6ED7792719222E479FBCC018D755068E3C5C66E305E9C52BAAD0019B35B21F1A3B2F1DCEE98B9D0FB8E28E95CCD36838D65282B87F03D0CD5C096164B2C24E149D9128976688518790B30C087F9786C7F8F2E797082D839467C3AE8F076EC973D333515D12CA3CF9C50772AA3434829AB45B1F63EEE12769812B95F7C8ED33E5CBB49851142737562E9EED8ABD525D4C9423CD48BAF00B58343AD12F3EDD0D55A8895974FD99201CB1A0AE42EFC728A3D0D691B78FB85D3FB9210AC0D75B0BEC391AF0731921DF2E1DA5CA56FC6A68A0FE99525B85635665E0F290B98018201558C29F197100ECD37B9112924F60707E4B30BCBC5B7187A09DA0628FAFCCD930F545570FA835D911C9E35B1B73ADF15F7432ABAB1682201A93EDC842400381C645BFC729C8DE1C06AA2171EAACD88E919F9CA772EBEAFEF4D616FA5C973963181807844A3C84A57767F9AACB6BBCAD3FA24333D585C70737C7F8486888D919FA1A9B2C1D2DEDFF7FD1422536D8B8D9CABB0DCEC000B141C31393D4249566587B1D3DAE7FEFF000000000000000000000000000000000B232E40", + // ).unwrap(); + // + // let sig = MLDSA44::sign_mu_deterministic(&sk, mu.as_slice().try_into().unwrap(), [0u8; 32]) + // .unwrap(); + // assert_eq!(&sig, expected_sig.as_slice()); + + // test to pin MLDSA87_GAMMA2_MINUS_BETA (produces the wrong sig val if MLDSA87_GAMMA2_MINUS_BETA is wrong) + // this can't be tested here because I don't have the seed for this KAT's sk + // let sk = MLDSA87PrivateKey::from_bytes(&hex::decode("381286DCCAFF85CC31536E8CFA4D6FFF74AA12D0004D0BC826F54D3EF0F632090C06B8D3D562D3EA6C5D9E486AEE773AB7CB8A08382A055C6339BC650523ABA7176BB4169C6294FC52FEC2E43BA13210D3CAF23124DF2D0B4F7BE6327B48DE145071503F0E9EAEF1136A0005F6F5B03FCAD1AF83C10EA38D288934417E86F3D69CB69044064090063248C6895AB0019B080D99189012892CE008495A0609031005C42668C0C64540C80D80164D58388A18A40C0804720AB55020110293B09061348D4AC40182006E089750DBB02823938CA28688421000511066932200D8A2805BB05003255023064D9B184E83448282148103454A540020DA048ECCC24011904D6348801C004244300922A6314B860C0A4985A1184C18888112442D531690E280249486488C300909997119030DC09820E20664618089E4B01041208484349023058601A83123A63103050464226954006C1AA930884052A082402221690449411A83050B278AC4003200B521CC345083A20D60C84019330E00162941A82DCB446592844C024631611608E2842988023121B4844B288921187188C0011103201238280BA6112303490107629C9625C416445236708338929022084AC60C1B00648202324024315B867100104DD1228E58404C1082119AC245DBB204D0084E5BA20D20245159342821B004C8166DD3260419B10100486010A90CD122489914404C288624080E1B436C49A00C59942051108CA2466822A1714A982140366E24A1251C3421E3060A91C40098185223865114C38DD2002D00B03189A8409B940860A0701898488AA4449000621295280C10055292705A260A1C80211913281339421C0146E31030D9848C13202D99462E0C10881384302431712447418132860B948110112C5838898314825110711A982904260C599230102072C23666CAB669E406861C8469843012C49421241820DA12520BC3214B900DDBA004118808DCB465123085C1448E1C11859226128910840B1864E2286998880C90A6011B08495C180261C88DA1184621C109199389244925503625198490922030E28651C49685CA460410456A1B3862632809A202641BB341CB369054262C1C054E1042718348649A4605A2086041A2900A110151366CC49208C0C444A1C080D9408404C0708032851C43608CC4309CA6800A404E63481019B830CAB82944160461348E84184A24181013019188362C93246612C06463961000C3909B9600438685E1188E18C6059CA064844429108445C9000281266AE11804E01004591426E3328023C405E198710C16328896640C33825B2245C9082619236E44224C12126AE4B811C98845100732DA40825BB2316392895C1826218265639820D30022802220DB9624E29440E2042D2432424832295828461A9145D2988D21999010112D5A866D93A844CC42481282288C988422A791249001113270104045E12610A11411A3820D0BB64DE138019A046124C76412496E1BA004A3B08C49026E5C0069214144C8082913C83018940560228A0AC9258286444A102509361018C32111302A1BB5092047614C10511A422699A40041426ED40681DCB48122020A00336D53C284138751D9C851C238325C809101174013366D64A86019962022A38858B02C08078002170ACBA670213805D0222624A640A1A08D181350E0B42CE0225001A6886444011B29890B000092026C48B62508B60060926019953110B311D022215B006C80B008D18205133924DBC049544820C318069C102AC888645A248018C04D0A038010A891A4086461880C0B002A02B98D24220EDB246D21C76DA4126A58026190B2311935858B462102C144C9864998B2641210486110899896850816050B1980E2C6080886610B040D1A1880A4B80104C52564449021109291202863242120066A4CA208190222529491120886933206C49211CA000461162A00000E43022E98928881228E0C456511882102849149C21010920D8B126E10B42014234101A521A2C220813291D2344580324A1C29824906824B2060001060D0322A43208050342D1013010A094ED0C06448262050886013A07000037114948420A30D21055282A8911149125A342CDC880849C8288306650B16501489048C3626A408709C908DA0100AC90060D046718BA208190760CB8085A1008E4A222A01858192844C98B22164C8000199887457577111119A9C0D695E5008224F1F8D3B293D5067D12EA5D9B1ACD120060BE64483AA869B34C317DC13C55FA59FB1939381A21B8E043795F95E4ECB1592217A42B07F3C9731ED0ABFB2CD487CDCF6F956D5DECAA394378DCCB079CE795E96A9BDE857502A1EE1DBB3D6400EFE25F81EA9AA71A2124C05A0E97A271BF8B166385A3AB1E01E235AF62546933E80906B47627F0DD508B22CD0FB34DC2459B21C1437DDBEF93F603CEF19929437CDC85AEA07303F84662E64973F65BA31B0142FA866EBD1CD858AC538E9A598EB1A2A80D463E84125461926FD53DA52C9D53EC1C8EE2E5FC63A7D3259FC69C7A973421A9B4B3BCF4E938450C17ED123D7428C6D17CBC6EDCC4673750B726A36E187A42221BBE039A9EAE4A01528B5CA3074A7448193E8F29C65AB065A551FA7827ADD240C06AAD74692AEE6C44BDD6EAB9F0DC88D3564C3EA09AEA275507B8B085DC4B3BBA5EE7E9A35A3F25FBF2855A76383DC498935BEFC14D32A0EF90EB2F604C0DADFA4B5C7D8FD79BAA318F68B2A529944F87CC4FA80F60C4BC351A95E00EFD0A0D8ECD2F998F30030CF6D9BEEF2F7D63077DBAFC7AD5E7667F8FA671E5B69BC0B0435C80B28ED28D8D884FD60766E030A570282D8110419820D437FBA52C82CC99C94631B5C8AA9B681DEC98957A2E19C6123721B0DD4722D62F7722B3678E5752DD91C4D0EF6A932960EC1312724C56F58F21D0BFCA70340DD4C48ACC5606387663313472E8194B65FCC048A858D6480DD9C2B83841E85205E6C5E9A95776BA29835BBBF8E567200C84E5499CDFBA85355A2067A170BB5897889A73C204CA5A93118E51A4595281A80B952CB35D5589673270142CDE60402FCA4C8D9E437134DA79E7944522FB72D35F3FDFF2C15D4585AF10C3B64A7A40F2418A336F4EE66CCB98AB0D1B082C62802C87A9778B86D9F304847A76420507978FAC7987E8BF667A8E40F15FA1D9B0DD6299458BA196D24E9C6906C26D04708B08F99FDD9D110094D5B36F48213B213C66F5F55F97D4BFB500157E03A074AF090B7609204A71CD1642D11BF34C76400C124D55CF7A80DA98FFCF52AB2B229720357ED071616BD8E03D7276561ADA1A746C4EC37C0B21F964DCE7B1AA303A9CD7CB8108D7E18E32DB22BCB89FB7E6408324ABF03E296572648FD2E335CD4463A64EC5CBDC8AD7953618D8B2E309F04A74302562FDE3CFFE805C4BB4C661EF131D37674AD6F9C5BBF2290727AA67EA3848C48E3C3CA5A35009FB91C0CC383005604C096422407F4337C17D63D885592A5F15F9851F5BF27EB5AB5095B95AC69C2F36A1664B0AE71E4BB131C3A576B41155A2F997BCBCA494584788ACF2F5CCBAE28D09E69F3A39C2CC394B3465358E1F2A23D2E8A3083E3E26E90121553309A1B8E24932CB64FE659EBCF38AF0D1A8819FDA08CF4F2B03D79208061E113F9B89A8B9563E5163C6CBCC9BC2048C15E2AE39CC2B64ABD6DD6CCE5D01ACCD8CC08A4F266491446C1CB9337DAEEFC6777CBEB3291F60CBCC3194706492B7F953009DD5E70245180651DA918647F05A2902F4719F6D97CF3AF5D855FD1A1C76C29910D518771C604AD7B802E264F39BAF16AD772B0EC9DA32FCA34A85FE2E2447CAF2EE925EFF5A442EE9DA327C38EAA22B8E67DE4A7AFC312CBFDEB44856F09015237E59D26244631A8B14AFED134805DEF32FF49058C72C2A7A4B77518EE2B162DAF6B90F1C96AF5225453C5E4D8CD401C6FE1C626D4CA037F380BF288DC428B9680B595E10BDDC9D8A2D2BC85DC316BD9239D557535F66B3802FFEA1095DD4DC2F1A9A1E6190619BC5A2E063EF8C95066F5AF92B3D99A418E26C68F8AB464374FF3A789F07B2C5A99084C7275DDC481BC1BBBE8BD6171DABEC4A5B01F115EBFADAB26C9AC17A4D4EBE37BC294F0ADE593A0DD4AAC258D106457748FDF0059752442E4B96F1294D6D92F6D9657E131D0288EB3AED6C89390E05DC84FA5EC0EAE33F4DA4DF2A7614FF1B4ADBBBC9ACE849366AB82D5902BF7C5C66C095BD92DABB145BCDF8A744FAD2FCF44AD868C349A6DF2F509EE92687123E39DDA96A735AAF57C09F5EEEDFE3CFBC0B9C05B5D51E2F66F8C0E77AB0302AE603DEC8CFCC79485948C012BFDF7D0B9E8FF0D6545EBC781054183785D95173510E65E0E52CEDBB241D8E23C997DA523B974C53ED07A8DA63958768C9520DA7EE6B7093721EDD8633B6192E12D19065F63EB79504B92C7BC31F176DBF4FF21F5DDE5E414FE6555C8D76A2A77B3B3CE1EF545CFF4E776EF9B440506392ECBDED301490678328FFA2D83DE6099B750C3C99A9797A14FA372A57BB2CB72BDBFC640144D1C100899E2E12F2A23147C0B30E4F162184E1E69C1FA770B46D213F52CB4B36864289FCC3DAD1D54216EF9D9FC81B709D6045DCE41E91E5A0C8B5518EEAAE07D2574DB1D53ACA5B2A4A2FCD6237388A86B4C58B4200511103CCCB2420E62789C13F87C82A90751E121788274C028D2106D5FBB0D9D3128618FCD7DC5B4801C01A2410ADA95A7CA902A8F03E2D538D783AFEE2B7DC674C1A71DCAFCDFFCCB00273B942D79A5435DA1CFB2C44C3FB3A4E221AA1B2868EC9F4FA43A522562AAC34BBDA07D337A12748A1B5120D176CD117110D4A7061BA6AEF3EFBAD21E998B78192214380FD84A5D08F9B11282A15BD9226AA54F019213AB6C0BFE8291ADF09B680A16770EAFB2B7042274EEB896DC7B6903113862B1BD5C523787693E65B6C2A85845768A3BC54DD7A02C4ECE6CA3DBAD2D83DC65CA1DBF7C3744EE4E4748507DCE7F913F4332CF5214D8EFC1B4E67C86AEDF09D545C354FFE6B8A4B8F15E765A8F8CB13B24062A17084C52E82964CF6657C808326ABAAFF60545BADEA8A44DAF0A038EDFA6F4A66C57C721A6ED2E8736EF92F4BB8D230DA58DAD187ABF58DFD17FD11A9764D3608D288AA2EF3F1DE94DD7DF07E829D5D07CAB1FFADDDB4DD460D9FF3DAAE1D918AF2AE195B0F95CFD4E1543B4920B20CC808C6473C58933B370C45E47416E973F42FE6FC9C4FB5FA95DDE7D8325A8837648625F722EF95B0D08803FF0299E6A8108122A1C7172FA96ABB263240FC560CF3E1BFFCB2ACA913A9788D5C02EA0EFAD07BEF958A144B15A902AFD13A0AF976044065068C77100066F68982C0B98BB77F814AB624EED9C84531969DB1257C7AEC11DC682434809C7594A02116D6840DD99630629C150CB0CEFDA1E851F35C50CEEE6689B8450E6E9C4F8DAC4F487AE4DFCC60B8641A780BBD724036C21B38BEB1C8ED30EE9AA03DF899678FFFECE5200DD2270A9332B9557E577BB7E5B3378E4512F0BF439CA3BC16E8575DC46B2E497988C7C6F4EF529A730D7D418FF548AA7710F6F3230503D92A8B6228FD19CDC53F1A40165BA1F3EF1FC68BD5717AAF0810AA56AB90604BC0DF466D92403C1B9CCE0B2662D415EA69A1BE094AA5583DFC472480EE0430A93B21BE5B1BD4E27026DF5ACBE51D891B0E49F70DDE93FF43CE43A4F9B82AB951DE14A8A9BF4650FC38D769F4850688103D0D3E4A9BA645DAEC785A571D573ED6ABB22D987C2B52C5102CAB5B14610196B8D71DE69103E59B5C2A682390290B508A693CF87AD0214DC8DF2541FFAC89B38E19D703AC52D1624216FAB0227BC1045A34FD658111F92DB7B97FDF1CDC56FF7CABDA8BC26EF8347A92A2D8A3C56C150C5E3094F3D34D4CEC3C371602907419D8259DA0C74E32F63A9225852B5C9208785E56D0F4DB3349F5DBD06A10397965453D4E70D2E1E7FE56AD98143720BF25FD8204497D0A5EE1F37BD8AD4364D9BAAF2BE2BF519316D1314E0D4801CB90FE52F66A82FCB4708EFDE4FD45F70C38E70B59206C221B42E840B72FE1FD0DE5C7B0B8C0BD1B6561899FC5771764733108B69F81888FDA9E8CF60044F3BDD3289637F6ACBAA5B9B1145DFF8CE247B438320535C514BCBD445146BC00EF8E1DF3CDE218775DBB135FDBE92B7F303B1C344FDDC2E381420544DC9694DBB142704A0F092BA281537176F17B0C1561F0BE320D79677887DEE7C27E01232BAD43D49D9D1FC1A6D8C887443FDD756EE7561C24FFE0FAEEE66DE9A6F05CF687EF4CF456C7DD624DF017E11F66231793FE81C958AF0DD89C0D088B17DF94304659B79E9F9C5196BC1A9F22CB5698C751B829427CFB437B16FE1E3960A70B223E7C46CFD9F1C6195386A170AA07074D7BF9F634CD834939F8520B73D2648D1994CE80FC17E549DFC87CC6164A9CFD77E6E1968D9C9B582FB70736B6163D1E01C4632D594E3A3532E61E1CE87FE5AB5A2E11F98224E95D7136B34D814F138CC5C557690B57DF3EFCE187CE9A01D1CDDE645DD10814E3B3FDD69947A0746C68D9F77B9AFC61474C55CFF7EAB24AA5643E6B451A4C6D84C2E6E3D7015C6F00FB264D55D8B2CFF5F87DB3050E7DA34F7612EE116A088180BD0E1F3AA1BD63B2CC029B3E96C9EFF4C2D1A2C53F25084503639819E5A6E73BC07EFA5212CD9075A6547710A1237AC4991445ECC76D0C11BA557E0BDBD7651A4A6E66E3F9DBCD532C2301BDBDF63307AB425E6AB902164925BC424C5E7B2892DBBED51E0AB5BF33976243D5AB4DB7272EC292D1DBFC494372B51526C9DC1380C3BA563D2CCFA3A20EE7F800ED8C2A7D199D2EA6F1FF7F4B6B4AC51041C974D740FE7E1EA1D7311B81E5ADBB600CDF62BE0").unwrap()).unwrap(); + // // because this is from the bc-test-data with busted mu values, need to fix the (busted) mu + // let mu = hex::decode("d6961b5a9cc085939e493d4cfabfabb4442604d4058c4bfea3fb5c25cd7a3e4551b34bd9735be6e05925132e0aac4aa374534867289a6af4503baf449e6f6b73").unwrap(); + // let expected_sig = hex::decode( + // "1A9FF550C3F682A043D145B8A8ED2A2C2F92914E1D3E4F9C52EFD2A478EB028ED77C2E9C9450909F7A4032EA4059B51EAD6D9F61A8EA867AFCB794CA32AAD3180675DA90252E11952E936F2ECC62E1386BDCCD12DC22D23A2E21FCB009486376C85E2CA99C51D8F68AE93506120BEF1CCBC38E1A5DDF940E37DD0EB650503B969F9760D0F19448164FDBF0983077DA415648D70289BF6163C32DCC6AA8CC3B7ACF261237EC70D216FD157C1ACA6BA135470CCB4FFDC1710644AC9DD5A67E037FDBFDCE2A4627555D48AD1C8E537928B6A954FC006C5A34ED3483E2D241B618F59FD8481F69B6199E91A6545CFD485ED8665A82F75E36B27662562BC0D729D19426CD4AC19DAD71AE298FCFC278BB3B89B28E7713BAB58C4DC7BEEFA8444B6AC5CBF252F71CC7146D6EB43E0E9981C971BA9EA12BDD4BED8C20CB4BF91E805F93307AA6DEDFD083307CCF9FD540E674A40D68DB0538C8598CEE8A7186B65C4A71A4B053CACCA88887F96101547029FC74765DDE92ED69956B30C09BECEDE6DD947A7C25FF087C30AE4DACDB9496C51EEAC61C5C6B9D3AA48E255999A1F76811455B567188F26778D8C62758E1A9FA884CBCF72E04C7A6B4BDB13B208158C67D5B6A2CF60F8D27FDFE5D2E37947203D0C204D7937A10420DED707F73DE8771F7866B9537E1265101EDDC3D7C192A3B209B0E5A0D72495F1AB0C776FBBE892DA53A0638F98392E816B7C0DF714DDE62B473DEDE5876555112A554BA9EB37A363D78290C0B8CA56DDB600F0773CDF68898876BD34714F5F6C1FD38D50FDDFE778621133F14A1270E6C20E089753269D5B590318AD9AAEBD4BADA6EE5C48044CB11CFD14D153A8EE69335EA6D0C7435C33B94CE2C6E97F1D3CB80A70F66FD06F0A480FEE71B7B6A6E2DC7741269D85F05EAFDBD22D5DF71AFD9AB2D24F24729DF492926525F03336B515CCA54BB08989C16D42C1DEF1996E99659BB2F9F96004C258EF140E5306FD8CA549C99267B237584C733921192E89C8EBFB9934C3335B722F7E3EB519924A3F02A2B7E08D8C23F49C1F1262968D77887C1DB0D9941B203882C1703A8B345503E0E3CA472D7D7E11330D8B53704B8C59D12CF280857065031BA223B0D59C87C85AD214339DB60D6A2ECB4B908C07F0E2B285D0DEAA8CBF7B2BBCED1A3AA443C9580D1C7A6C5D618F6E1C5CB60E81F8E4397373850F5C18FECB489261932CCABA6CF42840DEB52A62CF0D829E33C2D4685ACB4F5CDD3A1B98D2777C09AF4DD926297E6B22037A37026062A6A42187976758B7406032B50A42D9028BFF12737F2E2CD1BAE8F78D10D87AAD2FCB4524B083D5542151A520A5B3C850F2879760875B0C034D714B7890B9BB7E86FFD946C9C4E583D0B7B1AE0A29553C50C3C4C30AEA7DE67BB9DC08E7845BA2ED6B6D6BBB1F65DAB6D269CE4B625B05A4BA5BB7272E4E7976AE6ACBE1AD43CD73DBBD215E9F603363919802497A7DC9B52C084BCD5E441C0B11FBB53142E56D1F182686614C97233A176741FE46E2F5BB749BE5A34790FF242331BA007EA53291487C96F998A2927C25863030DB6AD1860252DC2834DC3612E84F2499F39856DA4EADD223C5D2CC610026E54657ED6B9DD718DE6299979F76C0BED0933CE1F820D65F32614CE3DDFEF863A1A894534A9B93B5A0D54EBE30094BA323C5874BADBAAC2485D83363793D2CB96E129CB6C46C8A504797276C313034068539FCA7FD5929151B1D38F93243B93A772FCE64381534F9ECAF57899397E9DF6DF6CB9619B7CEC5EC498B020E708889C762776EA9436BD86B23550FD3F5D61CBDA5D1B40C629662D8D987B208B6A2015ACED52288A555E149688737323B5B5B0E6EDBE1D70A1D127113A39DAAABD281FC18109FDCD90C49FB5647E69A8EA116AAAAB1E0E5D73286DE32437B41ABAB779B20BDEDCD92F6D6D683587D33228D4A83DCCAFAFD6334680F4F9F370A5EDF4A70683BBEDBC064021DFEC2E47034275B8416D085B4086BB374FFF58E2F33E0D0920F638C9752098207C5E28341D8AD70DCA78CA47A2268F28B3350DD69B2EE15A7AF278C01EBE1DFAA8AD4739F7E5E408E2A39FCE7B8FCA51EDD592B8D2766FED9919425EC7CD80C4836795C924222D73AC0B380E00BFD20ABB704959CCA10B5EFFD9A0F72ADC823C74E90E6E95B2A3DE9D8B9F1C1A161D64E7393B259990D2BB4636B02A216ED12D2AA0B5E26C0BFB7D34B3230C6090DF5422F2A6E80DC8DE9DA3B5A1CDA4971E96C7DBB7F6992A60AD02B14C5E7CD6C9977DA0D3DA84CB07810325581F9D7BED0BEEC12E6C30338D903BF0F655EE8AF3779C87265911032433690CA981D0CCBC4F98869F316B6918B3135D405B34A3C7069112FF17E44554ECBEA8DC61269EFB5E58EF2AB1FEAA15B426879A344CE7DCE621082370F0F4F9B0E2602E942A1BB156B6DCEA3D9DD2B4FDE908E91B1913CC8BB6FB46961B9669EDEB713843C2ADB164EABB9D9615C4AB97AD17143891EEB6529C1C0E1542C0A36F70E93DA415D9ECACD6DAA5465840084C99F8F68CD236A8EE23B8A14EEB186909F6A9A4B1D1D2360DD548E57CBBF41C8A86FBCA6679E55341BAEA6F2F01898A7DE07E4BD91F6674DB7B7FD0667154DF75E543DABDDDBB4283BA0F4D8E38108BBD4BD65B82D9C84F0D8CD2E559D01E399C621056323B8CC79C53B8B8C9DEABAD2C0BE1DFC067EFCE38C591287D61545C36F36B81E7ACA5F00A68926C61090ABAA6B48F19DF830F4D94E65673CA03F50ECE3FA3BF6C235498D402E4B0521135255D85000260421C2D28D0D560C535CE6CBAA62E0491C5B305352CF1BA8E8A33F76F22D6CD72A15D07D6930DCB507EB88AFEDA15E810FC728602312B3053CFD2D8314DDE6A63083248480680A2882E173B8D89C14646FBD844A5C790DBA9B842A41F48B6BED1FB4AFE6826F95082A33C9568A40AA4B5B40CF1E723F90AF1A1DC5FE08C91B5D24B8BC1FFABA36B308A6C2184DBFC3B3D36916B2B744239D548F4F5A05254EC548F3437520609C5E562C82BEB9D3ECF1C1F9AF5E84DF9AEA3998B23D51741CDD936C24E23F1DEC3DC58BAE1FEF9A2F1B71A2604B699E212AFD349944DB53A39554CFA94E0EB747722674707A6F99F0204EE68AA20094DE58AC97F733FA76C928DBD668F80584F6FEDB7218BA74E126A0316B5FCF38F970484117B88BC8545ED9FD67466E7777E66B0F41F593E46135665096DA1CC75C90437A01379925F3327983207CE482F6AE3780B1265D6054CD2AE6B0418D2857D59FA7E94FC32AB5E94CD2C83452F420C8861F55B0A5A6BDC8C5C4157B33D6AE11C3C9C2E8919A3D09B9ED8FC09594D365A7E85F3D9647E324116518F6CDA1ADA250F194A87AFCE0D9D60B768AFFF8FCC7057AC73FB520DC707F9BC7408EFBCC3F85D5F618941441CED77C09B3E697141DE2AD6C3A4551884EE1E88D196E1AEA08285D47D81C219BE827D07891F8273BA4B3A26C3813EEA482674EA8EB4FCFBF04EC0C9428A7686816B53EE0749FDE60F50460B672D58C06F103304C28908FCD079A12574B170ABFC7E1F39FED5131D0672ECB3983810C316DD38E2FEF6DC99FB7FB5315F2D6496CB45E1385EABB69537628D9DFB0FAC95B5E5D4B05488B18E0068B0407CC737B0E711A0179EEDA1C7FAED33AFC5AF4B8C318B98244C97255563BAD20784AADE6A618388FFAE1C286A4C5EB891E236FE57FF414D619E8BAFFC84C225C59D02534721490A50BD39C163A1EEB6D811646E54D426AE9B3449C3E428ECEAC6E23F6CBD8EA06D9682385554DEC853C5C0ECAF26F5CF5BC6EABC4A957215FC497084E9A8CAFC00ACD228B4F8245F9230FAF8306635DB89663C5D9A31C648948500FBE0771E2BF15C52395468CF50C6C8953DBB6DE252E6649C6D2D060EE34F53960B5B7EA0981B7BEF3D0A1D64853D9EE06EC4D73F4A75DAC5E5A2B59682E6B8293E74ADA3322C1204FA66C6E8B5EAFD796305AA884249D00E3DAB6267A87844D595B3BEC430BB416BD4A4F38505F046425ECA84495DC0928DC4D9FA18BFB800C1B0578B2EECE2B97153A1C296E36036F9366E3443B236E4E124F82EC405CE2ACA48DF43E543C514727CE182B34D1131E366E394D07F9C72022FAD6E4AEB12CD675E7EB837A7EB13BF4D9DE553DD19DFBD29A6E2157B58D893F3E4B574AF01F103989D8FE2FAEE269BAED7094BE606678719E6E82DA97D3DB43B3FD72863180E1B4D7BE55A721301B1D81963BD37C3259D55FA6D43FBC217A77B38B83CEAA4853A921EC7F138F666BFBBFA49B4672DDBBF1CEC212B530771A39EA244B547F28375936A3E8F692216358A944542AF2D559D6FA216A5CC60DBC892D2BF38CAF1745F5CF6336754A21378F25DE5F472FDD8A1B33189B5134647F7965FE0E573C508B61980DF059B5D9B3AFFCE632230FE873F6462E533D4528DB8B03DD464D3020B5DC22525AE33AE1FC345DC2486D5647863ECE3DD49EAE7A5AE128AD8E39065F77EEE3456653E3FDA5135A45A365B8043F2F019825BA9CEBDBA96C8EEE7A8544DBBC60218776455843E5EC61D9DFD7DF3ACD6DF5E130A5D2F958F5E26F3B1A80029EBFD13F7EB24301750EB8608E7FC73882797E2342A37DF45486ACA0603D411592A13934799D203ECCC197C0F103180D5A0C1CCC3BFA3CF1DB199ED934B4A5B52CA9BA66FB9C97D85B06977C83533F16F6B7F3D6E5945B802362E7ED9C42E873017EA4E0C9E7AC96087AA7FD269D363A8BE8FD39A0234CA842ED209865164D39C4EF592E7223F3C3684EAE0A2AC757D3CBAFB2E873C52B09BAA4788190EDA773D946A5CC67D59C6EDF9EF20394D2F40DF35D20E5F67E1891C6E61798B1A8343E22B7FDF274A996F679A4EAA1505DA4EFE1C4D94A69EAECE3DA05D6CFB63294D7433D9885EE18EC94FF1F23DD6FD9F99BBA2D5BEF46E0A477CC434F86B2FD67FD55B06AA2B698A70FAA1894E925BCBB2E9F229A1858E789920A1AFB5FCDFBD4D3F904F87FD863372CB7C103BE365AD8848141AEE2534D42BCBF5A954F8BF62DF7C29F9ECAEAE7543B56E7188D440A0BB4760FD0ADC019796611E4A31152849818CCF0B146C9CCBCF4FBED7969BE81C49D92FB26A63249DB99EE6CBE935677AB40F50A267815FFBA5BFD7A72C5EBEE8DF1DDF713516F2E4550C9E74FB1C7E2CBA3ABCB91E315A1ABC8E9D48CED3971A0B73DFAE6955CF3CFDD925D86B4F3FD182327599379F9E13D7EA5E405C335B1D95B6DDE4DE69C492F98F51974BCB66C317439653A9014166C922E61150FF9902F9C22C29B7F15A27121BEB39835D835CB7135C3992E82C765A9131485893DC7C853937E1F0C9E0EB30E0502224195E318AB98C56E4211C926240BDB95A1C9345BF9AC10E2482950974895268C12DD92D0EC7E941A162A57106C0E2A3D36376488EB22694D4F68C0FD03FC8ABE4F5EDDF3E47A9E9F7612C2ADD9F847309DDBAD4DC71C3EB2838F29CAC79E8AAF3E88865C8C4FC5FDDDD5EE0CEAC09985CCCD5A5AA6D009BCE39AF3F8C8AF865F584AAF03FB8431F6BEBDA7EE1F1F0B3D672BC6840F3417F497CDE3CD3DC2A214C2C895F61A20A7CB184DC6DEC73002923A6B50B671329F2A16E611E0099B5F0438AFBDD2285D1AA097DD74C4E5D7CC6F75E4313C67E91C71AA49B5048780C0331BDB8917247BF059D9CCDF6FDC885C2950C6FB84BA8ABC463C2DD67629F8C28600081C10C75EE88DB112C432A516983A933A584A957646E2B5B4F9255055A0558574D46EE3E074CFF5FAEBFEAB61FFC994E5B9F46E16552590257B6C4FBF80597B11A705BA730FA495D31FCB0B3E568FA264A6C4512E9B598162AC242437DD47A319FE31A5D9622343B71286BE7E2CCC6709978044170B6B7C8F0F1D109B9D79F86C9A2B4E2758AA97569F47F242BC0233CC44E0E51A02EA805D15A30E193776174FA43787B621293CAA785B609AC5FB851A90647C6A35547AB985E5CCFF969EA452A047C5D7E6CC86404DE41FF4FDDD2B20474BEA70C843CE3A3B383E62F2BC234B4D4962275A3C73E530ED2CC8BF45CB2086FFAA5A299B5236A46066294D80B938A18D71B718B083E685173D10725DAEACCEAA6821A7C9362E5DA01DE8DA4DC54C4C7882E61056FCE7EDEE3A84E3414AA7055DAB55349D3DA50EE216C797C4E5B7D7A225D35E29B150F7499086442509ECC8240CE53A08032C97F6A07ACA7C599DDAE5E3B38E9D78FAF9FE804EA5AD23DE442794991D6633F3CE3D83B08F8FE5D862ECE02B8E710EDD552CE1CE465353A6A3FC014E60F0A8ACE5FD7E0A6E524FA7453E795B96C2DE5E2081460C3754DF5A782984D8CD5C7ED1200B45C45E58387EBA329B93F63143BE6D4CC911D305EAEB6685A07B1E4F982E17904B775F6255E8279FE7B5B445AA0523023313343478D9CA9B40E2048B2D1D637468C91D2EFFF0611164254646782C6D77197A1A7B2C9D5E0F0FD4E576E7782ED0C50ACBCD2192A5275808BEE000000000000000000000000000000090F16202A30353C", + // ).unwrap(); + // + // let sig = MLDSA87::sign_mu_deterministic(&sk, mu.as_slice().try_into().unwrap(), [0u8; 32]) + // .unwrap(); + // assert_eq!(&sig, expected_sig.as_slice()); } #[test] - fn sig_val_z_too_big() { + fn malformed_sig_val() { // This signature value was manually generated in the debugger to have a z containing several // coefficients of (1<<17) -1, which are just below GAMMA1, and in aggregate should cause check_norm() to fail. // ... a condition that generally does not show up with well-behaved inputs. @@ -425,9 +612,11 @@ mod mldsa_tests { let msg = b"The quick brown fox jumped over the lazy dog"; let seed = KeyMaterial256::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap(), KeyType::Seed, - ).unwrap(); + ) + .unwrap(); let (pk, _sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); @@ -443,11 +632,18 @@ mod mldsa_tests { Err(SignatureError::SignatureVerificationFailed) => (), _ => panic!("Expected verification to fail due to busted signature"), } + + // a signature of all 1's exercises the "leftover bytes" check in the sig_decode() + match MLDSA44::verify(&pk, msg, None, &[1u8; MLDSA44_SIG_LEN]) { + Err(SignatureError::SignatureVerificationFailed) => (), + _ => panic!("Expected verification to fail due to busted signature"), + } } /// Tests that no private data is displayed #[test] fn test_display() { + use bouncycastle_mldsa_lowmemory::Polynomial; // Polynomials (could) contain private data, // and therefore should be protected against accidental crash dumps: @@ -460,8 +656,6 @@ mod mldsa_tests { assert_eq!(format!("{:?}", p), "Polynomial (data masked)"); } - - #[test] fn keypair_consistency_check() { // this is common to all parameter sets, so I'll just test MLDSA44 @@ -473,11 +667,11 @@ mod mldsa_tests { // failure case: different but valid key let (pk2, sk2) = MLDSA44::keygen().unwrap(); match MLDSA44::keypair_consistency_check(&pk, &sk2) { - Err(SignatureError::ConsistencyCheckFailed()) => { /* good */ }, + Err(SignatureError::ConsistencyCheckFailed()) => { /* good */ } _ => panic!("Expected error for different key"), }; match MLDSA44::keypair_consistency_check(&pk2, &sk) { - Err(SignatureError::ConsistencyCheckFailed()) => { /* good */ }, + Err(SignatureError::ConsistencyCheckFailed()) => { /* good */ } _ => panic!("Expected error for different key"), }; @@ -486,7 +680,7 @@ mod mldsa_tests { pk_bytes[17] ^= 0x01; let pk2 = MLDSA44PublicKey::from_bytes(&pk_bytes).unwrap(); match MLDSA44::keypair_consistency_check(&pk2, &sk) { - Err(SignatureError::ConsistencyCheckFailed()) => { /* good */ }, + Err(SignatureError::ConsistencyCheckFailed()) => { /* good */ } _ => panic!("Expected error for different key"), }; @@ -494,7 +688,7 @@ mod mldsa_tests { sk_bytes[17] ^= 0x01; let sk2 = MLDSA44PrivateKey::from_bytes(&sk_bytes).unwrap(); match MLDSA44::keypair_consistency_check(&pk, &sk2) { - Err(SignatureError::ConsistencyCheckFailed()) => { /* good */ }, + Err(SignatureError::ConsistencyCheckFailed()) => { /* good */ } _ => panic!("Expected error for different key"), }; } @@ -534,7 +728,6 @@ mod mldsa_tests { let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, None).unwrap(); - let sig = MLDSA44::sign_mu(&sk, &mu).unwrap(); MLDSA44::verify(&pk, msg, None, &sig).unwrap(); diff --git a/crypto/mldsa_lowmemory/tests/wycheproof.rs b/crypto/mldsa_lowmemory/tests/wycheproof.rs new file mode 100644 index 0000000..ff9839c --- /dev/null +++ b/crypto/mldsa_lowmemory/tests/wycheproof.rs @@ -0,0 +1,756 @@ +//! Test against the project wycheproof repo available at: +//! https://github.com/C2SP/wycheproof +//! Requires that the wycheproof repository is cloned and available for testing at "../wycheproof" +//! relative to the root of this git project. +//! +//! This test file exercises the following test sets: +//! +//! * mldsa_44_sign_seed_test +//! * mldsa_44_verify_test +//! * mldsa_65_sign_seed_test +//! * mldsa_65_verify_test +//! * mldsa_87_sign_seed_test +//! * mldsa_87_verify_test +//! +//! Note: the following test sets are not tested because this low memory implementation requires +//! seed-based private keys and has no functionality for operating on non-seed private keys. +//! +//! * mldsa_44_sign_noseed_test +//! * mldsa_65_sign_noseed_test +//! * mldsa_87_sign_noseed_test + +#![allow(dead_code)] + +use bouncycastle_core::errors::SignatureError; +use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; +use bouncycastle_core::traits::{SecurityStrength, Signature, SignaturePublicKey}; +use bouncycastle_hex as hex; +use bouncycastle_mldsa_lowmemory::{ + MLDSA44, MLDSA44PublicKey, MLDSA65, MLDSA65PublicKey, MLDSA87, MLDSA87PublicKey, + MLDSAPublicKeyTrait, MLDSATrait, MuBuilder, +}; + +#[cfg(test)] +mod wycheproof { + use crate::{MLDSASignSeedTestCase, MLDSAVerifyTestCase, ParameterSet}; + use std::fs; + use std::path::Path; + use std::sync::Once; + + const TEST_DATA_PATH_RELATIVE: &str = "../../../wycheproof/testvectors_v1"; + const TEST_DATA_PATH: &str = "../wycheproof/testvectors_v1"; + + static TEST_DATA_CHECK: Once = Once::new(); + + fn test_for_presence_of_test_data() -> Result<(), ()> { + let found: u8; + if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + found = 1; + } else if Path::new(TEST_DATA_PATH).exists() { + found = 2; + } else { + found = 3; + }; + + // just print once + TEST_DATA_CHECK.call_once(|| match found { + 1 => println!("wycheproof found at: {:?}", TEST_DATA_PATH_RELATIVE), + 2 => println!("wycheproof found at: {:?}", TEST_DATA_PATH), + _ => println!("WARNING: wycheproof directory not found; tests will be skipped"), + }); + + if found == 1 || found == 2 { Ok(()) } else { Err(()) } + } + + #[test] + fn mldsa_44_sign_seed_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string( + TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_44_sign_seed_test.json", + ) + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_44_sign_seed_test.json") + .unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSASignSeedTestCase::parse(contents, ParameterSet::Mldsa44); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa44(); + } + + println!("mldsa_44_sign_seed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_44_verify_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_44_verify_test.json") + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_44_verify_test.json").unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSAVerifyTestCase::parse(contents, ParameterSet::Mldsa44); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa44(); + } + + println!("mldsa_44_verify_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_65_sign_seed_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string( + TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_65_sign_seed_test.json", + ) + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_65_sign_seed_test.json") + .unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSASignSeedTestCase::parse(contents, ParameterSet::Mldsa65); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa65(); + } + + println!("mldsa_65_sign_seed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_65_verify_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_65_verify_test.json") + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_65_verify_test.json").unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSAVerifyTestCase::parse(contents, ParameterSet::Mldsa65); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa65(); + } + + println!("mldsa_65_verify_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_87_sign_seed_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string( + TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_87_sign_seed_test.json", + ) + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_87_sign_seed_test.json") + .unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSASignSeedTestCase::parse(contents, ParameterSet::Mldsa87); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa87(); + } + + println!("mldsa_87_sign_seed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mldsa_87_verify_test() { + if test_for_presence_of_test_data().is_err() { + return; + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_87_verify_test.json") + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_87_verify_test.json").unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + + let test_cases = MLDSAVerifyTestCase::parse(contents, ParameterSet::Mldsa87); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mldsa87(); + } + + println!("mldsa_87_verify_test: all {} test cases passed.", num_test_cases); + } +} + +/* Structs for holding test data */ + +#[derive(Clone, Debug, PartialEq)] +enum ParameterSet { + Mldsa44, + Mldsa65, + Mldsa87, +} + +#[derive(Clone)] +struct MLDSASignSeedTestCase { + parameter_set: ParameterSet, + // testGroup-level fields, copied onto every test case in the group + private_seed: String, + public_key: String, + // test-level fields + tc_id: u32, + comment: String, + msg: Option, + mu: String, + ctx: Option, + sig: String, + result: String, +} + +impl MLDSASignSeedTestCase { + fn new(parameter_set: ParameterSet) -> Self { + Self { + parameter_set, + private_seed: String::new(), + public_key: String::new(), + tc_id: 0, + comment: String::new(), + msg: None, + mu: String::new(), + ctx: None, + sig: String::new(), + result: String::new(), + } + } + + fn parse(data: String, parameter_set: ParameterSet) -> Vec { + let json: serde_json::Value = + serde_json::from_str(&data).expect("test data is not valid JSON"); + + let mut test_cases = Vec::::new(); + + let groups = json["testGroups"].as_array().expect("testGroups is not an array"); + for group in groups { + // The private/public key are defined once per group and shared by + // every test in that group. + let private_seed = group["privateSeed"].as_str().unwrap_or("").to_string(); + let public_key = group["publicKey"].as_str().unwrap_or("").to_string(); + + let tests = group["tests"].as_array().expect("tests is not an array"); + for test in tests { + test_cases.push(Self { + parameter_set: parameter_set.clone(), + private_seed: private_seed.clone(), + public_key: public_key.clone(), + tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, + comment: test["comment"].as_str().unwrap_or("").to_string(), + msg: match test["msg"].as_str() { + Some(msg) => Some(msg.to_string()), + None => None, + }, + mu: test["mu"].as_str().unwrap_or("").to_string(), + ctx: test["ctx"].as_str().map(|s| s.to_string()), + sig: test["sig"].as_str().unwrap_or("").to_string(), + result: test["result"].as_str().unwrap_or("").to_string(), + }); + } + } + + test_cases + } + + fn run_mldsa44(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa44); + + /* Load the keys */ + + let mut seed = match KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.private_seed).unwrap(), + KeyType::Seed, + ) { + Ok(seed) => seed, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + }; + // allow an all-zero seed for testing + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => (), + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + } + + let (pk, sk) = match MLDSA44::keygen_from_seed(&seed) { + Ok((pk, sk)) => (pk, sk), + Err(e) => { + panic!("{:?}", e) + } + }; + + let loaded_pk = + MLDSA44PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()).unwrap(); + assert_eq!(loaded_pk, pk); + + /* Compute the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + // build mu + let mu: [u8; 64] = if self.msg.is_none() { + // we can't compute it, so just take the one provided + hex::decode(&self.mu).unwrap().as_slice().try_into().unwrap() + } else { + match MuBuilder::compute_mu( + &pk.compute_tr(), + &hex::decode(self.msg.clone().unwrap()).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ) { + Ok(mu) => mu, + Err(SignatureError::LengthError(_)) => { + if self.result == "invalid" { + /* good -- test passed */ + return; + } else { + panic!("failed to compute mu") + } + } + _ => panic!("failed to compute mu"), + } + }; + assert_eq!(mu, hex::decode(&self.mu).unwrap().as_slice()); + + // generate the signature using an all-zero signing nonce + let sig = MLDSA44::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); + + let res = MLDSA44::verify_mu_internal( + &pk, + &mu, + &hex::decode(&self.sig).unwrap().try_into().unwrap(), + ); + + if self.result == "valid" { + assert!(res); + } else { + assert!(!res); + }; + } + + fn run_mldsa65(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa65); + + /* Load the keys */ + + let mut seed = match KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.private_seed).unwrap(), + KeyType::Seed, + ) { + Ok(seed) => seed, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + }; + // allow an all-zero seed for testing + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => (), + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + } + + let (pk, sk) = match MLDSA65::keygen_from_seed(&seed) { + Ok((pk, sk)) => (pk, sk), + Err(e) => { + panic!("{:?}", e) + } + }; + + let loaded_pk = + MLDSA65PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()).unwrap(); + assert_eq!(loaded_pk, pk); + + /* Compute the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + // build mu + let mu: [u8; 64] = if self.msg.is_none() { + // we can't compute it, so just take the one provided + hex::decode(&self.mu).unwrap().as_slice().try_into().unwrap() + } else { + match MuBuilder::compute_mu( + &pk.compute_tr(), + &hex::decode(self.msg.clone().unwrap()).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ) { + Ok(mu) => mu, + Err(SignatureError::LengthError(_)) => { + if self.result == "invalid" { + /* good -- test passed */ + return; + } else { + panic!("failed to compute mu") + } + } + _ => panic!("failed to compute mu"), + } + }; + assert_eq!(mu, hex::decode(&self.mu).unwrap().as_slice()); + + // generate the signature using an all-zero signing nonce + let sig = MLDSA65::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); + + let res = MLDSA65::verify_mu_internal( + &pk, + &mu, + &hex::decode(&self.sig).unwrap().try_into().unwrap(), + ); + + if self.result == "valid" { + assert!(res); + } else { + assert!(!res); + }; + } + + fn run_mldsa87(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa87); + + /* Load the keys */ + + let mut seed = match KeyMaterial256::from_bytes_as_type( + &hex::decode(&self.private_seed).unwrap(), + KeyType::Seed, + ) { + Ok(seed) => seed, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + }; + // allow an all-zero seed for testing + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => (), + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + } + + let (pk, sk) = match MLDSA87::keygen_from_seed(&seed) { + Ok((pk, sk)) => (pk, sk), + Err(e) => { + panic!("{:?}", e) + } + }; + + let loaded_pk = + MLDSA87PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()).unwrap(); + assert_eq!(loaded_pk, pk); + + /* Compute the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + // build mu + let mu: [u8; 64] = if self.msg.is_none() { + // we can't compute it, so just take the one provided + hex::decode(&self.mu).unwrap().as_slice().try_into().unwrap() + } else { + match MuBuilder::compute_mu( + &pk.compute_tr(), + &hex::decode(self.msg.clone().unwrap()).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ) { + Ok(mu) => mu, + Err(SignatureError::LengthError(_)) => { + if self.result == "invalid" { + /* good -- test passed */ + return; + } else { + panic!("failed to compute mu") + } + } + _ => panic!("failed to compute mu"), + } + }; + assert_eq!(mu, hex::decode(&self.mu).unwrap().as_slice()); + + // generate the signature using an all-zero signing nonce + let sig = MLDSA87::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); + assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); + + let res = MLDSA87::verify_mu_internal( + &pk, + &mu, + &hex::decode(&self.sig).unwrap().try_into().unwrap(), + ); + + if self.result == "valid" { + assert!(res); + } else { + assert!(!res); + }; + } +} + +#[derive(Clone)] +struct MLDSAVerifyTestCase { + parameter_set: ParameterSet, + // testGroup-level fields, copied onto every test case in the group + public_key: String, + // test-level fields + tc_id: u32, + comment: String, + msg: String, + ctx: Option, + sig: String, + result: String, +} + +impl MLDSAVerifyTestCase { + fn new(parameter_set: ParameterSet) -> Self { + Self { + parameter_set, + public_key: String::new(), + tc_id: 0, + comment: String::new(), + msg: String::new(), + ctx: None, + sig: String::new(), + result: String::new(), + } + } + + fn parse(data: String, parameter_set: ParameterSet) -> Vec { + let json: serde_json::Value = + serde_json::from_str(&data).expect("test data is not valid JSON"); + + let mut test_cases = Vec::::new(); + + let groups = json["testGroups"].as_array().expect("testGroups is not an array"); + for group in groups { + // The private/public key are defined once per group and shared by + // every test in that group. + let public_key = group["publicKey"].as_str().unwrap_or("").to_string(); + + let tests = group["tests"].as_array().expect("tests is not an array"); + for test in tests { + test_cases.push(Self { + parameter_set: parameter_set.clone(), + public_key: public_key.clone(), + tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, + comment: test["comment"].as_str().unwrap_or("").to_string(), + msg: test["msg"].as_str().unwrap_or("").to_string(), + ctx: test["ctx"].as_str().map(|s| s.to_string()), + sig: test["sig"].as_str().unwrap_or("").to_string(), + result: test["result"].as_str().unwrap_or("").to_string(), + }); + } + } + + test_cases + } + + fn run_mldsa44(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa44); + + /* Load the key */ + + let pk = match MLDSA44PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()) { + Ok(pk) => pk, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + }; + + /* Verify the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + match MLDSA44::verify( + &pk, + &hex::decode(&self.msg).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + &hex::decode(&self.sig).unwrap(), + ) { + Ok(()) => { + if self.result != "valid" { + panic!("signature passed when it should have failed:"); + } + } + Err(e) => { + if self.result != "invalid" { + panic!("signature failed when it should have passed: {:?}", e); + } + } + } + } + + fn run_mldsa65(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa65); + + /* Load the key */ + + let pk = match MLDSA65PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()) { + Ok(pk) => pk, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + }; + + /* Verify the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + match MLDSA65::verify( + &pk, + &hex::decode(&self.msg).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + &hex::decode(&self.sig).unwrap(), + ) { + Ok(()) => { + if self.result != "valid" { + panic!("signature passed when it should have failed:"); + } + } + Err(e) => { + if self.result != "invalid" { + panic!("signature failed when it should have passed: {:?}", e); + } + } + } + } + + fn run_mldsa87(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mldsa87); + + /* Load the key */ + + let pk = match MLDSA87PublicKey::from_bytes(&hex::decode(&self.public_key).unwrap()) { + Ok(pk) => pk, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + }; + + /* Verify the signature */ + + let ctx_vec = self.ctx.as_ref().map(|ctx| hex::decode(ctx).unwrap()); + + match MLDSA87::verify( + &pk, + &hex::decode(&self.msg).unwrap(), + ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + &hex::decode(&self.sig).unwrap(), + ) { + Ok(()) => { + if self.result != "valid" { + panic!("signature passed when it should have failed"); + } + } + Err(e) => { + if self.result != "invalid" { + panic!("signature failed when it should have passed: {:?}", e); + } + } + } + } +} diff --git a/crypto/mlkem/src/aux_functions.rs b/crypto/mlkem/src/aux_functions.rs index 264654f..fd9474e 100644 --- a/crypto/mlkem/src/aux_functions.rs +++ b/crypto/mlkem/src/aux_functions.rs @@ -1,12 +1,10 @@ //! Implements auxiliary functions for ML-DSA as defined in Section 7 of FIPS 204. +use crate::matrix::Vector; use crate::mlkem::{N, q, q_inv}; -use crate::polynomial::{Polynomial}; +use crate::{Matrix, Polynomial}; use bouncycastle_core::traits::XOF; use bouncycastle_sha3::{SHAKE128, SHAKE256}; -use crate::Matrix; -use crate::matrix::Vector; - pub(crate) fn expandA(rho: &[u8; 32]) -> Matrix { let mut A_hat = Matrix::::new(); @@ -31,7 +29,7 @@ pub(crate) fn byte_encode(F: &Polynomial) let mut B = [0u8; PACK_LEN]; - for i in 0 .. N { + for i in 0..N { let mut alpha = F[i]; // For efficiency, the library is happy to work with values outside the range [0..q], @@ -45,8 +43,7 @@ pub(crate) fn byte_encode(F: &Polynomial) // 4: 𝑏[𝑖⋅𝑑 + 𝑗] ← π‘Ž mod 2 // constant-time note: yes, % is not constant-time, // but all of the values in (i*d + j) % 8 are loop indices and not part of the secret key. - B[(i*d + j)/8] |= tmp << ((i*d + j) % 8); - + B[(i * d + j) / 8] |= tmp << ((i * d + j) % 8); // 5: π‘Ž ← (π‘Ž βˆ’ 𝑏[𝑖⋅𝑑 + 𝑗])/2 // β–· note π‘Ž βˆ’ 𝑏[𝑖⋅𝑑 + 𝑗] is always even @@ -76,7 +73,7 @@ pub(crate) fn byte_decode(B: &[u8; PACK_L // 3: F[i] = SUM_j=0..d-1{ 𝑏[𝑖 β‹… 𝑑 + 𝑗] β‹… 2𝑗 } mod m for j in 0..d { // select the next bit, according to bitcount, then shift it up by j - F[i] |= (((B[(i*d + j)/8] >> (i*d + j)%8) & 1) as i16) << j; // there's supposed to be a `mod m` here, but that shouldn't matter; we'll check it below anyway. + F[i] |= (((B[(i * d + j) / 8] >> (i * d + j) % 8) & 1) as i16) << j; // there's supposed to be a `mod m` here, but that shouldn't matter; we'll check it below anyway. } } @@ -102,9 +99,9 @@ pub(crate) fn sample_ntt(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { // SHAKE is fairly inefficient if you just squeeze 3 bytes at a time, so we'll do a block. // size doesn't really matter, so long as it's a multiple of 3. // 288 seemed to be the sweet spot from playing with benchmarks - // It's probably around the average rejection rate, and 288 is a multiple of both 3 (required for this alg) + // It's probably around the average rejection rate, and 216 is a multiple of both 3 (required for this alg) // and 8 (efficient for SHAKE). - let mut C = [0u8; 288]; + let mut C = [0u8; 216]; xof.squeeze_out(&mut C); let mut idx: usize = 0; @@ -119,12 +116,12 @@ pub(crate) fn sample_ntt(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { // 6: 𝑑1 ← 𝐢[0] + 256 β‹… (𝐢[1] mod 16) // β–· 0 ≀ 𝑑1 < 2^12 - let d1: i16 = (C[idx+0] as i16) | ((C[idx+1] as i32) << 8) as i16 & 0xFFF; + let d1: i16 = (C[idx] as i16) | ((C[idx + 1] as i32) << 8) as i16 & 0xFFF; debug_assert!(d1 < 2 << 12); // 7: 𝑑2 ← ⌊𝐢[1]/16βŒ‹ + 16 β‹… 𝐢[2] // β–· 0 ≀ 𝑑2 < 2^12 - let d2: i16 = ((C[idx+1] as i16) >> 4) | ((C[idx+2] as i32) << 4) as i16 & 0xFFF; + let d2: i16 = ((C[idx + 1] as i16) >> 4) | ((C[idx + 2] as i32) << 4) as i16 & 0xFFF; debug_assert!(d2 < 2 << 12); // 8: if 𝑑1 < π‘ž then @@ -163,8 +160,8 @@ pub(crate) fn sample_poly_cbd(bytes: &[u8]) -> Polynomial { match eta { 2 => { - for i in 0..N/8 { - let t = u32::from_le_bytes(bytes[4*i .. 4*i + 4].try_into().unwrap()); + for i in 0..N / 8 { + let t = u32::from_le_bytes(bytes[4 * i..4 * i + 4].try_into().unwrap()); let mut d = t & 0x55555555; d += (t >> 1) & 0x55555555; for j in 0..8usize { @@ -179,7 +176,7 @@ pub(crate) fn sample_poly_cbd(bytes: &[u8]) -> Polynomial { } } 3 => { - for i in 0..N/4 { + for i in 0..N / 4 { let t = little_endian_to_u24(bytes, 3 * i); let mut d = t & 0x00249249; d += (t >> 1) & 0x00249249; @@ -204,7 +201,6 @@ pub(crate) fn sample_poly_cbd(bytes: &[u8]) -> Polynomial { /// SamplePolyCBDπœ‚1(PRFπœ‚1 (𝜎, 𝑁 )) /// Performs both the PRF and SamplePolyCBD steps pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8) -> Polynomial { - // Alg 13: 9: 𝐬[𝑖] ← SamplePolyCBDπœ‚1(PRFπœ‚1 (𝜎, 𝑁 )) // β–· 𝐬[𝑖] ∈ β„€256 sampled from CBD match eta { @@ -220,7 +216,7 @@ pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8) -> Polynomial }; sample_poly_cbd::(&buf) - }, + } 3 => { let buf = { let mut xof = SHAKE256::new(); @@ -232,13 +228,16 @@ pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8) -> Polynomial }; sample_poly_cbd::(&buf) - }, - _ => unreachable!() + } + _ => unreachable!(), } } /// Internal helper for keygen since both s_hat and e_hat have identical sampling code -pub(crate) fn sample_vector_CBD(b: &[u8; 32], mut n: u8) -> Vector { +pub(crate) fn sample_vector_CBD( + b: &[u8; 32], + mut n: u8, +) -> Vector { let mut v = Vector::::new(); for i in 0..k { @@ -297,18 +296,27 @@ pub(crate) fn barrett_reduce(a: i16) -> i16 { a - (((t as i32) * q as i32) as i16) } -pub(super) fn cond_sub_q(a: i16) -> i16 { - let tmp = a - q; - tmp + ((tmp >> 15) & q) -} - +// not currently used, but I'll leave it here because it's useful for debugging if you want to output values +// that are normalized to [0,q] to compare against intermediate results from other libraries. +// pub(super) fn cond_sub_q(a: i16) -> i16 { +// let tmp = a - q; +// tmp + ((tmp >> 15) & q) +// } /// Multiplication of polynomials in Zq\[X]/(X^2-zeta) /// used for multiplication of elements in Rq in NTT domain /// /// Borrowed from: /// https://github.com/pq-crystals/kyber/blob/main/ref/ntt.c#L139 -pub(crate) fn ntt_base_mult(r: &mut [i16], off: usize, a0: i16, a1: i16, b0: i16, b1: i16, zeta: i16) { +pub(crate) fn ntt_base_mult( + r: &mut [i16], + off: usize, + a0: i16, + a1: i16, + b0: i16, + b1: i16, + zeta: i16, +) { let mut out_val0 = mul_mont(a1, b1); out_val0 = mul_mont(out_val0, zeta); out_val0 += mul_mont(a0, b0); @@ -319,29 +327,46 @@ pub(crate) fn ntt_base_mult(r: &mut [i16], off: usize, a0: i16, a1: i16, b0: i16 r[off + 1] = out_val1; } -pub(crate) fn pack_ciphertext(u: &Vector, v: &Polynomial) -> [u8; CT_LEN] { +pub(crate) fn pack_ciphertext( + u: &Vector, + v: &Polynomial, +) -> [u8; CT_LEN] { let mut out = [0u8; CT_LEN]; // each of the N i16's will take du bits, so a polynomial takes N * du bits, then we have k of them - let lim: usize = k* (N * (du as usize) / 8); + let lim: usize = k * (N * (du as usize) / 8); u.compress_pol_vec::(&mut out[..lim]); v.compress_poly::(&mut out[lim..]); out } -pub(crate) fn unpack_ciphertext_u(c: &[u8; CT_LEN]) -> Vector { +pub(crate) fn unpack_ciphertext_u< + const k: usize, + const CT_LEN: usize, + const du: i16, + const dv: i16, +>( + c: &[u8; CT_LEN], +) -> Vector { // each of the N i16's will take du bits, so a polynomial takes N * du bits, then we have k of them - let lim: usize = k* (N * (du as usize) / 8); + let lim: usize = k * (N * (du as usize) / 8); let u = Vector::::decompress_pol_vec::(&c[..lim]); u } -pub(crate) fn unpack_ciphertext_v(c: &[u8; CT_LEN]) -> Polynomial { +pub(crate) fn unpack_ciphertext_v< + const k: usize, + const CT_LEN: usize, + const du: i16, + const dv: i16, +>( + c: &[u8; CT_LEN], +) -> Polynomial { // each of the N i16's will take du bits, so a polynomial takes N * du bits, then we have k of them - let lim: usize = k* (N * (du as usize) / 8); + let lim: usize = k * (N * (du as usize) / 8); let v = Polynomial::decompress_poly::(&c[lim..]); diff --git a/crypto/mlkem/src/mlkem_keys.rs b/crypto/mlkem/src/mlkem_keys.rs index bcf75ba..11d438f 100644 --- a/crypto/mlkem/src/mlkem_keys.rs +++ b/crypto/mlkem/src/mlkem_keys.rs @@ -1,15 +1,15 @@ +use core::fmt; +use core::fmt::{Debug, Display, Formatter}; use crate::aux_functions::{byte_decode, byte_encode, expandA}; use crate::matrix::{Matrix, Vector}; -use crate::mlkem::{POLY_BYTES, H, q}; +use crate::mlkem::{H, POLY_BYTES, q}; +use crate::mlkem::{MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512_k}; +use crate::mlkem::{MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM768_k}; +use crate::mlkem::{MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM1024_k}; use crate::{ML_KEM_512_NAME, ML_KEM_768_NAME, ML_KEM_1024_NAME}; -use crate::mlkem::{MLKEM512_k, MLKEM512_PK_LEN, MLKEM512_SK_LEN}; -use crate::mlkem::{MLKEM768_k, MLKEM768_PK_LEN, MLKEM768_SK_LEN}; -use crate::mlkem::{MLKEM1024_k, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN}; -use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial, KeyType}; -use bouncycastle_core::traits::{Hash, KEMPrivateKey, KEMPublicKey, Secret, SecurityStrength}; use bouncycastle_core::errors::KEMError; -use core::fmt; -use core::fmt::{Debug, Display, Formatter}; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::traits::{Hash, KEMPrivateKey, KEMPublicKey, Secret, SecurityStrength}; use bouncycastle_sha3::SHA3_256; @@ -24,31 +24,54 @@ use crate::mlkem::MLKEMTrait; /// ML-KEM-512 Public Key pub type MLKEM512PublicKey = MLKEMPublicKey; /// ML-KEM-512 Private Key -pub type MLKEM512PrivateKey = MLKEMPrivateKey; +pub type MLKEM512PrivateKey = + MLKEMPrivateKey; /// ML-KEM-768 Public Key pub type MLKEM768PublicKey = MLKEMPublicKey; /// ML-KEM-768 Private Key -pub type MLKEM768PrivateKey = MLKEMPrivateKey; +pub type MLKEM768PrivateKey = + MLKEMPrivateKey; /// ML-KEM-1024 Public Key pub type MLKEM1024PublicKey = MLKEMPublicKey; /// ML-KEM-1024 Private Key -pub type MLKEM1024PrivateKey = MLKEMPrivateKey; - +pub type MLKEM1024PrivateKey = + MLKEMPrivateKey; /* Pre-expanded keys for repeated operations */ /// ML-KEM-512 Public Key with a pre-expanded public matrix A for repeated encaps operations. -pub type MLKEM512PublicKeyExpanded = MLKEMPublicKeyExpanded; +pub type MLKEM512PublicKeyExpanded = + MLKEMPublicKeyExpanded; /// ML-KEM-512 Private Key with a pre-expanded public matrix A for repeated decaps operations. -pub type MLKEM512PrivateKeyExpanded = MLKEMPrivateKeyExpanded; +pub type MLKEM512PrivateKeyExpanded = MLKEMPrivateKeyExpanded< + MLKEM512_k, + MLKEM512PublicKey, + MLKEM512PrivateKey, + MLKEM512_SK_LEN, + MLKEM512_PK_LEN, +>; /// ML-KEM-768 Public Key with a pre-expanded public matrix A for repeated encaps operations. -pub type MLKEM768PublicKeyExpanded = MLKEMPublicKeyExpanded; +pub type MLKEM768PublicKeyExpanded = + MLKEMPublicKeyExpanded; /// ML-KEM-768 Private Key with a pre-expanded public matrix A for repeated decaps operations. -pub type MLKEM768PrivateKeyExpanded = MLKEMPrivateKeyExpanded; +pub type MLKEM768PrivateKeyExpanded = MLKEMPrivateKeyExpanded< + MLKEM768_k, + MLKEM768PublicKey, + MLKEM768PrivateKey, + MLKEM768_SK_LEN, + MLKEM768_PK_LEN, +>; /// ML-KEM-1024 Public Key with a pre-expanded public matrix A for repeated encaps operations. -pub type MLKEM1024PublicKeyExpanded = MLKEMPublicKeyExpanded; +pub type MLKEM1024PublicKeyExpanded = + MLKEMPublicKeyExpanded; /// ML-KEM-1024 Private Key with a pre-expanded public matrix A for repeated decaps operations. -pub type MLKEM1024PrivateKeyExpanded = MLKEMPrivateKeyExpanded; +pub type MLKEM1024PrivateKeyExpanded = MLKEMPrivateKeyExpanded< + MLKEM1024_k, + MLKEM1024PublicKey, + MLKEM1024PrivateKey, + MLKEM1024_SK_LEN, + MLKEM1024_PK_LEN, +>; /// An ML-KEM public key. #[derive(Clone)] @@ -70,16 +93,20 @@ pub trait MLKEMPublicKeyTrait : KEMPublicKe fn compute_hash(&self) -> [u8; 32]; } -pub(crate) trait MLKEMPublicKeyInternalTrait : MLKEMPublicKeyTrait { +pub(crate) trait MLKEMPublicKeyInternalTrait: + MLKEMPublicKeyTrait +{ /// Not exposing a constructor publicly because you should have to get an instance either by /// running a keygen, or by decoding an existing key. - fn new(t_hat: Vector, rho: [u8; 32], ) -> Self; + fn new(t_hat: Vector, rho: [u8; 32]) -> Self; /// Get a ref to t1 fn t_hat(&self) -> &Vector; } -impl MLKEMPublicKeyTrait for MLKEMPublicKey { +impl MLKEMPublicKeyTrait + for MLKEMPublicKey +{ fn pk_decode(pk: &[u8; PK_LEN]) -> Result { let (pk_chunks, last_chunk) = pk.as_chunks::(); @@ -126,12 +153,16 @@ impl MLKEMPublicKeyTrait for MLK } } -impl MLKEMPublicKeyInternalTrait for MLKEMPublicKey { +impl MLKEMPublicKeyInternalTrait + for MLKEMPublicKey +{ fn new(t_hat: Vector, rho: [u8; 32]) -> Self { Self { rho, t_hat } } - fn t_hat(&self) -> &Vector { &self.t_hat } + fn t_hat(&self) -> &Vector { + &self.t_hat + } } impl KEMPublicKey for MLKEMPublicKey { @@ -164,7 +195,9 @@ impl KEMPublicKey for MLKEMPublicK } fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != PK_LEN { return Err(KEMError::DecodingError("Provided key bytes are the incorrect length")) } + if bytes.len() != PK_LEN { + return Err(KEMError::DecodingError("Provided key bytes are the incorrect length")); + } let bytes_sized: [u8; PK_LEN] = bytes[..PK_LEN].try_into().unwrap(); Self::pk_decode(&bytes_sized) } @@ -208,21 +241,23 @@ impl Display for MLKEMPublicKey /// against the same public key, which causes the MLKEMPublicKey struct to take up more memory, but results /// in more efficient repeated encaps() operations. #[derive(Clone)] -pub struct MLKEMPublicKeyExpanded, const PK_LEN: usize> { +pub struct MLKEMPublicKeyExpanded< + const k: usize, + PK: MLKEMPublicKeyInternalTrait, + const PK_LEN: usize, +> { pub(crate) ek: PK, pub(crate) A_hat: Matrix, } impl, const PK_LEN: usize> -MLKEMPublicKeyInternalTrait for MLKEMPublicKeyExpanded { + MLKEMPublicKeyInternalTrait for MLKEMPublicKeyExpanded +{ fn new(t_hat: Vector, rho: [u8; 32]) -> Self { let ek = PK::new(t_hat, rho); let A_hat = ek.A_hat(); - Self { - ek, - A_hat, - } + Self { ek, A_hat } } fn t_hat(&self) -> &Vector { @@ -231,7 +266,8 @@ MLKEMPublicKeyInternalTrait for MLKEMPublicKeyExpanded } impl, const PK_LEN: usize> -KEMPublicKey for MLKEMPublicKeyExpanded { + KEMPublicKey for MLKEMPublicKeyExpanded +{ fn encode(&self) -> [u8; PK_LEN] { let mut pk = [0u8; PK_LEN]; self.encode_out(&mut pk); @@ -244,24 +280,30 @@ KEMPublicKey for MLKEMPublicKeyExpanded { } fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != PK_LEN { return Err(KEMError::DecodingError("Provided key bytes are the incorrect length")) } + if bytes.len() != PK_LEN { + return Err(KEMError::DecodingError("Provided key bytes are the incorrect length")); + } let bytes_sized: [u8; PK_LEN] = bytes[..PK_LEN].try_into().unwrap(); Self::pk_decode(&bytes_sized) } } -impl, const PK_LEN: usize> -PartialEq for MLKEMPublicKeyExpanded { +impl, const PK_LEN: usize> PartialEq + for MLKEMPublicKeyExpanded +{ fn eq(&self, other: &Self) -> bool { self.encode() == other.encode() } } -impl, const PK_LEN: usize> -Eq for MLKEMPublicKeyExpanded {} +impl, const PK_LEN: usize> Eq + for MLKEMPublicKeyExpanded +{ +} -impl, const PK_LEN: usize> -Debug for MLKEMPublicKeyExpanded { +impl, const PK_LEN: usize> Debug + for MLKEMPublicKeyExpanded +{ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let alg = match k { 2 => ML_KEM_512_NAME, @@ -274,8 +316,9 @@ Debug for MLKEMPublicKeyExpanded { } } -impl, const PK_LEN: usize> -Display for MLKEMPublicKeyExpanded { +impl, const PK_LEN: usize> Display + for MLKEMPublicKeyExpanded +{ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let alg = match k { 2 => ML_KEM_512_NAME, @@ -289,7 +332,8 @@ Display for MLKEMPublicKeyExpanded { } impl, const PK_LEN: usize> -MLKEMPublicKeyTrait for MLKEMPublicKeyExpanded { + MLKEMPublicKeyTrait for MLKEMPublicKeyExpanded +{ fn pk_decode(pk: &[u8; PK_LEN]) -> Result { let ek = PK::pk_decode(pk)?; let A_hat = ek.A_hat(); @@ -306,16 +350,14 @@ MLKEMPublicKeyTrait for MLKEMPublicKeyExpanded { } impl, const PK_LEN: usize> From<&PK> -for MLKEMPublicKeyExpanded { + for MLKEMPublicKeyExpanded +{ /// Fully expands the intermediate values needed for performing multiple encaps operations /// against the same public key, which causes the MLKEMPublicKey struct to take up fn from(ek: &PK) -> Self { let A_hat = ek.A_hat(); - Self { - ek: ek.clone(), - A_hat, - } + Self { ek: ek.clone(), A_hat } } } @@ -354,9 +396,8 @@ impl< /* dk_pke */ // Alg 13; line 20: dkPKE ← ByteEncode12(𝐬) for i in 0..k { - out[i*POLY_BYTES .. (i+1)*POLY_BYTES].copy_from_slice(&byte_encode::<12, POLY_BYTES>( - &self.s_hat[i] - )); + out[i * POLY_BYTES..(i + 1) * POLY_BYTES] + .copy_from_slice(&byte_encode::<12, POLY_BYTES>(&self.s_hat[i])); } pos += k * POLY_BYTES; @@ -383,7 +424,8 @@ pub trait MLKEMPrivateKeyTrait< const k: usize, PK: MLKEMPublicKeyInternalTrait, const SK_LEN: usize, - const PK_LEN: usize> : KEMPrivateKey { + const PK_LEN: usize, +>: KEMPrivateKey { /// Get a ref to the seed, if there is one stored with this private key fn seed(&self) -> Option>; @@ -395,16 +437,16 @@ pub trait MLKEMPrivateKeyTrait< fn sk_decode(sk: &[u8; SK_LEN]) -> Result; } -pub(crate) trait MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, const PK_LEN: usize> { +pub(crate) trait MLKEMPrivateKeyInternalTrait< + const k: usize, + PK: MLKEMPublicKeyTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> +{ /// Not exposing a constructor publicly because you should have to get an instance either by /// running a keygen, or by decoding an existing key. - fn new( - s_hat: Vector, - ek: PK, - h: [u8; 32], - z: [u8; 32], - seed_d: Option<[u8; 32]>, - ) -> Self; + fn new(s_hat: Vector, ek: PK, h: [u8; 32], z: [u8; 32], seed_d: Option<[u8; 32]>) -> Self; /// Get a ref to s_hat fn s_hat(&self) -> &Vector; @@ -412,9 +454,13 @@ pub(crate) trait MLKEMPrivateKeyInternalTrait &[u8; 32]; } - -impl, const SK_LEN: usize, const PK_LEN: usize> - MLKEMPrivateKeyTrait for MLKEMPrivateKey { +impl< + const k: usize, + PK: MLKEMPublicKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> MLKEMPrivateKeyTrait for MLKEMPrivateKey +{ fn seed(&self) -> Option> { if self.seed_d.is_none() { None @@ -453,7 +499,7 @@ impl, const SK_LEN: u // for (s_i, sk_chunk) in s_hat.0.iter_mut().zip(sk_chunks) { for i in 0..k { s_hat[i] = byte_decode::<12, POLY_BYTES>( - sk[i*POLY_BYTES .. (i+1)*POLY_BYTES].try_into().unwrap() + sk[i * POLY_BYTES..(i + 1) * POLY_BYTES].try_into().unwrap(), ); // FIPS 203 says: @@ -461,9 +507,9 @@ impl, const SK_LEN: u // segment of its input into an integer modulo 212 = 4096 and then reduces the result // modulo π‘ž. This is no longer a one-to-one operation. Indeed, some 12-bit segments could // correspond to an integer greater than π‘ž βˆ’ 1 = 3328 but less than 4096." - // Since we are here in the d=12 case, we can and should check that all coeffs are less than q-1 + // Since we are here in the d=12 case, we can and should check that all coeffs are less than q for coeff in s_hat[i].coeffs.iter() { - if *coeff < -q || *coeff >= q { + if *coeff < 0 || *coeff >= q { return Err(KEMError::DecodingError("Invalid or corrupted key")); } } @@ -480,9 +526,11 @@ impl, const SK_LEN: u // This satisfies the "Decapsulation input check #3) in FIPS 203 section 7.3. // We're doing it here on key load rather than as part of the decapsulation for performance - // because if you're doing multiple decapsulations, you only need to perform this check once. + // because if you're doing multiple decapsulations, you only need to perform this check once. if h_pk != ek.compute_hash() { - return Err(KEMError::ConsistencyCheckFailed("Corrupted private key: computed hash of ek != h_ek stored in private key")); + return Err(KEMError::ConsistencyCheckFailed( + "Corrupted private key: computed hash of ek != h_ek stored in private key", + )); } /* z */ @@ -492,8 +540,13 @@ impl, const SK_LEN: u } } -impl, const SK_LEN: usize, const PK_LEN: usize> - MLKEMPrivateKeyInternalTrait for MLKEMPrivateKey { +impl< + const k: usize, + PK: MLKEMPublicKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> MLKEMPrivateKeyInternalTrait for MLKEMPrivateKey +{ /// Note to future maintainers: FIPS 203 section 7.3 requires that ek be hashed and compared to pk_hash. fn new( s_hat: Vector, @@ -502,22 +555,25 @@ impl, const SK_LEN: u z: [u8; 32], seed_d: Option<[u8; 32]>, ) -> Self { - Self { - s_hat, - ek, - pk_hash, - z, - seed_d: seed_d.clone(), - } + Self { s_hat, ek, pk_hash, z, seed_d: seed_d.clone() } } - fn s_hat(&self) -> &Vector { &self.s_hat } + fn s_hat(&self) -> &Vector { + &self.s_hat + } - fn z(&self) -> &[u8; 32] { &self.z } + fn z(&self) -> &[u8; 32] { + &self.z + } } -impl, const SK_LEN: usize, const PK_LEN: usize -> KEMPrivateKey for MLKEMPrivateKey { +impl< + const k: usize, + PK: MLKEMPublicKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> KEMPrivateKey for MLKEMPrivateKey +{ fn encode(&self) -> [u8; SK_LEN] { let mut out = [0u8; SK_LEN]; self.encode_out(&mut out); @@ -530,6 +586,9 @@ impl, const SK_LEN: u } fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != SK_LEN { + return Err(KEMError::DecodingError("Provided key bytes are the incorrect length")); + } if bytes.len() != SK_LEN { return Err(KEMError::DecodingError("Provided key bytes are the incorrect length")) } let bytes_sized: [u8; SK_LEN] = bytes[..SK_LEN].try_into().unwrap(); @@ -537,11 +596,21 @@ impl, const SK_LEN: u } } -impl, const SK_LEN: usize, const PK_LEN: usize> - Eq for MLKEMPrivateKey {} +impl< + const k: usize, + PK: MLKEMPublicKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> Eq for MLKEMPrivateKey +{ +} -impl, const SK_LEN: usize, const PK_LEN: usize> - PartialEq for MLKEMPrivateKey +impl< + const k: usize, + PK: MLKEMPublicKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> PartialEq for MLKEMPrivateKey { fn eq(&self, other: &Self) -> bool { let self_encoded = self.encode(); @@ -550,12 +619,22 @@ impl, const SK_LEN: u } } -impl, const SK_LEN: usize, const PK_LEN: usize> -Secret for MLKEMPrivateKey {} +impl< + const k: usize, + PK: MLKEMPublicKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> Secret for MLKEMPrivateKey +{ +} /// Debug impl mainly to prevent the secret key from being printed in logs. -impl, const SK_LEN: usize, const PK_LEN: usize> - fmt::Debug for MLKEMPrivateKey +impl< + const k: usize, + PK: MLKEMPublicKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> fmt::Debug for MLKEMPrivateKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let alg = match k { @@ -575,8 +654,12 @@ impl, const SK_LEN: u } /// Display impl mainly to prevent the secret key from being printed in logs. -impl, const SK_LEN: usize, const PK_LEN: usize> - Display for MLKEMPrivateKey +impl< + const k: usize, + PK: MLKEMPublicKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> Display for MLKEMPrivateKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let alg = match k { @@ -596,19 +679,23 @@ impl, const SK_LEN: u } /// Zeroizing drop -impl, const SK_LEN: usize, const PK_LEN: usize> -Drop for MLKEMPrivateKey +impl< + const k: usize, + PK: MLKEMPublicKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> Drop for MLKEMPrivateKey { fn drop(&mut self) { // s_hat, has its own zeroizing drop self.pk_hash.fill(0u8); self.z.fill(0u8); - if self.seed_d.is_some() { self.seed_d.as_mut().unwrap().fill(0u8); } + if self.seed_d.is_some() { + self.seed_d.as_mut().unwrap().fill(0u8); + } } } - - /// A fully expanded ML-KEM private key that includes the intermediate values needed for performing /// multiple decaps operations with the same private key, which causes the private key struct to /// take up more memory, but results in more efficient repeated decaps() operations. @@ -616,9 +703,10 @@ Drop for MLKEMPrivateKey pub struct MLKEMPrivateKeyExpanded< const k: usize, PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, - const PK_LEN: usize + const PK_LEN: usize, > { _phantom: core::marker::PhantomData, pub(crate) dk: SK, @@ -628,11 +716,12 @@ pub struct MLKEMPrivateKeyExpanded< impl< const k: usize, PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, - const PK_LEN: usize -> From<&SK> -for MLKEMPrivateKeyExpanded { + const PK_LEN: usize, +> From<&SK> for MLKEMPrivateKeyExpanded +{ /// Fully expands the intermediate values needed for performing multiple encaps operations /// against the same public key, which causes the MLKEMPublicKey struct to take up fn from(dk: &SK) -> Self { @@ -649,10 +738,12 @@ for MLKEMPrivateKeyExpanded { impl< const k: usize, PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, - const PK_LEN: usize -> KEMPrivateKey for MLKEMPrivateKeyExpanded { + const PK_LEN: usize, +> KEMPrivateKey for MLKEMPrivateKeyExpanded +{ fn encode(&self) -> [u8; SK_LEN] { self.dk.encode() } @@ -669,10 +760,12 @@ impl< impl< const k: usize, PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, - const PK_LEN: usize -> PartialEq for MLKEMPrivateKeyExpanded { + const PK_LEN: usize, +> PartialEq for MLKEMPrivateKeyExpanded +{ fn eq(&self, other: &Self) -> bool { self.dk.eq(&other.dk) } @@ -681,26 +774,34 @@ impl< impl< const k: usize, PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, - const PK_LEN: usize -> Eq for MLKEMPrivateKeyExpanded {} + const PK_LEN: usize, +> Eq for MLKEMPrivateKeyExpanded +{ +} impl< const k: usize, PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, - const PK_LEN: usize -> Secret for MLKEMPrivateKeyExpanded {} + const PK_LEN: usize, +> Secret for MLKEMPrivateKeyExpanded +{ +} impl< const k: usize, PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, - const PK_LEN: usize -> Drop for MLKEMPrivateKeyExpanded { + const PK_LEN: usize, +> Drop for MLKEMPrivateKeyExpanded +{ fn drop(&mut self) { // Nothing to do since self.sk already impls zeroizing Drop } @@ -709,10 +810,12 @@ impl< impl< const k: usize, PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, - const PK_LEN: usize -> Debug for MLKEMPrivateKeyExpanded { + const PK_LEN: usize, +> Debug for MLKEMPrivateKeyExpanded +{ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let alg = match k { 2 => ML_KEM_512_NAME, @@ -733,10 +836,12 @@ impl< impl< const k: usize, PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, - const PK_LEN: usize -> Display for MLKEMPrivateKeyExpanded { + const PK_LEN: usize, +> Display for MLKEMPrivateKeyExpanded +{ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let alg = match k { 2 => ML_KEM_512_NAME, @@ -757,10 +862,13 @@ impl< impl< const k: usize, PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait + MLKEMPrivateKeyInternalTrait, + SK: MLKEMPrivateKeyTrait + + MLKEMPrivateKeyInternalTrait, const SK_LEN: usize, - const PK_LEN: usize -> MLKEMPrivateKeyTrait for MLKEMPrivateKeyExpanded { + const PK_LEN: usize, +> MLKEMPrivateKeyTrait + for MLKEMPrivateKeyExpanded +{ fn seed(&self) -> Option> { self.dk.seed() } diff --git a/crypto/mlkem/src/polynomial.rs b/crypto/mlkem/src/polynomial.rs index ae518fc..13bc411 100644 --- a/crypto/mlkem/src/polynomial.rs +++ b/crypto/mlkem/src/polynomial.rs @@ -4,16 +4,20 @@ use core::fmt; use core::fmt::{Debug, Display, Formatter}; use core::ops::{Index, IndexMut}; -use bouncycastle_core::traits::Secret; -use crate::aux_functions::{barrett_reduce, cond_sub_q, montgomery_reduce, mul_mont, ntt_base_mult, ZETAS, ZETAS_INV}; +use crate::aux_functions::{ + ZETAS, ZETAS_INV, barrett_reduce, montgomery_reduce, mul_mont, ntt_base_mult, +}; 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. #[derive(Clone)] -pub struct Polynomial{ pub(crate) coeffs: [i16; N] } +pub struct Polynomial { + pub(crate) coeffs: [i16; N], +} /// Convenience function to avoid ".0" all over the place. impl Index for Polynomial { @@ -40,10 +44,10 @@ impl Polynomial { pub(crate) fn from_msg(m: [u8; 32]) -> Self { let mut w = Polynomial::new(); - for (i, b) in m.iter().enumerate().take(N/8) { + for (i, b) in m.iter().enumerate() { for j in 0..8 { let mask = -(((*b >> j) & 1) as i16); - w[8 * i + j] = mask /*as i32*/ & ((q + 1) / 2); + w[8 * i + j] = mask & ((q + 1) / 2); } } @@ -51,22 +55,22 @@ impl Polynomial { } /// Convert a Polynomial back into a message m - pub(crate) fn to_msg(mut self) -> [u8; 32] { - - const LOWER: i32 = q as i32 >> 2; // 832 + pub(crate) fn to_msg(self) -> [u8; 32] { + const LOWER: i32 = q as i32 >> 2; // 832 const UPPER: i32 = q as i32 - LOWER; // 2497 let mut msg = [0u8; 32]; // you would expect to use a full reduce() here, but since this is data coming from - // out matrix math and not from an attacker, we can get away with the lighter cond_sub_q() - self.cond_sub_q(); + // out matrix math and not from an attacker, we can get away with the lighter cond_sub_q(). + // Actually; further testing against the bc-test-data set of KATs shows that everything passes even with nothing + // self.cond_sub_q(); // for (i, item) in msg.iter_mut().enumerate().take(N/8) { for i in 0 .. N/8 { for j in 0..8 { let c_j = self[8 * i + j] as i32; - let t = (((LOWER - c_j) & (c_j - UPPER)) >> 31) & 0x0000000000000001; + let t = (((LOWER - c_j) & (c_j - UPPER)) >> 31) & 0x01; msg[i] |= (t << j) as u8; } } @@ -131,8 +135,9 @@ impl Polynomial { // s.cond_sub_q(); match dv { - 4 => { // MLKEM512 and MLKEM768 - for i in 0..N/8 { + 4 => { + // MLKEM512 and MLKEM768 + for i in 0..N / 8 { // fill the temp array t for (j, item) in t.iter_mut().enumerate() { *item = ((((self[8 * i + j] as i32) << 4) + (q as i32 /2)) @@ -185,8 +190,9 @@ impl Polynomial { // if self.m_engine.poly_compressed_bytes() == 128 { match dv { - 4 => { // MLKEM512 and MLKEM768 - for i in 0..N/2 { + 4 => { + // MLKEM512 and MLKEM768 + for i in 0..N / 2 { v[2 * i] = (((((compressed_v[idx] & 15) as i16) as i32 * (q as i32)) + 8) >> 4) as i16; @@ -223,11 +229,13 @@ impl Polynomial { v } - pub(crate) fn cond_sub_q(&mut self) { - for i in 0..N { - self[i] = cond_sub_q(self[i]); - } - } + // not currently used, but I'll leave it here because it's useful for debugging if you want to output values + // that are normalized to [0,q] to compare against intermediate results from other libraries. + // pub(crate) fn cond_sub_q(&mut self) { + // for i in 0..N { + // self[i] = cond_sub_q(self[i]); + // } + // } /// Algorithm 9 NTT(𝑓) /// Computes the NTT representation 𝑓_hat of the given polynomial 𝑓 ∈ π‘…π‘ž. diff --git a/crypto/mlkem/tests/bc_test_data.rs b/crypto/mlkem/tests/bc_test_data.rs index d85ee4a..7313f04 100644 --- a/crypto/mlkem/tests/bc_test_data.rs +++ b/crypto/mlkem/tests/bc_test_data.rs @@ -4,18 +4,52 @@ #[cfg(test)] mod bc_test_data { - use std::{fs}; + use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; - use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial512, KeyType}; - use bouncycastle_core::traits::{KEMPrivateKey, KEMPublicKey, SecurityStrength, KEM}; - use bouncycastle_mlkem::{MLKEM1024PrivateKey, MLKEM1024PublicKey, MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey, MLKEMTrait, MLKEM1024, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM512, MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM768, MLKEM768_PK_LEN, MLKEM768_SK_LEN}; - - const TEST_DATA_PATH: &str = "../../../bc-test-data/pqc/crypto/mlkem"; + use bouncycastle_mlkem::{ + MLKEM512, MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512PrivateKey, MLKEM512PublicKey, + MLKEM768, MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM768PrivateKey, MLKEM768PublicKey, + MLKEM1024, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM1024PrivateKey, MLKEM1024PublicKey, + MLKEMTrait, + }; + use std::fs; + use std::path::Path; + use std::process::exit; + use std::sync::Once; + + const TEST_DATA_PATH_RELATIVE: &str = "../../../bc-test-data/pqc/crypto/mlkem"; + const TEST_DATA_PATH: &str = "../bc-test-data/pqc/crypto/mlkem"; + + static TEST_DATA_CHECK: Once = Once::new(); + + fn test_for_presence_of_test_data() { + TEST_DATA_CHECK.call_once(|| { + if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + println!("bc-test-data found at: {:?}", TEST_DATA_PATH_RELATIVE); + } else if !Path::new(TEST_DATA_PATH).exists() { + println!("bc-test-data found at: {:?}", TEST_DATA_PATH); + } else { + println!("bc-test-data directory not found"); + exit(0); + } + }); + } #[test] #[allow(non_snake_case)] fn ML_KEM_keyGen() { - let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-KEM-keyGen.txt").unwrap(); + test_for_presence_of_test_data(); + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/ML-KEM-keyGen.txt").unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-KEM-keyGen.txt").unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + let test_cases = KeyGenTestCase::parse(contents); for test_case in test_cases { @@ -42,7 +76,21 @@ mod bc_test_data { impl KeyGenTestCase { fn new() -> Self { - Self { vs_id: 0, algorithm: String::new(), mode: String::new(), revision: String::new(), is_sample: false, tg_id: 0, test_type: String::new(), parameter_set: String::new(), tc_id: 0, z: String::new(), d: String::new(), ek: String::new(), dk: String::new() } + Self { + vs_id: 0, + algorithm: String::new(), + mode: String::new(), + revision: String::new(), + is_sample: false, + tg_id: 0, + test_type: String::new(), + parameter_set: String::new(), + tc_id: 0, + z: String::new(), + d: String::new(), + ek: String::new(), + dk: String::new(), + } } fn is_full(&self) -> bool { @@ -56,7 +104,9 @@ mod bc_test_data { let (tag, value) = match line.split_once(" = ") { Some(pair) => pair, None => { - if test_case.is_full() { test_cases.push(test_case.clone()); } + if test_case.is_full() { + test_cases.push(test_case.clone()); + } continue; } }; @@ -103,23 +153,29 @@ mod bc_test_data { match self.parameter_set.as_str() { "ML-KEM-512" => { let (pk, sk) = MLKEM512::keygen_from_seed(&seed).unwrap(); - let pk_sized: [u8; MLKEM512_PK_LEN] = hex::decode(&self.ek).unwrap().try_into().unwrap(); + let pk_sized: [u8; MLKEM512_PK_LEN] = + hex::decode(&self.ek).unwrap().try_into().unwrap(); assert_eq!(pk.encode(), pk_sized); - let sk_sized: [u8; MLKEM512_SK_LEN] = hex::decode(&self.dk).unwrap().try_into().unwrap(); + let sk_sized: [u8; MLKEM512_SK_LEN] = + hex::decode(&self.dk).unwrap().try_into().unwrap(); assert_eq!(sk.encode(), sk_sized); }, "ML-KEM-768" => { let (pk, sk) = MLKEM768::keygen_from_seed(&seed).unwrap(); - let pk_sized: [u8; MLKEM768_PK_LEN] = hex::decode(&self.ek).unwrap().try_into().unwrap(); + let pk_sized: [u8; MLKEM768_PK_LEN] = + hex::decode(&self.ek).unwrap().try_into().unwrap(); assert_eq!(pk.encode(), pk_sized); - let sk_sized: [u8; MLKEM768_SK_LEN] = hex::decode(&self.dk).unwrap().try_into().unwrap(); + let sk_sized: [u8; MLKEM768_SK_LEN] = + hex::decode(&self.dk).unwrap().try_into().unwrap(); assert_eq!(sk.encode(), sk_sized); }, "ML-KEM-1024" => { let (pk, sk) = MLKEM1024::keygen_from_seed(&seed).unwrap(); - let pk_sized: [u8; MLKEM1024_PK_LEN] = hex::decode(&self.ek).unwrap().try_into().unwrap(); + let pk_sized: [u8; MLKEM1024_PK_LEN] = + hex::decode(&self.ek).unwrap().try_into().unwrap(); assert_eq!(pk.encode(), pk_sized); - let sk_sized: [u8; MLKEM1024_SK_LEN] = hex::decode(&self.dk).unwrap().try_into().unwrap(); + let sk_sized: [u8; MLKEM1024_SK_LEN] = + hex::decode(&self.dk).unwrap().try_into().unwrap(); assert_eq!(sk.encode(), sk_sized); }, val => panic!("Invalid parameter set: {}", val), @@ -130,7 +186,18 @@ mod bc_test_data { #[test] #[allow(non_snake_case)] fn ML_KEM_encapDecap() { - let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-KEM-encapDecap.txt").unwrap(); + test_for_presence_of_test_data(); + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/ML-KEM-encapDecap.txt") + .unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-KEM-encapDecap.txt").unwrap() + } else { + println!("Current working directory: {:?}", std::env::current_dir().unwrap()); + panic!("Test data directory not found") + }; + let test_cases = EncapDecapTestCase::parse(contents); let num_tests = test_cases.len(); @@ -162,7 +229,23 @@ mod bc_test_data { impl EncapDecapTestCase { fn new() -> Self { - Self { vs_id: 0, algorithm: String::new(), mode: String::new(), revision: String::new(), is_sample: false, tg_id: 0, test_type: String::new(), parameter_set: String::new(), function: String::new(), tc_id: 0, ek: String::new(), dk: String::new(), m: String::new(), c: String::new(), k: String::new() } + Self { + vs_id: 0, + algorithm: String::new(), + mode: String::new(), + revision: String::new(), + is_sample: false, + tg_id: 0, + test_type: String::new(), + parameter_set: String::new(), + function: String::new(), + tc_id: 0, + ek: String::new(), + dk: String::new(), + m: String::new(), + c: String::new(), + k: String::new(), + } } fn is_full(&self) -> bool { @@ -176,7 +259,9 @@ mod bc_test_data { let (tag, value) = match line.split_once(" = ") { Some(pair) => pair, None => { - if test_case.is_full() { test_cases.push(test_case.clone()); } + if test_case.is_full() { + test_cases.push(test_case.clone()); + } continue; } }; @@ -211,7 +296,8 @@ mod bc_test_data { "ML-KEM-512" => { match self.function.as_str() { "encapsulation" => { - let pk = MLKEM512PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap(); + let pk = MLKEM512PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()) + .unwrap(); let m: [u8; 32] = hex::decode(&self.m).unwrap().try_into().unwrap(); let (ss, ct) = MLKEM512::encaps_internal(&pk, None, m); @@ -222,7 +308,9 @@ mod bc_test_data { assert_eq!(ct, expected_ct.as_slice()); }, "decapsulation" => { - let sk = MLKEM512PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()).unwrap(); + let sk = + MLKEM512PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()) + .unwrap(); let ct = hex::decode(&self.c).unwrap(); let ss = MLKEM512::decaps(&sk, ct.as_slice()).unwrap(); @@ -235,7 +323,8 @@ mod bc_test_data { "ML-KEM-768" => { match self.function.as_str() { "encapsulation" => { - let pk = MLKEM768PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap(); + let pk = MLKEM768PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()) + .unwrap(); let m: [u8; 32] = hex::decode(&self.m).unwrap().try_into().unwrap(); let (ss, ct) = MLKEM768::encaps_internal(&pk, None, m); @@ -246,7 +335,9 @@ mod bc_test_data { assert_eq!(ct, expected_ct.as_slice()); }, "decapsulation" => { - let sk = MLKEM768PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()).unwrap(); + let sk = + MLKEM768PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()) + .unwrap(); let ct = hex::decode(&self.c).unwrap(); let ss = MLKEM768::decaps(&sk, ct.as_slice()).unwrap(); @@ -259,7 +350,9 @@ mod bc_test_data { "ML-KEM-1024" => { match self.function.as_str() { "encapsulation" => { - let pk = MLKEM1024PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap(); + let pk = + MLKEM1024PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()) + .unwrap(); let m: [u8; 32] = hex::decode(&self.m).unwrap().try_into().unwrap(); let (ss, ct) = MLKEM1024::encaps_internal(&pk, None, m); @@ -270,7 +363,9 @@ mod bc_test_data { assert_eq!(ct, expected_ct.as_slice()); }, "decapsulation" => { - let sk = MLKEM1024PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()).unwrap(); + let sk = + MLKEM1024PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()) + .unwrap(); let ct = hex::decode(&self.c).unwrap(); let ss = MLKEM1024::decaps(&sk, ct.as_slice()).unwrap(); @@ -284,4 +379,4 @@ mod bc_test_data { } } } -} \ No newline at end of file +} diff --git a/crypto/mlkem/tests/mlkem_key_tests.rs b/crypto/mlkem/tests/mlkem_key_tests.rs index 830196f..7317d50 100644 --- a/crypto/mlkem/tests/mlkem_key_tests.rs +++ b/crypto/mlkem/tests/mlkem_key_tests.rs @@ -1,18 +1,25 @@ #[cfg(test)] mod mlkem_key_tests { use bouncycastle_core::errors::KEMError; - use bouncycastle_core::key_material::{KeyMaterial512, KeyType}; - use bouncycastle_core::traits::{KEMPrivateKey, KEMPublicKey, KEM}; - use bouncycastle_mlkem::{MLKEMPrivateKeyTrait, MLKEMPublicKeyTrait, MLKEMTrait}; - use bouncycastle_mlkem::{MLKEM512, MLKEM768, MLKEM1024}; - use bouncycastle_mlkem::{MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey, MLKEM1024PrivateKey, MLKEM1024PublicKey}; - use bouncycastle_mlkem::{MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512_CT_LEN, MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM768_CT_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM1024_CT_LEN, MLKEM_SS_LEN}; + use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{KEM, 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, + MLKEMPrivateKeyTrait, MLKEMPublicKeyTrait, MLKEMTrait, + }; + use bouncycastle_mlkem::{MLKEM512, MLKEM768, MLKEM1024}; + use bouncycastle_mlkem::{ + MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey, + MLKEM1024PrivateKey, MLKEM1024PublicKey, + }; #[test] fn core_framework_tests() { - use bouncycastle_core_test_framework::kem::{TestFrameworkKEMKeys}; + use bouncycastle_core_test_framework::kem::TestFrameworkKEMKeys; + let tf = TestFrameworkKEMKeys::new(); tf.test_keys::(); @@ -22,7 +29,6 @@ mod mlkem_key_tests { #[test] fn pk_from_sk() { - /* MLDSA44 */ let expected_sk_bytes: [u8; MLKEM512_SK_LEN] = hex::decode("70554fd436344f2785b1b3b1bac184b6679003336c26f15a7de878c4825c6be03f3c4a480f75b7486aad31d3a00518623fd207ab528dd62721495835ae0062c367b74a71baf10aad0e8a2902076be31348beb15ccc0957cdebb4aff226756bbc601b6568ab784acbaeb34702f0f86a26202118b22b23f83558776c79c14dba983379c803e0dcc3160a11757030e69c6919798d81eb698a9a4483a99e5a5cb2c31c9a661799f3cc89c790706ea041629045d42a83aed88860e394c69187e2105d28cc14ec393592d67dd00aa43fe8b4eae4414002866b5c713c6a8d7d16cf78b819d6f12e9e5a74233908f0b15e3c4ba8329c5cdda55c84928e3aa8063e5aa9676403f91735b11010c7f593091364dc86445bc804840a9a21724212469f8a7b0ce0ac698eb86cad39a7f4824d9a5163aac21ee6808b053c8a3facb0b6744b5262bbcb26a43f664c8732b64cfc7acf099605f41c796060976ac433833fe00343fb1828300a424741116e4b45bb276ea81129a0db4c6e60bce611101e8c625474925e0222679308a3e7708d1972a7b423eb232851c36d2ed53d3ed3bb7500637061a5dc2292fa1c466c07354683328bec2c1ed2cb5c99b78eca0969038cf7c34dd118724e31cae086206b34302b520f5d177aded5b3cce02acce808ea26bcc072625fdb93f17458a5fc1d4da394380a1f57e9cc66109438a075f0d2813fcc4a199cc76db3823f270b0061594192940411a37ffbafae2c150165cec5c6bf73c595fb92cd15312607da070778652bd9944bc48bc7d1a534338bad0bad6656c5d502ce7850ab1587244eeb58f439ab5e08574a718c8aac3d77c798bba1542733be73448f23fb70c0e5353a27c88322c5218493afbb38086434d6d60a56ba887dd498c3ab26a0870993815aa6a40975f218adca1582d64ffc8652fbb3a9a6fbc304f91945fa4aaef2878fd715df70113d2379f44886f812c83ff2b719a69e1ec74ae4b15accd3aed5a53ce76a7b0982471633b973cb40a1a0015d0a424fa11a479c023017436d2a2900e993eb5a0a067400c7f4aadf201fc4fa31264a63bae95cc8d65c3995815e597d104355cf29aa5333c93251869d5bcdbe487124f602b8b6a66c16c4761648ad765cf5d8006b515e905a7f0ac076b0c62efa328153e7ca5701699f1305f1e6bc6f90b0e49b693512b6ce992a8b8016ddfc1a662c7e3f9619cbd869dd771af30896ccd5918ac6cb77466c5e779996d67ff9aabc97503f2c7b7e2d000d86450fb1807ca4cabda465825a31c789a1b7a491ab3872765d320d0b71920fa213c94093416b83b8124e69f65e62cb5000dcc37aa9a0fff73970c4772f357d24189ca6f5305568c0e2376a3762a68c605e563c5d209572e0fc7532ca294729535567b5fc413c5e8792d2464536cc808f98add74664f141566f9016a90a541829a98a0464ce41a8bb44c2d4fa3c2c209460728ef14a1a7c4c9b98d12203b4cc3529160a9ab2d7838f7ff6b53ae05aa31a7d646b7afa6c45932526a3c3755619be994c211c2a31c05b3447836cb2150be1829dae6b04c5535cff546e392ba797411720f924f490a5ac5495f21356d550b782a64c1688b6b655bcc7842197a434c2f6563b5b7f09a78bcc488232783561d16f4cbab6755400050781570c66604b817ad1252294736e8b01861a4b5a74519b8b6fe51489a5072392e587626c713776575d33806a1c8e2732af97c2680f51666331c4eb8bbc0431c4f96832daf1b3c45528fba153f6c78b1c198702947ccd337727a46fb53ba11de5cb4191346859516cb6ad72400f3cf209b236aef35a580ac87eb3e30fafd66973ca8a7dd2675af41f7a17b61433cd1af80f7708869f665488497980b1ac10a0cdcb636a00ed8681b35e429124ca80350725b85f83a5eac3a4a3cc1600903e65293560b9b336e5af0d529dac1a048119302cb7a9bcc110b94851bf02117f199dc485a852b7473f09b831a6831d5b54c0b790d225cf6bb92d9462a26cdb33dda5123c7aaf0e26a0b83655eea28bf3a8074725018fd6bae4b601cf61baab71a7a3d35197a343e74b4a272c125d540896426d85b7958d3b38a6ba987ec37225c7b44cdb12dde4539b4ab082363683f04bf7a09cc5c41dfe830a1b162e0b324334362f084a14467723344badd000f8d8c537c48f998f05307cebd1ede0b81c3bc59a065a1b6d63b26c82f101ff648063b376e2bb6c5b7455f655a50c2feadade150efa0e0e6f365aea202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f").unwrap() .try_into().unwrap(); @@ -35,7 +41,6 @@ mod mlkem_key_tests { assert_eq!(sk_bytes.len(), expected_sk_bytes.len()); assert_eq!(sk_bytes, expected_sk_bytes.as_slice()); - // Decode and re-encode the pk, make sure you get the same thing let decoded_pk = MLKEM512PublicKey::from_bytes(&expected_pk_bytes).unwrap(); let pk_bytes = decoded_pk.encode(); @@ -54,47 +59,69 @@ mod mlkem_key_tests { // 3) does it reject a private key if the H(ek) is wrong? let seed = KeyMaterial512::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f - 101112131415161718191a1b1c1d1e1f - 202122232425262728292a2b2c2d2e2f - 303132333435363738393a3b3c3d3e3f").unwrap(), + &hex::decode( + "000102030405060708090a0b0c0d0e0f + 101112131415161718191a1b1c1d1e1f + 202122232425262728292a2b2c2d2e2f + 303132333435363738393a3b3c3d3e3f", + ) + .unwrap(), KeyType::Seed, - ).unwrap(); + ) + .unwrap(); let (pk, sk) = MLKEM512::keygen_from_seed(&seed).unwrap(); // generation of KAT // let h_ek = pk.compute_hash(); // println!("H(ek) for public key: {}", hex::encode(h_ek)); - let expected_h_ek: [u8; 32] = hex::decode("82f101ff648063b376e2bb6c5b7455f655a50c2feadade150efa0e0e6f365aea").unwrap().try_into().unwrap(); + let expected_h_ek: [u8; 32] = + hex::decode("82f101ff648063b376e2bb6c5b7455f655a50c2feadade150efa0e0e6f365aea") + .unwrap() + .try_into() + .unwrap(); assert_eq!(pk.compute_hash(), expected_h_ek); assert_eq!(sk.pk_hash(), &expected_h_ek); - // 3) does it reject a private key if the H(ek) is wrong? let mut sk_bytes: [u8; MLKEM512_SK_LEN] = sk.encode(); // h is: // dk[768π‘˜ + 32 ∢ 768π‘˜ + 64] // k for MLKEM512 is 2 - sk_bytes[768 * 2 .. (768 * 2) + 32].fill(1); + sk_bytes[768 * 2..(768 * 2) + 32].fill(1); // now try loading it match MLKEM512PrivateKey::from_bytes(&sk_bytes) { Ok(_) => panic!("Expected error loading private key with invalid H(ek)"), - Err(KEMError::ConsistencyCheckFailed(_)) => { /* good */ }, + Err(KEMError::ConsistencyCheckFailed(_)) => { /* good */ } _ => panic!("Unexpected error loading private key with invalid H(ek)"), } + + // check that pk and sk give the same pk_hash + assert_eq!(pk.compute_hash(), expected_h_ek); + assert_eq!(sk.pk_hash(), &expected_h_ek); + + /* and with Expanded Keys */ + let pk_expanded = MLKEM512PublicKeyExpanded::from(&pk); + assert_eq!(pk_expanded.compute_hash(), expected_h_ek); + + let sk_expanded = MLKEM512PrivateKeyExpanded::from(&sk); + assert_eq!(sk_expanded.pk_hash(), &expected_h_ek); } #[test] fn encode_decode() { let seed = KeyMaterial512::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f - 101112131415161718191a1b1c1d1e1f - 202122232425262728292a2b2c2d2e2f - 303132333435363738393a3b3c3d3e3f").unwrap(), + &hex::decode( + "000102030405060708090a0b0c0d0e0f + 101112131415161718191a1b1c1d1e1f + 202122232425262728292a2b2c2d2e2f + 303132333435363738393a3b3c3d3e3f", + ) + .unwrap(), KeyType::Seed, - ).unwrap(); + ) + .unwrap(); let (pk1, sk1) = MLKEM512::keygen_from_seed(&seed).unwrap(); let pk1_bytes = pk1.encode(); @@ -108,28 +135,68 @@ mod mlkem_key_tests { let sk2_bytes = sk2.encode(); assert_eq!(sk2_bytes.len(), MLKEM512_SK_LEN); assert_eq!(sk1_bytes, sk2_bytes); + + /* Expanded Keys */ + let pk_expanded = MLKEM512PublicKeyExpanded::from(&pk1); + assert_eq!(pk_expanded.encode(), pk1_bytes); + + let mut pk_expanded_bytes = [0u8; MLKEM512_PK_LEN]; + let bytes_written = pk_expanded.encode_out(&mut pk_expanded_bytes); + assert_eq!(bytes_written, MLKEM512_PK_LEN); + + let sk_expanded = MLKEM512PrivateKeyExpanded::from(&sk1); + assert_eq!(sk_expanded.encode(), sk1_bytes); + + let mut sk_expanded_bytes = [0u8; MLKEM512_SK_LEN]; + let bytes_written = sk_expanded.encode_out(&mut sk_expanded_bytes); + assert_eq!(bytes_written, MLKEM512_SK_LEN); } #[test] fn seed() { let seed = KeyMaterial512::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f - 101112131415161718191a1b1c1d1e1f - 202122232425262728292a2b2c2d2e2f - 303132333435363738393a3b3c3d3e3f").unwrap(), + &hex::decode( + "000102030405060708090a0b0c0d0e0f + 101112131415161718191a1b1c1d1e1f + 202122232425262728292a2b2c2d2e2f + 303132333435363738393a3b3c3d3e3f", + ) + .unwrap(), KeyType::Seed, - ).unwrap(); + ) + .unwrap(); let (_pk, sk) = MLKEM512::keygen_from_seed(&seed).unwrap(); assert!(sk.seed().is_some()); assert_eq!(sk.seed().as_ref().unwrap(), &seed); - - + + // When you pop the seed out, its SecurityStrength will match the ML-DSA algorithm + let (_pk, sk) = MLKEM512::keygen_from_seed(&seed).unwrap(); + assert_eq!(sk.seed().unwrap().security_strength(), SecurityStrength::_128bit); + + let (_pk, sk) = MLKEM768::keygen_from_seed(&seed).unwrap(); + assert_eq!(sk.seed().unwrap().security_strength(), SecurityStrength::_192bit); + + let (_pk, sk) = MLKEM1024::keygen_from_seed(&seed).unwrap(); + assert_eq!(sk.seed().unwrap().security_strength(), SecurityStrength::_256bit); // now load a key from bytes so that it doesn't have a seed + let (_pk, sk) = MLKEM512::keygen_from_seed(&seed).unwrap(); let sk_bytes = sk.encode(); - let sk2 = MLKEM512PrivateKey::from_bytes(&sk_bytes).unwrap(); - assert!(sk2.seed().is_none()); + let sk_no_seed = MLKEM512PrivateKey::from_bytes(&sk_bytes).unwrap(); + assert!(sk_no_seed.seed().is_none()); + + /* Expanded key */ + let (_pk, sk) = MLKEM512::keygen_from_seed(&seed).unwrap(); + let sk_expanded = MLKEM512PrivateKeyExpanded::from(&sk); + match sk_expanded.seed() { + Some(s) => assert_eq!(s, seed), + None => panic!("Expected expanded key to have seed"), + } + + // now try an expanded key that doesn't have a seed + let sk_expanded_no_seed = MLKEM512PrivateKeyExpanded::from(&sk_no_seed); + assert!(sk_expanded_no_seed.seed().is_none()); } #[test] @@ -140,19 +207,18 @@ mod mlkem_key_tests { // so let's test these conditions in both private key s_hat and public key t_hat match MLKEM512PrivateKey::from_bytes(&[255u8; MLKEM512_SK_LEN]) { - Err(KEMError::DecodingError(_)) => { /* good */ }, - _ => panic!("Expected malformed key to be rejected") + Err(KEMError::DecodingError(_)) => { /* good */ } + _ => panic!("Expected malformed key to be rejected"), }; match MLKEM512PublicKey::from_bytes(&[255u8; MLKEM512_PK_LEN]) { - Err(KEMError::DecodingError(_)) => { /* good */ }, - _ => panic!("Expected malformed key to be rejected") + Err(KEMError::DecodingError(_)) => { /* good */ } + _ => panic!("Expected malformed key to be rejected"), }; } #[test] fn test_eq() { - // MLKEM512 let (pk, sk) = MLKEM512::keygen().unwrap(); @@ -175,7 +241,6 @@ mod mlkem_key_tests { bytes[17] ^= 0x01; assert_ne!(sk, MLKEM512PrivateKey::from_bytes(&bytes).unwrap()); - // MLKEM768 let (pk, sk) = MLKEM768::keygen().unwrap(); @@ -198,7 +263,6 @@ mod mlkem_key_tests { bytes[17] ^= 0x01; assert_ne!(sk, MLKEM768PrivateKey::from_bytes(&bytes).unwrap()); - // MLKEM1024 let (pk, sk) = MLKEM1024::keygen().unwrap(); @@ -220,6 +284,32 @@ mod mlkem_key_tests { let mut bytes = sk.encode(); bytes[17] ^= 0x01; assert_ne!(sk, MLKEM1024PrivateKey::from_bytes(&bytes).unwrap()); + + /* Expanded keys */ + + let (pk, sk) = MLKEM512::keygen().unwrap(); + let pk_expanded = MLKEM512PublicKeyExpanded::from_bytes(&pk.encode()).unwrap(); + let sk_expanded = MLKEM512PrivateKeyExpanded::from_bytes(&sk.encode()).unwrap(); + + // basic equality checks + assert_eq!(pk_expanded, pk_expanded); + assert_eq!(pk_expanded, pk_expanded.clone()); + assert_eq!(pk_expanded, MLKEM512PublicKeyExpanded::from_bytes(&pk.encode()).unwrap()); + assert_eq!(pk_expanded.encode(), pk.encode()); + + assert_eq!(sk_expanded, sk_expanded); + assert_eq!(sk_expanded, sk_expanded.clone()); + assert_eq!(sk_expanded, MLKEM512PrivateKeyExpanded::from_bytes(&sk.encode()).unwrap()); + assert_eq!(sk_expanded.encode(), sk.encode()); + + // inequality checks + let mut bytes = pk.encode(); + bytes[17] ^= 0x01; + assert_ne!(pk_expanded, MLKEM512PublicKeyExpanded::from_bytes(&bytes).unwrap()); + + let mut bytes = sk.encode(); + bytes[17] ^= 0x01; + assert_ne!(sk_expanded, MLKEM512PrivateKeyExpanded::from_bytes(&bytes).unwrap()); } /// Tests that no private data is displayed @@ -229,7 +319,6 @@ mod mlkem_key_tests { let (pk768, sk768) = MLKEM768::keygen().unwrap(); let (pk1024, sk1024) = MLKEM1024::keygen().unwrap(); - /*** MLDSAPublicKey ***/ // fmt @@ -252,8 +341,6 @@ mod mlkem_key_tests { let pk_str = format!("{:?}", pk1024); assert!(pk_str.contains("MLKEMPublicKey { alg: ML-KEM-1024, pub_key_hash:")); - - /*** MLDSAPrivateKey ***/ // fmt let sk_str = format!("{}", sk512); @@ -279,9 +366,9 @@ mod mlkem_key_tests { /// Tests that no private data is displayed #[test] fn test_display_expanded_key() { - use bouncycastle_mlkem::{MLKEM512PublicKeyExpanded, MLKEM512PrivateKeyExpanded}; - use bouncycastle_mlkem::{MLKEM768PublicKeyExpanded, MLKEM768PrivateKeyExpanded}; - use bouncycastle_mlkem::{MLKEM1024PublicKeyExpanded, MLKEM1024PrivateKeyExpanded}; + use bouncycastle_mlkem::{MLKEM512PrivateKeyExpanded, MLKEM512PublicKeyExpanded}; + use bouncycastle_mlkem::{MLKEM768PrivateKeyExpanded, MLKEM768PublicKeyExpanded}; + use bouncycastle_mlkem::{MLKEM1024PrivateKeyExpanded, MLKEM1024PublicKeyExpanded}; let (pk512, sk512) = MLKEM512::keygen().unwrap(); let pk512 = MLKEM512PublicKeyExpanded::from(&pk512); @@ -295,7 +382,6 @@ mod mlkem_key_tests { let pk1024 = MLKEM1024PublicKeyExpanded::from(&pk1024); let sk1024 = MLKEM1024PrivateKeyExpanded::from(&sk1024); - /*** MLDSAPublicKey ***/ // fmt @@ -318,8 +404,6 @@ mod mlkem_key_tests { let pk_str = format!("{:?}", pk1024); assert!(pk_str.contains("MLKEMPublicKeyExpanded { alg: ML-KEM-1024, pub_key_hash:")); - - /*** MLDSAPrivateKey ***/ // fmt let sk_str = format!("{}", sk512); diff --git a/crypto/mlkem_lowmemory/src/aux_functions.rs b/crypto/mlkem_lowmemory/src/aux_functions.rs index a8784a6..a5f5043 100644 --- a/crypto/mlkem_lowmemory/src/aux_functions.rs +++ b/crypto/mlkem_lowmemory/src/aux_functions.rs @@ -1,7 +1,7 @@ //! Implements auxiliary functions for ML-DSA as defined in Section 7 of FIPS 204. use crate::mlkem::{N, q, q_inv}; -use crate::polynomial::{Polynomial}; +use crate::polynomial::Polynomial; use bouncycastle_core::traits::XOF; use bouncycastle_sha3::{SHAKE128, SHAKE256}; @@ -14,7 +14,7 @@ pub(crate) fn byte_encode(F: &Polynomial) let mut B = [0u8; PACK_LEN]; - for i in 0 .. N { + for i in 0..N { let mut alpha = F[i]; // For efficiency, the library is happy to work with values outside the range [0..q], @@ -88,9 +88,9 @@ pub(crate) fn sample_ntt(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { // SHAKE is fairly inefficient if you just squeeze 3 bytes at a time, so we'll do a block. // size doesn't really matter, so long as it's a multiple of 3. // 288 seemed to be the sweet spot from playing with benchmarks - // It's probably around the average rejection rate, and 288 is a multiple of both 3 (required for this alg) + // It's probably around the average rejection rate, and 216 is a multiple of both 3 (required for this alg) // and 8 (efficient for SHAKE). - let mut C = [0u8; 288]; + let mut C = [0u8; 216]; xof.squeeze_out(&mut C); let mut idx: usize = 0; @@ -105,7 +105,7 @@ pub(crate) fn sample_ntt(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { // 6: 𝑑1 ← 𝐢[0] + 256 β‹… (𝐢[1] mod 16) // β–· 0 ≀ 𝑑1 < 2^12 - let d1: i16 = (C[idx+0] as i16) | ((C[idx+1] as i32) << 8) as i16 & 0xFFF; + let d1: i16 = (C[idx] as i16) | ((C[idx + 1] as i32) << 8) as i16 & 0xFFF; debug_assert!(d1 < 2 << 12); // 7: 𝑑2 ← ⌊𝐢[1]/16βŒ‹ + 16 β‹… 𝐢[2] @@ -269,18 +269,27 @@ pub(crate) fn barrett_reduce(a: i16) -> i16 { a - (((t as i32) * q as i32) as i16) } -pub(super) fn cond_sub_q(a: i16) -> i16 { - let tmp = a - q; - tmp + ((tmp >> 15) & q) -} - +// not currently used, but I'll leave it here because it's useful for debugging if you want to output values +// that are normalized to [0,q] to compare against intermediate results from other libraries. +// pub(super) fn cond_sub_q(a: i16) -> i16 { +// let tmp = a - q; +// tmp + ((tmp >> 15) & q) +// } /// Multiplication of polynomials in Zq\[X]/(X^2-zeta) /// used for multiplication of elements in Rq in NTT domain /// /// Borrowed from: /// https://github.com/pq-crystals/kyber/blob/main/ref/ntt.c#L139 -pub(crate) fn ntt_base_mult(r: &mut [i16], off: usize, a0: i16, a1: i16, b0: i16, b1: i16, zeta: i16) { +pub(crate) fn ntt_base_mult( + r: &mut [i16], + off: usize, + a0: i16, + a1: i16, + b0: i16, + b1: i16, + zeta: i16, +) { let mut out_val0 = mul_mont(a1, b1); out_val0 = mul_mont(out_val0, zeta); out_val0 += mul_mont(a0, b0); diff --git a/crypto/mlkem_lowmemory/src/polynomial.rs b/crypto/mlkem_lowmemory/src/polynomial.rs index a8c7420..bec4ee4 100644 --- a/crypto/mlkem_lowmemory/src/polynomial.rs +++ b/crypto/mlkem_lowmemory/src/polynomial.rs @@ -1,18 +1,22 @@ //! Represents a polynomial over the ML-DSA ring. +use crate::aux_functions::{ + ZETAS, ZETAS_INV, barrett_reduce, montgomery_reduce, mul_mont, ntt_base_mult, +}; +use crate::mlkem::{N, q}; +use bouncycastle_core::traits::Secret; use core::fmt; use core::fmt::{Debug, Display, Formatter}; use core::ops::{Index, IndexMut}; -use bouncycastle_core::traits::Secret; -use crate::aux_functions::{barrett_reduce, cond_sub_q, montgomery_reduce, mul_mont, ntt_base_mult, ZETAS, ZETAS_INV}; -use crate::mlkem::{N, q}; /// 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. #[derive(Clone)] -pub struct Polynomial{ pub(crate) coeffs: [i16; N] } +pub struct Polynomial { + pub(crate) coeffs: [i16; N], +} /// Convenience function to avoid ".0" all over the place. impl Index for Polynomial { @@ -39,10 +43,10 @@ impl Polynomial { pub(crate) fn from_msg(m: [u8; 32]) -> Self { let mut w = Polynomial::new(); - for (i, b) in m.iter().enumerate().take(N/8) { + for (i, b) in m.iter().enumerate() { for j in 0..8 { let mask = -(((*b >> j) & 1) as i16); - w[8 * i + j] = mask /*as i32*/ & ((q + 1) / 2); + w[8 * i + j] = mask & ((q + 1) / 2); } } @@ -50,19 +54,19 @@ impl Polynomial { } /// Convert a Polynomial back into a message m - pub(crate) fn to_msg(mut self) -> [u8; 32] { - - const LOWER: i32 = q as i32 >> 2; // 832 + pub(crate) fn to_msg(self) -> [u8; 32] { + const LOWER: i32 = q as i32 >> 2; // 832 const UPPER: i32 = q as i32 - LOWER; // 2497 let mut msg = [0u8; 32]; // you would expect to use a full reduce() here, but since this is data coming from // out matrix math and not from an attacker, we can get away with the lighter cond_sub_q() - self.cond_sub_q(); + // Actually; further testing against the bc-test-data set of KATs shows that everything passes even with nothing + // self.cond_sub_q(); // for (i, item) in msg.iter_mut().enumerate().take(N/8) { - for i in 0 .. N/8 { + for i in 0..N / 8 { for j in 0..8 { let c_j = self[8 * i + j] as i32; let t = (((LOWER - c_j) & (c_j - UPPER)) >> 31) & 0x0000000000000001; @@ -98,18 +102,10 @@ impl Polynomial { /// Borrowed from: /// https://github.com/pq-crystals/kyber/blob/main/ref/poly.c#L290 pub(crate) fn base_mult_montgomery(&mut self, b: &Polynomial) { - for i in 0..(N/4) { + for i in 0..(N / 4) { let a1: i16 = self[4 * i]; let a2: i16 = self[4 * i + 1]; - ntt_base_mult( - &mut self.coeffs, - 4 * i, - a1, - a2, - b[4 * i], - b[4 * i + 1], - ZETAS[64 + i], - ); + ntt_base_mult(&mut self.coeffs, 4 * i, a1, a2, b[4 * i], b[4 * i + 1], ZETAS[64 + i]); let a1: i16 = self[4 * i + 2]; let a2: i16 = self[4 * i + 3]; @@ -162,12 +158,12 @@ impl Polynomial { // s.cond_sub_q(); match dv { - 4 => { // MLKEM512 and MLKEM768 - for i in 0..N/8 { + 4 => { + // MLKEM512 and MLKEM768 + for i in 0..N / 8 { // fill the temp array t for (j, item) in t.iter_mut().enumerate() { - *item = ((((self[8 * i + j] as i32) << 4) + (q as i32 /2)) - / (q as i32) + *item = ((((self[8 * i + j] as i32) << 4) + (q as i32 / 2)) / (q as i32) & 15) as u8; } @@ -177,13 +173,13 @@ impl Polynomial { out[idx + 3] = t[6] | (t[7] << 4); idx += 4; } - }, - 5 => { // MLKEM1024 - for i in 0..N/8 { + } + 5 => { + // MLKEM1024 + for i in 0..N / 8 { // fill the temp array t for (j, item) in t.iter_mut().enumerate() { - *item = (((((self[8 * i + j] as i32) << 5) + (q as i32 /2)) - / (q as i32)) + *item = (((((self[8 * i + j] as i32) << 5) + (q as i32 / 2)) / (q as i32)) & 31) as u8; } @@ -194,7 +190,7 @@ impl Polynomial { out[idx + 4] = (t[6] >> 2) | (t[7] << 3); idx += 5; } - }, + } _ => unreachable!(), }; } @@ -216,49 +212,47 @@ impl Polynomial { // if self.m_engine.poly_compressed_bytes() == 128 { match dv { - 4 => { // MLKEM512 and MLKEM768 - for i in 0..N/2 { + 4 => { + // MLKEM512 and MLKEM768 + for i in 0..N / 2 { v[2 * i] = - (((((compressed_v[idx] & 15) as i16) as i32 * (q as i32)) + 8) >> 4) - as i16; + (((((compressed_v[idx] & 15) as i16) as i32 * (q as i32)) + 8) >> 4) as i16; v[2 * i + 1] = - (((((compressed_v[idx] >> 4) as i16) as i32 * (q as i32)) + 8) >> 4) - as i16; + (((((compressed_v[idx] >> 4) as i16) as i32 * (q as i32)) + 8) >> 4) as i16; idx += 1; } - }, - 5 => { // MLKEM1024 + } + 5 => { + // MLKEM1024 let mut t = [0u8; 8]; - for i in 0..N/8 { + for i in 0..N / 8 { t[0] = compressed_v[idx]; - t[1] = - (compressed_v[idx] >> 5) | (compressed_v[idx + 1] << 3); + t[1] = (compressed_v[idx] >> 5) | (compressed_v[idx + 1] << 3); t[2] = compressed_v[idx + 1] >> 2; - t[3] = (compressed_v[idx + 1] >> 7) - | (compressed_v[idx + 2] << 1); - t[4] = (compressed_v[idx + 2] >> 4) - | (compressed_v[idx + 3] << 4); + t[3] = (compressed_v[idx + 1] >> 7) | (compressed_v[idx + 2] << 1); + t[4] = (compressed_v[idx + 2] >> 4) | (compressed_v[idx + 3] << 4); t[5] = compressed_v[idx + 3] >> 1; - t[6] = (compressed_v[idx + 3] >> 6) - | (compressed_v[idx + 4] << 2); + t[6] = (compressed_v[idx + 3] >> 6) | (compressed_v[idx + 4] << 2); t[7] = compressed_v[idx + 4] >> 3; idx += 5; for (j, item) in t.iter_mut().enumerate() { v[8 * i + j] = (((*item & 31) as i32 * (q as i32) + 16) >> 5) as i16; } } - }, + } _ => unreachable!(), } v } - pub(crate) fn cond_sub_q(&mut self) { - for i in 0..N { - self[i] = cond_sub_q(self[i]); - } - } + // not currently used, but I'll leave it here because it's useful for debugging if you want to output values + // that are normalized to [0,q] to compare against intermediate results from other libraries. + // pub(crate) fn cond_sub_q(&mut self) { + // for i in 0..N { + // self[i] = cond_sub_q(self[i]); + // } + // } /// Algorithm 9 NTT(𝑓) /// Computes the NTT representation 𝑓_hat of the given polynomial 𝑓 ∈ π‘…π‘ž. diff --git a/crypto/mlkem_lowmemory/tests/mlkem_key_tests.rs b/crypto/mlkem_lowmemory/tests/mlkem_key_tests.rs index 01e708a..c7c942b 100644 --- a/crypto/mlkem_lowmemory/tests/mlkem_key_tests.rs +++ b/crypto/mlkem_lowmemory/tests/mlkem_key_tests.rs @@ -1,17 +1,23 @@ #[cfg(test)] mod mlkem_key_tests { - use bouncycastle_core::key_material::{KeyMaterial512, KeyType}; - use bouncycastle_core::traits::{KEMPrivateKey, KEMPublicKey, KEM}; - use bouncycastle_mlkem_lowmemory::{MLKEMPrivateKeyTrait, MLKEMPublicKeyTrait, MLKEMTrait}; - use bouncycastle_mlkem_lowmemory::{MLKEM512, MLKEM768, MLKEM1024}; - use bouncycastle_mlkem_lowmemory::{MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey, MLKEM1024PrivateKey, MLKEM1024PublicKey}; - use bouncycastle_mlkem_lowmemory::{MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512_CT_LEN, MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM768_CT_LEN, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM1024_CT_LEN, MLKEM_SS_LEN}; + use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{KEM, 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, + }; + use bouncycastle_mlkem_lowmemory::{MLKEM512, MLKEM768, MLKEM1024}; + use bouncycastle_mlkem_lowmemory::{ + MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey, + MLKEM1024PrivateKey, MLKEM1024PublicKey, + }; + use bouncycastle_mlkem_lowmemory::{MLKEMPrivateKeyTrait, MLKEMPublicKeyTrait, MLKEMTrait}; #[test] fn core_framework_tests() { - use bouncycastle_core_test_framework::kem::{TestFrameworkKEMKeys}; + use bouncycastle_core_test_framework::kem::TestFrameworkKEMKeys; let tf = TestFrameworkKEMKeys::new(); tf.test_keys::(); @@ -58,10 +64,12 @@ mod mlkem_key_tests { // 3) does it reject a private key if the H(ek) is wrong? let seed = KeyMaterial512::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f - 101112131415161718191a1b1c1d1e1f - 202122232425262728292a2b2c2d2e2f - 303132333435363738393a3b3c3d3e3f").unwrap(), + &hex::decode( + "000102030405060708090a0b0c0d0e0f + 101112131415161718191a1b1c1d1e1f + 202122232425262728292a2b2c2d2e2f + 303132333435363738393a3b3c3d3e3f", + ).unwrap(), KeyType::Seed, ).unwrap(); let (_pk, mut sk) = MLKEM512::keygen_from_seed(&seed).unwrap(); @@ -73,7 +81,9 @@ mod mlkem_key_tests { // generation of KAT // let h_ek = pk.compute_hash(); // println!("H(ek) for public key: {}", hex::encode(h_ek)); - let expected_h_ek: [u8; 32] = hex::decode("82f101ff648063b376e2bb6c5b7455f655a50c2feadade150efa0e0e6f365aea").unwrap().try_into().unwrap(); + let expected_h_ek: [u8; 32] = + hex::decode("82f101ff648063b376e2bb6c5b7455f655a50c2feadade150efa0e0e6f365aea") + .unwrap().try_into().unwrap(); assert_eq!(pk.compute_hash(), expected_h_ek); assert_eq!(sk.pk_hash(), &expected_h_ek); @@ -82,10 +92,12 @@ mod mlkem_key_tests { #[test] fn encode_decode() { let seed = KeyMaterial512::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f - 101112131415161718191a1b1c1d1e1f - 202122232425262728292a2b2c2d2e2f - 303132333435363738393a3b3c3d3e3f").unwrap(), + &hex::decode( + "000102030405060708090a0b0c0d0e0f + 101112131415161718191a1b1c1d1e1f + 202122232425262728292a2b2c2d2e2f + 303132333435363738393a3b3c3d3e3f", + ).unwrap(), KeyType::Seed, ).unwrap(); @@ -106,10 +118,12 @@ mod mlkem_key_tests { #[test] fn seed() { let seed = KeyMaterial512::from_bytes_as_type( - &hex::decode("000102030405060708090a0b0c0d0e0f - 101112131415161718191a1b1c1d1e1f - 202122232425262728292a2b2c2d2e2f - 303132333435363738393a3b3c3d3e3f").unwrap(), + &hex::decode( + "000102030405060708090a0b0c0d0e0f + 101112131415161718191a1b1c1d1e1f + 202122232425262728292a2b2c2d2e2f + 303132333435363738393a3b3c3d3e3f", + ).unwrap(), KeyType::Seed, ).unwrap(); @@ -117,6 +131,16 @@ mod mlkem_key_tests { assert!(sk.seed().is_some()); assert_eq!(sk.seed().as_ref().unwrap(), &seed); + + // When you pop the seed out, its SecurityStrength will match the ML-DSA algorithm + let (_pk, sk) = MLKEM512::keygen_from_seed(&seed).unwrap(); + assert_eq!(sk.seed().unwrap().security_strength(), SecurityStrength::_128bit); + + let (_pk, sk) = MLKEM768::keygen_from_seed(&seed).unwrap(); + assert_eq!(sk.seed().unwrap().security_strength(), SecurityStrength::_192bit); + + let (_pk, sk) = MLKEM1024::keygen_from_seed(&seed).unwrap(); + assert_eq!(sk.seed().unwrap().security_strength(), SecurityStrength::_256bit); } #[test] @@ -244,4 +268,4 @@ mod mlkem_key_tests { let sk_str = format!("{:?}", sk1024); assert!(sk_str.contains("MLKEMSeedPrivateKey { alg: ML-KEM-1024, pub_key_hash:")); } -} \ No newline at end of file +} From f27f05e1c2f2f1f0011b9401dc9ec9fcb95af187 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Thu, 4 Jun 2026 23:28:18 -0500 Subject: [PATCH 02/35] wycheproof tests passing for all of mlsda and mlkem --- crypto/mldsa/tests/bc_test_data.rs | 114 ++- crypto/mldsa/tests/mldsa_tests.rs | 4 +- crypto/mldsa/tests/wycheproof.rs | 173 +--- crypto/mldsa_lowmemory/tests/bc_test_data.rs | 97 +- crypto/mldsa_lowmemory/tests/wycheproof.rs | 117 +-- crypto/mlkem/Cargo.toml | 1 + crypto/mlkem/src/aux_functions.rs | 1 + crypto/mlkem/src/mlkem_keys.rs | 2 + crypto/mlkem/tests/bc_test_data.rs | 97 +- crypto/mlkem/tests/wycheproof.rs | 892 ++++++++++++++++++ crypto/mlkem_lowmemory/Cargo.toml | 1 + crypto/mlkem_lowmemory/src/aux_functions.rs | 46 +- .../mlkem_lowmemory/src/low_memory_helpers.rs | 193 ++-- crypto/mlkem_lowmemory/src/mlkem_keys.rs | 242 +++-- crypto/mlkem_lowmemory/tests/bc_test_data.rs | 641 +++++++------ crypto/mlkem_lowmemory/tests/wycheproof.rs | 742 +++++++++++++++ 16 files changed, 2508 insertions(+), 855 deletions(-) create mode 100644 crypto/mlkem/tests/wycheproof.rs create mode 100644 crypto/mlkem_lowmemory/tests/wycheproof.rs diff --git a/crypto/mldsa/tests/bc_test_data.rs b/crypto/mldsa/tests/bc_test_data.rs index 1b305c1..77ae858 100644 --- a/crypto/mldsa/tests/bc_test_data.rs +++ b/crypto/mldsa/tests/bc_test_data.rs @@ -27,7 +27,6 @@ mod bc_test_data { use bouncycastle_sha2::SHA512; use std::fs; use std::path::Path; - use std::process::exit; use std::sync::Once; const TEST_DATA_PATH_RELATIVE: &str = "../../../bc-test-data/pqc/crypto/mldsa"; @@ -35,31 +34,44 @@ mod bc_test_data { static TEST_DATA_CHECK: Once = Once::new(); - fn test_for_presence_of_test_data() { - TEST_DATA_CHECK.call_once(|| { - if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - println!("bc-test-data found at: {:?}", TEST_DATA_PATH_RELATIVE); - } else if !Path::new(TEST_DATA_PATH).exists() { - println!("bc-test-data found at: {:?}", TEST_DATA_PATH); - } else { - println!("bc-test-data directory not found"); - exit(0); - } + fn get_test_data(filename: &str) -> Result { + let found: u8; + if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + found = 1; + } else if Path::new(TEST_DATA_PATH).exists() { + found = 2; + } else { + found = 3; + }; + + // just print once + TEST_DATA_CHECK.call_once(|| match found { + 1 => println!("wycheproof found at: {:?}", TEST_DATA_PATH_RELATIVE), + 2 => println!("wycheproof found at: {:?}", TEST_DATA_PATH), + _ => println!("WARNING: wycheproof directory not found; tests will be skipped"), }); - } - #[test] - #[allow(non_snake_case)] - fn ML_DSA_keyGen() { - test_for_presence_of_test_data(); + if !found == 3 { + return Err(()); + } let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/ML-DSA-keyGen.txt").unwrap() + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/" + filename).unwrap() } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-keyGen.txt").unwrap() + fs::read_to_string(TEST_DATA_PATH.to_string() + "/" + filename).unwrap() } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + return Err(()); + }; + + Ok(contents) + } + + #[test] + #[allow(non_snake_case)] + fn ML_DSA_keyGen() { + let contents = match get_test_data("ML-DSA-keyGen.txt") { + Ok(contents) => contents, + Err(()) => return, }; let test_cases = KeyGenTestCase::parse(contents); @@ -191,12 +203,9 @@ mod bc_test_data { #[test] #[allow(non_snake_case)] fn ML_DSA_sigGen() { - test_for_presence_of_test_data(); - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/ML-DSA-sigGen.txt").unwrap() - } else { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-sigGen.txt").unwrap() + let contents = match get_test_data("ML-DSA-sigGen.txt") { + Ok(contents) => contents, + Err(()) => return, }; let test_cases = SigGenTestCase::parse(contents); @@ -370,12 +379,9 @@ mod bc_test_data { #[allow(unused)] #[allow(non_snake_case)] fn ML_DSA_sigVer() { - test_for_presence_of_test_data(); - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/ML-DSA-sigVer.txt").unwrap() - } else { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-sigVer.txt").unwrap() + let contents = match get_test_data("ML-DSA-sigVer.txt") { + Ok(contents) => contents, + Err(()) => return, }; let test_cases = SigVerTestCase::parse(contents); @@ -550,13 +556,10 @@ mod bc_test_data { #[allow(unused)] #[allow(non_snake_case)] fn ML_DSA_rsp() { - test_for_presence_of_test_data(); - // MLDsa44 - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa44.rsp").unwrap() - } else { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa44.rsp").unwrap() + let contents = match get_test_data("mldsa44.rsp") { + Ok(contents) => contents, + Err(()) => return, }; let test_cases = MldsaRspTestCase::::parse(contents); @@ -565,10 +568,9 @@ mod bc_test_data { } // MLDsa65 - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa65.rsp").unwrap() - } else { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa65.rsp").unwrap() + let contents = match get_test_data("mldsa65.rsp") { + Ok(contents) => contents, + Err(()) => return, }; let test_cases = MldsaRspTestCase::::parse(contents); @@ -577,10 +579,9 @@ mod bc_test_data { } // MLDsa87 - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa87.rsp").unwrap() - } else { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa87.rsp").unwrap() + let contents = match get_test_data("mldsa87.rsp") { + Ok(contents) => contents, + Err(()) => return, }; let test_cases = MldsaRspTestCase::::parse(contents); @@ -589,10 +590,9 @@ mod bc_test_data { } // MLDsa44sha512 - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa44sha512.rsp").unwrap() - } else { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa44sha512.rsp").unwrap() + let contents = match get_test_data("mldsa44sha512.rsp") { + Ok(contents) => contents, + Err(()) => return, }; let test_cases = MldsaRspTestCase::::parse(contents); @@ -601,10 +601,9 @@ mod bc_test_data { } // MLDsa65sha512 - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa65sha512.rsp").unwrap() - } else { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa65sha512.rsp").unwrap() + let contents = match get_test_data("mldsa65sha512.rsp") { + Ok(contents) => contents, + Err(()) => return, }; let test_cases = MldsaRspTestCase::::parse(contents); @@ -613,10 +612,9 @@ mod bc_test_data { } // MLDsa87sha512 - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa87sha512.rsp").unwrap() - } else { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa87sha512.rsp").unwrap() + let contents = match get_test_data("mldsa87sha512.rsp") { + Ok(contents) => contents, + Err(()) => return, }; let test_cases = MldsaRspTestCase::::parse(contents); diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index ef015eb..b5a1b5f 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -486,7 +486,7 @@ mod mldsa_tests { #[test] fn test_sign_mu_deterministic_from_seed() { - // I don't have a KAT, so I'll test against the regular implementation + // I don't have a KAT, so I'll test dynamically against the regular implementation // ML-DSA-44 @@ -703,7 +703,7 @@ mod mldsa_tests { // test to pin MLDSA65_GAMMA1_MINUS_BETA (produces the wrong sig val if MLDSA65_GAMMA1_MINUS_BETA is wrong) let sk = MLDSA65PrivateKey::from_bytes(&hex::decode("FAC3F0A1C095D7F14FE867233429E7FBECC3E56C1C47C5776F60FCC952271C16D701337CC4DB701037240B2CC96700E76D912695D01ECFD144A1A4480C9A7AFC6AEBFF85B31A51CEDCF03C089DB78D57D5736998094CED527BF62306D163DD076746390725A575F658812850340A6A22F21D1A4488F81CC9C51E34C7195E24E62743418082738460173216564147714141447322747378303064332088852786387656485685067602315841385530303073748288551114488671126472324211001632768375217888105730287103413366631348063456705643028124418424362865147136784028750605280522354664402012156867463840124312702746274317185820204146418146260428165381878680661671606625835681411564078617545272781118153404548772767065605505661113346550212253001708208871437608544687461742554212252531618480788375428626465682802665176276452816778253827888713776811675325120157277407836344676441366411518042352072878407260148473360521722835364503831153320866470060388717774746280482741018426182467855676081768764502460378420511274573338543580057135466786576641657141853703461273673864078756846882878275266371317370221447843022470612874036187668341702635480511522823511802187750128755208466178350262551186287764073638143070751153170178714610652685824431071335605871487821771381233638082075233653821363404721684841385534088177538652001225820118825132746376784420308558673853184746535462708561415062307838047410305386755301311878544252655175427735642725125852462520300512237334101417850348822685720422134232331416163464745560268223741265100778127023844048536253761851061180163560244303674215200615686636064370316373872671703546376007034507065834571177568604078030182100382824024244355146222000521887855141800051638076135301876078282783854476883578855568168226642036114282711266780675104628153263815046834464683021517741886573830273710441308840745348777606127672844722386630210200025302263135713023513144334831232011681183146135820156511536562848603567353564216431366041187872531665824337234157375170402400366431125740874338582777401587182726007834342527487856427375026483665335500311348218318404831768517434724867376286526385537375022315084547851162348038628580233345112714217535531172243054028425076334167574124642505645643703357747258152772101731762154367415443402180013310507803185561248014235117864462560741807827475862248680132084577068144204516541344387456176453454137741684473663420305220270013380651567340128517050186661163071768085126283043061688144133578020758648635074223303518423535803274805136544782015667213011858810228662670086040427500742786645441425845543080676585818187373073177826648377838253361800836061614077137087700227656343025125400007421502577613446081061602370815457845047316447020371536762643681172462742533741043757221578526022521413633247403254078850774373623685012472806652753134016260818410320635707047651107738122602141422051241873175027656625672307441218576154467471554748806141478270237022035541450228312172183426588573284554185388468111581188733140148403753651886533044825265188582527046117408104630522280766840883618814145722050535737005141474643706767817456415644868337004352380220517858504287578807218203860374826463502187671017880147716606130536501355309DEDE7F9572F3915A1C1B014B2FB3D7591DB0BED20443D420E8D4D3458381AF3506F93C6CB3561449E50A9E3349DC9B8CAD8AB1ECBF946CF6ECBBBC11A8B32B295D1E3CB23DF662E8A8625EE91ACCC74EE9AB059FB12A46AAEC343FF1B21BED35E135F46D05EE11877126A9961C897BFEA0D552E03BCB313AD1BEF5B21FFF09005417B0F531120D037D1106C4FFF3692C607108563B272D054D98299D1FEC0646536152FD6F915FBBD8ED1E864904149F919DA295CB79F4EE6A9123E82FC5DF0BC379743B0A650D8D26799B02C0A6F278B323BB7F58811E75CDDD6BA27200DD7BD5B94F6AF7AC76B791F12A1C464E2E112A591A12E3EE569C7B0BBF726C634737A7388D676BF36DA496E3A6DD4744D83D5379642261B61D6A08A164BE30360BEC7E05473A66CCDE29DE5F68006932739DD876E29954419750B6C2E282432E0E900F30C6DEFA5042216C398189F1072F3738DEA06AB44F7611BD995430B2204FCA4474A2A8DFAFC44607F9AABB80D5FF235BA93A74BCE8297D45A35322116117A5BD9C960A18758040EC670D8F0B5F75515DFAD91A60FE8F26A5048F9A955210B6CFFC7F6C963531E9B6EB0DAC6D744E63A22AC9E2935FCBD8FFDC6D1D5621266A338FFD3E28257C5F04B209DD1013C2EA6FBE2D197BBBC9B8F1A6DB84E361F1E1D6235566A289861E193CF51604CE29DB66C546C20CD3D769E128BE0C10C7E8EA640F23778F426DE99A675726CB837EBF6F2EDFD886E7522FEA3E9AE793E34B702493C6575828021B8A4E1DAB64DE98DB52126120B8B192AA8977143E11E7819A2C116598CA301670C51569432DC1C86270F72E474CC35B4A7621F0EF883BD9B187DF4EBF2BECB7487BE331A45E9CB43E5DB9225B21724D60CFFBE5A3817765B5DB2E111AE6445BFEDD6631A1FE8048E2008E5DF450E0FE622319217567AB202BE5F6E9D8859CADEB2210784D9FB1778BA039A98A2C47435EBA76EF170B9ACB2D3884D64831E910D2CE1E8346881490FEABFC5882C7CF9D6101916E4021B9CB723189345049B5048400307E0236F177329EE6CF9A67848A4D7C98349A94A68400EA141BDA866F5071F3F4A7B8B96372C5442B43A003962DA6A5F07B9A572E7EB8A057BE06C7AF19D4EA728B7BF7F0AA493C413190CEE7D9AA90033378A2FEAFB9F1F360A904BFFA62FE1161240FCCAD33473A93FDAD26DAADBBA4CD05BD1F5897C40FD60BBEA8ED0404347131B2251D4CE06A65BB5EC31BFF5D03CFEAFFD7D06D09CFE8D88706A0AED09DFBCD273C65E1797D65136325A78E56CC4836B39EBD5A964229D2E9A163FE0E12BF0355D6265D01B7F94874E579119AA7E8C7EA69C09A2FA46069FA64CF0CD2ADDE12ADCE6B422676C0F05951B5C85404E25455505730986415B693604F557E09A6C6F48F5C193BF7DDC0ADFFC91CCBE0D3F7577F9AF85BC039CF2787ACE3010A413E3A0613C323B00C5A42776789382EF804BA918646A27ADFF1C3BDB8D86D4D1237ADF4190FDA2D87510ED68CF9234A766EFAFF779EF9FC4F8337E147FB5C83227E97F67D497F675DA66D55A88E9611459D98473DBDDC0730B2F8C6FA606A22E26679E68BCA03A50DF3731992A97063A4A6AFFF8C268290422A9D094B85BD6851177BE2BC04FFC64CD96E866731B552DD3C779D49C7781BEB8DA3F85B942A9780E47675B11E5DD77ED1F343775E876A737CFA3CBDF5CBBD7614A3EABE4DB919BC3E13ADE651F45133A04DC5DE0DDBC0BE01DDD615BFE6666769CC663F4941131A80DE835C851EBC67DFA48024C07FFB50F5C7E0A5686D1AA9EA22E45F6DE2A07DFEF28E90B00C24074897A76FFBAAEDE6BBB9A0DC76D35E2163169C426B1F5221D0A33F06C25072A2B164521736C441D4547EBE8AC4C408D30EA34ABF595BCC4CDF6157E86D8F8C5950E9EF27290EA077D812941A1DBBB4C6D06D52583DF55547F657346373CA37BFE9CDD6010CB5B0033F643EE0D6C9052699FC8E2739B69E1896BA14C7BDAAA7305F5F424350979F60868CB7E9884C818444C0FC0481216D06AF22076B726DB17A496CD45E9BA344C9F49AAE12E2771DDC9AD40908A00F8A19B85F120DCB1BE238E77E15ABB6E24E4E8BC55A68DFA7D473ACF7519EB85F714C0F49908BF2A6F70AAFF77A3DF2500992E304E98F6641505B1A5DF02C66118492354EA720A7078827A1B769B308440864FDE478F312DD497F7C507AEEB9AE85933607F20E4FD1906FB6FD074B8935D0038A2793489136E8305131FB95296A42CCB66F4F90F7214642929DC6E6843CE5B83E58E3F22CA8A5586A1D93527D75691B1E485953FE1D3E56E1C3071965B8E8EBBFE0F3FAB5F24FC245D2AFC423AD67DBD22E69ED2619E42CA0AF8F30C06D969CD7C69AD0783E17D018DEF90E75198EFADE2AE031FAC1905D5911D198635A1972D9CD8C1E99F049FBD96E6F88A25773B9F6048B6C136B4110DA4F587EA5C27700BBB6800854C9A4D1375F9777E09B1E53AFAF73EECC5E7711F7B19E7026408DAA10E93B8D916ACB0A4AD57848C2B9F767C48709382A2CC57B9DF56264B8EA91A529B4ACC7BE8DD3F3DF30276CD459CAF32F65118B6914E22507C2D5E032038BC272442FF16A80224EE35A4CA90B2564EA78B3856858D985644B683F62FF5AFC04F4F6FAED8434B6CF4EAF116FE231FB7931968D7BBA18BA347628304EB13EC8BF83FAE853E208FD276EA60B2DA751B058D82EDEEB4099BE90A581B52415B3E043B39D570C6E3996F42733A1459A5E3667009FF1124C106D2ACA45107D242ED4FF486F1991A01B5E471D6D360C19D3D064B578147A82DEA65FE0F8B7897B7347038522A2EBEB7ED217E43C33D5B7ADDD9942B43F9D5B8D87416C425F3E6CA3BCD3D995051C02EA4EE84BCB3B877F27FB16993522A91539D3FF43925671FB256573017302DDCA5A562FFCEC07A1204560252C78C2D797DD4EBF300922D2891AF7D220D32C3AF68F20CC096F6D5F9038E5AE7BAD090F99F9AE7FF3A615EC4A7F1F759A52428F94DBD496A81B0B71278EE4127AA72599C97618BF1D053C471E85D888C2971D816E5147BB07AB2192FA473641495CE64596D543D37DF340181EC609E8EA8F72676C2DCDA357C19051A8642D99FA00F962D28B4830E81C8D0CD2DF26ABE514803C8192608E29B9ACB3B150A664C474C6E064F1BCAAD0A08344E911FBF8425A0893D71325012113B8F2FD74FC0E2966AAFD72F280D18D8D02F779B0B2C61DD5E4385B5B82242AC327C3DDB6AB8F288E8DDA24F1E5812A111860058DEA190208928F1AD208974AA2EDD53BAFF400888AA9D74C0071C65C30AFE4A02B99876621A0542BBCE337ECFC06F0A236C3E9C672B48E61EA70CDF82421C14B7F0D25474ED2B04DA1C570598B14CFF438BBC44AB3C931F47D65DD0E767DCD7DDA522CC4CB07EB78962A50D281BEC69B54A77D26C902D3759560DAB544404BE0AA3F80D3B84EC4731DFFD1579533004D2D1EBA09148AA84C93B6A2049AD019A0BDAB9F99389FA4").unwrap()).unwrap(); - // because this is from the bc-test-data with busted mu values, need to fix the (busted) mu + // because this is from the bc-test-data with busted mu values, need to pin the (busted) mu let mu = hex::decode("dd6438e69fdbe810789fb22940fb451c28ac570c8e195f4aebc22db848cabe970a76c92011702d672478fc09f2bdc10b0b7c2b1752a9d99ea7ac3a6381684540").unwrap(); let expected_sig = hex::decode( "C3263802CBF2818D55BAAE9C2BE8C1EE0123802BDAB7C87CF9E6CF06E2A8823E750FFC8FB917F9052275A2CC68C2ADF4CFC78D0144FD3E96694C116CE1672BBCA6A3E1F6BBE8A0C34E5C1874DE1798CFC7766FE1BA906A1C6EF40EAB1A914705C3B0F14ED8D212B81D400281F68644B2EF72330DA2BD16C792D8160896177E920EB8E198AB5A38F5FFC5088663D0D0E0EB30C342F41B886FF209C8939CA424C2C24380E4F5AD805242CD25F4AC445AB883CD5826FA4135F4D549B12662C687B0FAFF068F8C18077B463E910E7A17B3BA9A76418C79FC53CADC75E48E977EAEA1FE748EDF0A1C0CDF9D037812B1BE505BD39E4EDA489CD56369A1B3F793554D7DD815AB1A7DE536018F0E0D73B95C8EBA07626D92B0365F6F71FAEA07E1E80FEEA666EB57865D6D13E46B801FECE841A2179BA8DA1FC1C1AA53743F187F426DFA34510E11AF1D9570FAC5DF77C9BF7ACE64BB130DB2192959091DDFEED580C722ACE89F84729540EEE8CFF6D8E4AF91C1B0EBB42A65F47B80F4A300431D7DEC90BE739E31C9906D7403B82C196266D4FCC6659C5AA549D0ADC3AAAD1F9DB7C80E17839957DF682C10B1938DD9A292C65E0146566B1EE24529C925691BAC109CB308C4303C3513FF5B62275E84C8C94DD6CFBF2E0F9F0B735FEFC44095FA2E01009C37C33BBDF082B305AD479C743076EC2EFAC0176F15A0EFBFA504894A3F8C6B7ACB096182B71131F46B1EC7343C9DE78F85CC0352F97C188667898ACB4EB4173D715FEA3B69DCB1B47904C8AB51EB4915357995B5F69F979776C6CBF114BCB0A154D82D17A9251ED545FDBE7E04AB06B99662D18673F08D933CA0B84FBEBE17F783D4039078465C378E9AEF18423C7B86C411DF9B4A77A2BC8804C4E5A5F9BC5777166C221F60107D2107AB55BB53E354267F8329B71A77BFF920EA94D9240B436E8CBA65940C0734D6928F57257A16154BFA5D8C9459979DEF567EE0965239C0DBDF122723636F2C5C5CAC6C607BDB9FDE8B8557A687E3A68970BFFAD695BADA74CE3C681B81AC8CE3747B844B7530A606F6320ABE91F16E79EFFBCB8446319D888D957252836BE6D88C018EED2F5C46369FE88CAEE624905E71C41252B70F115F781E0D4D851D2820718DA03FA1E9F9490AA06D74E3D15A44CFC98E57AB49A63285451C2A43D5ACA6733952814C05391431052DD9FE5D4AFAFE7027E5E5C8E9247FD5B574B438C4E541D6F408964BFEACC54FF873C6976F4383AE2517D4860E3D775280DE63D2EB01709282170E33A2F5FBD80937B0D23188C978BA370B5BB154FE0F2FA0361A2F1E46FBAFE6494FEAFF26097EBD2E307301C8649FAF82B57788B62E6C69DE6F6B3ECCDF17C10CBA41C3D1A8025F5BE98D65F2B37AC2527BA60E99B89FDA9028845F6EE6E20C2C8E2E8D8627C4D5859CD141920133D4D4B3AC8B91C1B14B6F6B8DE35E9674F312B938DADB90C335931081DB90640883512125BD793467C78AB84208146EAE0D62004E51031377BBDE89AA0AF0609E9AE315EA8A043C368167073422018900F9C653535A96FF73DA466377CF462314F361E08ABA6CACAB9AA666633FC6602D645CEEF305829B90AFC8BBDA9F8E40EFFD62FC31A1492914E22DE711DC3102D64AF46D13ED766CF36E51629409F69EC4DF98CA97EC93DA4BA215C567C73104FDF6863229E2A2C9A3CA11DD3C8D505CF88944428F9D500029B770FDEB16388F63C50FCAD1953E2AC759730E7CAA930362274B6A3E0ABE1211613F3C36FEFD09454401B6110D95201BD022A3B6C68DD054AE0B0C547AFF5C7421EBF0879098112286C962A6164C799A7A20CCF3D4189B2AA240F895C54CB2F9E6C2C1E2BA9BEB185CCBD82CEC60A49D8CBBD3C26AC0BC5C43E2D5672E21F19ACFF857217A243D4C90600D3737EE78638AD6239C3AF4E79FCE030DDB0527EE428ADA0C1C9EB867593092CD1A5B77347FEF4541B71AB33932F8F1BE31AA475B9F4BBC07F218377E436E348F47607D9140E6114A05D2A072409C390EBC79231CB6DC2E3A698776F9C32B44C10F156AF450A8DAD6DEC0D294392DF6E17DDD312BB22997CF1D8A6EC55DFE401EBA486CF9C8C0E6E713D48C0CA8CD26EAF88DC0B34F86F35B6617C45173131779ED98E880BBA3520835F29604B6F32AB1DA8034F6F559E81050BBCD7833B0D71E90101339386AE3BC653CA50F37BBBEB64377987BD892CA51280499B6EB051345EF9BF1F6EE766142491F96E27787ED8991338F60D2A4BAABF43ACE7A6072C6BEB6D0FFF79A3B956DD5CF6348F6E02104E1F9F33B2AEDFFA6B833A00CB21BAD6AD04047DD845180131506080272925960DA888E393B43EA5770530347EAF9E9812E87A317D1BA66C61CACD55A8FFD6355BB39E596D33F4E8355A6B51C4E70697D4C5BE9FFE64FB11EA714D81B20677DBA268E8A9A5F61E8E268DDE6F6FC93F3FAD688312885681E0F67474689680532DB88E178AAF2FDD19E37B091020FF01008BF0FA8017075C2F02B5E7DCACFD1F337A24815C6D9A9E1678EE2EE1721A61ED774A7DE98051F799D4F3A5407C1F725B618AD3B0C17FD13D762005C143A9AD78B13DF4B376D924BD9AE37502446D2263046BC7CE97CB6371CBA72FE0224FE71D501884E9AA820F92E1CCC3F7F4D57214F0842C96B85BFEFC3767B81AD76F778B00EB2BAE3C99D75EAF295901B9DB273F4288FA4EA74650B90D114B081B1D061D97EF211A84726756396F36BCACC45A8EF3F57E2331EE4FBCEED55C48605B4A3D842067933F8AD6D72C1793B17515977EEB8404CB55388EA3CB43F504A5325870473C14287DE22D55015391D973A9556B93B0943C64F5CE8DCD365C1F1DB7B56B2FC91BF5552E5233C28755E366BCF303B33CE2FE608A5BF6348EAB272E4FC6B77F13367E12085E008C8CE17095727D83266CC321ACCC9C8B58F2D149C205A4DC6D3A599CBA0E3FC4EE042C3461445A536FB9AC09D2FEA87C7E0E8BE39B2D0894EA1E203CAECC5F79F13ACAA917401E75E8F36CBA4D34EB639725FE41E64912F48A70D1BA47309D23E40DBCFF986568B8536B5F464C5B169D5E0F90ADE7F46004098E5880340DB738A7B9C849A71D01B8F8F9FEA8B3B331210553C469A5EE257B4EB02EC8932787C54086C5DE6A82096F0AB32B716BBCBCAF26BAAA792931D946FA1D59EE8A9924484E38B02F89D12971903C675B9FE420DACB1C0172351A3A44376E6AF6F25B261173E98B04DD2E49BD795A17E9C3E1D7DD1EECAEC4B8A5AE4B266E4DC0618E41AB8F82A4F652D2A07814DDEBA856B6000EDAA8A8A0542466A83243AD8128B1C468215EB58CC436BBE6B7A8D8CB12FA78131E1887B3253BD623055D72B13E175AB7DB00E8D7680360990C1D9EAD06F04F51F739734DEDCB8AA5ED3BCFEFB49D5C89ED291C4AF1EC09BDFD24029B7A219067DAF493C53809BAAAF830C8D4A3D643AA1B0CFABE58D0B1A5E9C0357AC421459FF0B0766CF82E08F95BF8B6C65D81BFC342A07BA51C914D7F1FFFAE1E069BEA449EC9BCF898BC29CAF4FDDDE6846361A4393F6C6987C766037526A9FE42008FB59DEE597547B8F61C62085BCA0686B6237C384107ADF094C91A230D783F55D39F1E57128A92F0FF0CD92D6B454DF4A3AFA2E1E4DF2C6C9E3CA3B304E27A8D7F44C75EEF09F475ECE74B07A80DBE1B4F2F309756BCFEC0C2D78DEA4CDF3BC864FADBEAA1B9475BC7EA4ED80D26B14B61EDE93AA2A777C8403DAA7BEC36C5503A2114A97CCA8B04565F17A4F0DFAA4985A5FCBB4398323233ABCC728CB2E0D1E2CCD36645E5915985D39ADAE871AA5B1CF3B001FDC7D9F0BD82FCE569A5C6BBD216E09C11CC6AAE067686E95BB28C441F189AFA83A9E117D944927C4A76FED756A35E5EE0B0DF4159187CFFE5F6EC19F8DAB04791E04FA0A3DF55EC95B96D7AEE099867987986D3FF1422DFDA40100C226F8A24468393F21E11D3B583DD8A5FEED01832ACD1E24E2D5A8CE16C24629B9964F8B4E0B2B0F30601FA688711065190665ACEA6A971DF8FB708C056D68540A78FA750B43E0D54086502D4B19171F61B51AC075F7F09260B95F59BF343E1210BCD39C04B13267122B6CD78D2578EA3534EDF243D0FD520A212EBA25407E2B8119068B2CD1D1BA672E5CC73BE9676C9E2D906D44DEBA9573E7FC909E69BD9CCAEBE6600E30D3588CB8EE2E5CE95C65F3FD24E4D527CE764FEF539639155086FE564B607A1082A74CDE886527F8D821E42AE88281A1CFF38EB5159955C1326C2D425D1C09205128E0386CA5C71B911896543799C3B1ADE8391E4370519DB17122A1A5EB6338208984E7347FC174D6DC4D59AF64767115A344A11E2049CDA0AE0BE6D7AE0592FC3468DB58E29CD3010E3EB17F736EE0E6062BF1F345A804F1733C73FE30C70B6FB625D00E529A400C069074235969485142D37F3D963D8492EA4026867ADE6108D982E20B188504A6724E6448992A3F7962AC2F01222BBE0715069FDE7D9DD93A6AC8AD321163FDBF9429EB2DD38F44BCA086AEA91642D4C85DFCF322F0B822B6EF73C570B564DD721FC3E73ED50A7FC88644C4340951C20B1E2C4A65B0BEC3C5E10E4C5F769CABD7E571748485A3BFD0D2204748DAE3E5187A7D97A7000000000000000000000000000000030D151D2328", diff --git a/crypto/mldsa/tests/wycheproof.rs b/crypto/mldsa/tests/wycheproof.rs index 5a00b99..bc7fd34 100644 --- a/crypto/mldsa/tests/wycheproof.rs +++ b/crypto/mldsa/tests/wycheproof.rs @@ -42,7 +42,7 @@ mod wycheproof { static TEST_DATA_CHECK: Once = Once::new(); - fn test_for_presence_of_test_data() -> Result<(), ()> { + fn get_test_data(filename: &str) -> Result { let found: u8; if Path::new(TEST_DATA_PATH_RELATIVE).exists() { found = 1; @@ -59,28 +59,27 @@ mod wycheproof { _ => println!("WARNING: wycheproof directory not found; tests will be skipped"), }); - if found == 1 || found == 2 { Ok(()) } else { Err(()) } - } - - #[test] - fn mldsa_44_sign_noseed_test() { - if test_for_presence_of_test_data().is_err() { - return; + if !found == 3 { + return Err(()); } let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string( - TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_44_sign_noseed_test.json", - ) - .unwrap() + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/" + filename).unwrap() } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_44_sign_noseed_test.json") - .unwrap() + fs::read_to_string(TEST_DATA_PATH.to_string() + "/" + filename).unwrap() } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + return Err(()); }; + Ok(contents) + } + + #[test] + fn mldsa_44_sign_noseed_test() { + let contents = match get_test_data("mldsa_44_sign_noseed_test.json") { + Ok(contents) => contents, + Err(_) => return, + }; let test_cases = MLDSASignNoSeedTestCase::parse(contents, ParameterSet::Mldsa44); let num_test_cases = test_cases.len(); @@ -93,23 +92,10 @@ mod wycheproof { #[test] fn mldsa_44_sign_seed_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string( - TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_44_sign_seed_test.json", - ) - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_44_sign_seed_test.json") - .unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_44_sign_seed_test.json") { + Ok(contents) => contents, + Err(_) => return, }; - let test_cases = MLDSASignSeedTestCase::parse(contents, ParameterSet::Mldsa44); let num_test_cases = test_cases.len(); @@ -122,20 +108,10 @@ mod wycheproof { #[test] fn mldsa_44_verify_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_44_verify_test.json") - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_44_verify_test.json").unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_44_verify_test.json") { + Ok(contents) => contents, + Err(_) => return, }; - let test_cases = MLDSAVerifyTestCase::parse(contents, ParameterSet::Mldsa44); let num_test_cases = test_cases.len(); @@ -148,23 +124,10 @@ mod wycheproof { #[test] fn mldsa_65_sign_noseed_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string( - TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_65_sign_noseed_test.json", - ) - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_65_sign_noseed_test.json") - .unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_65_sign_noseed_test.json") { + Ok(contents) => contents, + Err(_) => return, }; - let test_cases = MLDSASignNoSeedTestCase::parse(contents, ParameterSet::Mldsa65); let num_test_cases = test_cases.len(); @@ -177,23 +140,10 @@ mod wycheproof { #[test] fn mldsa_65_sign_seed_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string( - TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_65_sign_seed_test.json", - ) - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_65_sign_seed_test.json") - .unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_65_sign_seed_test.json") { + Ok(contents) => contents, + Err(_) => return, }; - let test_cases = MLDSASignSeedTestCase::parse(contents, ParameterSet::Mldsa65); let num_test_cases = test_cases.len(); @@ -206,20 +156,10 @@ mod wycheproof { #[test] fn mldsa_65_verify_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_65_verify_test.json") - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_65_verify_test.json").unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_65_verify_test.json") { + Ok(contents) => contents, + Err(_) => return, }; - let test_cases = MLDSAVerifyTestCase::parse(contents, ParameterSet::Mldsa65); let num_test_cases = test_cases.len(); @@ -232,21 +172,9 @@ mod wycheproof { #[test] fn mldsa_87_sign_noseed_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string( - TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_87_sign_noseed_test.json", - ) - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_87_sign_noseed_test.json") - .unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_87_sign_noseed_test.json") { + Ok(contents) => contents, + Err(_) => return, }; let test_cases = MLDSASignNoSeedTestCase::parse(contents, ParameterSet::Mldsa87); @@ -261,23 +189,10 @@ mod wycheproof { #[test] fn mldsa_87_sign_seed_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string( - TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_87_sign_seed_test.json", - ) - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_87_sign_seed_test.json") - .unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_87_sign_seed_test.json") { + Ok(contents) => contents, + Err(_) => return, }; - let test_cases = MLDSASignSeedTestCase::parse(contents, ParameterSet::Mldsa87); let num_test_cases = test_cases.len(); @@ -290,20 +205,10 @@ mod wycheproof { #[test] fn mldsa_87_verify_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_87_verify_test.json") - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_87_verify_test.json").unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_87_verify_test.json") { + Ok(contents) => contents, + Err(_) => return, }; - let test_cases = MLDSAVerifyTestCase::parse(contents, ParameterSet::Mldsa87); let num_test_cases = test_cases.len(); diff --git a/crypto/mldsa_lowmemory/tests/bc_test_data.rs b/crypto/mldsa_lowmemory/tests/bc_test_data.rs index 9d98971..706c82a 100644 --- a/crypto/mldsa_lowmemory/tests/bc_test_data.rs +++ b/crypto/mldsa_lowmemory/tests/bc_test_data.rs @@ -39,26 +39,46 @@ mod bc_test_data { static TEST_DATA_CHECK: Once = Once::new(); - fn test_for_presence_of_test_data() { - TEST_DATA_CHECK.call_once(|| { - if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - println!("bc-test-data found at: {:?}", TEST_DATA_PATH_RELATIVE); - } else if !Path::new(TEST_DATA_PATH).exists() { - println!("bc-test-data found at: {:?}", TEST_DATA_PATH); - } else { - println!("bc-test-data directory not found"); - exit(0); - } + fn get_test_data(filename: &str) -> Result { + let found: u8; + if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + found = 1; + } else if Path::new(TEST_DATA_PATH).exists() { + found = 2; + } else { + found = 3; + }; + + // just print once + TEST_DATA_CHECK.call_once(|| match found { + 1 => println!("wycheproof found at: {:?}", TEST_DATA_PATH_RELATIVE), + 2 => println!("wycheproof found at: {:?}", TEST_DATA_PATH), + _ => println!("WARNING: wycheproof directory not found; tests will be skipped"), }); + + if !found == 3 { + return Err(()); + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/" + filename).unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/" + filename).unwrap() + } else { + return Err(()); + }; + + Ok(contents) } #[test] #[allow(non_snake_case)] fn ML_DSA_keyGen() { - test_for_presence_of_test_data(); + let contents = match get_test_data("ML-DSA-keyGen.txt") { + Ok(contents) => contents, + Err(()) => return, + }; - let contents = - fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-keyGen.txt").unwrap(); let test_cases = KeyGenTestCase::parse(contents); for test_case in test_cases { @@ -190,10 +210,10 @@ mod bc_test_data { // #[test] #[allow(non_snake_case)] fn ML_DSA_sigGen() { - test_for_presence_of_test_data(); - - let contents = - fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-sigGen.txt").unwrap(); + let contents = match get_test_data("ML-DSA-sigGen.txt") { + Ok(contents) => contents, + Err(()) => return, + }; let test_cases = SigGenTestCase::parse(contents); let num_tests = test_cases.len(); @@ -364,10 +384,10 @@ mod bc_test_data { // #[test] #[allow(non_snake_case)] fn ML_DSA_sigVer() { - test_for_presence_of_test_data(); - - let contents = - fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-DSA-sigVer.txt").unwrap(); + let contents = match get_test_data("ML-DSA-sigVer.txt") { + Ok(contents) => contents, + Err(()) => return, + }; let test_cases = SigVerTestCase::parse(contents); for test_case in test_cases { @@ -536,48 +556,61 @@ mod bc_test_data { // #[test] #[allow(non_snake_case)] fn ML_DSA_rsp() { - test_for_presence_of_test_data(); - // MLDsa44 - let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa44.rsp").unwrap(); + let contents = match get_test_data("mldsa44.rsp") { + Ok(contents) => contents, + Err(()) => return, + }; let test_cases = MldsaRspTestCase::::parse(contents); for test_case in test_cases { test_case.run("MLDsa44"); } // MLDsa65 - let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa65.rsp").unwrap(); + let contents = match get_test_data("mldsa65.rsp") { + Ok(contents) => contents, + Err(()) => return, + }; let test_cases = MldsaRspTestCase::::parse(contents); for test_case in test_cases { test_case.run("MLDsa65"); } // MLDsa87 - let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa87.rsp").unwrap(); + let contents = match get_test_data("mldsa87.rsp") { + Ok(contents) => contents, + Err(()) => return, + }; let test_cases = MldsaRspTestCase::::parse(contents); for test_case in test_cases { test_case.run("MLDsa87"); } // MLDsa44 - let contents = - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa44sha512.rsp").unwrap(); + let contents = match get_test_data("mldsa44sha512.rsp") { + Ok(contents) => contents, + Err(()) => return, + }; let test_cases = MldsaRspTestCase::::parse(contents); for test_case in test_cases { test_case.run("MLDsa44"); } // MLDsa65 - let contents = - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa65sha512.rsp").unwrap(); + let contents = match get_test_data("mldsa65sha512.rsp") { + Ok(contents) => contents, + Err(()) => return, + }; let test_cases = MldsaRspTestCase::::parse(contents); for test_case in test_cases { test_case.run("MlDsa65"); } // MLDsa87 - let contents = - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa87sha512.rsp").unwrap(); + let contents = match get_test_data("mldsa87sha512.rsp") { + Ok(contents) => contents, + Err(()) => return, + }; let test_cases = MldsaRspTestCase::::parse(contents); for test_case in test_cases { test_case.run("MlDsa87"); diff --git a/crypto/mldsa_lowmemory/tests/wycheproof.rs b/crypto/mldsa_lowmemory/tests/wycheproof.rs index ff9839c..ca9df24 100644 --- a/crypto/mldsa_lowmemory/tests/wycheproof.rs +++ b/crypto/mldsa_lowmemory/tests/wycheproof.rs @@ -42,7 +42,7 @@ mod wycheproof { static TEST_DATA_CHECK: Once = Once::new(); - fn test_for_presence_of_test_data() -> Result<(), ()> { + fn get_test_data(filename: &str) -> Result { let found: u8; if Path::new(TEST_DATA_PATH_RELATIVE).exists() { found = 1; @@ -59,28 +59,27 @@ mod wycheproof { _ => println!("WARNING: wycheproof directory not found; tests will be skipped"), }); - if found == 1 || found == 2 { Ok(()) } else { Err(()) } - } - - #[test] - fn mldsa_44_sign_seed_test() { - if test_for_presence_of_test_data().is_err() { - return; + if !found == 3 { + return Err(()); } let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string( - TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_44_sign_seed_test.json", - ) - .unwrap() + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/" + filename).unwrap() } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_44_sign_seed_test.json") - .unwrap() + fs::read_to_string(TEST_DATA_PATH.to_string() + "/" + filename).unwrap() } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + return Err(()); }; + Ok(contents) + } + + #[test] + fn mldsa_44_sign_seed_test() { + let contents = match get_test_data("mldsa_44_sign_seed_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; let test_cases = MLDSASignSeedTestCase::parse(contents, ParameterSet::Mldsa44); let num_test_cases = test_cases.len(); @@ -93,20 +92,10 @@ mod wycheproof { #[test] fn mldsa_44_verify_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_44_verify_test.json") - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_44_verify_test.json").unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_44_verify_test.json") { + Ok(contents) => contents, + Err(()) => return, }; - let test_cases = MLDSAVerifyTestCase::parse(contents, ParameterSet::Mldsa44); let num_test_cases = test_cases.len(); @@ -119,23 +108,10 @@ mod wycheproof { #[test] fn mldsa_65_sign_seed_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string( - TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_65_sign_seed_test.json", - ) - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_65_sign_seed_test.json") - .unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_65_sign_seed_test.json") { + Ok(contents) => contents, + Err(()) => return, }; - let test_cases = MLDSASignSeedTestCase::parse(contents, ParameterSet::Mldsa65); let num_test_cases = test_cases.len(); @@ -148,20 +124,10 @@ mod wycheproof { #[test] fn mldsa_65_verify_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_65_verify_test.json") - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_65_verify_test.json").unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_65_verify_test.json") { + Ok(contents) => contents, + Err(()) => return, }; - let test_cases = MLDSAVerifyTestCase::parse(contents, ParameterSet::Mldsa65); let num_test_cases = test_cases.len(); @@ -174,23 +140,10 @@ mod wycheproof { #[test] fn mldsa_87_sign_seed_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string( - TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_87_sign_seed_test.json", - ) - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_87_sign_seed_test.json") - .unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_87_sign_seed_test.json") { + Ok(contents) => contents, + Err(()) => return, }; - let test_cases = MLDSASignSeedTestCase::parse(contents, ParameterSet::Mldsa87); let num_test_cases = test_cases.len(); @@ -203,20 +156,10 @@ mod wycheproof { #[test] fn mldsa_87_verify_test() { - if test_for_presence_of_test_data().is_err() { - return; - } - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/mldsa_87_verify_test.json") - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/mldsa_87_verify_test.json").unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("mldsa_87_verify_test.json") { + Ok(contents) => contents, + Err(()) => return, }; - let test_cases = MLDSAVerifyTestCase::parse(contents, ParameterSet::Mldsa87); let num_test_cases = test_cases.len(); diff --git a/crypto/mlkem/Cargo.toml b/crypto/mlkem/Cargo.toml index 8e6bb95..8235352 100644 --- a/crypto/mlkem/Cargo.toml +++ b/crypto/mlkem/Cargo.toml @@ -14,6 +14,7 @@ bouncycastle-core-test-framework.workspace = true bouncycastle-hex.workspace = true bouncycastle-rng.workspace = true criterion.workspace = true +serde_json = "1.0" [[bench]] name = "mlkem_benches" diff --git a/crypto/mlkem/src/aux_functions.rs b/crypto/mlkem/src/aux_functions.rs index fd9474e..0d331ef 100644 --- a/crypto/mlkem/src/aux_functions.rs +++ b/crypto/mlkem/src/aux_functions.rs @@ -38,6 +38,7 @@ pub(crate) fn byte_encode(F: &Polynomial) for j in 0..d { // alpha % 2, but without using % for constant-time reasons + // although "& 1" may lead to other, more subtle timing issues. Research topic. let tmp = (alpha & 1) as u8; // 4: 𝑏[𝑖⋅𝑑 + 𝑗] ← π‘Ž mod 2 diff --git a/crypto/mlkem/src/mlkem_keys.rs b/crypto/mlkem/src/mlkem_keys.rs index 11d438f..df04429 100644 --- a/crypto/mlkem/src/mlkem_keys.rs +++ b/crypto/mlkem/src/mlkem_keys.rs @@ -389,6 +389,8 @@ impl< /// As described on Algorithm 16 line /// 3: dk ← (dkPKE β€– ek β€– H(ek) β€– 𝑧) fn sk_encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { + out.fill(0); + debug_assert_eq!(SK_LEN, /* dk_pke*/12*k*32 + /*ek*/PK_LEN + /*H(ek)*/32 + /*z*/32); let mut pos = 0usize; diff --git a/crypto/mlkem/tests/bc_test_data.rs b/crypto/mlkem/tests/bc_test_data.rs index 7313f04..5c14a28 100644 --- a/crypto/mlkem/tests/bc_test_data.rs +++ b/crypto/mlkem/tests/bc_test_data.rs @@ -15,7 +15,6 @@ mod bc_test_data { }; use std::fs; use std::path::Path; - use std::process::exit; use std::sync::Once; const TEST_DATA_PATH_RELATIVE: &str = "../../../bc-test-data/pqc/crypto/mlkem"; @@ -23,31 +22,43 @@ mod bc_test_data { static TEST_DATA_CHECK: Once = Once::new(); - fn test_for_presence_of_test_data() { - TEST_DATA_CHECK.call_once(|| { - if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - println!("bc-test-data found at: {:?}", TEST_DATA_PATH_RELATIVE); - } else if !Path::new(TEST_DATA_PATH).exists() { - println!("bc-test-data found at: {:?}", TEST_DATA_PATH); - } else { - println!("bc-test-data directory not found"); - exit(0); - } + fn get_test_data(filename: &str) -> Result { + let found: u8; + if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + found = 1; + } else if Path::new(TEST_DATA_PATH).exists() { + found = 2; + } else { + found = 3; + }; + + // just print once + TEST_DATA_CHECK.call_once(|| match found { + 1 => println!("wycheproof found at: {:?}", TEST_DATA_PATH_RELATIVE), + 2 => println!("wycheproof found at: {:?}", TEST_DATA_PATH), + _ => println!("WARNING: wycheproof directory not found; tests will be skipped"), }); - } - #[test] - #[allow(non_snake_case)] - fn ML_KEM_keyGen() { - test_for_presence_of_test_data(); + if !found == 3 { + return Err(()); + } let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/ML-KEM-keyGen.txt").unwrap() + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/" + filename).unwrap() } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-KEM-keyGen.txt").unwrap() + fs::read_to_string(TEST_DATA_PATH.to_string() + "/" + filename).unwrap() } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + return Err(()); + }; + + Ok(contents) + } + #[test] + #[allow(non_snake_case)] + fn ML_KEM_keyGen() { + let contents = match get_test_data("ML-KEM-keyGen.txt") { + Ok(contents) => contents, + Err(()) => return, }; let test_cases = KeyGenTestCase::parse(contents); @@ -136,13 +147,10 @@ mod bc_test_data { assert_eq!(self.mode, "keyGen"); let mut seed_bytes = [0u8; 64]; - seed_bytes[..32].copy_from_slice(&hex::decode(&self.d).unwrap()); - seed_bytes[32..].copy_from_slice(&hex::decode(&self.z).unwrap()); + seed_bytes[..32].copy_from_slice(&*hex::decode(&self.d).unwrap()); + seed_bytes[32..].copy_from_slice(&*hex::decode(&self.z).unwrap()); - let mut seed = KeyMaterial512::from_bytes_as_type( - &seed_bytes, - KeyType::Seed, - ).unwrap(); + let mut seed = KeyMaterial512::from_bytes_as_type(&seed_bytes, KeyType::Seed).unwrap(); // for the purposes of the test cases, accept an all-zero seed seed.allow_hazardous_operations(); @@ -159,7 +167,7 @@ mod bc_test_data { let sk_sized: [u8; MLKEM512_SK_LEN] = hex::decode(&self.dk).unwrap().try_into().unwrap(); assert_eq!(sk.encode(), sk_sized); - }, + } "ML-KEM-768" => { let (pk, sk) = MLKEM768::keygen_from_seed(&seed).unwrap(); let pk_sized: [u8; MLKEM768_PK_LEN] = @@ -168,7 +176,7 @@ mod bc_test_data { let sk_sized: [u8; MLKEM768_SK_LEN] = hex::decode(&self.dk).unwrap().try_into().unwrap(); assert_eq!(sk.encode(), sk_sized); - }, + } "ML-KEM-1024" => { let (pk, sk) = MLKEM1024::keygen_from_seed(&seed).unwrap(); let pk_sized: [u8; MLKEM1024_PK_LEN] = @@ -177,7 +185,7 @@ mod bc_test_data { let sk_sized: [u8; MLKEM1024_SK_LEN] = hex::decode(&self.dk).unwrap().try_into().unwrap(); assert_eq!(sk.encode(), sk_sized); - }, + } val => panic!("Invalid parameter set: {}", val), } } @@ -186,16 +194,9 @@ mod bc_test_data { #[test] #[allow(non_snake_case)] fn ML_KEM_encapDecap() { - test_for_presence_of_test_data(); - - let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { - fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/ML-KEM-encapDecap.txt") - .unwrap() - } else if Path::new(TEST_DATA_PATH).exists() { - fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-KEM-encapDecap.txt").unwrap() - } else { - println!("Current working directory: {:?}", std::env::current_dir().unwrap()); - panic!("Test data directory not found") + let contents = match get_test_data("ML-KEM-encapDecap.txt") { + Ok(contents) => contents, + Err(()) => return, }; let test_cases = EncapDecapTestCase::parse(contents); @@ -306,7 +307,7 @@ mod bc_test_data { assert_eq!(ss, expected_ss.as_slice()); assert_eq!(ct, expected_ct.as_slice()); - }, + } "decapsulation" => { let sk = MLKEM512PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()) @@ -316,10 +317,10 @@ mod bc_test_data { let expected_ss = hex::decode(&self.k).unwrap(); assert_eq!(ss.ref_to_bytes(), expected_ss.as_slice()); - }, + } _ => panic!("Invalid function: {}", self.function), }; - }, + } "ML-KEM-768" => { match self.function.as_str() { "encapsulation" => { @@ -333,7 +334,7 @@ mod bc_test_data { assert_eq!(ss, expected_ss.as_slice()); assert_eq!(ct, expected_ct.as_slice()); - }, + } "decapsulation" => { let sk = MLKEM768PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()) @@ -343,10 +344,10 @@ mod bc_test_data { let expected_ss = hex::decode(&self.k).unwrap(); assert_eq!(ss.ref_to_bytes(), expected_ss.as_slice()); - }, + } _ => panic!("Invalid function: {}", self.function), }; - }, + } "ML-KEM-1024" => { match self.function.as_str() { "encapsulation" => { @@ -361,7 +362,7 @@ mod bc_test_data { assert_eq!(ss, expected_ss.as_slice()); assert_eq!(ct, expected_ct.as_slice()); - }, + } "decapsulation" => { let sk = MLKEM1024PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()) @@ -371,10 +372,10 @@ mod bc_test_data { let expected_ss = hex::decode(&self.k).unwrap(); assert_eq!(ss.ref_to_bytes(), expected_ss.as_slice()); - }, + } _ => panic!("Invalid function: {}", self.function), }; - }, + } val => panic!("Invalid parameter set: {}", val), } } diff --git a/crypto/mlkem/tests/wycheproof.rs b/crypto/mlkem/tests/wycheproof.rs new file mode 100644 index 0000000..ee3ddc9 --- /dev/null +++ b/crypto/mlkem/tests/wycheproof.rs @@ -0,0 +1,892 @@ +//! Test against the project wycheproof repo available at: +//! https://github.com/C2SP/wycheproof +//! Requires that the wycheproof repository is cloned and available for testing at "../wycheproof" +//! relative to the root of this git project. +//! +//! This test file exercises the following test sets: +//! +//! * mlkem_512_encaps_test.json +//! * mlkem_512_keygen_seed_test.json +//! * mlkem_512_semi_expanded_decaps_test.json +//! * mlkem_512_test.json +//! * mlkem_768_encaps_test.json +//! * mlkem_768_keygen_seed_test.json +//! * mlkem_768_semi_expanded_decaps_test.json +//! * mlkem_768_test.json +//! * mlkem_1024_encaps_test.json +//! * mlkem_1024_keygen_seed_test.json +//! * mlkem_1024_semi_expanded_decaps_test.json +//! * mlkem_1024_test.json + +#![allow(dead_code)] + +use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; +use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength}; +use bouncycastle_hex as hex; +use bouncycastle_mlkem::{ + MLKEM512, MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768, MLKEM768PrivateKey, + MLKEM768PublicKey, MLKEM1024, MLKEM1024PrivateKey, MLKEM1024PublicKey, MLKEMTrait, +}; + +#[cfg(test)] +mod wycheproof { + use crate::{ + MLKEMEncapsTestCase, MLKEMKeygenSeedTestCase, MLKEMSemiExpandedDecapsTestCase, + MLKEMTestCase, ParameterSet, + }; + use std::fs; + use std::path::Path; + use std::sync::Once; + + const TEST_DATA_PATH_RELATIVE: &str = "../../../wycheproof/testvectors_v1"; + const TEST_DATA_PATH: &str = "../wycheproof/testvectors_v1"; + + static TEST_DATA_CHECK: Once = Once::new(); + + fn get_test_data(filename: &str) -> Result { + let found: u8; + if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + found = 1; + } else if Path::new(TEST_DATA_PATH).exists() { + found = 2; + } else { + found = 3; + }; + + // just print once + TEST_DATA_CHECK.call_once(|| match found { + 1 => println!("wycheproof found at: {:?}", TEST_DATA_PATH_RELATIVE), + 2 => println!("wycheproof found at: {:?}", TEST_DATA_PATH), + _ => println!("WARNING: wycheproof directory not found; tests will be skipped"), + }); + + if !found == 3 { + return Err(()); + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/" + filename).unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/" + filename).unwrap() + } else { + return Err(()); + }; + + Ok(contents) + } + + #[test] + fn mlkem_512_encaps_test() { + let contents = match get_test_data("mlkem_512_encaps_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMEncapsTestCase::parse(contents, ParameterSet::Mlkem512); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem512(); + } + + println!("mlkem_512_encaps_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_512_keygen_seed_test() { + let contents = match get_test_data("mlkem_512_keygen_seed_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMKeygenSeedTestCase::parse(contents, ParameterSet::Mlkem512); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem512(); + } + + println!("mlkem_512_keygen_seed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_512_semi_expanded_decaps_test() { + let contents = match get_test_data("mlkem_512_semi_expanded_decaps_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMSemiExpandedDecapsTestCase::parse(contents, ParameterSet::Mlkem512); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem512(); + } + + println!("mlkem_512_semi_expanded_decaps_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_512_test() { + let contents = match get_test_data("mlkem_512_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMTestCase::parse(contents, ParameterSet::Mlkem512); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem512(); + } + + println!("mlkem_512_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_768_encaps_test() { + let contents = match get_test_data("mlkem_768_encaps_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMEncapsTestCase::parse(contents, ParameterSet::Mlkem768); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem768(); + } + + println!("mlkem_768_encaps_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_768_keygen_seed_test() { + let contents = match get_test_data("mlkem_768_keygen_seed_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMKeygenSeedTestCase::parse(contents, ParameterSet::Mlkem768); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem768(); + } + + println!("mlkem_768_keygen_seed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_768_semi_expanded_decaps_test() { + let contents = match get_test_data("mlkem_768_semi_expanded_decaps_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMSemiExpandedDecapsTestCase::parse(contents, ParameterSet::Mlkem768); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem768(); + } + + println!("mlkem_768_semi_expanded_decaps_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_768_test() { + let contents = match get_test_data("mlkem_768_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMTestCase::parse(contents, ParameterSet::Mlkem768); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem768(); + } + + println!("mlkem_768_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_1024_encaps_test() { + let contents = match get_test_data("mlkem_1024_encaps_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMEncapsTestCase::parse(contents, ParameterSet::Mlkem1024); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem1024(); + } + + println!("mlkem_1024_encaps_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_1024_keygen_seed_test() { + let contents = match get_test_data("mlkem_1024_keygen_seed_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMKeygenSeedTestCase::parse(contents, ParameterSet::Mlkem1024); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem1024(); + } + + println!("mlkem_1024_keygen_seed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_1024_semi_expanded_decaps_test() { + let contents = match get_test_data("mlkem_1024_semi_expanded_decaps_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMSemiExpandedDecapsTestCase::parse(contents, ParameterSet::Mlkem1024); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem1024(); + } + + println!("mlkem_1024_semi_expanded_decaps_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_1024_test() { + let contents = match get_test_data("mlkem_1024_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMTestCase::parse(contents, ParameterSet::Mlkem1024); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem1024(); + } + + println!("mlkem_1024_test: all {} test cases passed.", num_test_cases); + } +} + +/* Structs for holding test data */ + +#[derive(Clone, Debug, PartialEq)] +enum ParameterSet { + Mlkem512, + Mlkem768, + Mlkem1024, +} + +#[derive(Clone)] +struct MLKEMEncapsTestCase { + parameter_set: ParameterSet, + tc_id: u32, + comment: String, + m: String, + ek: String, + c: String, + k: String, + result: String, +} + +impl MLKEMEncapsTestCase { + fn new(parameter_set: ParameterSet) -> Self { + Self { + parameter_set, + tc_id: 0, + comment: String::new(), + m: String::new(), + ek: String::new(), + c: String::new(), + k: String::new(), + result: String::new(), + } + } + + fn parse(data: String, parameter_set: ParameterSet) -> Vec { + let json: serde_json::Value = + serde_json::from_str(&data).expect("test data is not valid JSON"); + + let mut test_cases = Vec::::new(); + + let groups = json["testGroups"].as_array().expect("testGroups is not an array"); + for group in groups { + let tests = group["tests"].as_array().expect("tests is not an array"); + for test in tests { + test_cases.push(Self { + parameter_set: parameter_set.clone(), + tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, + comment: test["comment"].as_str().unwrap_or("").to_string(), + m: test["m"].as_str().unwrap_or("").to_string(), + ek: test["ek"].as_str().unwrap_or("").to_string(), + c: test["c"].as_str().unwrap_or("").to_string(), + k: test["K"].as_str().unwrap_or("").to_string(), + result: test["result"].as_str().unwrap_or("").to_string(), + }); + } + } + + test_cases + } + + fn run_mlkem512(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem512); + + /* Load the key */ + + let ek = match MLKEM512PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()) { + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e); + } + } + Ok(pk) => pk, + }; + + /* Perform the deterministic encaps and compare results */ + + let (k, ct) = + MLKEM512::encaps_internal(&ek, None, hex::decode(&self.m).unwrap().try_into().unwrap()); + + if self.result == "valid" { + assert_eq!(k, hex::decode(&self.k).unwrap().as_slice()); + assert_eq!(ct, hex::decode(&self.c).unwrap().as_slice()); + } else { + // is there anything to test here? + } + } + + fn run_mlkem768(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem768); + + /* Load the key */ + + let ek = match MLKEM768PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()) { + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e); + } + } + Ok(pk) => pk, + }; + + /* Perform the deterministic encaps and compare results */ + + let (k, ct) = + MLKEM768::encaps_internal(&ek, None, hex::decode(&self.m).unwrap().try_into().unwrap()); + + if self.result == "valid" { + assert_eq!(k, hex::decode(&self.k).unwrap().as_slice()); + assert_eq!(ct, hex::decode(&self.c).unwrap().as_slice()); + } else { + // is there anything to test here? + } + } + + fn run_mlkem1024(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem1024); + + /* Load the key */ + + let ek = match MLKEM1024PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()) { + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e); + } + } + Ok(pk) => pk, + }; + + /* Perform the deterministic encaps and compare results */ + + let (k, ct) = MLKEM1024::encaps_internal( + &ek, + None, + hex::decode(&self.m).unwrap().try_into().unwrap(), + ); + + if self.result == "valid" { + assert_eq!(k, hex::decode(&self.k).unwrap().as_slice()); + assert_eq!(ct, hex::decode(&self.c).unwrap().as_slice()); + } else { + // is there anything to test here? + } + } +} + +#[derive(Clone)] +struct MLKEMKeygenSeedTestCase { + parameter_set: ParameterSet, + tc_id: u32, + comment: String, + seed: String, + ek: String, + dk: String, + result: String, +} + +impl MLKEMKeygenSeedTestCase { + fn new(parameter_set: ParameterSet) -> Self { + Self { + parameter_set, + tc_id: 0, + comment: String::new(), + seed: String::new(), + ek: String::new(), + dk: String::new(), + result: String::new(), + } + } + + fn parse(data: String, parameter_set: ParameterSet) -> Vec { + let json: serde_json::Value = + serde_json::from_str(&data).expect("test data is not valid JSON"); + + let mut test_cases = Vec::::new(); + + let groups = json["testGroups"].as_array().expect("testGroups is not an array"); + for group in groups { + let tests = group["tests"].as_array().expect("tests is not an array"); + for test in tests { + test_cases.push(Self { + parameter_set: parameter_set.clone(), + tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, + comment: test["comment"].as_str().unwrap_or("").to_string(), + seed: test["seed"].as_str().unwrap_or("").to_string(), + ek: test["ek"].as_str().unwrap_or("").to_string(), + dk: test["dk"].as_str().unwrap_or("").to_string(), + result: test["result"].as_str().unwrap_or("").to_string(), + }); + } + } + + test_cases + } + + fn run_mlkem512(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem512); + + // currently, the wycheproof tests contain only valid tests, so just run them; no errors to check + + let seed = + KeyMaterial512::from_bytes_as_type(&hex::decode(&self.seed).unwrap(), KeyType::Seed) + .unwrap(); + + let (ek, dk) = MLKEM512::keygen_from_seed(&seed).unwrap(); + + assert_eq!(ek, MLKEM512PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap()); + assert_eq!(&ek.encode(), hex::decode(&self.ek).unwrap().as_slice()); + + assert_eq!(dk, MLKEM512PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()).unwrap()); + assert_eq!(&dk.encode(), hex::decode(&self.dk).unwrap().as_slice()); + } + + fn run_mlkem768(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem768); + + // currently, the wycheproof tests contain only valid tests, so just run them; no errors to check + + let seed = + KeyMaterial512::from_bytes_as_type(&hex::decode(&self.seed).unwrap(), KeyType::Seed) + .unwrap(); + + let (ek, dk) = MLKEM768::keygen_from_seed(&seed).unwrap(); + + assert_eq!(ek, MLKEM768PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap()); + assert_eq!(&ek.encode(), hex::decode(&self.ek).unwrap().as_slice()); + + assert_eq!(dk, MLKEM768PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()).unwrap()); + assert_eq!(&dk.encode(), hex::decode(&self.dk).unwrap().as_slice()); + } + + fn run_mlkem1024(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem1024); + + // currently, the wycheproof tests contain only valid tests, so just run them; no errors to check + + let seed = + KeyMaterial512::from_bytes_as_type(&hex::decode(&self.seed).unwrap(), KeyType::Seed) + .unwrap(); + + let (ek, dk) = MLKEM1024::keygen_from_seed(&seed).unwrap(); + + assert_eq!(ek, MLKEM1024PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap()); + assert_eq!(&ek.encode(), hex::decode(&self.ek).unwrap().as_slice()); + + assert_eq!(dk, MLKEM1024PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()).unwrap()); + assert_eq!(&dk.encode(), hex::decode(&self.dk).unwrap().as_slice()); + } +} + +#[derive(Clone)] +struct MLKEMSemiExpandedDecapsTestCase { + parameter_set: ParameterSet, + tc_id: u32, + comment: String, + dk: String, + c: String, + result: String, +} + +impl MLKEMSemiExpandedDecapsTestCase { + fn new(parameter_set: ParameterSet) -> Self { + Self { + parameter_set, + tc_id: 0, + comment: String::new(), + dk: String::new(), + c: String::new(), + result: String::new(), + } + } + + fn parse(data: String, parameter_set: ParameterSet) -> Vec { + let json: serde_json::Value = + serde_json::from_str(&data).expect("test data is not valid JSON"); + + let mut test_cases = Vec::::new(); + + let groups = json["testGroups"].as_array().expect("testGroups is not an array"); + for group in groups { + let tests = group["tests"].as_array().expect("tests is not an array"); + for test in tests { + test_cases.push(Self { + parameter_set: parameter_set.clone(), + tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, + comment: test["comment"].as_str().unwrap_or("").to_string(), + dk: test["dk"].as_str().unwrap_or("").to_string(), + c: test["c"].as_str().unwrap_or("").to_string(), + result: test["result"].as_str().unwrap_or("").to_string(), + }); + } + } + + test_cases + } + + fn run_mlkem512(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem512); + + /* Load the private key */ + let _dk = match MLKEM512PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()) { + Ok(dk) => dk, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("Failed to load private key: {:?}", e); + } + } + }; + + // these tests provide c, but not the output key k, + // so there's really no reason to perform the decaps since there's really nothing to check it against + } + + fn run_mlkem768(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem768); + + /* Load the private key */ + let _dk = match MLKEM768PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()) { + Ok(dk) => dk, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("Failed to load private key: {:?}", e); + } + } + }; + + // these tests provide c, but not the output key k, + // so there's really no reason to perform the decaps since there's really nothing to check it against + } + + fn run_mlkem1024(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem1024); + + /* Load the private key */ + let _dk = match MLKEM1024PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()) { + Ok(dk) => dk, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("Failed to load private key: {:?}", e); + } + } + }; + + // these tests provide c, but not the output key k, + // so there's really no reason to perform the decaps since there's really nothing to check it against + } +} + +#[derive(Clone)] +struct MLKEMTestCase { + parameter_set: ParameterSet, + tc_id: u32, + comment: String, + seed: String, + ek: String, + c: String, + k: String, + result: String, +} + +impl MLKEMTestCase { + fn new(parameter_set: ParameterSet) -> Self { + Self { + parameter_set, + tc_id: 0, + comment: String::new(), + seed: String::new(), + ek: String::new(), + c: String::new(), + k: String::new(), + result: String::new(), + } + } + + fn parse(data: String, parameter_set: ParameterSet) -> Vec { + let json: serde_json::Value = + serde_json::from_str(&data).expect("test data is not valid JSON"); + + let mut test_cases = Vec::::new(); + + let groups = json["testGroups"].as_array().expect("testGroups is not an array"); + for group in groups { + let tests = group["tests"].as_array().expect("tests is not an array"); + for test in tests { + test_cases.push(Self { + parameter_set: parameter_set.clone(), + tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, + comment: test["comment"].as_str().unwrap_or("").to_string(), + seed: test["seed"].as_str().unwrap_or("").to_string(), + ek: test["ek"].as_str().unwrap_or("").to_string(), + c: test["c"].as_str().unwrap_or("").to_string(), + k: test["K"].as_str().unwrap_or("").to_string(), + result: test["result"].as_str().unwrap_or("").to_string(), + }); + } + } + + test_cases + } + + fn run_mlkem512(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem512); + + /* Load the private key */ + let mut seed = match KeyMaterial512::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) { + Ok(seed) => seed, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("Failed to load seed: {:?}", e); + } + } + }; + // allow an all-zero seed for testing + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => (), + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + } + + let (ek, dk) = match MLKEM512::keygen_from_seed(&seed) { + Ok((ek, dk)) => (ek, dk), + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Failed to generate key pair: {:?}", e); + } + } + }; + + // check that the derived ek matches the provided one + assert_eq!(&ek.encode(), &hex::decode(&self.ek).unwrap().as_slice()); + + // these tests don't provide m, so can't test deterministic encaps + + // test decaps + let k = match MLKEM512::decaps(&dk, &hex::decode(&self.c).unwrap().as_slice()) { + Ok(k) => k, + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Failed to decapsulate: {:?}", e); + } + } + }; + + assert_eq!(k.ref_to_bytes(), hex::decode(&self.k).unwrap().as_slice()); + } + + fn run_mlkem768(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem768); + + /* Load the private key */ + let mut seed = match KeyMaterial512::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) { + Ok(seed) => seed, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("Failed to load seed: {:?}", e); + } + } + }; + // allow an all-zero seed for testing + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => (), + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + } + + let (ek, dk) = match MLKEM768::keygen_from_seed(&seed) { + Ok((ek, dk)) => (ek, dk), + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Failed to generate key pair: {:?}", e); + } + } + }; + + // check that the derived ek matches the provided one + assert_eq!(&ek.encode(), &hex::decode(&self.ek).unwrap().as_slice()); + + // these tests don't provide m, so can't test deterministic encaps + + // test decaps + let k = match MLKEM768::decaps(&dk, &hex::decode(&self.c).unwrap().as_slice()) { + Ok(k) => k, + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Failed to decapsulate: {:?}", e); + } + } + }; + + assert_eq!(k.ref_to_bytes(), hex::decode(&self.k).unwrap().as_slice()); + } + + fn run_mlkem1024(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem1024); + + /* Load the private key */ + let mut seed = match KeyMaterial512::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) { + Ok(seed) => seed, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("Failed to load seed: {:?}", e); + } + } + }; + // allow an all-zero seed for testing + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => (), + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + } + + let (ek, dk) = match MLKEM1024::keygen_from_seed(&seed) { + Ok((ek, dk)) => (ek, dk), + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Failed to generate key pair: {:?}", e); + } + } + }; + + // check that the derived ek matches the provided one + assert_eq!(&ek.encode(), &hex::decode(&self.ek).unwrap().as_slice()); + + // these tests don't provide m, so can't test deterministic encaps + + // test decaps + let k = match MLKEM1024::decaps(&dk, &hex::decode(&self.c).unwrap().as_slice()) { + Ok(k) => k, + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Failed to decapsulate: {:?}", e); + } + } + }; + + assert_eq!(k.ref_to_bytes(), hex::decode(&self.k).unwrap().as_slice()); + } +} diff --git a/crypto/mlkem_lowmemory/Cargo.toml b/crypto/mlkem_lowmemory/Cargo.toml index 73079c8..8ccb067 100644 --- a/crypto/mlkem_lowmemory/Cargo.toml +++ b/crypto/mlkem_lowmemory/Cargo.toml @@ -14,6 +14,7 @@ bouncycastle-core-test-framework.workspace = true bouncycastle-hex.workspace = true bouncycastle-rng.workspace = true criterion.workspace = true +serde_json = "1.0" [[bench]] name = "mlkem_benches" diff --git a/crypto/mlkem_lowmemory/src/aux_functions.rs b/crypto/mlkem_lowmemory/src/aux_functions.rs index a5f5043..2246b74 100644 --- a/crypto/mlkem_lowmemory/src/aux_functions.rs +++ b/crypto/mlkem_lowmemory/src/aux_functions.rs @@ -9,10 +9,13 @@ use bouncycastle_sha3::{SHAKE128, SHAKE256}; /// 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] { - debug_assert_eq!(PACK_LEN, 32 * d); +pub(crate) fn byte_encode( + F: &Polynomial, + B: &mut [u8; PACK_LEN], +) { + B.fill(0); - let mut B = [0u8; PACK_LEN]; + debug_assert_eq!(PACK_LEN, 32 * d); for i in 0..N { let mut alpha = F[i]; @@ -20,16 +23,17 @@ pub(crate) fn byte_encode(F: &Polynomial) // For efficiency, the library is happy to work with values outside the range [0..q], // but we need to reduce it for the canonical encoding. alpha = barrett_reduce(alpha); + alpha = cond_sub_q(alpha); for j in 0..d { // alpha % 2, but without using % for constant-time reasons + // although "& 1" may lead to other, more subtle timing issues. Research topic. let tmp = (alpha & 1) as u8; // 4: 𝑏[𝑖⋅𝑑 + 𝑗] ← π‘Ž mod 2 // constant-time note: yes, % is not constant-time, // but all of the values in (i*d + j) % 8 are loop indices and not part of the secret key. - B[(i*d + j)/8] |= tmp << ((i*d + j) % 8); - + B[(i * d + j) / 8] |= tmp << ((i * d + j) % 8); // 5: π‘Ž ← (π‘Ž βˆ’ 𝑏[𝑖⋅𝑑 + 𝑗])/2 // β–· note π‘Ž βˆ’ 𝑏[𝑖⋅𝑑 + 𝑗] is always even @@ -42,8 +46,6 @@ pub(crate) fn byte_encode(F: &Polynomial) alpha >>= 1; } } - - B } /// Algorithm 6 ByteDecode_d(𝐡) @@ -59,11 +61,12 @@ pub(crate) fn byte_decode(B: &[u8; PACK_L // 3: F[i] = SUM_j=0..d-1{ 𝑏[𝑖 β‹… 𝑑 + 𝑗] β‹… 2𝑗 } mod m for j in 0..d { // select the next bit, according to bitcount, then shift it up by j - F[i] |= (((B[(i*d + j)/8] >> (i*d + j)%8) & 1) as i16) << j; // there's supposed to be a `mod m` here, but that shouldn't matter; we'll check it below anyway. + F[i] |= (((B[(i * d + j) / 8] >> (i * d + j) % 8) & 1) as i16) << j; // there's supposed to be a `mod m` here, but that shouldn't matter; we'll check it below anyway. } // assert the mod m + // We'll relax these because it's being checked above in MLKEMPublicKey::pk_decode() debug_assert!(F[i] >= 0); - debug_assert!(F[i] <= if d<12 {2< Polynomial { // 7: 𝑑2 ← ⌊𝐢[1]/16βŒ‹ + 16 β‹… 𝐢[2] // β–· 0 ≀ 𝑑2 < 2^12 - let d2: i16 = ((C[idx+1] as i16) >> 4) | ((C[idx+2] as i32) << 4) as i16 & 0xFFF; + let d2: i16 = ((C[idx + 1] as i16) >> 4) | ((C[idx + 2] as i32) << 4) as i16 & 0xFFF; debug_assert!(d2 < 2 << 12); // 8: if 𝑑1 < π‘ž then @@ -149,8 +152,8 @@ pub(crate) fn sample_poly_cbd(bytes: &[u8]) -> Polynomial { match eta { 2 => { - for i in 0..N/8 { - let t = u32::from_le_bytes(bytes[4*i .. 4*i + 4].try_into().unwrap()); + for i in 0..N / 8 { + let t = u32::from_le_bytes(bytes[4 * i..4 * i + 4].try_into().unwrap()); let mut d = t & 0x55555555; d += (t >> 1) & 0x55555555; for j in 0..8usize { @@ -165,7 +168,7 @@ pub(crate) fn sample_poly_cbd(bytes: &[u8]) -> Polynomial { } } 3 => { - for i in 0..N/4 { + for i in 0..N / 4 { let t = little_endian_to_u24(bytes, 3 * i); let mut d = t & 0x00249249; d += (t >> 1) & 0x00249249; @@ -190,7 +193,6 @@ pub(crate) fn sample_poly_cbd(bytes: &[u8]) -> Polynomial { /// SamplePolyCBDπœ‚1(PRFπœ‚1 (𝜎, 𝑁 )) /// Performs both the PRF and SamplePolyCBD steps pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8) -> Polynomial { - // Alg 13: 9: 𝐬[𝑖] ← SamplePolyCBDπœ‚1(PRFπœ‚1 (𝜎, 𝑁 )) // β–· 𝐬[𝑖] ∈ β„€256 sampled from CBD match eta { @@ -206,7 +208,7 @@ pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8) -> Polynomial }; sample_poly_cbd::(&buf) - }, + } 3 => { let buf = { let mut xof = SHAKE256::new(); @@ -218,8 +220,8 @@ pub(crate) fn sample_poly_CBD(b: &[u8; 32], n: u8) -> Polynomial }; sample_poly_cbd::(&buf) - }, - _ => unreachable!() + } + _ => unreachable!(), } } @@ -269,12 +271,10 @@ pub(crate) fn barrett_reduce(a: i16) -> i16 { a - (((t as i32) * q as i32) as i16) } -// not currently used, but I'll leave it here because it's useful for debugging if you want to output values -// that are normalized to [0,q] to compare against intermediate results from other libraries. -// pub(super) fn cond_sub_q(a: i16) -> i16 { -// let tmp = a - q; -// tmp + ((tmp >> 15) & q) -// } +pub(super) fn cond_sub_q(a: i16) -> i16 { + let tmp = a - q; + tmp + ((tmp >> 15) & q) +} /// Multiplication of polynomials in Zq\[X]/(X^2-zeta) /// used for multiplication of elements in Rq in NTT domain diff --git a/crypto/mlkem_lowmemory/src/low_memory_helpers.rs b/crypto/mlkem_lowmemory/src/low_memory_helpers.rs index a7ab96d..3ad71fa 100644 --- a/crypto/mlkem_lowmemory/src/low_memory_helpers.rs +++ b/crypto/mlkem_lowmemory/src/low_memory_helpers.rs @@ -3,8 +3,8 @@ //! what it needs in pieces, which generally means handling the matrices and vectors row-wise or entry-wise. use crate::aux_functions::{byte_decode, byte_encode, sample_ntt, sample_poly_CBD}; -use crate::polynomial::{Polynomial}; -use crate::mlkem::{q, N, POLY_BYTES}; +use crate::mlkem::{N, POLY_BYTES, q}; +use crate::polynomial::Polynomial; /// Computes the element [i,j] of the A_hat public matrix pub(crate) fn expandA_elem(rho: &[u8; 32], i: usize, j: usize) -> Polynomial { @@ -27,10 +27,10 @@ pub(crate) fn compute_A_hat_dot_s_hat( A_i0 }; - for j in 1 .. k { + for j in 1..k { let mut A_ij = expandA_elem(rho, row, j); let mut s_j = sample_poly_CBD::(sigma, j as u8); - s_j.ntt(); // now s_hat_j + s_j.ntt(); // now s_hat_j A_ij.base_mult_montgomery(&s_j); t_hat_i.add(&A_ij); } @@ -46,7 +46,7 @@ pub(crate) fn compute_A_hat_dot_s_hat( pub(crate) fn compute_A_hat_dot_y_hat( rho: &[u8; 32], r: &[u8; 32], - row: usize + row: usize, ) -> Polynomial { // 4: for (𝑖 ← 0; 𝑖 < π‘˜; 𝑖++) // β–· re-generate matrix 𝐀 ∈ (β„€256_π‘ž )π‘˜Γ—π‘˜ sampled in Alg. 13 @@ -68,7 +68,7 @@ pub(crate) fn compute_A_hat_dot_y_hat( A_0i }; - for j in 1 .. k { + for j in 1..k { let mut A_ji = expandA_elem(&rho, j, row); let mut y_j = sample_poly_CBD::(r, /*N*/ j as u8); y_j.ntt(); @@ -85,7 +85,7 @@ pub(crate) fn compute_A_hat_dot_y_hat( pub(crate) fn compute_t_hat_dot_y_hat_row( r: &[u8; 32], t_hat_i: &Polynomial, - row: usize + row: usize, ) -> Polynomial { let mut y_i = sample_poly_CBD::(r, /*N*/ row as u8); y_i.ntt(); @@ -95,23 +95,37 @@ pub(crate) fn compute_t_hat_dot_y_hat_row( y_i } -pub(crate) fn pack_t_hat_row(t_hat_i: &Polynomial, row: usize, t_hat_packed: &mut [u8; T_PACKED_LEN]) { - t_hat_packed[row * POLY_BYTES .. (row + 1) * POLY_BYTES].copy_from_slice( - &byte_encode::<12, POLY_BYTES>(&t_hat_i) +pub(crate) fn pack_t_hat_row( + t_hat_i: &Polynomial, + row: usize, + t_hat_packed: &mut [u8; T_PACKED_LEN], +) { + byte_encode::<12, POLY_BYTES>( + &t_hat_i, + t_hat_packed[row * POLY_BYTES..(row + 1) * POLY_BYTES].as_mut().try_into().unwrap(), ); } -pub(crate) fn unpack_t_hat_row(t_hat_packed: &[u8; T_PACKED_LEN], row: usize) -> Polynomial { +pub(crate) fn unpack_t_hat_row( + t_hat_packed: &[u8; T_PACKED_LEN], + row: usize, +) -> Polynomial { byte_decode::<12, POLY_BYTES>( - t_hat_packed[row * POLY_BYTES .. (row + 1)*POLY_BYTES].try_into().unwrap()) + t_hat_packed[row * POLY_BYTES..(row + 1) * POLY_BYTES].try_into().unwrap(), + ) } -pub(crate) fn pack_s_hat_row(s_hat_i: &Polynomial, row: usize, s_hat_packed: &mut [u8]) { - debug_assert!(s_hat_packed.len() >= k*POLY_BYTES); - - s_hat_packed[row*POLY_BYTES .. (row+1)*POLY_BYTES].copy_from_slice(&byte_encode::<12, POLY_BYTES>( +pub(crate) fn pack_s_hat_row( + s_hat_i: &Polynomial, + row: usize, + s_hat_packed: &mut [u8], +) { + debug_assert!(s_hat_packed.len() >= k * POLY_BYTES); + + byte_encode::<12, POLY_BYTES>( s_hat_i, - )); + s_hat_packed[row * POLY_BYTES..(row + 1) * POLY_BYTES].as_mut().try_into().unwrap(), + ); } /// This is an optimized version of @@ -121,7 +135,7 @@ pub(crate) fn pack_s_hat_row(s_hat_i: &Polynomial, row: usize, s pub(crate) fn compress_u_row( u_i: Polynomial, row: usize, - ct: &mut [u8; CT_LEN] + ct: &mut [u8; CT_LEN], ) { // make sure we have received a dv assert!(du == 10 || du == 11); @@ -130,66 +144,61 @@ pub(crate) fn compress_u_row( // let mut u_i = u_i.clone(); // u_i.conditional_sub_q(); - // figure out where in the ct array we're going to write to // each of the N i16's will take du bits, so a polynomial takes N * du bits, then we have k of them let start: usize = row * (N * (du as usize) / 8); let end: usize = (row + 1) * (N * (du as usize) / 8); - let out = &mut ct[start .. end]; + let out = &mut ct[start..end]; let mut idx = 0; match du { - 10 => { // MLKEM512 and MLKEM 768 + 10 => { + // MLKEM512 and MLKEM 768 let mut t = [0i16; 4]; - for j in 0..N/4 { - // fill the temp array t - for (l, item) in t.iter_mut().enumerate() { - *item = (((((u_i[4 * j + l] as u32) << 10) as i32 - + (q as i32 / 2)) - / q as i32) - & 0x3FF) as i16; - } - - out[idx] = t[0] as u8; - out[idx + 1] = ((t[0] >> 8) | (t[1] << 2)) as u8; - out[idx + 2] = ((t[1] >> 6) | (t[2] << 4)) as u8; - out[idx + 3] = ((t[2] >> 4) | (t[3] << 6)) as u8; - out[idx + 4] = (t[3] >> 2) as u8; - idx += 5; + for j in 0..N / 4 { + // fill the temp array t + for (l, item) in t.iter_mut().enumerate() { + *item = (((((u_i[4 * j + l] as u32) << 10) as i32 + (q as i32 / 2)) / q as i32) + & 0x3FF) as i16; } - }, + + out[idx] = t[0] as u8; + out[idx + 1] = ((t[0] >> 8) | (t[1] << 2)) as u8; + out[idx + 2] = ((t[1] >> 6) | (t[2] << 4)) as u8; + out[idx + 3] = ((t[2] >> 4) | (t[3] << 6)) as u8; + out[idx + 4] = (t[3] >> 2) as u8; + idx += 5; + } + } 11 => { let mut t = [0i16; 8]; - for j in 0..N/8 { - for (l, item) in t.iter_mut().enumerate() { - *item = (((((u_i[8 * j + l] as u32) << 11) as i32 - + (q as i32 / 2)) - / q as i32) - & 0x7FF) as i16; - } - - out[idx] = t[0] as u8; - out[idx + 1] = ((t[0] >> 8) | (t[1] << 3)) as u8; - out[idx + 2] = ((t[1] >> 5) | (t[2] << 6)) as u8; - out[idx + 3] = (t[2] >> 2) as u8; - out[idx + 4] = ((t[2] >> 10) | (t[3] << 1)) as u8; - out[idx + 5] = ((t[3] >> 7) | (t[4] << 4)) as u8; - out[idx + 6] = ((t[4] >> 4) | (t[5] << 7)) as u8; - out[idx + 7] = (t[5] >> 1) as u8; - out[idx + 8] = ((t[5] >> 9) | (t[6] << 2)) as u8; - out[idx + 9] = ((t[6] >> 6) | (t[7] << 5)) as u8; - out[idx + 10] = (t[7] >> 3) as u8; - idx += 11; + for j in 0..N / 8 { + for (l, item) in t.iter_mut().enumerate() { + *item = (((((u_i[8 * j + l] as u32) << 11) as i32 + (q as i32 / 2)) / q as i32) + & 0x7FF) as i16; } - }, + + out[idx] = t[0] as u8; + out[idx + 1] = ((t[0] >> 8) | (t[1] << 3)) as u8; + out[idx + 2] = ((t[1] >> 5) | (t[2] << 6)) as u8; + out[idx + 3] = (t[2] >> 2) as u8; + out[idx + 4] = ((t[2] >> 10) | (t[3] << 1)) as u8; + out[idx + 5] = ((t[3] >> 7) | (t[4] << 4)) as u8; + out[idx + 6] = ((t[4] >> 4) | (t[5] << 7)) as u8; + out[idx + 7] = (t[5] >> 1) as u8; + out[idx + 8] = ((t[5] >> 9) | (t[6] << 2)) as u8; + out[idx + 9] = ((t[6] >> 6) | (t[7] << 5)) as u8; + out[idx + 10] = (t[7] >> 3) as u8; + idx += 11; + } + } _ => unreachable!(), } } - pub(crate) fn unpack_ciphertext_u_row( row: usize, - ct: &[u8; CT_LEN] + ct: &[u8; CT_LEN], ) -> Polynomial { let mut u_i = Polynomial::new(); @@ -200,82 +209,78 @@ pub(crate) fn unpack_ciphertext_u_row( // each of the N i16's will take du bits, so a polynomial takes N * du bits, then we have k of them let start: usize = row * (N * (du as usize) / 8); let end: usize = (row + 1) * (N * (du as usize) / 8); - let compressed_u_i = &ct[start .. end]; + let compressed_u_i = &ct[start..end]; let mut idx = 0; match du { - 10 => { // MLKEM512 and MLKEM768 + 10 => { + // MLKEM512 and MLKEM768 let mut t = [0i16; 4]; - for j in 0..(N/4) { - t[0] = ((compressed_u_i[idx] as u16) - | (compressed_u_i[idx + 1] as u16) << 8) - as i16; + for j in 0..(N / 4) { + t[0] = + ((compressed_u_i[idx] as u16) | (compressed_u_i[idx + 1] as u16) << 8) as i16; t[1] = (((compressed_u_i[idx + 1] as u16) >> 2) - | (compressed_u_i[idx + 2] as u16) << 6) - as i16; + | (compressed_u_i[idx + 2] as u16) << 6) as i16; t[2] = (((compressed_u_i[idx + 2] as u16) >> 4) - | (compressed_u_i[idx + 3] as u16) << 4) - as i16; + | (compressed_u_i[idx + 3] as u16) << 4) as i16; t[3] = (((compressed_u_i[idx + 3] as u16) >> 6) - | (compressed_u_i[idx + 4] as u16) << 2) - as i16; + | (compressed_u_i[idx + 4] as u16) << 2) as i16; idx += 5; for (l, item) in t.iter().enumerate() { - u_i[4 * j + l] = - ((((*item & 0x3FF) as i32) * (q as i32) + 512) >> 10) as i16; + u_i[4 * j + l] = ((((*item & 0x3FF) as i32) * (q as i32) + 512) >> 10) as i16; } } - }, - 11 => { // MLKEM1024 + } + 11 => { + // MLKEM1024 let mut t = [0i16; 8]; - for j in 0..N/8 { - t[0] = (compressed_u_i[idx] as i32 - | ((compressed_u_i[idx + 1] as u16) as i32) << 8) + for j in 0..N / 8 { + t[0] = (compressed_u_i[idx] as i32 | ((compressed_u_i[idx + 1] as u16) as i32) << 8) as i16; t[1] = ((compressed_u_i[idx + 1] >> 3) as i32 - | ((compressed_u_i[idx + 2] as u16) as i32) << 5) - as i16; + | ((compressed_u_i[idx + 2] as u16) as i32) << 5) as i16; t[2] = ((compressed_u_i[idx + 2] >> 6) as i32 | ((compressed_u_i[idx + 3] as u16) as i32) << 2 | (((compressed_u_i[idx + 4] as i32) << 10) as u16) as i32) as i16; t[3] = ((compressed_u_i[idx + 4] >> 1) as i32 - | ((compressed_u_i[idx + 5] as u16) as i32) << 7) - as i16; + | ((compressed_u_i[idx + 5] as u16) as i32) << 7) as i16; t[4] = ((compressed_u_i[idx + 5] >> 4) as i32 - | ((compressed_u_i[idx + 6] as u16) as i32) << 4) - as i16; + | ((compressed_u_i[idx + 6] as u16) as i32) << 4) as i16; t[5] = ((compressed_u_i[idx + 6] >> 7) as i32 | ((compressed_u_i[idx + 7] as u16) as i32) << 1 | (((compressed_u_i[idx + 8] as i32) << 9) as u16) as i32) as i16; t[6] = ((compressed_u_i[idx + 8] >> 2) as i32 - | ((compressed_u_i[idx + 9] as u16) as i32) << 6) - as i16; + | ((compressed_u_i[idx + 9] as u16) as i32) << 6) as i16; t[7] = ((compressed_u_i[idx + 9] >> 5) as i32 | ((compressed_u_i[idx + 10] as u16) as i32) << 3) as i16; idx += 11; for (l, item) in t.iter().enumerate() { - u_i[8 * j + l] = - ((((*item & 0x7FF) as i32) * (q as i32) + 1024) >> 11) as i16; + u_i[8 * j + l] = ((((*item & 0x7FF) as i32) * (q as i32) + 1024) >> 11) as i16; } } - }, + } _ => unreachable!(), } u_i } -pub(crate) fn unpack_ciphertext_v( - c: &[u8; CT_LEN] +pub(crate) fn unpack_ciphertext_v< + const k: usize, + const CT_LEN: usize, + const du: i16, + const dv: i16, +>( + c: &[u8; CT_LEN], ) -> Polynomial { // each of the N i16's will take du bits, so a polynomial takes N * du bits, then we have k of them - let lim: usize = k* (N * (du as usize) / 8); + let lim: usize = k * (N * (du as usize) / 8); let v = Polynomial::decompress_poly::(&c[lim..]); v -} \ No newline at end of file +} diff --git a/crypto/mlkem_lowmemory/src/mlkem_keys.rs b/crypto/mlkem_lowmemory/src/mlkem_keys.rs index 5571ab9..b897d17 100644 --- a/crypto/mlkem_lowmemory/src/mlkem_keys.rs +++ b/crypto/mlkem_lowmemory/src/mlkem_keys.rs @@ -1,17 +1,28 @@ -use crate::aux_functions::{sample_poly_CBD}; -use crate::mlkem::{POLY_BYTES, H, G}; +use crate::aux_functions::sample_poly_CBD; +use crate::low_memory_helpers::{ + compute_A_hat_dot_s_hat, pack_s_hat_row, pack_t_hat_row, unpack_t_hat_row, +}; +use crate::mlkem::{G, H, POLY_BYTES, q}; +use crate::mlkem::{ + MLKEM512_ETA1, MLKEM512_FULL_SK_LEN, MLKEM512_LAMBDA, MLKEM512_PK_LEN, MLKEM512_SK_LEN, + MLKEM512_T_PACKED_LEN, MLKEM512_k, +}; +use crate::mlkem::{ + MLKEM768_ETA1, MLKEM768_FULL_SK_LEN, MLKEM768_LAMBDA, MLKEM768_PK_LEN, MLKEM768_SK_LEN, + MLKEM768_T_PACKED_LEN, MLKEM768_k, +}; +use crate::mlkem::{ + MLKEM1024_ETA1, MLKEM1024_FULL_SK_LEN, MLKEM1024_LAMBDA, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, + MLKEM1024_T_PACKED_LEN, MLKEM1024_k, +}; +use crate::polynomial::Polynomial; use crate::{ML_KEM_512_NAME, ML_KEM_768_NAME, ML_KEM_1024_NAME}; -use crate::mlkem::{MLKEM512_k, MLKEM512_ETA1, MLKEM512_LAMBDA, MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM512_FULL_SK_LEN, MLKEM512_T_PACKED_LEN}; -use crate::mlkem::{MLKEM768_k, MLKEM768_ETA1, MLKEM768_LAMBDA, MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM768_FULL_SK_LEN, MLKEM768_T_PACKED_LEN}; -use crate::mlkem::{MLKEM1024_k, MLKEM1024_ETA1, MLKEM1024_LAMBDA, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM1024_FULL_SK_LEN, MLKEM1024_T_PACKED_LEN}; -use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial, KeyType}; +use bouncycastle_core::errors::KEMError; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{Hash, KEMPrivateKey, KEMPublicKey, Secret, SecurityStrength}; -use bouncycastle_core::errors::{KEMError}; +use bouncycastle_sha3::SHA3_256; use core::fmt; use core::fmt::{Debug, Display, Formatter}; -use bouncycastle_sha3::SHA3_256; -use crate::low_memory_helpers::{compute_A_hat_dot_s_hat, pack_s_hat_row, pack_t_hat_row}; -use crate::polynomial::{Polynomial}; // imports just for docs @@ -20,16 +31,39 @@ use crate::polynomial::{Polynomial}; /// ML-KEM-512 Public Key pub type MLKEM512PublicKey = MLKEMPublicKey; /// ML-KEM-512 Private Key -pub type MLKEM512PrivateKey = MLKEMSeedPrivateKey; +pub type MLKEM512PrivateKey = MLKEMSeedPrivateKey< + MLKEM512_k, + MLKEM512_ETA1, + MLKEM512_LAMBDA, + MLKEM512_SK_LEN, + MLKEM512_FULL_SK_LEN, + MLKEM512_PK_LEN, + MLKEM512_T_PACKED_LEN, +>; /// ML-KEM-768 Public Key pub type MLKEM768PublicKey = MLKEMPublicKey; /// ML-KEM-768 Private Key -pub type MLKEM768PrivateKey = MLKEMSeedPrivateKey; +pub type MLKEM768PrivateKey = MLKEMSeedPrivateKey< + MLKEM768_k, + MLKEM768_ETA1, + MLKEM768_LAMBDA, + MLKEM768_SK_LEN, + MLKEM768_FULL_SK_LEN, + MLKEM768_PK_LEN, + MLKEM768_T_PACKED_LEN, +>; /// ML-KEM-1024 Public Key pub type MLKEM1024PublicKey = MLKEMPublicKey; /// ML-KEM-1024 Private Key -pub type MLKEM1024PrivateKey = MLKEMSeedPrivateKey; - +pub type MLKEM1024PrivateKey = MLKEMSeedPrivateKey< + MLKEM1024_k, + MLKEM1024_ETA1, + MLKEM1024_LAMBDA, + MLKEM1024_SK_LEN, + MLKEM1024_FULL_SK_LEN, + MLKEM1024_PK_LEN, + MLKEM1024_T_PACKED_LEN, +>; /// An ML-KEM public key. #[derive(Clone)] @@ -39,12 +73,15 @@ pub struct MLKEMPublicKey : KEMPublicKey { +pub trait MLKEMPublicKeyTrait: + KEMPublicKey +{ /// Algorithm 23 pkDecode(π‘π‘˜) /// Reverses the procedure pkEncode. /// Input: Public key π‘π‘˜ ∈ 𝔹32+32π‘˜(bitlen (π‘žβˆ’1)βˆ’π‘‘). /// Output: 𝜌 ∈ 𝔹32, 𝐭1 ∈ π‘…π‘˜ with coefficients in [0, 2bitlen (π‘žβˆ’1)βˆ’π‘‘ βˆ’ 1]. - fn pk_decode(pk: &[u8; PK_LEN]) -> Self; + // todo: go make the equivalent thing also throw an error in the non-optimized impl + fn pk_decode(pk: &[u8; PK_LEN]) -> Result; /// Get a ref to t_hat_packed byte array fn t_hat_packed(&self) -> &[u8; T_PACKED_LEN]; @@ -59,17 +96,34 @@ pub trait MLKEMPublicKeyTrait : MLKEMPublicKeyTrait { + const PK_LEN: usize, +>: MLKEMPublicKeyTrait +{ /// Not exposing a constructor publicly because you should have to get an instance either by /// running a keygen, or by decoding an existing key. fn new(t_hat: [u8; T_PACKED_LEN], rho: [u8; 32]) -> Self; } impl -MLKEMPublicKeyTrait for MLKEMPublicKey { - fn pk_decode(pk: &[u8; PK_LEN]) -> Self { - Self::new(pk[..T_PACKED_LEN].try_into().unwrap(), pk[T_PACKED_LEN..].try_into().unwrap()) + MLKEMPublicKeyTrait for MLKEMPublicKey +{ + fn pk_decode(pk: &[u8; PK_LEN]) -> Result { + let pk = Self::new( + pk[..T_PACKED_LEN].try_into().unwrap(), + pk[T_PACKED_LEN..].try_into().unwrap(), + ); + + // check that all entries are in range + for i in 0..k { + let p = unpack_t_hat_row(&pk.t_hat_packed, i); + for w in p.coeffs.iter() { + if *w >= q { + return Err(KEMError::DecodingError("Invalid public key")); + } + } + } + + Ok(pk) } fn t_hat_packed(&self) -> &[u8; T_PACKED_LEN] { @@ -94,20 +148,23 @@ MLKEMPublicKeyTrait for MLKEMPublicKey -MLKEMPublicKeyInternalTrait for MLKEMPublicKey { + MLKEMPublicKeyInternalTrait + for MLKEMPublicKey +{ fn new(t_hat_packed: [u8; T_PACKED_LEN], rho: [u8; 32]) -> Self { Self { rho, t_hat_packed } } } -impl -KEMPublicKey for MLKEMPublicKey { +impl KEMPublicKey + for MLKEMPublicKey +{ /// Algorithm 22 pkEncode(𝜌, 𝐭1) /// Encodes a public key for ML-DSA into a byte string. /// Input:𝜌 ∈ 𝔹32, 𝐭1 ∈ π‘…π‘˜ with coefficients in [0, 2bitlen (π‘žβˆ’1)βˆ’π‘‘ βˆ’ 1]. /// Output: Public key π‘π‘˜ ∈ 𝔹32+32π‘˜(bitlen (π‘žβˆ’1)βˆ’π‘‘). fn encode(&self) -> [u8; PK_LEN] { - debug_assert_eq!(PK_LEN, 32 + 12*k*32); + debug_assert_eq!(PK_LEN, 32 + 12 * k * 32); let mut pk = [0u8; PK_LEN]; self.encode_out(&mut pk); @@ -117,7 +174,7 @@ KEMPublicKey for MLKEMPublicKey { fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize { debug_assert_eq!(self.t_hat_packed.len(), T_PACKED_LEN); - out[.. T_PACKED_LEN].copy_from_slice(&self.t_hat_packed); + out[..T_PACKED_LEN].copy_from_slice(&self.t_hat_packed); debug_assert_eq!(out[T_PACKED_LEN..].len(), 32); out[T_PACKED_LEN..].copy_from_slice(&self.rho); @@ -125,24 +182,30 @@ KEMPublicKey for MLKEMPublicKey { } fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != PK_LEN { return Err(KEMError::DecodingError("Provided key bytes are the incorrect length")) } + if bytes.len() != PK_LEN { + return Err(KEMError::DecodingError("Provided key bytes are the incorrect length")); + } let bytes_sized: [u8; PK_LEN] = bytes[..PK_LEN].try_into().unwrap(); - Ok(Self::pk_decode(&bytes_sized)) + Self::pk_decode(&bytes_sized) } } -impl -Eq for MLKEMPublicKey { } +impl Eq + for MLKEMPublicKey +{ +} -impl -PartialEq for MLKEMPublicKey { +impl PartialEq + for MLKEMPublicKey +{ fn eq(&self, other: &Self) -> bool { bouncycastle_utils::ct::ct_eq_bytes(&self.encode(), &other.encode()) } } -impl -Debug for MLKEMPublicKey { +impl Debug + for MLKEMPublicKey +{ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let alg = match k { 2 => ML_KEM_512_NAME, @@ -155,8 +218,9 @@ Debug for MLKEMPublicKey { } } -impl -Display for MLKEMPublicKey { +impl Display + for MLKEMPublicKey +{ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let alg = match k { 2 => ML_KEM_512_NAME, @@ -169,10 +233,6 @@ Display for MLKEMPublicKey { } } - - - - /// An ML-KEM private key. #[derive(Clone)] pub struct MLKEMSeedPrivateKey< @@ -182,7 +242,7 @@ pub struct MLKEMSeedPrivateKey< const SK_LEN: usize, const FULL_SK_LEN: usize, const PK_LEN: usize, - const T_PACKED_LEN: usize + const T_PACKED_LEN: usize, > { rho: [u8; 32], sigma: [u8; 32], @@ -198,8 +258,9 @@ impl< const SK_LEN: usize, const FULL_SK_LEN: usize, const PK_LEN: usize, - const T_PACKED_LEN: usize -> MLKEMSeedPrivateKey { + const T_PACKED_LEN: usize, +> MLKEMSeedPrivateKey +{ /// Create a new MLKEMSeedPrivateKey from a 64-byte KeyMaterial. /// Seed SecurityStrength must match algorithm security strength: 128-bit (ML-KEM-512), 192-bit (ML-KEM-768), or 256-bit (ML-KEM-1024). pub fn new(seed: &KeyMaterial<64>) -> Result { @@ -241,7 +302,6 @@ impl< } } - /// General trait for all ML-KEM private keys types. pub trait MLKEMPrivateKeyTrait< const k: usize, @@ -249,7 +309,8 @@ pub trait MLKEMPrivateKeyTrait< const FULL_SK_LEN: usize, const PK_LEN: usize, const T_PACKED_LEN: usize, -> : KEMPrivateKey { +>: KEMPrivateKey +{ /// New from KeyMaterial. Can throw a KEMError if the KeyMaterial does not contain sufficient entropy. fn from_keymaterial(seed: &KeyMaterial<64>) -> Result; /// Get a ref to the seed, which there always will be for a MLKEMSeedPrivateKey @@ -281,14 +342,18 @@ pub trait MLKEMPrivateKeyTrait< /// /// As described on Algorithm 16 line /// 3: dk ← (dkPKE β€– ek β€– H(ek) β€– 𝑧) - fn full_sk_encode_out(&self, out: &mut [u8; FULL_SK_LEN]) -> usize; + fn encode_full_sk_out(&self, out: &mut [u8; FULL_SK_LEN]) -> usize; /// Decode the private key. fn sk_decode(sk: &[u8; SK_LEN]) -> Self; } -pub(crate) trait MLKEMPrivateKeyInternalTrait { - +pub(crate) trait MLKEMPrivateKeyInternalTrait< + const k: usize, + const SK_LEN: usize, + const PK_LEN: usize, + const T_PACKED_LEN: usize, +> +{ fn z(&self) -> &[u8; 32]; fn compute_s_hat_row(&self, idx: usize) -> Polynomial; @@ -299,7 +364,6 @@ pub(crate) trait MLKEMPrivateKeyInternalTrait [u8; T_PACKED_LEN]; } - impl< const k: usize, const eta1: i16, @@ -307,8 +371,10 @@ impl< const SK_LEN: usize, const FULL_SK_LEN: usize, const PK_LEN: usize, - const T_PACKED_LEN: usize -> MLKEMPrivateKeyTrait for MLKEMSeedPrivateKey { + const T_PACKED_LEN: usize, +> MLKEMPrivateKeyTrait + for MLKEMSeedPrivateKey +{ fn from_keymaterial(seed: &KeyMaterial<64>) -> Result { Self::new(seed) } @@ -318,12 +384,13 @@ impl< tmp[32..].copy_from_slice(&self.z); let mut seed = KeyMaterial::<64>::from_bytes_as_type(&tmp, KeyType::Seed).unwrap(); seed.allow_hazardous_operations(); - seed.set_security_strength( match k { + seed.set_security_strength(match k { 2 => SecurityStrength::_128bit, 3 => SecurityStrength::_192bit, 4 => SecurityStrength::_256bit, _ => unreachable!("Invalid mlkem param set"), - }).unwrap(); + }) + .unwrap(); seed.drop_hazardous_operations(); Some(seed) @@ -348,7 +415,7 @@ impl< /// 3: dk ← (dkPKE β€– ek β€– H(ek) β€– 𝑧) fn encode_full_sk(&self) -> [u8; FULL_SK_LEN] { let mut out = [0u8; FULL_SK_LEN]; - self.full_sk_encode_out(&mut out); + self.encode_full_sk_out(&mut out); out } @@ -359,7 +426,7 @@ impl< /// /// As described on Algorithm 16 line /// 3: dk ← (dkPKE β€– ek β€– H(ek) β€– 𝑧) - fn full_sk_encode_out(&self, out: &mut [u8; FULL_SK_LEN]) -> usize { + fn encode_full_sk_out(&self, out: &mut [u8; FULL_SK_LEN]) -> usize { out.fill(0); let mut pos = 0usize; @@ -367,9 +434,6 @@ impl< /* dk_pke */ // Alg 13; line 20: dkPKE ← ByteEncode12(𝐬) for i in 0..k { - // out[i*POLY_BYTES .. (i+1)*POLY_BYTES].copy_from_slice(&byte_encode::<12, POLY_BYTES>( - // &self.compute_s_hat_row(i), - // )); pack_s_hat_row::(&self.compute_s_hat_row(i), i, out); } pos += k * POLY_BYTES; @@ -377,16 +441,15 @@ impl< /* ek */ // Alg 13; line 19: ekPKE ← ByteEncode12(𝐭)β€–πœŒ let pk = self.pk(); - out[pos .. pos + PK_LEN].copy_from_slice(&pk.encode()); + out[pos..pos + PK_LEN].copy_from_slice(&pk.encode()); pos += PK_LEN; /* H(ek) */ - out[pos .. pos + 32].copy_from_slice(&pk.compute_hash()); + out[pos..pos + 32].copy_from_slice(&pk.compute_hash()); pos += 32; /* z */ - out[pos .. pos + 32].copy_from_slice(&self.z); - + out[pos..pos + 32].copy_from_slice(&self.z); FULL_SK_LEN } @@ -403,10 +466,13 @@ impl< const SK_LEN: usize, const FULL_SK_LEN: usize, const PK_LEN: usize, - const T_PACKED_LEN: usize -> MLKEMPrivateKeyInternalTrait for MLKEMSeedPrivateKey { - - fn z(&self) -> &[u8; 32] { &self.z } + const T_PACKED_LEN: usize, +> MLKEMPrivateKeyInternalTrait + for MLKEMSeedPrivateKey +{ + fn z(&self) -> &[u8; 32] { + &self.z + } fn compute_s_hat_row(&self, idx: usize) -> Polynomial { debug_assert!(idx < k); @@ -433,7 +499,7 @@ impl< fn t_hat_packed(&self) -> [u8; T_PACKED_LEN] { let mut t_hat_packed = [0u8; T_PACKED_LEN]; - for i in 0 .. k { + for i in 0..k { // first half of // 18: 𝐭_hat ← 𝐀_hat ∘ 𝐬_hat + 𝐞_hat let mut t_hat_i = compute_A_hat_dot_s_hat::(&self.rho, &self.sigma, i); @@ -449,7 +515,7 @@ impl< // Note: here n = k let mut e_i = sample_poly_CBD::(&self.sigma, (k + i) as u8); - e_i.ntt(); // technically now e_hat_i + e_i.ntt(); // technically now e_hat_i t_hat_i.add(&e_i); } t_hat_i.poly_reduce(); @@ -469,7 +535,9 @@ impl< const FULL_SK_LEN: usize, const PK_LEN: usize, const T_PACKED_LEN: usize, -> KEMPrivateKey for MLKEMSeedPrivateKey { +> KEMPrivateKey + for MLKEMSeedPrivateKey +{ /// Encode the private key as a 64-byte seed (d || z) fn encode(&self) -> [u8; SK_LEN] { let mut sk = [0u8; SK_LEN]; @@ -509,7 +577,9 @@ impl< const FULL_SK_LEN: usize, const PK_LEN: usize, const T_PACKED_LEN: usize, -> Eq for MLKEMSeedPrivateKey {} +> Eq for MLKEMSeedPrivateKey +{ +} impl< const k: usize, @@ -536,7 +606,9 @@ impl< const FULL_SK_LEN: usize, const PK_LEN: usize, const T_PACKED_LEN: usize, -> Secret for MLKEMSeedPrivateKey {} +> Secret for MLKEMSeedPrivateKey +{ +} /// Debug impl mainly to prevent the secret key from being printed in logs. impl< @@ -550,19 +622,14 @@ impl< > fmt::Debug for MLKEMSeedPrivateKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let alg = match k { - 2 => ML_KEM_512_NAME, - 3 => ML_KEM_768_NAME, - 4 => ML_KEM_1024_NAME, - _ => panic!("Unsupported key length"), - }; + let alg = match k { + 2 => ML_KEM_512_NAME, + 3 => ML_KEM_768_NAME, + 4 => ML_KEM_1024_NAME, + _ => panic!("Unsupported key length"), + }; let pk_hash = self.pk().compute_hash(); - write!( - f, - "MLKEMSeedPrivateKey {{ alg: {}, pub_key_hash: {:x?} }}", - alg, - &pk_hash, - ) + write!(f, "MLKEMSeedPrivateKey {{ alg: {}, pub_key_hash: {:x?} }}", alg, &pk_hash,) } } @@ -585,12 +652,7 @@ impl< _ => panic!("Unsupported key length"), }; let pk_hash = self.pk().compute_hash(); - write!( - f, - "MLKEMSeedPrivateKey {{ alg: {}, pub_key_hash: {:x?} }}", - alg, - &pk_hash, - ) + write!(f, "MLKEMSeedPrivateKey {{ alg: {}, pub_key_hash: {:x?} }}", alg, &pk_hash,) } } diff --git a/crypto/mlkem_lowmemory/tests/bc_test_data.rs b/crypto/mlkem_lowmemory/tests/bc_test_data.rs index 848a60f..8379b2e 100644 --- a/crypto/mlkem_lowmemory/tests/bc_test_data.rs +++ b/crypto/mlkem_lowmemory/tests/bc_test_data.rs @@ -4,290 +4,357 @@ // This whole file doesn't work because the bc-test-data repository only has full private keys and not seeds -// #[cfg(test)] -// mod bc_test_data { -// use std::{fs}; -// use bouncycastle_hex as hex; -// use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial512, KeyType}; -// use bouncycastle_core::traits::{KEMPrivateKey, KEMPublicKey, SecurityStrength, KEM}; -// use bouncycastle_mlkem_lowmemory::{MLKEM1024PrivateKey, MLKEM1024PublicKey, MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey, MLKEMPrivateKeyTrait, MLKEMTrait, MLKEM1024, MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM512, MLKEM512_PK_LEN, MLKEM512_SK_LEN, MLKEM768, MLKEM768_PK_LEN, MLKEM768_SK_LEN}; -// use bouncycastle_mlkem_lowmemory::mlkem::{MLKEM1024_FULL_SK_LEN, MLKEM512_FULL_SK_LEN, MLKEM768_FULL_SK_LEN}; -// -// const TEST_DATA_PATH: &str = "../../../bc-test-data/pqc/crypto/mlkem"; -// -// #[test] -// #[allow(non_snake_case)] -// fn ML_KEM_keyGen() { -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-KEM-keyGen.txt").unwrap(); -// let test_cases = KeyGenTestCase::parse(contents); -// -// for test_case in test_cases { -// test_case.run(); -// } -// } -// -// #[derive(Clone)] -// struct KeyGenTestCase { -// vs_id: u32, -// algorithm: String, -// mode: String, -// revision: String, -// is_sample: bool, -// tg_id: u32, -// test_type: String, -// parameter_set: String, -// tc_id: u32, -// z: String, -// d: String, -// ek: String, -// dk: String, -// } -// -// impl KeyGenTestCase { -// fn new() -> Self { -// Self { vs_id: 0, algorithm: String::new(), mode: String::new(), revision: String::new(), is_sample: false, tg_id: 0, test_type: String::new(), parameter_set: String::new(), tc_id: 0, z: String::new(), d: String::new(), ek: String::new(), dk: String::new() } -// } -// -// fn is_full(&self) -> bool { -// !self.algorithm.is_empty() -// } -// -// fn parse(data: String) -> Vec { -// let mut test_cases = Vec::::new(); -// let mut test_case = KeyGenTestCase::new(); -// for line in data.lines() { -// let (tag, value) = match line.split_once(" = ") { -// Some(pair) => pair, -// None => { -// if test_case.is_full() { test_cases.push(test_case.clone()); } -// continue; -// } -// }; -// -// match tag { -// "vsId" => test_case.vs_id = value.parse().unwrap(), -// "algorithm" => test_case.algorithm = value.to_string(), -// "mode" => test_case.mode = value.to_string(), -// "revision" => test_case.revision = value.to_string(), -// "isSample" => test_case.is_sample = value.parse().unwrap(), -// "tgId" => test_case.tg_id = value.parse().unwrap(), -// "testType" => test_case.test_type = value.to_string(), -// "parameterSet" => test_case.parameter_set = value.to_string(), -// "tcId" => test_case.tc_id = value.parse().unwrap(), -// "z" => test_case.z = value.to_string(), -// "d" => test_case.d = value.to_string(), -// "ek" => test_case.ek = value.to_string(), -// "dk" => test_case.dk = value.to_string(), -// val => panic!("Invalid tag: {}", val), -// } -// } -// -// test_cases -// } -// -// fn run(&self) { -// assert_eq!(self.mode, "keyGen"); -// -// let mut seed_bytes = [0u8; 64]; -// seed_bytes[..32].copy_from_slice(&hex::decode(&self.d).unwrap()); -// seed_bytes[32..].copy_from_slice(&hex::decode(&self.z).unwrap()); -// -// let mut seed = KeyMaterial512::from_bytes_as_type( -// &seed_bytes, -// KeyType::Seed, -// ).unwrap(); -// -// // for the purposes of the test cases, accept an all-zero seed -// seed.allow_hazardous_operations(); -// seed.set_key_type(KeyType::Seed).unwrap(); -// seed.set_security_strength(SecurityStrength::_256bit).unwrap(); -// seed.drop_hazardous_operations(); -// -// match self.parameter_set.as_str() { -// "ML-KEM-512" => { -// let (pk, sk) = MLKEM512::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLKEM512_PK_LEN] = hex::decode(&self.ek).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLKEM512_FULL_SK_LEN] = hex::decode(&self.dk).unwrap().try_into().unwrap(); -// assert_eq!(sk.full_sk_encode(), sk_sized); -// }, -// "ML-KEM-768" => { -// let (pk, sk) = MLKEM768::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLKEM768_PK_LEN] = hex::decode(&self.ek).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLKEM768_FULL_SK_LEN] = hex::decode(&self.dk).unwrap().try_into().unwrap(); -// assert_eq!(sk.full_sk_encode(), sk_sized); -// }, -// "ML-KEM-1024" => { -// let (pk, sk) = MLKEM1024::keygen_from_seed(&seed).unwrap(); -// let pk_sized: [u8; MLKEM1024_PK_LEN] = hex::decode(&self.ek).unwrap().try_into().unwrap(); -// assert_eq!(pk.encode(), pk_sized); -// let sk_sized: [u8; MLKEM1024_FULL_SK_LEN] = hex::decode(&self.dk).unwrap().try_into().unwrap(); -// assert_eq!(sk.full_sk_encode(), sk_sized); -// }, -// val => panic!("Invalid parameter set: {}", val), -// } -// } -// } -// -// #[test] -// #[allow(non_snake_case)] -// fn ML_KEM_encapDecap() { -// let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-KEM-encapDecap.txt").unwrap(); -// let test_cases = EncapDecapTestCase::parse(contents); -// -// let num_tests = test_cases.len(); -// for test_case in test_cases { -// test_case.run(); -// } -// -// println!("SUCCESS! ML-DSA-sigGen test cases passed: {}!", num_tests); -// } -// -// #[derive(Clone)] -// struct EncapDecapTestCase { -// vs_id: u32, -// algorithm: String, -// mode: String, -// revision: String, -// is_sample: bool, -// tg_id: u32, -// test_type: String, -// parameter_set: String, -// function: String, -// tc_id: u32, -// ek: String, -// dk: String, -// m: String, -// c: String, -// k: String, -// } -// -// impl EncapDecapTestCase { -// fn new() -> Self { -// Self { vs_id: 0, algorithm: String::new(), mode: String::new(), revision: String::new(), is_sample: false, tg_id: 0, test_type: String::new(), parameter_set: String::new(), function: String::new(), tc_id: 0, ek: String::new(), dk: String::new(), m: String::new(), c: String::new(), k: String::new() } -// } -// -// fn is_full(&self) -> bool { -// !self.algorithm.is_empty() -// } -// -// fn parse(data: String) -> Vec { -// let mut test_cases = Vec::::new(); -// let mut test_case = EncapDecapTestCase::new(); -// for line in data.lines() { -// let (tag, value) = match line.split_once(" = ") { -// Some(pair) => pair, -// None => { -// if test_case.is_full() { test_cases.push(test_case.clone()); } -// continue; -// } -// }; -// -// match tag { -// "vsId" => test_case.vs_id = value.parse().unwrap(), -// "algorithm" => test_case.algorithm = value.to_string(), -// "mode" => test_case.mode = value.to_string(), -// "revision" => test_case.revision = value.to_string(), -// "isSample" => test_case.is_sample = value.parse().unwrap(), -// "tgId" => test_case.tg_id = value.parse().unwrap(), -// "testType" => test_case.test_type = value.to_string(), -// "parameterSet" => test_case.parameter_set = value.to_string(), -// "function" => test_case.function = value.to_string(), -// "tcId" => test_case.tc_id = value.parse().unwrap(), -// "ek" => test_case.ek = value.to_string(), -// "dk" => test_case.dk = value.to_string(), -// "m" => test_case.m = value.to_string(), -// "c" => test_case.c = value.to_string(), -// "k" => test_case.k = value.to_string(), -// val => panic!("Invalid tag: {}", val), -// } -// } -// -// test_cases -// } -// -// fn run(&self) { -// assert_eq!(self.mode, "encapDecap"); -// -// let mut seed = [0u8; 64]; -// seed[..32].copy_from_slice(&hex::decode(&self.).unwrap()); -// -// match self.parameter_set.as_str() { -// "ML-KEM-512" => { -// match self.function.as_str() { -// "encapsulation" => { -// let pk = MLKEM512PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap(); -// let m: [u8; 32] = hex::decode(&self.m).unwrap().try_into().unwrap(); -// let (ss, ct) = MLKEM512::encaps_internal(&pk, m); -// -// let expected_ss = hex::decode(&self.k).unwrap(); -// let expected_ct = hex::decode(&self.c).unwrap(); -// -// assert_eq!(ss, expected_ss.as_slice()); -// assert_eq!(ct, expected_ct.as_slice()); -// }, -// "decapsulation" => { -// let sk = MLKEM512PrivateKey::from_bytes(&hex::decode(&self.).unwrap()).unwrap(); -// let ct = hex::decode(&self.c).unwrap(); -// let ss = MLKEM512::decaps(&sk, ct.as_slice()).unwrap(); -// -// let expected_ss = hex::decode(&self.k).unwrap(); -// assert_eq!(ss.ref_to_bytes(), expected_ss.as_slice()); -// }, -// _ => panic!("Invalid function: {}", self.function), -// }; -// }, -// "ML-KEM-768" => { -// match self.function.as_str() { -// "encapsulation" => { -// let pk = MLKEM768PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap(); -// let m: [u8; 32] = hex::decode(&self.m).unwrap().try_into().unwrap(); -// let (ss, ct) = MLKEM768::encaps_internal(&pk, m); -// -// let expected_ss = hex::decode(&self.k).unwrap(); -// let expected_ct = hex::decode(&self.c).unwrap(); -// -// assert_eq!(ss, expected_ss.as_slice()); -// assert_eq!(ct, expected_ct.as_slice()); -// }, -// "decapsulation" => { -// let sk = MLKEM768PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()).unwrap(); -// let ct = hex::decode(&self.c).unwrap(); -// let ss = MLKEM768::decaps(&sk, ct.as_slice()).unwrap(); -// -// let expected_ss = hex::decode(&self.k).unwrap(); -// assert_eq!(ss.ref_to_bytes(), expected_ss.as_slice()); -// }, -// _ => panic!("Invalid function: {}", self.function), -// }; -// }, -// "ML-KEM-1024" => { -// match self.function.as_str() { -// "encapsulation" => { -// let pk = MLKEM1024PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap(); -// let m: [u8; 32] = hex::decode(&self.m).unwrap().try_into().unwrap(); -// let (ss, ct) = MLKEM1024::encaps_internal(&pk, m); -// -// let expected_ss = hex::decode(&self.k).unwrap(); -// let expected_ct = hex::decode(&self.c).unwrap(); -// -// assert_eq!(ss, expected_ss.as_slice()); -// assert_eq!(ct, expected_ct.as_slice()); -// }, -// "decapsulation" => { -// let sk = MLKEM1024PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()).unwrap(); -// let ct = hex::decode(&self.c).unwrap(); -// let ss = MLKEM1024::decaps(&sk, ct.as_slice()).unwrap(); -// -// let expected_ss = hex::decode(&self.k).unwrap(); -// assert_eq!(ss.ref_to_bytes(), expected_ss.as_slice()); -// }, -// _ => panic!("Invalid function: {}", self.function), -// }; -// }, -// val => panic!("Invalid parameter set: {}", val), -// } -// } -// } -// } \ No newline at end of file +#[cfg(test)] +mod bc_test_data { + use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{KEMPublicKey, SecurityStrength}; + use bouncycastle_hex as hex; + use bouncycastle_mlkem_lowmemory::mlkem::{ + MLKEM512_FULL_SK_LEN, MLKEM768_FULL_SK_LEN, MLKEM1024_FULL_SK_LEN, + }; + use bouncycastle_mlkem_lowmemory::{ + MLKEM512, MLKEM512_PK_LEN, MLKEM768, MLKEM768_PK_LEN, MLKEM1024, MLKEM1024_PK_LEN, + MLKEMPrivateKeyTrait, MLKEMTrait, + }; + use std::fs; + use std::path::Path; + use std::sync::Once; + + const TEST_DATA_PATH_RELATIVE: &str = "../../../bc-test-data/pqc/crypto/mlkem"; + const TEST_DATA_PATH: &str = "../bc-test-data/pqc/crypto/mlkem"; + + static TEST_DATA_CHECK: Once = Once::new(); + + fn get_test_data(filename: &str) -> Result { + let found: u8; + if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + found = 1; + } else if Path::new(TEST_DATA_PATH).exists() { + found = 2; + } else { + found = 3; + }; + + // just print once + TEST_DATA_CHECK.call_once(|| match found { + 1 => println!("wycheproof found at: {:?}", TEST_DATA_PATH_RELATIVE), + 2 => println!("wycheproof found at: {:?}", TEST_DATA_PATH), + _ => println!("WARNING: wycheproof directory not found; tests will be skipped"), + }); + + if !found == 3 { + return Err(()); + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/" + filename).unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/" + filename).unwrap() + } else { + return Err(()); + }; + + Ok(contents) + } + + #[test] + #[allow(non_snake_case)] + fn ML_KEM_keyGen() { + let contents = match get_test_data("ML-KEM-keyGen.txt") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = KeyGenTestCase::parse(contents); + + for test_case in test_cases { + test_case.run(); + } + } + + #[derive(Clone)] + struct KeyGenTestCase { + vs_id: u32, + algorithm: String, + mode: String, + revision: String, + is_sample: bool, + tg_id: u32, + test_type: String, + parameter_set: String, + tc_id: u32, + z: String, + d: String, + ek: String, + dk: String, + } + + impl KeyGenTestCase { + fn new() -> Self { + Self { + vs_id: 0, + algorithm: String::new(), + mode: String::new(), + revision: String::new(), + is_sample: false, + tg_id: 0, + test_type: String::new(), + parameter_set: String::new(), + tc_id: 0, + z: String::new(), + d: String::new(), + ek: String::new(), + dk: String::new(), + } + } + + fn is_full(&self) -> bool { + !self.algorithm.is_empty() + } + + fn parse(data: String) -> Vec { + let mut test_cases = Vec::::new(); + let mut test_case = KeyGenTestCase::new(); + for line in data.lines() { + let (tag, value) = match line.split_once(" = ") { + Some(pair) => pair, + None => { + if test_case.is_full() { + test_cases.push(test_case.clone()); + } + continue; + } + }; + + match tag { + "vsId" => test_case.vs_id = value.parse().unwrap(), + "algorithm" => test_case.algorithm = value.to_string(), + "mode" => test_case.mode = value.to_string(), + "revision" => test_case.revision = value.to_string(), + "isSample" => test_case.is_sample = value.parse().unwrap(), + "tgId" => test_case.tg_id = value.parse().unwrap(), + "testType" => test_case.test_type = value.to_string(), + "parameterSet" => test_case.parameter_set = value.to_string(), + "tcId" => test_case.tc_id = value.parse().unwrap(), + "z" => test_case.z = value.to_string(), + "d" => test_case.d = value.to_string(), + "ek" => test_case.ek = value.to_string(), + "dk" => test_case.dk = value.to_string(), + val => panic!("Invalid tag: {}", val), + } + } + + test_cases + } + + fn run(&self) { + assert_eq!(self.mode, "keyGen"); + + let mut seed_bytes = [0u8; 64]; + seed_bytes[..32].copy_from_slice(&*hex::decode(&self.d).unwrap()); + seed_bytes[32..].copy_from_slice(&*hex::decode(&self.z).unwrap()); + + let mut seed = KeyMaterial512::from_bytes_as_type(&seed_bytes, KeyType::Seed).unwrap(); + + // for the purposes of the test cases, accept an all-zero seed + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit).unwrap(); + seed.drop_hazardous_operations(); + + match self.parameter_set.as_str() { + "ML-KEM-512" => { + let (pk, sk) = MLKEM512::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLKEM512_PK_LEN] = + hex::decode(&self.ek).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLKEM512_FULL_SK_LEN] = + hex::decode(&self.dk).unwrap().try_into().unwrap(); + assert_eq!(sk.encode_full_sk(), sk_sized); + } + "ML-KEM-768" => { + let (pk, sk) = MLKEM768::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLKEM768_PK_LEN] = + hex::decode(&self.ek).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLKEM768_FULL_SK_LEN] = + hex::decode(&self.dk).unwrap().try_into().unwrap(); + assert_eq!(sk.encode_full_sk(), sk_sized); + } + "ML-KEM-1024" => { + let (pk, sk) = MLKEM1024::keygen_from_seed(&seed).unwrap(); + let pk_sized: [u8; MLKEM1024_PK_LEN] = + hex::decode(&self.ek).unwrap().try_into().unwrap(); + assert_eq!(pk.encode(), pk_sized); + let sk_sized: [u8; MLKEM1024_FULL_SK_LEN] = + hex::decode(&self.dk).unwrap().try_into().unwrap(); + assert_eq!(sk.encode_full_sk(), sk_sized); + } + val => panic!("Invalid parameter set: {}", val), + } + } + } + + // Doesn't work here because the bc-test-data doesn't include seeds + // #[test] + // #[allow(non_snake_case)] + // fn ML_KEM_encapDecap() { + // let contents = fs::read_to_string(TEST_DATA_PATH.to_string() + "/ML-KEM-encapDecap.txt").unwrap(); + // let test_cases = EncapDecapTestCase::parse(contents); + // + // let num_tests = test_cases.len(); + // for test_case in test_cases { + // test_case.run(); + // } + // + // println!("SUCCESS! ML-DSA-sigGen test cases passed: {}!", num_tests); + // } + + // #[derive(Clone)] + // struct EncapDecapTestCase { + // vs_id: u32, + // algorithm: String, + // mode: String, + // revision: String, + // is_sample: bool, + // tg_id: u32, + // test_type: String, + // parameter_set: String, + // function: String, + // tc_id: u32, + // ek: String, + // dk: String, + // m: String, + // c: String, + // k: String, + // } + // + // impl EncapDecapTestCase { + // fn new() -> Self { + // Self { vs_id: 0, algorithm: String::new(), mode: String::new(), revision: String::new(), is_sample: false, tg_id: 0, test_type: String::new(), parameter_set: String::new(), function: String::new(), tc_id: 0, ek: String::new(), dk: String::new(), m: String::new(), c: String::new(), k: String::new() } + // } + // + // fn is_full(&self) -> bool { + // !self.algorithm.is_empty() + // } + // + // fn parse(data: String) -> Vec { + // let mut test_cases = Vec::::new(); + // let mut test_case = EncapDecapTestCase::new(); + // for line in data.lines() { + // let (tag, value) = match line.split_once(" = ") { + // Some(pair) => pair, + // None => { + // if test_case.is_full() { test_cases.push(test_case.clone()); } + // continue; + // } + // }; + // + // match tag { + // "vsId" => test_case.vs_id = value.parse().unwrap(), + // "algorithm" => test_case.algorithm = value.to_string(), + // "mode" => test_case.mode = value.to_string(), + // "revision" => test_case.revision = value.to_string(), + // "isSample" => test_case.is_sample = value.parse().unwrap(), + // "tgId" => test_case.tg_id = value.parse().unwrap(), + // "testType" => test_case.test_type = value.to_string(), + // "parameterSet" => test_case.parameter_set = value.to_string(), + // "function" => test_case.function = value.to_string(), + // "tcId" => test_case.tc_id = value.parse().unwrap(), + // "ek" => test_case.ek = value.to_string(), + // "dk" => test_case.dk = value.to_string(), + // "m" => test_case.m = value.to_string(), + // "c" => test_case.c = value.to_string(), + // "k" => test_case.k = value.to_string(), + // val => panic!("Invalid tag: {}", val), + // } + // } + // + // test_cases + // } + // + // fn run(&self) { + // assert_eq!(self.mode, "encapDecap"); + // + // let mut seed = [0u8; 64]; + // seed[..32].copy_from_slice(&*hex::decode(&self.).unwrap()); + // + // match self.parameter_set.as_str() { + // "ML-KEM-512" => { + // match self.function.as_str() { + // "encapsulation" => { + // let pk = MLKEM512PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap(); + // let m: [u8; 32] = hex::decode(&self.m).unwrap().try_into().unwrap(); + // let (ss, ct) = MLKEM512::encaps_internal(&pk, m); + // + // let expected_ss = hex::decode(&self.k).unwrap(); + // let expected_ct = hex::decode(&self.c).unwrap(); + // + // assert_eq!(ss, expected_ss.as_slice()); + // assert_eq!(ct, expected_ct.as_slice()); + // }, + // "decapsulation" => { + // let sk = MLKEM512PrivateKey::from_bytes(&hex::decode(&self.).unwrap()).unwrap(); + // let ct = hex::decode(&self.c).unwrap(); + // let ss = MLKEM512::decaps(&sk, ct.as_slice()).unwrap(); + // + // let expected_ss = hex::decode(&self.k).unwrap(); + // assert_eq!(ss.ref_to_bytes(), expected_ss.as_slice()); + // }, + // _ => panic!("Invalid function: {}", self.function), + // }; + // }, + // "ML-KEM-768" => { + // match self.function.as_str() { + // "encapsulation" => { + // let pk = MLKEM768PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap(); + // let m: [u8; 32] = hex::decode(&self.m).unwrap().try_into().unwrap(); + // let (ss, ct) = MLKEM768::encaps_internal(&pk, m); + // + // let expected_ss = hex::decode(&self.k).unwrap(); + // let expected_ct = hex::decode(&self.c).unwrap(); + // + // assert_eq!(ss, expected_ss.as_slice()); + // assert_eq!(ct, expected_ct.as_slice()); + // }, + // "decapsulation" => { + // let sk = MLKEM768PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()).unwrap(); + // let ct = hex::decode(&self.c).unwrap(); + // let ss = MLKEM768::decaps(&sk, ct.as_slice()).unwrap(); + // + // let expected_ss = hex::decode(&self.k).unwrap(); + // assert_eq!(ss.ref_to_bytes(), expected_ss.as_slice()); + // }, + // _ => panic!("Invalid function: {}", self.function), + // }; + // }, + // "ML-KEM-1024" => { + // match self.function.as_str() { + // "encapsulation" => { + // let pk = MLKEM1024PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap(); + // let m: [u8; 32] = hex::decode(&self.m).unwrap().try_into().unwrap(); + // let (ss, ct) = MLKEM1024::encaps_internal(&pk, m); + // + // let expected_ss = hex::decode(&self.k).unwrap(); + // let expected_ct = hex::decode(&self.c).unwrap(); + // + // assert_eq!(ss, expected_ss.as_slice()); + // assert_eq!(ct, expected_ct.as_slice()); + // }, + // "decapsulation" => { + // let sk = MLKEM1024PrivateKey::from_bytes(&hex::decode(&self.dk).unwrap()).unwrap(); + // let ct = hex::decode(&self.c).unwrap(); + // let ss = MLKEM1024::decaps(&sk, ct.as_slice()).unwrap(); + // + // let expected_ss = hex::decode(&self.k).unwrap(); + // assert_eq!(ss.ref_to_bytes(), expected_ss.as_slice()); + // }, + // _ => panic!("Invalid function: {}", self.function), + // }; + // }, + // val => panic!("Invalid parameter set: {}", val), + // } + // } + // } + // } +} diff --git a/crypto/mlkem_lowmemory/tests/wycheproof.rs b/crypto/mlkem_lowmemory/tests/wycheproof.rs new file mode 100644 index 0000000..b331a3f --- /dev/null +++ b/crypto/mlkem_lowmemory/tests/wycheproof.rs @@ -0,0 +1,742 @@ +//! Test against the project wycheproof repo available at: +//! https://github.com/C2SP/wycheproof +//! Requires that the wycheproof repository is cloned and available for testing at "../wycheproof" +//! relative to the root of this git project. +//! +//! This test file exercises the following test sets: +//! +//! * mlkem_512_encaps_test.json +//! * mlkem_512_keygen_seed_test.json +//! * mlkem_512_test.json +//! * mlkem_768_encaps_test.json +//! * mlkem_768_keygen_seed_test.json +//! * mlkem_768_test.json +//! * mlkem_1024_encaps_test.json +//! * mlkem_1024_keygen_seed_test.json +//! * mlkem_1024_test.json +//! +//! Note: the following test sets are not tested because this low memory implementation requires +//! seed-based private keys and has no functionality for operating on non-seed private keys. +//! +//! * mlkem_512_semi_expanded_decaps_test.json +//! * mlkem_768_semi_expanded_decaps_test.json +//! * mlkem_1024_semi_expanded_decaps_test.json + +#![allow(dead_code)] + +use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; +use bouncycastle_core::traits::{KEM, KEMPublicKey, SecurityStrength}; +use bouncycastle_hex as hex; +use bouncycastle_mlkem_lowmemory::{ + MLKEM512, MLKEM512PublicKey, MLKEM768, MLKEM768PublicKey, MLKEM1024, MLKEM1024PublicKey, + MLKEMPrivateKeyTrait, MLKEMTrait, +}; + +#[cfg(test)] +mod wycheproof { + use crate::{MLKEMEncapsTestCase, MLKEMKeygenSeedTestCase, MLKEMTestCase, ParameterSet}; + use std::fs; + use std::path::Path; + use std::sync::Once; + + const TEST_DATA_PATH_RELATIVE: &str = "../../../wycheproof/testvectors_v1"; + const TEST_DATA_PATH: &str = "../wycheproof/testvectors_v1"; + + static TEST_DATA_CHECK: Once = Once::new(); + + fn get_test_data(filename: &str) -> Result { + let found: u8; + if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + found = 1; + } else if Path::new(TEST_DATA_PATH).exists() { + found = 2; + } else { + found = 3; + }; + + // just print once + TEST_DATA_CHECK.call_once(|| match found { + 1 => println!("wycheproof found at: {:?}", TEST_DATA_PATH_RELATIVE), + 2 => println!("wycheproof found at: {:?}", TEST_DATA_PATH), + _ => println!("WARNING: wycheproof directory not found; tests will be skipped"), + }); + + if !found == 3 { + return Err(()); + } + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/" + filename).unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/" + filename).unwrap() + } else { + return Err(()); + }; + + Ok(contents) + } + + #[test] + fn mlkem_512_encaps_test() { + let contents = match get_test_data("mlkem_512_encaps_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMEncapsTestCase::parse(contents, ParameterSet::Mlkem512); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem512(); + } + + println!("mlkem_512_encaps_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_512_keygen_seed_test() { + let contents = match get_test_data("mlkem_512_keygen_seed_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMKeygenSeedTestCase::parse(contents, ParameterSet::Mlkem512); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem512(); + } + + println!("mlkem_512_keygen_seed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_512_test() { + let contents = match get_test_data("mlkem_512_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMTestCase::parse(contents, ParameterSet::Mlkem512); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem512(); + } + + println!("mlkem_512_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_768_encaps_test() { + let contents = match get_test_data("mlkem_768_encaps_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMEncapsTestCase::parse(contents, ParameterSet::Mlkem768); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem768(); + } + + println!("mlkem_768_encaps_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_768_keygen_seed_test() { + let contents = match get_test_data("mlkem_768_keygen_seed_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMKeygenSeedTestCase::parse(contents, ParameterSet::Mlkem768); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem768(); + } + + println!("mlkem_768_keygen_seed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_768_test() { + let contents = match get_test_data("mlkem_768_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMTestCase::parse(contents, ParameterSet::Mlkem768); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem768(); + } + + println!("mlkem_768_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_1024_encaps_test() { + let contents = match get_test_data("mlkem_1024_encaps_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMEncapsTestCase::parse(contents, ParameterSet::Mlkem1024); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem1024(); + } + + println!("mlkem_1024_encaps_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_1024_keygen_seed_test() { + let contents = match get_test_data("mlkem_1024_keygen_seed_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMKeygenSeedTestCase::parse(contents, ParameterSet::Mlkem1024); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem1024(); + } + + println!("mlkem_1024_keygen_seed_test: all {} test cases passed.", num_test_cases); + } + + #[test] + fn mlkem_1024_test() { + let contents = match get_test_data("mlkem_1024_test.json") { + Ok(contents) => contents, + Err(()) => return, + }; + + let test_cases = MLKEMTestCase::parse(contents, ParameterSet::Mlkem1024); + + let num_test_cases = test_cases.len(); + for test_case in test_cases { + test_case.run_mlkem1024(); + } + + println!("mlkem_1024_test: all {} test cases passed.", num_test_cases); + } +} + +/* Structs for holding test data */ + +#[derive(Clone, Debug, PartialEq)] +enum ParameterSet { + Mlkem512, + Mlkem768, + Mlkem1024, +} + +#[derive(Clone)] +struct MLKEMEncapsTestCase { + parameter_set: ParameterSet, + tc_id: u32, + comment: String, + m: String, + ek: String, + c: String, + k: String, + result: String, +} + +impl MLKEMEncapsTestCase { + fn new(parameter_set: ParameterSet) -> Self { + Self { + parameter_set, + tc_id: 0, + comment: String::new(), + m: String::new(), + ek: String::new(), + c: String::new(), + k: String::new(), + result: String::new(), + } + } + + fn parse(data: String, parameter_set: ParameterSet) -> Vec { + let json: serde_json::Value = + serde_json::from_str(&data).expect("test data is not valid JSON"); + + let mut test_cases = Vec::::new(); + + let groups = json["testGroups"].as_array().expect("testGroups is not an array"); + for group in groups { + let tests = group["tests"].as_array().expect("tests is not an array"); + for test in tests { + test_cases.push(Self { + parameter_set: parameter_set.clone(), + tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, + comment: test["comment"].as_str().unwrap_or("").to_string(), + m: test["m"].as_str().unwrap_or("").to_string(), + ek: test["ek"].as_str().unwrap_or("").to_string(), + c: test["c"].as_str().unwrap_or("").to_string(), + k: test["K"].as_str().unwrap_or("").to_string(), + result: test["result"].as_str().unwrap_or("").to_string(), + }); + } + } + + test_cases + } + + fn run_mlkem512(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem512); + + /* Load the key */ + + let ek = match MLKEM512PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()) { + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e); + } + } + Ok(pk) => pk, + }; + + /* Perform the deterministic encaps and compare results */ + + // there's some weird stuff in the wycheproof tests that give a 64 byte m, even though + // it's only allowed to be 32 bytes. + // I guess we treat that as an error that we're meant to catch? + let m: [u8; 32] = match hex::decode(&self.m).unwrap().try_into() { + Ok(m) => m, + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Invalid message length: {:?}", e); + } + } + }; + + let (k, ct) = MLKEM512::encaps_internal(&ek, m); + + if self.result == "valid" { + assert_eq!(k, hex::decode(&self.k).unwrap().as_slice()); + assert_eq!(ct, hex::decode(&self.c).unwrap().as_slice()); + } else { + // is there anything to test here? + } + } + + fn run_mlkem768(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem768); + + /* Load the key */ + + let ek = match MLKEM768PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()) { + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e); + } + } + Ok(pk) => pk, + }; + + /* Perform the deterministic encaps and compare results */ + + let (k, ct) = + MLKEM768::encaps_internal(&ek, hex::decode(&self.m).unwrap().try_into().unwrap()); + + if self.result == "valid" { + assert_eq!(k, hex::decode(&self.k).unwrap().as_slice()); + assert_eq!(ct, hex::decode(&self.c).unwrap().as_slice()); + } else { + // is there anything to test here? + } + } + + fn run_mlkem1024(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem1024); + + /* Load the key */ + + let ek = match MLKEM1024PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()) { + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e); + } + } + Ok(pk) => pk, + }; + + /* Perform the deterministic encaps and compare results */ + + let (k, ct) = + MLKEM1024::encaps_internal(&ek, hex::decode(&self.m).unwrap().try_into().unwrap()); + + if self.result == "valid" { + assert_eq!(k, hex::decode(&self.k).unwrap().as_slice()); + assert_eq!(ct, hex::decode(&self.c).unwrap().as_slice()); + } else { + // is there anything to test here? + } + } +} + +#[derive(Clone)] +struct MLKEMKeygenSeedTestCase { + parameter_set: ParameterSet, + tc_id: u32, + comment: String, + seed: String, + ek: String, + dk: String, + result: String, +} + +impl MLKEMKeygenSeedTestCase { + fn new(parameter_set: ParameterSet) -> Self { + Self { + parameter_set, + tc_id: 0, + comment: String::new(), + seed: String::new(), + ek: String::new(), + dk: String::new(), + result: String::new(), + } + } + + fn parse(data: String, parameter_set: ParameterSet) -> Vec { + let json: serde_json::Value = + serde_json::from_str(&data).expect("test data is not valid JSON"); + + let mut test_cases = Vec::::new(); + + let groups = json["testGroups"].as_array().expect("testGroups is not an array"); + for group in groups { + let tests = group["tests"].as_array().expect("tests is not an array"); + for test in tests { + test_cases.push(Self { + parameter_set: parameter_set.clone(), + tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, + comment: test["comment"].as_str().unwrap_or("").to_string(), + seed: test["seed"].as_str().unwrap_or("").to_string(), + ek: test["ek"].as_str().unwrap_or("").to_string(), + dk: test["dk"].as_str().unwrap_or("").to_string(), + result: test["result"].as_str().unwrap_or("").to_string(), + }); + } + } + + test_cases + } + + fn run_mlkem512(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem512); + + // currently, the wycheproof tests contain only valid tests, so just run them; no errors to check + + let seed = + KeyMaterial512::from_bytes_as_type(&hex::decode(&self.seed).unwrap(), KeyType::Seed) + .unwrap(); + + let (ek, dk) = MLKEM512::keygen_from_seed(&seed).unwrap(); + + assert_eq!(ek, MLKEM512PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap()); + assert_eq!(&ek.encode(), hex::decode(&self.ek).unwrap().as_slice()); + + assert_eq!(dk.encode_full_sk(), hex::decode(&self.dk).unwrap().as_slice()); + } + + fn run_mlkem768(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem768); + + // currently, the wycheproof tests contain only valid tests, so just run them; no errors to check + + let seed = + KeyMaterial512::from_bytes_as_type(&hex::decode(&self.seed).unwrap(), KeyType::Seed) + .unwrap(); + + let (ek, dk) = MLKEM768::keygen_from_seed(&seed).unwrap(); + + assert_eq!(ek, MLKEM768PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap()); + assert_eq!(&ek.encode(), hex::decode(&self.ek).unwrap().as_slice()); + + assert_eq!(dk.encode_full_sk(), hex::decode(&self.dk).unwrap().as_slice()); + } + + fn run_mlkem1024(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem1024); + + // currently, the wycheproof tests contain only valid tests, so just run them; no errors to check + + let seed = + KeyMaterial512::from_bytes_as_type(&hex::decode(&self.seed).unwrap(), KeyType::Seed) + .unwrap(); + + let (ek, dk) = MLKEM1024::keygen_from_seed(&seed).unwrap(); + + assert_eq!(ek, MLKEM1024PublicKey::from_bytes(&hex::decode(&self.ek).unwrap()).unwrap()); + assert_eq!(&ek.encode(), hex::decode(&self.ek).unwrap().as_slice()); + + assert_eq!(dk.encode_full_sk(), hex::decode(&self.dk).unwrap().as_slice()); + } +} + +#[derive(Clone)] +struct MLKEMTestCase { + parameter_set: ParameterSet, + tc_id: u32, + comment: String, + seed: String, + ek: String, + c: String, + k: String, + result: String, +} + +impl MLKEMTestCase { + fn new(parameter_set: ParameterSet) -> Self { + Self { + parameter_set, + tc_id: 0, + comment: String::new(), + seed: String::new(), + ek: String::new(), + c: String::new(), + k: String::new(), + result: String::new(), + } + } + + fn parse(data: String, parameter_set: ParameterSet) -> Vec { + let json: serde_json::Value = + serde_json::from_str(&data).expect("test data is not valid JSON"); + + let mut test_cases = Vec::::new(); + + let groups = json["testGroups"].as_array().expect("testGroups is not an array"); + for group in groups { + let tests = group["tests"].as_array().expect("tests is not an array"); + for test in tests { + test_cases.push(Self { + parameter_set: parameter_set.clone(), + tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, + comment: test["comment"].as_str().unwrap_or("").to_string(), + seed: test["seed"].as_str().unwrap_or("").to_string(), + ek: test["ek"].as_str().unwrap_or("").to_string(), + c: test["c"].as_str().unwrap_or("").to_string(), + k: test["K"].as_str().unwrap_or("").to_string(), + result: test["result"].as_str().unwrap_or("").to_string(), + }); + } + } + + test_cases + } + + fn run_mlkem512(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem512); + + /* Load the private key */ + let mut seed = match KeyMaterial512::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) { + Ok(seed) => seed, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("Failed to load seed: {:?}", e); + } + } + }; + // allow an all-zero seed for testing + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => (), + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + } + + let (ek, dk) = match MLKEM512::keygen_from_seed(&seed) { + Ok((ek, dk)) => (ek, dk), + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Failed to generate key pair: {:?}", e); + } + } + }; + + // check that the derived ek matches the provided one + assert_eq!(&ek.encode(), &hex::decode(&self.ek).unwrap().as_slice()); + + // these tests don't provide m, so can't test deterministic encaps + + // test decaps + let k = match MLKEM512::decaps(&dk, &hex::decode(&self.c).unwrap().as_slice()) { + Ok(k) => k, + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Failed to decapsulate: {:?}", e); + } + } + }; + + assert_eq!(k.ref_to_bytes(), hex::decode(&self.k).unwrap().as_slice()); + } + + fn run_mlkem768(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem768); + + /* Load the private key */ + let mut seed = match KeyMaterial512::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) { + Ok(seed) => seed, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("Failed to load seed: {:?}", e); + } + } + }; + // allow an all-zero seed for testing + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => (), + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + } + + let (ek, dk) = match MLKEM768::keygen_from_seed(&seed) { + Ok((ek, dk)) => (ek, dk), + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Failed to generate key pair: {:?}", e); + } + } + }; + + // check that the derived ek matches the provided one + assert_eq!(&ek.encode(), &hex::decode(&self.ek).unwrap().as_slice()); + + // these tests don't provide m, so can't test deterministic encaps + + // test decaps + let k = match MLKEM768::decaps(&dk, &hex::decode(&self.c).unwrap().as_slice()) { + Ok(k) => k, + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Failed to decapsulate: {:?}", e); + } + } + }; + + assert_eq!(k.ref_to_bytes(), hex::decode(&self.k).unwrap().as_slice()); + } + + fn run_mlkem1024(&self) { + assert_eq!(self.parameter_set, ParameterSet::Mlkem1024); + + /* Load the private key */ + let mut seed = match KeyMaterial512::from_bytes_as_type( + &hex::decode(&self.seed).unwrap(), + KeyType::Seed, + ) { + Ok(seed) => seed, + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("Failed to load seed: {:?}", e); + } + } + }; + // allow an all-zero seed for testing + seed.allow_hazardous_operations(); + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => (), + Err(e) => { + if self.result == "invalid" { + /* good */ + return; + } else { + panic!("{:?}", e) + } + } + } + + let (ek, dk) = match MLKEM1024::keygen_from_seed(&seed) { + Ok((ek, dk)) => (ek, dk), + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Failed to generate key pair: {:?}", e); + } + } + }; + + // check that the derived ek matches the provided one + assert_eq!(&ek.encode(), &hex::decode(&self.ek).unwrap().as_slice()); + + // these tests don't provide m, so can't test deterministic encaps + + // test decaps + let k = match MLKEM1024::decaps(&dk, &hex::decode(&self.c).unwrap().as_slice()) { + Ok(k) => k, + Err(e) => { + if self.result == "invalid" { + return; + } else { + panic!("Failed to decapsulate: {:?}", e); + } + } + }; + + assert_eq!(k.ref_to_bytes(), hex::decode(&self.k).unwrap().as_slice()); + } +} From ee084276537502da32c297033292d7d126e8d9a0 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sun, 7 Jun 2026 16:49:59 -0500 Subject: [PATCH 03/35] brought 0.1.2alpha todo list from main --- alpha_0.1.2_release_notes.md | 61 +++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index 41a6069..06dcefa 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -1,21 +1,58 @@ # TODO + [remove this section before publication] -[ ] EdDSA -[ ] ML-DSA + EdDSA Composite -[ ] Ensure that all crates have `#![forbid(missing_docs)]` -[ ] Apply Secret trait consistently across the library --> study the `Zeroize` trait in RustCrypto -[ ] Change all "[u8;0]" to "[]" throughout the code and docs ... or better yet, change the APIs to take an Option<> -[ ] For all higher-level algorithms, put a cargo #[cfg(feature='rng')] around the keygen that takes an rng so that the dependency on bouncycastle_rng is optional. -[ ] Enhance the default HashDRBG instantiation to take in NIST-compatible CPU jitter entropy -[ ] Get an opinion from Bob Beck or Dennis about the factories ... Are they worth it? -[ ] Do a pass over KeyMaterial, taking into account Dennis Jackson's input (maybe ping him for a phone call?) -[ ] Need a rust expert: I use a bunch of #![feature(_)]'s that are only available in nightly. ... what should I do about that? -[ ] Open github issues +* ML-DSA & ML-KEM + * Check the crate release checklist and run claude against the style guide (maybe Francis could cross-check me) + * Run Crucible testing + * Add factories for ML-DSA and ML-KEM (if we are keeping factories, see below) +* Split the Signature trait into a Signer and a Verifier so that, for example, we can implement the verifier for MTC in + a different struct from the signer; or so that you can get FIPS compliance on old algorithms that are currently only + FIPS-allowed for verification of existing signatures but not for creation of new ones. +* Check out Megan's email May 13 about KeyMaterial: "I was wondering if there might be scope for a closure based + approach that could + guarantee encapsulation of the state change from safe to hazardous back to safe again." +* Anywhere that you have an `_out(.. out: &mut [u8])`, start by zeroizing it with .fill(0); .. a good task for Claude? + And should be documented in the style guide? +* Go back to previous algs and apply memory optimization tricks like internal functions. And add a docs section "Memory + Usage" that measures with valgrind. +* Ensure that all crates have `#![forbid(missing_docs)]` +* Apply Secret trait consistently across the library --> study the `Zeroize` trait in RustCrypto +* Change all "[u8;0]" to "[]" throughout the code and docs ... or better yet, change the APIs to take an Option<> +* Change all `-> Vec` to `-> [u8; CONST_LEN]`, and the `output: &mut [u8]` to `output: &mut [u8; CONST_LEN]` where + 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? +* 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 + problem of the caller to properly seed the RNG? +* Factories ... Are they worth it? Michael Richardson says Very Yes. If we are keeping them, then we need a serious + re-engineering of them because I really dislike that currently they make it hard for the underlying primitive to have + static one-shot APIs. +* Add back the Memoable trait from nursery (maybe under a different name) that lets you serialize out the + intermediate state, especially important for SHA2, SHA3, and HMAC because TLS needs to be able to fork a state, + finalize() a copy and then keep feeding the other copy. +* Do some science about perf impacts of acting on a local hard-copy vs acting in-place on some specific bit of + memory +* Change the tone of the documentation (both the crate docs and the inline comments) to be less individual ("I" + statements) and be more factual ("it is", or "the project", or "the bc-rust library" as appropriate). +* Relax the requirement on XOF that once you start squeezing, you can't absorb anymore. This will likely need to be an + exposed "bell & whistle" because it is an obvious way to do something like the TLS handshake transcript where you need + to periodically spit out hashes and then continue absorbing more input. We'll need to study the SHA3 / SHAKE FIPS + documents because it might be that this is forbidden as part of the definition of SHAKE, but is allowed if you use the + KECCAK primitive raw. We need to make a decision on how to handle this, and provide some sample code in crate docs. +* Need a rust expert: I use a bunch of #![feature(_)]'s that are only available in nightly. ... what should I do + about that? +* Research task: no_std means that everything is on the stack, which can cause you to blow your stack limit. Research + how an application that itself is not no_std can put our large structs (like key objects) on the heap. Is this what + Box is for? +* Deal with as many of the inline TODOs as possible +* Close all open github issues and document them in this file. # 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. * Github issues resolved: - * #2, or whatever \ No newline at end of file + * #2, or whatever \ No newline at end of file From 8022962999938b61ccd55dfe1246155d7a69ed42 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 8 Jun 2026 12:45:41 -0500 Subject: [PATCH 04/35] Merging Quant-TheodoreFelix's improvement to ct::Condition. Closes #7 --- .../publish_doc_benches_to_ghpages.yaml | 37 +- CONTRIBUTING.md | 19 +- crypto/core/src/errors.rs | 1 + crypto/core/src/key_material.rs | 6 +- crypto/core/tests/key_material_tests.rs | 38 +- crypto/mldsa_lowmemory/src/lib.rs | 2 +- crypto/utils/src/ct.rs | 163 +- crypto/utils/tests/ct_tests.rs | 268 +-- mem_usage_benches/bench_mldsa_mem_usage.rs | 1793 ++++++++++++++++- src/bench_mldsa_mem_usage.rs | 469 +++++ 10 files changed, 2412 insertions(+), 384 deletions(-) create mode 100644 src/bench_mldsa_mem_usage.rs diff --git a/.github/workflows/publish_doc_benches_to_ghpages.yaml b/.github/workflows/publish_doc_benches_to_ghpages.yaml index 80e7d87..819803b 100644 --- a/.github/workflows/publish_doc_benches_to_ghpages.yaml +++ b/.github/workflows/publish_doc_benches_to_ghpages.yaml @@ -37,21 +37,22 @@ jobs: with: name: code_quality_stats path: ./code_stats.txt - run_benches: - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' - steps: - - name: Checkout code - uses: actions/checkout@v4 - - run: cargo bench --all - - name: Save artifacts - uses: actions/upload-artifact@v4 - with: - name: bc-rust-benches - path: ./target/criterion + # the benches run crazy slow on the github agent. So there's really no point because it's not useful data. + # run_benches: + # runs-on: ubuntu-latest + # if: github.ref == 'refs/heads/main' + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + # - run: cargo bench --all + # - name: Save artifacts + # uses: actions/upload-artifact@v4 + # with: + # name: bc-rust-benches + # path: ./target/criterion collect_ghpages: if: github.ref == 'refs/heads/main' - needs: [build_docs, code_stats, run_benches] + needs: [build_docs, code_stats] runs-on: ubuntu-latest steps: - run: mkdir ./gh-pages @@ -65,11 +66,11 @@ jobs: with: name: code_quality_stats path: ./gh-pages/ - - name: Get benches from previous job - uses: actions/download-artifact@v4 - with: - name: bc-rust-benches - path: ./gh-pages/benches + # - name: Get benches from previous job + # uses: actions/download-artifact@v4 + # with: + # name: bc-rust-benches + # path: ./gh-pages/benches - name: Archive Compatibility Matrix For Download uses: actions/upload-pages-artifact@v3 with: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 104f2ff..d402afd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,23 @@ before posting anything public. See [Security Policy](SECURITY.md). If a related discussion or issue doesn't exist, and the issue is not security related, you can [open a new issue](https://github.com/bcgit/bc-java/issues/new). An issue can be converted into a discussion if regarded as one. +## Coding philosophy + +> Slow is smooth, smooth is fast. + +There is a time and a place for "Move fast and break things", but the source code of a crypto library is not one of them. + +This project takes the philosophy that taking the time to do things right pays off in the long run, both in terms of +the runtime and memory footprint of the code, and it terms of the time required for a future maintainer to get up to speed with the code +and avoid introducing bugs due to the code being hard to understand. + +Some specifics: + +* Respect that the innovative process sometimes requires exploring several dead-ends before you find the most elegant solution. +* Public APIs of a library should be both ergonomic and expressive. When defining a new trait or public function, ask yourself whether a programmer who is new to cryptography is likely to use this in a way that will get them into trouble. +* Variables should be well-named, well-structured, and well-commented (a comment-to-code ration of 1:1 is a goal to be strived for!). Think about memory footprint and, where possible, use unnamed scopes to allow the compiler to pop intermediate value variables off the stack as soon as they are no longer needed. +* Always run your code through `cargo mutants` and get the issue count as low as your can. As a first pass, this forces you to write thorough unit tests. As a second pass, this draws your attention to bits of your code that cannot be tested from the outside. Often this means that the code can be simplified without affecting functionality (as defined by your set of unit tests) -- "simpler code" usually means faster runtime and easier future maintenance. + ## Contribute to the code For substantial, non-trivial contributions, you may be asked to sign a contributor assignment agreement. Optionally, you can also have your name and contact information listed in [Contributors](https://www.bouncycastle.org/contributors.html). @@ -56,5 +73,5 @@ Don't forget to self-review. Please follow these simple guidelines: #### Your pull request is merged -For acceptance, pull requests need to meet specific quality criteria, including tests for anything substantial. Someone on the Bouncy Castle core team will review the pull request when there is time, and let you know if something is missing or suggest improvements. If it is a useful and generic feature it will be integrated in Bouncy Castle to be available in a later release. +Someone on the Bouncy Castle core team will review the pull request when there is time, and let you know if something is missing or suggest improvements. If it is a useful and generic feature it will be integrated in Bouncy Castle to be available in a later release. diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index de80d9a..9b52bc6 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -145,3 +145,4 @@ impl From for SignatureError { impl From for SignatureError { fn from(e: RNGError) -> SignatureError { Self::RNGError(e) } } + diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index 56aae3f..df17c88 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -39,7 +39,7 @@ use crate::errors::KeyMaterialError; use crate::traits::{RNG, SecurityStrength, Secret}; -use bouncycastle_utils::{ct, max, min}; +use bouncycastle_utils::{ct, min}; use core::cmp::{Ordering, PartialOrd}; use core::fmt; @@ -569,8 +569,8 @@ impl KeyMaterialTrait for KeyMaterial { } self.buf[self.key_len..new_key_len].copy_from_slice(other.ref_to_bytes()); self.key_len += other.key_len(); - self.key_type = max(&self.key_type, &other.key_type()).clone(); - self.security_strength = max(&self.security_strength, &other.security_strength()).clone(); + self.key_type = min(&self.key_type, &other.key_type()).clone(); + self.security_strength = min(&self.security_strength, &other.security_strength()).clone(); Ok(self.key_len()) } diff --git a/crypto/core/tests/key_material_tests.rs b/crypto/core/tests/key_material_tests.rs index 4bb3083..41fca0b 100644 --- a/crypto/core/tests/key_material_tests.rs +++ b/crypto/core/tests/key_material_tests.rs @@ -620,8 +620,9 @@ mod test_key_material { assert_eq!(zeroized_key.key_len(), 8); zeroized_key.concatenate(&key2).unwrap(); assert_eq!(zeroized_key.key_len(), 24); - // should take max(Zeroized, BytesLowEntropy) - assert_eq!(zeroized_key.key_type(), KeyType::BytesLowEntropy); + // The result takes the lesser (min) of the two key types: min(Zeroized, BytesLowEntropy). + // Folding in zeroized (uninitialized) bytes taints the whole buffer as Zeroized. + assert_eq!(zeroized_key.key_type(), KeyType::Zeroized); assert_eq!(zeroized_key.security_strength(), SecurityStrength::None); // This should be symmetric, so test it in the other direction too. @@ -634,8 +635,8 @@ mod test_key_material { let mut key2 = KeyMaterial256::from_bytes(&[1u8; 16]).unwrap(); key2.concatenate(&zeroized_key).unwrap(); assert_eq!(key2.key_len(), 24); - // should take max(Zeroized, BytesLowEntropy) - assert_eq!(key2.key_type(), KeyType::BytesLowEntropy); + // The result takes the lesser (min) of the two key types: min(BytesLowEntropy, Zeroized). + assert_eq!(key2.key_type(), KeyType::Zeroized); assert_eq!(key2.security_strength(), SecurityStrength::None); // now try it with keys of different key types @@ -644,10 +645,11 @@ mod test_key_material { let full_entropy_key = KeyMaterial256::from_bytes_as_type(&[2u8; 16], KeyType::BytesFullEntropy).unwrap(); low_entropy_key.concatenate(&full_entropy_key).unwrap(); - // should take max(BytesLowEntropy, BytesFullEntropy) - assert_eq!(low_entropy_key.key_type(), KeyType::BytesFullEntropy); - // should take max(None, _128Bit) - assert_eq!(low_entropy_key.security_strength(), SecurityStrength::_128bit); + // Conservative model: concatenating a full-entropy key with a low-entropy key yields a + // low-entropy key. min(BytesLowEntropy, BytesFullEntropy) == BytesLowEntropy. + assert_eq!(low_entropy_key.key_type(), KeyType::BytesLowEntropy); + // min(None, _128bit) == None (and BytesLowEntropy keys must have strength None anyway). + assert_eq!(low_entropy_key.security_strength(), SecurityStrength::None); // and in the other direction too let low_entropy_key = @@ -655,22 +657,22 @@ mod test_key_material { let mut full_entropy_key = KeyMaterial256::from_bytes_as_type(&[2u8; 16], KeyType::BytesFullEntropy).unwrap(); full_entropy_key.concatenate(&low_entropy_key).unwrap(); - // should take max(BytesLowEntropy, BytesFullEntropy) - assert_eq!(full_entropy_key.key_type(), KeyType::BytesFullEntropy); - // should take max(None, _128Bit) - assert_eq!(full_entropy_key.security_strength(), SecurityStrength::_128bit); + // min(BytesFullEntropy, BytesLowEntropy) == BytesLowEntropy. + assert_eq!(full_entropy_key.key_type(), KeyType::BytesLowEntropy); + // min(_128bit, None) == None. + assert_eq!(full_entropy_key.security_strength(), SecurityStrength::None); // now with full entropy keys at different security levels - let mut low_entropy_key = + let mut full_entropy_key_112 = KeyMaterial512::from_bytes_as_type(&[1u8; 16], KeyType::BytesFullEntropy).unwrap(); // Now we're gonna explictly tag it at the 112bit security level -- does not require allow_hazardous_operations(). - low_entropy_key.set_security_strength(SecurityStrength::_112bit).unwrap(); + full_entropy_key_112.set_security_strength(SecurityStrength::_112bit).unwrap(); let full_entropy_key = KeyMaterial256::from_bytes_as_type(&[2u8; 32], KeyType::BytesFullEntropy).unwrap(); - low_entropy_key.concatenate(&full_entropy_key).unwrap(); - assert_eq!(low_entropy_key.key_type(), KeyType::BytesFullEntropy); - // should take max(_112Bit, _256Bit) - assert_eq!(low_entropy_key.security_strength(), SecurityStrength::_256bit); + full_entropy_key_112.concatenate(&full_entropy_key).unwrap(); + assert_eq!(full_entropy_key_112.key_type(), KeyType::BytesFullEntropy); + // The combined key keeps the lower of the two security strengths: min(_112bit, _256bit). + assert_eq!(full_entropy_key_112.security_strength(), SecurityStrength::_112bit); } #[test] diff --git a/crypto/mldsa_lowmemory/src/lib.rs b/crypto/mldsa_lowmemory/src/lib.rs index ccc5a33..4463d77 100644 --- a/crypto/mldsa_lowmemory/src/lib.rs +++ b/crypto/mldsa_lowmemory/src/lib.rs @@ -45,7 +45,7 @@ //! //! We also get a surprising amount of memory-savings by good coding hygiene: //! Using un-named scopes to tell the compiler when an intermediate variable is no longer needed and -//! con be popped off the stack. This sometimes requires re-ordering the steps of the algorithms given in +//! can be popped off the stack. This sometimes requires re-ordering the steps of the algorithms given in //! FIPS 204 so that variables can be created, used, and released in a self-contained block. //! Sometimes this is not possible and we have to make a choice between keeping the variable around //! or releasing it and re-deriving it later. diff --git a/crypto/utils/src/ct.rs b/crypto/utils/src/ct.rs index 24ca9bf..96b1950 100644 --- a/crypto/utils/src/ct.rs +++ b/crypto/utils/src/ct.rs @@ -38,7 +38,9 @@ impl Condition { // MikeO: TODO: there are a bunch of impls in here that seem to be generic and not related to i64, // MikeO: TODO: could those be moved to a generic impl for Condition ? - pub const TRUE: Self = Self(1); + /// TRUE is the bit vector of all 1's + pub const TRUE: Self = Self(-1); + /// FALSE is the bit vector of all 0's pub const FALSE: Self = Self(0); pub const fn from_bool() -> Self { @@ -111,13 +113,31 @@ impl Condition { *dst = self.select(src, *dst); } - // MikeO: TODO: I have no idea what this does, .negate(-1) seems to give -3 ?? Is that a bug? /// Conditionally negate the value. + /// + /// negate(-1) gives -3 + /// + /// `value` is `-1` (i.e., all bits are `1`, `...1111`) + /// + /// Condition `self.0` is 1 (`...0001`) (assuming `TRUE`) + /// + /// XOR operation was executed as `value ^ self.0` + /// + /// Then `...1111 XOR ...0001 = ...1110` (i.e., `-2`) + /// + /// Subtraction operation is `wrapping_sub(self.0)` + /// + /// Then `-2 - 1 = -3` + /// + /// As a result, `1`, which is the negation of `-1`, should be returned, but `-3` is output. + /// + /// Therefore, if the [Self::TRUE] constant value of the i64 [Condition] implementation is changed to `-1`, + /// the test also runs normally. pub const fn negate(self, value: i64) -> i64 { (value ^ self.0).wrapping_sub(self.0) } - const fn or_halves(value: i64) -> i64 { + pub const fn or_halves(value: i64) -> i64 { (value | (value >> 32)) & 0xFFFFFFFF } @@ -136,93 +156,36 @@ impl Condition { } } -// TODO: ... this doesn't ... work. We should get this working and then then do u8. +// TODO: We should do Condition. // TODO: then and change Hex and Base64 to use this. // TODO: (there's probably no noticeable performance difference u8 and u64 bit ops on a 64-bit machine, // TODO: but there would be on a 8, 16, or 32-bit machine.) -// impl Condition { -// pub const TRUE: Self = Self(1); -// pub const FALSE: Self = Self(0); -// -// pub const fn new() -> Self { -// Self((VALUE as u64).wrapping_neg()) -// } -// -// pub const fn from_bool(value: bool) -> Self { -// Self((value as u64).wrapping_neg()) -// } -// -// pub const fn is_bit_set(value: u64, bit: u64) -> Self { -// Self(((value >> bit) & 1).wrapping_neg()) -// } -// -// // MikeO: TODO ?? What does "negative" mean for an unsigned value? -// pub const fn is_negative(value: u64) -> Self { -// Self(((value as i64) >> 63) as u64) -// } -// -// pub const fn is_not_zero(value: u64) -> Self { -// Self::is_negative(Self::or_halves(value).wrapping_neg()) -// } -// -// pub const fn is_zero(value: u64) -> Self { -// Self::is_negative(Self::or_halves(value).wrapping_sub(1)) -// } -// -// // MikeO: TODO: I borrowed this formula from Botan, but rust complains about u64 subtraction overflow if x < y, so this works in C but won't work in rust. -// // MikeO: TODO: I played with u64.wrapping_sub(y) but that doesn't work either. -// pub const fn is_lt(x: u64, y: u64) -> Self { -// Self::is_zero(x ^ ((x ^ y) | (x.wrapping_sub(y)) ^ x)) -// } -// -// // Note: haven't found a clever way to make this const, since it either needs a (non-const) not (!) or a boolean OR is_zero. -// // pub fn is_lte(x: i64, y: i64) -> Self { !Self::is_gt(x, y) } -// -// // pub const fn is_gt(x: i64, y: i64) -> Self { Self::is_lt(y, x) } -// -// // Note: haven't found a clever way to make this const, since it either needs a (non-const) not (!) or a boolean OR is_zero. -// // pub fn is_gte(x: i64, y: i64) -> Self { !Self::is_lt(x, y) } -// -// pub fn is_in_list(value: u64, list: &[u64]) -> Self { -// // Research question: is this actually constant-time? -// // A clever compiler might turn this into a short-circuiting loop. -// // A quick google search shows that rust doesn't have the ability to annotate specific code blocks -// // as no-optimize; the only option is to insert direct assembly. -// -// let mut c = Self::FALSE; -// for i in 0..list.len() { -// let diff = value ^ list[i]; -// c |= Condition::::is_zero(diff); -// } -// -// c -// } -// -// pub fn mov(self, src: u64, dst: &mut u64) { -// *dst = self.select(src, *dst); -// } -// -// // MikeO: TODO: This needs a docstring because I have no idea what this does. -// pub const fn negate(self, value: u64) -> u64 { -// (value ^ self.0).wrapping_sub(self.0) -// } -// -// const fn or_halves(value: u64) -> u64 { -// (value & 0xFFFFFFFF) | (value >> 32) -// } -// -// pub const fn select(self, true_value: u64, false_value: u64) -> u64 { -// (true_value & self.0) | (false_value & !self.0) -// } -// -// pub const fn swap(self, lhs: u64, rhs: u64) -> (u64, u64) { -// (self.select(rhs, lhs), self.select(lhs, rhs)) -// } -// -// pub const fn to_bool_var(self) -> bool { -// self.0 != 0 -// } -// } +impl Condition { + /// TRUE is the bit vector of all 1's + pub const TRUE: Self = Self(u64::MAX); + /// FALSE is the bit vector of all 0's + pub const FALSE: Self = Self(0); + + // this is the core logic for constant-time mask generation for unsigned integers + // Unlike signed integers where we can rely on Two's Complement via negation `-(v as i64)`, + // for u64 we must use wrapping subtraction to achieve the all-ones bit pattern (u64::MAX) for true + pub const fn from_bool() -> Self { + // If VALUE is true (1) -> 0 - 1 = u64::MAX (All 1s) + // If VALUE is false (0) -> 0 - 0 = 0 (All 0s) + Self(0u64.wrapping_sub(VALUE as u64)) + } + + // the select function manually for u64 + // although a fully generic impl would be the ultimate long-term goal + pub fn select(self, a: u64, b: u64) -> u64 { + let mask = self.0; + (a & mask) | (b & !mask) + } + + pub fn is_true(&self) -> bool { + self.0 != 0 + } +} impl BitAnd for Condition where @@ -327,22 +290,22 @@ pub fn conditional_copy_bytes( a: &[u8; LEN], b: &[u8; LEN], out: &mut [u8; LEN], - take_a: bool) { - - // we want the behaviour of + take_a: bool, +) { + // we want the behaviour of // if take_a { 0xFF } else { 0x00 } // but without using any branches that could leak timing signals - let mask: u8 = (take_a as u8) | - (take_a as u8) <<1 | - (take_a as u8) <<2 | - (take_a as u8) <<3 | - (take_a as u8) <<4 | - (take_a as u8) <<5 | - (take_a as u8) <<6 | - (take_a as u8) <<7; - + let mask: u8 = (take_a as u8) + | (take_a as u8) << 1 + | (take_a as u8) << 2 + | (take_a as u8) << 3 + | (take_a as u8) << 4 + | (take_a as u8) << 5 + | (take_a as u8) << 6 + | (take_a as u8) << 7; + debug_assert_eq!(mask, if take_a { 0xFF } else { 0x00 }); - + for i in 0..LEN { out[i] = std::hint::black_box(a[i] & mask) | std::hint::black_box(b[i] & !mask); } diff --git a/crypto/utils/tests/ct_tests.rs b/crypto/utils/tests/ct_tests.rs index c9d2712..8e9be76 100644 --- a/crypto/utils/tests/ct_tests.rs +++ b/crypto/utils/tests/ct_tests.rs @@ -143,10 +143,7 @@ mod i64_tests { let c1 = Condition::::TRUE; assert_eq!(c1.negate(1), -1); assert_eq!(c1.negate(0), 0); - - // MikeO: TODO: I don't understand what this function does well enough to test it. - // MikeO: TODO: is this failing test a real bug? - // assert_eq!(c1.negate(-1), 1); + assert_eq!(c1.negate(-1), 1); let c2 = Condition::::FALSE; assert_eq!(c2.negate(1), 1); @@ -154,10 +151,35 @@ mod i64_tests { assert_eq!(c2.negate(-1), -1); } - // MikeO: TODO: I don't understand what this function does well enough to test it. #[test] fn test_or_halves() { - // todo + // 0 input -> 0 output + assert_eq!(Condition::::or_halves(0), 0); + + // Lower 32 bits should be preserved + assert_eq!(Condition::::or_halves(1), 1); + assert_eq!(Condition::::or_halves(0x12345678), 0x12345678); + + // Upper 32 bits should be folded into lower 32 bits + // (1 << 32) OR (1 << 32 >> 32) => 0 OR 1 => 1 + assert_eq!(Condition::::or_halves(1 << 32), 1); + + // Mixed case: Upper 0x10000000 | Lower 0x00000001 => 0x10000001 + assert_eq!(Condition::::or_halves(0x10000000_00000001), 0x10000001); + + // Negative number check (-1) + // -1 is 0xFFFF...FFFF + // (-1 >> 32) is -1 (Arithmetic shift preserves sign) + // (-1 | -1) is -1 + // -1 & 0xFFFFFFFF is 0x00000000FFFFFFFF (i64 value: 4294967295) + assert_eq!(Condition::::or_halves(-1), 0xFFFFFFFF); + + // i64::MIN check (Only MSB set) + // i64::MIN = 0x80000000_00000000 + // (val >> 32) = 0xFFFFFFFF_80000000 (Sign extension) + // (val | shifted) = 0xFFFFFFFF_80000000 + // (& mask) = 0x00000000_80000000 + assert_eq!(Condition::::or_halves(i64::MIN), 0x80000000); } #[test] @@ -187,179 +209,67 @@ mod i64_tests { } } -// #[cfg(test)] -// mod u64_tests { -// use super::*; -// -// #[test] -// fn const_tests() { -// assert_eq!(Condition::::TRUE.to_bool_var(), true); -// assert_eq!(Condition::::FALSE.to_bool_var(), false); -// } -// -// #[test] -// fn from_bool() { -// assert_eq!(Condition::::new::().to_bool_var(), true); -// assert_eq!(Condition::::new::().to_bool_var(), false); -// -// let btrue: bool = true; -// let bfalse: bool = false; -// assert_eq!(Condition::::from_bool(btrue).to_bool_var(), true); -// assert_eq!(Condition::::from_bool(bfalse).to_bool_var(), false); -// } -// -// #[test] -// fn is_bit_set() { -// assert_eq!(Condition::::is_bit_set(1, 0).to_bool_var(), true); -// assert_eq!(Condition::::is_bit_set(1, 1).to_bool_var(), false); -// assert_eq!(Condition::::is_bit_set(8, 3).to_bool_var(), true); -// } -// -// // MikeO: TODO ?? What does "negative" mean for an unsigned value? -// #[test] -// fn is_negative() { -// // assert_eq!(Condition::::is_negative(-1).to_bool_var(), true); // << This doesn't compile, for obvious reasons. -// assert_eq!(Condition::::is_negative(0).to_bool_var(), false); -// assert_eq!(Condition::::is_negative(1).to_bool_var(), false); -// assert_eq!(Condition::::is_negative(1 << 12).to_bool_var(), false); -// } -// -// #[test] -// fn is_not_zero() { -// assert_eq!(Condition::::is_not_zero(1).to_bool_var(), true); -// assert_eq!(Condition::::is_not_zero(0).to_bool_var(), false); -// assert_eq!(Condition::::is_not_zero(1 << 12).to_bool_var(), true); -// } -// -// #[test] -// fn is_zero() { -// assert_eq!(Condition::::is_zero(1).to_bool_var(), false); -// assert_eq!(Condition::::is_zero(0).to_bool_var(), true); -// assert_eq!(Condition::::is_zero(1 << 12).to_bool_var(), false); -// } -// -// // TODO: turn this back on once implemented -// #[test] -// fn is_lt() { -// assert_eq!(Condition::::is_lt(1, 2).to_bool_var(), true); -// assert_eq!(Condition::::is_lt(2, 1).to_bool_var(), false); -// assert_eq!(Condition::::is_lt(2, 2).to_bool_var(), false); -// assert_eq!(Condition::::is_lt(0, 1).to_bool_var(), true); -// -// let mut i: u64 = 0; -// assert_eq!(Condition::::is_lt(i, 1).to_bool_var(), true); -// i = 1; -// assert_eq!(Condition::::is_lt(i, 1).to_bool_var(), false); -// } -// -// // TODO: turn this back on once implemented -// // #[test] -// // fn is_lte() { -// // assert_eq!(Condition::::is_lte(1, 2).to_bool_var(), true); -// // assert_eq!(Condition::::is_lte(2, 1).to_bool_var(), false); -// // assert_eq!(Condition::::is_lte(2, 2).to_bool_var(), true); -// // assert_eq!(Condition::::is_lte(0, 1).to_bool_var(), true); -// // assert_eq!(Condition::::is_lte(-100, -99).to_bool_var(), true); -// // assert_eq!(Condition::::is_lte(-98, 98).to_bool_var(), true); -// // } -// -// // #[test] -// // fn is_gt() { -// // assert_eq!(Condition::::is_gt(1, 2).to_bool_var(), false); -// // assert_eq!(Condition::::is_gt(2, 1).to_bool_var(), true); -// // assert_eq!(Condition::::is_gt(2, 2).to_bool_var(), false); -// // assert_eq!(Condition::::is_gt(0, 1).to_bool_var(), false); -// // assert_eq!(Condition::::is_gt(-100, -99).to_bool_var(), false); -// // assert_eq!(Condition::::is_gt(-98, 98).to_bool_var(), false); -// // } -// -// // #[test] -// // fn is_gte() { -// // assert_eq!(Condition::::is_gte(1, 2).to_bool_var(), false); -// // assert_eq!(Condition::::is_gte(2, 1).to_bool_var(), true); -// // assert_eq!(Condition::::is_gte(2, 2).to_bool_var(), true); -// // assert_eq!(Condition::::is_gte(0, 1).to_bool_var(), false); -// // assert_eq!(Condition::::is_gte(-100, -99).to_bool_var(), false); -// // assert_eq!(Condition::::is_gte(-98, 98).to_bool_var(), false); -// // } -// -// // TODO: turn this back on once implemented -// // #[test] -// // fn is_in_range() { -// // assert_eq!(Condition::::is_within_range(1, 0, 2).to_bool_var(), true); -// // assert_eq!(Condition::::is_within_range(2, 0, 1).to_bool_var(), false); -// // assert_eq!(Condition::::is_within_range(1, -5, 2).to_bool_var(), true); -// // assert_eq!(Condition::::is_within_range(0, -5, 5).to_bool_var(), true); -// // assert_eq!(Condition::::is_within_range(1, 0, 0).to_bool_var(), false); -// // } -// -// #[test] -// fn is_in_list() { -// assert_eq!(Condition::::is_in_list(1, &[1, 2, 3]).to_bool_var(), true); -// assert_eq!(Condition::::is_in_list(4, &[1, 2, 3]).to_bool_var(), false); -// assert_eq!(Condition::::is_in_list(3, &[1, 2, 3, 3, 3, 3]).to_bool_var(), true); -// } -// -// #[test] -// fn test_mov() { -// let src = 1u64; -// let mut dst = 2u64; -// let c1 = Condition::::TRUE; -// c1.mov(src, &mut dst); -// assert_eq!(dst, 1); -// -// let c2 = Condition::::FALSE; -// dst = 2; -// c2.mov(src, &mut dst); -// assert_eq!(dst, 2); -// } -// -// // MikeO: TODO: I don't understand what this function does well enough to test it. -// // #[test] -// // fn test_negate() { -// // let c1 = Condition::::TRUE; -// // assert_eq!(c1.negate(1), -1); -// // assert_eq!(c1.negate(0), 0); -// // assert_eq!(c1.negate(-1), 1); -// // -// // let c2 = Condition::::FALSE; -// // assert_eq!(c2.negate(1), 1); -// // assert_eq!(c2.negate(0), 0); -// // assert_eq!(c2.negate(-1),-1); -// // } -// -// // MikeO: TODO: I don't understand what this function does well enough to test it. -// #[test] -// fn test_or_halves() { -// todo!() -// } -// -// #[test] -// fn test_select() { -// let c = Condition::::TRUE; -// assert_eq!(c.select(1, 2), 1); -// assert_eq!((!c).select(1, 2), 2); -// -// // or the inverse behaviour if you start with 'false'. -// let cfalse = Condition::::FALSE; -// assert_eq!(cfalse.select(1, 2), 2); -// assert_eq!((!cfalse).select(1, 2), 1); -// } -// -// #[test] -// fn test_swap() { -// let c = Condition::::from_bool::(); -// let (lhs, rhs) = c.swap(1, 2); -// assert_eq!(lhs, 2); -// assert_eq!(rhs, 1); -// -// // or the inverse behaviour if you start with 'false'. -// let c = Condition::::from_bool::(); -// let (lhs, rhs) = c.swap(1, 2); -// assert_eq!(lhs, 1); -// assert_eq!(rhs, 2); -// } -// } +#[cfg(test)] +mod u64_tests { + use super::*; + + #[test] + fn const_tests() { + // Ensure TRUE/FALSE are correctly interpreted as boolean. + assert_eq!(Condition::::TRUE.is_true(), true); + assert_eq!(Condition::::FALSE.is_true(), false); + } + + #[test] + fn from_bool() { + // Compile-time const generics check + assert_eq!(Condition::::from_bool::().is_true(), true); + assert_eq!(Condition::::from_bool::().is_true(), false); + } + + #[test] + fn select() { + let t = Condition::::TRUE; + let f = Condition::::FALSE; + + let val1: u64 = 0xDEADBEEFCAFEBABE; + let val2: u64 = 0x0000000000000000; + + // This test is CRITICAL. + // If TRUE was defined as '1' (like i64), this would fail because 'select' relies on bitwise mask. + // It requires TRUE to be u64::MAX (all 1s) to preserve the full bits of val1. + assert_eq!(t.select(val1, val2), val1); + assert_eq!(f.select(val1, val2), val2); + + // Cross check with from_bool + let t_gen = Condition::::from_bool::(); + assert_eq!(t_gen.select(val1, val2), val1); + } + + #[test] + fn bit_ops() { + let t = Condition::::TRUE; + let f = Condition::::FALSE; + + // NOT + assert_eq!((!t).is_true(), false); + assert_eq!((!f).is_true(), true); + + // AND + assert_eq!((t & t).is_true(), true); + assert_eq!((t & f).is_true(), false); + assert_eq!((f & f).is_true(), false); + + // OR + assert_eq!((t | t).is_true(), true); + assert_eq!((t | f).is_true(), true); + assert_eq!((f | f).is_true(), false); + + // XOR + assert_eq!((t ^ t).is_true(), false); + assert_eq!((t ^ f).is_true(), true); + } +} #[cfg(test)] mod generic_impl_tests { diff --git a/mem_usage_benches/bench_mldsa_mem_usage.rs b/mem_usage_benches/bench_mldsa_mem_usage.rs index 6b648fe..6dc8351 100644 --- a/mem_usage_benches/bench_mldsa_mem_usage.rs +++ b/mem_usage_benches/bench_mldsa_mem_usage.rs @@ -22,49 +22,82 @@ use bouncycastle::core::key_material::{KeyMaterial256, KeyType}; use bouncycastle::core::traits::{Signature, SignaturePrivateKey, SignaturePublicKey}; -use bouncycastle::hex as hex; +use bouncycastle::hex; use bouncycastle::mldsa::MLDSA44_SIG_LEN; /// This prints the in-memory size of all the public and private key structs fn print_struct_sizes() { - use core::mem::size_of; use bouncycastle::mldsa; use bouncycastle::mldsa_lowmemory; - + use core::mem::size_of; println!("\nML-DSA-44"); println!("size_of: {}", size_of::()); - println!("size_of: {}", size_of::()); + println!( + "size_of: {}", + size_of::() + ); println!("size_of: {}", size_of::()); - println!("size_of: {}", size_of::()); - + println!( + "size_of: {}", + size_of::() + ); println!("\nML-DSA-65"); println!("size_of: {}", size_of::()); - println!("size_of: {}", size_of::()); + println!( + "size_of: {}", + size_of::() + ); println!("size_of: {}", size_of::()); - println!("size_of: {}", size_of::()); + println!( + "size_of: {}", + size_of::() + ); println!("\nML-DSA-87"); println!("size_of: {}", size_of::()); - println!("size_of: {}", size_of::()); + println!( + "size_of: {}", + size_of::() + ); println!("size_of: {}", size_of::()); - println!("size_of: {}", size_of::()); + println!( + "size_of: {}", + size_of::() + ); println!("\n\nlowmemory"); println!("\nML-KEM-512_lowmemory"); - println!("size_of: {}", size_of::()); - println!("size_of: {}", size_of::()); - + println!( + "size_of: {}", + size_of::() + ); + println!( + "size_of: {}", + size_of::() + ); println!("\nML-KEM-768_lowmemory"); - println!("size_of: {}", size_of::()); - println!("size_of: {}", size_of::()); + println!( + "size_of: {}", + size_of::() + ); + println!( + "size_of: {}", + size_of::() + ); println!("\nML-KEM-1024_lowmemory"); - println!("size_of: {}", size_of::()); - println!("size_of: {}", size_of::()); + println!( + "size_of: {}", + size_of::() + ); + println!( + "size_of: {}", + size_of::() + ); } /// This exists so I can use /usr/bin/time to measure the base memory footprint of the cargo bench harness @@ -75,91 +108,121 @@ fn bench_do_nothing() { } fn bench_mldsa44_keygen() { - use bouncycastle::mldsa::{MLDSATrait, MLDSA44}; + use bouncycastle::mldsa::{MLDSA44, MLDSATrait}; eprintln!("MLDSA44/KeyGen"); let seed = KeyMaterial256::from_bytes_as_type( - &[0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f], + &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, + ], KeyType::Seed, - ).unwrap(); + ) + .unwrap(); let (pk, _sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); println!("{:x?}", pk.encode()); } fn bench_mldsa44_lowmem_keygen() { - use bouncycastle::mldsa_lowmemory::{MLDSATrait, MLDSA44}; + use bouncycastle::mldsa_lowmemory::{MLDSA44, MLDSATrait}; eprintln!("MLDSA44_lowmemory/KeyGen"); let seed = KeyMaterial256::from_bytes_as_type( - &[0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f], + &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, + ], KeyType::Seed, - ).unwrap(); + ) + .unwrap(); let (pk, _sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); println!("{:x?}", pk.encode()); } fn bench_mldsa65_keygen() { - use bouncycastle::mldsa::{MLDSATrait, MLDSA65}; + use bouncycastle::mldsa::{MLDSA65, MLDSATrait}; eprintln!("MLDSA65/KeyGen"); let seed = KeyMaterial256::from_bytes_as_type( - &[0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f], + &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, + ], KeyType::Seed, - ).unwrap(); + ) + .unwrap(); let (pk, _sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); println!("{:x?}", pk.encode()); } fn bench_mldsa65_lowmemory_keygen() { - use bouncycastle::mldsa_lowmemory::{MLDSATrait, MLDSA65}; + use bouncycastle::mldsa_lowmemory::{MLDSA65, MLDSATrait}; eprintln!("MLDSA65_lowmemory/KeyGen"); let seed = KeyMaterial256::from_bytes_as_type( - &[0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f], + &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, + ], KeyType::Seed, - ).unwrap(); + ) + .unwrap(); let (pk, _sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); println!("{:x?}", pk.encode()); } fn bench_mldsa87_keygen() { - use bouncycastle::mldsa::{MLDSATrait, MLDSA87}; + use bouncycastle::mldsa::{MLDSA87, MLDSATrait}; eprintln!("MLDSA87/KeyGen"); let seed = KeyMaterial256::from_bytes_as_type( - &[0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f], + &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, + ], KeyType::Seed, - ).unwrap(); + ) + .unwrap(); let (pk, _sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); println!("{:x?}", pk.encode()); } fn bench_mldsa87_lowmemory_keygen() { - use bouncycastle::mldsa_lowmemory::{MLDSATrait, MLDSA87}; + use bouncycastle::mldsa_lowmemory::{MLDSA87, MLDSATrait}; eprintln!("MLDSA87_lowmemory/KeyGen"); let seed = KeyMaterial256::from_bytes_as_type( - &[0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f], + &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, + ], KeyType::Seed, - ).unwrap(); + ) + .unwrap(); let (pk, _sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); println!("{:x?}", pk.encode()); } fn bench_mldsa44_sign() { - use bouncycastle::mldsa::{MLDSATrait, MLDSA44, MLDSA44PrivateKey, MLDSA44_SK_LEN}; + use bouncycastle::mldsa::{MLDSA44, MLDSA44_SK_LEN, MLDSA44PrivateKey, MLDSATrait}; eprintln!("MLDSA44/Sign"); @@ -172,7 +235,180 @@ fn bench_mldsa44_sign() { // use bouncycastle_hex as hex; // eprintln!("sk:\n{}", &hex::encode(sk.encode())); - let sk = MLDSA44PrivateKey::from_bytes(&[0xd7,0xb2,0xb4,0x72,0x54,0xaa,0xe0,0xdb,0x45,0xe7,0x93,0x0d,0x4a,0x98,0xd2,0xc9,0x7d,0x8f,0x13,0x97,0xd1,0x78,0x9d,0xaf,0xa1,0x70,0x24,0xb3,0x16,0xe9,0xbe,0xc9,0x39,0xce,0x0f,0x7f,0x77,0xf8,0xdb,0x56,0x44,0xdc,0xda,0x36,0x6b,0xfe,0x47,0x34,0xbd,0x95,0xf4,0x35,0xff,0x9a,0x61,0x3a,0xa5,0x4a,0xa4,0x1c,0x2c,0x69,0x4c,0x04,0x32,0x9a,0x07,0xb1,0xfa,0xbb,0x48,0xf5,0x2a,0x30,0x9f,0x11,0xa1,0x89,0x8f,0x84,0x8e,0x23,0x22,0xff,0xe6,0x23,0xec,0x81,0x0d,0xb3,0xbe,0xe3,0x36,0x85,0x85,0x4a,0x88,0x26,0x9d,0xa3,0x20,0xd5,0x12,0x0b,0xfc,0xfe,0x89,0xa1,0x8e,0x30,0xf7,0x11,0x4d,0x83,0xaa,0x40,0x4a,0x64,0x6b,0x6c,0x99,0x73,0x89,0x86,0x0d,0x12,0x52,0x2e,0xe0,0x00,0x6e,0x23,0x84,0x81,0x91,0x86,0x61,0x9b,0x26,0x0d,0x11,0x86,0x64,0xd4,0xa6,0x28,0x22,0x18,0x44,0x82,0x40,0x28,0x98,0x14,0x61,0x48,0xa6,0x61,0x4c,0x42,0x48,0xa1,0x92,0x08,0xc2,0x38,0x29,0x51,0x24,0x48,0x08,0xa1,0x25,0xc2,0x08,0x31,0x08,0xc4,0x71,0x20,0x14,0x09,0x14,0x83,0x6c,0x18,0xa7,0x80,0x84,0x10,0x6e,0xc9,0xc0,0x70,0x22,0xb5,0x64,0x08,0xb0,0x61,0x0c,0x07,0x04,0x98,0x12,0x44,0x51,0x88,0x69,0x59,0x00,0x46,0x22,0x93,0x20,0x41,0x06,0x2e,0x42,0xb6,0x4c,0x01,0x16,0x49,0x14,0x28,0x4c,0x41,0xa8,0x51,0x80,0x46,0x0a,0x51,0x16,0x51,0x5a,0x08,0x20,0x02,0x22,0x44,0xdc,0x98,0x49,0xd1,0x32,0x51,0xe1,0x30,0x65,0xd3,0xc0,0x85,0x92,0xa8,0x51,0x12,0xa1,0x64,0x00,0x39,0x22,0x09,0x46,0x62,0x1c,0xc7,0x0c,0xd9,0x08,0x6d,0xd0,0x06,0x26,0x52,0x40,0x85,0x80,0x44,0x30,0x91,0x06,0x2c,0x50,0xc8,0x09,0x24,0xc5,0x84,0x1a,0x96,0x6d,0x4a,0x98,0x2c,0x99,0x06,0x6d,0xa4,0x44,0x32,0x20,0xa7,0x64,0x5a,0x32,0x6e,0x11,0xb5,0x70,0x20,0x92,0x61,0x24,0x13,0x8e,0x04,0x85,0x2c,0x0a,0x48,0x72,0xc8,0xa0,0x51,0xd3,0x08,0x2a,0x99,0x20,0x80,0x58,0x24,0x20,0x24,0x07,0x4e,0x59,0x14,0x88,0x10,0xa4,0x64,0x60,0xc0,0x6d,0xe0,0xb2,0x8d,0x1b,0x19,0x09,0x20,0x34,0x22,0xc0,0x24,0x41,0x09,0x43,0x71,0x0a,0x21,0x20,0x61,0xa2,0x01,0x52,0x22,0x52,0x1b,0x80,0x80,0x9a,0x34,0x00,0x13,0x93,0x4d,0xd3,0x32,0x29,0x22,0x17,0x0a,0x98,0x92,0x69,0x1a,0x14,0x51,0x20,0x27,0x21,0x9c,0xc0,0x20,0x62,0xa2,0x81,0x48,0x18,0x69,0x1a,0x85,0x4d,0x83,0x44,0x69,0x5b,0x20,0x41,0x03,0x12,0x42,0xcb,0x18,0x46,0x01,0xa9,0x0d,0x0c,0x02,0x31,0x83,0xb0,0x21,0x5a,0x22,0x4a,0xc8,0x92,0x05,0xd9,0x90,0x69,0x04,0x30,0x6a,0x4b,0x06,0x4a,0xd2,0xb2,0x01,0x1c,0x40,0x40,0x81,0x42,0x32,0x52,0x32,0x72,0x54,0xa6,0x40,0x5a,0x18,0x10,0x0c,0x32,0x12,0x92,0xc2,0x80,0x52,0x12,0x62,0x5c,0x82,0x28,0x0b,0xb4,0x6c,0x03,0x42,0x8d,0x53,0x10,0x0c,0x14,0x01,0x0e,0xe1,0x36,0x52,0x88,0x84,0x24,0x91,0x02,0x0a,0x63,0x46,0x26,0x20,0x06,0x29,0x11,0xc2,0x28,0xd0,0x20,0x48,0x02,0xb3,0x6c,0xa2,0x36,0x09,0x5a,0x86,0x48,0xcb,0xb4,0x61,0x8b,0x46,0x62,0xc4,0x40,0x82,0x1a,0x89,0x09,0x10,0x02,0x4d,0x24,0xb2,0x45,0x20,0x12,0x25,0x24,0xc9,0x05,0x88,0x28,0x8c,0xc9,0xc0,0x4d,0x59,0x48,0x22,0x0a,0x27,0x6e,0xc1,0x34,0x64,0x4c,0x90,0x60,0x5b,0x44,0x50,0x82,0x86,0x49,0x43,0x88,0x04,0x43,0xb2,0x8c,0x60,0x30,0x80,0xa2,0x88,0x2d,0x84,0xa4,0x6d,0x8c,0xa6,0x29,0xd0,0xc6,0x84,0x42,0x06,0x46,0x89,0x88,0x51,0x00,0xa9,0x8d,0x01,0x49,0x8d,0xe4,0x38,0x0d,0xa4,0x06,0x8d,0xd3,0x94,0x71,0x42,0xb2,0x6c,0x1a,0x84,0x61,0x1b,0xa3,0x28,0x42,0xb4,0x28,0x08,0xa0,0x71,0x1a,0xc5,0x31,0xe0,0xa0,0x4c,0x01,0x37,0x65,0x24,0x28,0x62,0x14,0x28,0x90,0x09,0x10,0x61,0xd9,0x40,0x22,0x1b,0x33,0x60,0x09,0x02,0x92,0xd0,0x24,0x81,0x20,0x04,0x08,0x49,0x18,0x44,0xa3,0x22,0x2d,0x5c,0x88,0x44,0x14,0x98,0x08,0xa4,0x46,0x61,0x01,0x95,0x64,0x0b,0x39,0x0a,0x0c,0x94,0x50,0xca,0x40,0x6a,0xd2,0xb2,0x20,0xc0,0x38,0x01,0x82,0x30,0x8e,0x13,0xb9,0x08,0x91,0x80,0x84,0x14,0x88,0x29,0xc0,0x18,0x91,0x12,0x35,0x0d,0xa0,0x24,0x22,0xe2,0x04,0x06,0xd9,0xc2,0x85,0x04,0x28,0x12,0x1c,0xc9,0x89,0x18,0x02,0x72,0xd2,0x40,0x29,0xc2,0x08,0x12,0xd8,0x06,0x2a,0x99,0x94,0x71,0x9b,0xb8,0x68,0x23,0x84,0x29,0x1a,0x22,0x89,0x14,0x45,0x11,0xdc,0x82,0x44,0x50,0x96,0x45,0x0c,0x44,0x84,0xc0,0xb2,0x04,0x9a,0xa6,0x05,0x43,0x86,0x2c,0x44,0x32,0x6e,0x88,0x44,0x21,0x20,0xa8,0x4c,0x9a,0x30,0x70,0xe3,0xb8,0x2d,0x63,0x26,0x88,0x03,0x25,0x49,0x03,0x43,0x8c,0x48,0xa8,0x09,0xca,0x14,0x72,0x53,0x34,0x4e,0x12,0x43,0x08,0x1b,0xa7,0x04,0x59,0x30,0x22,0xd9,0x94,0x80,0xe2,0x34,0x22,0x81,0x42,0x12,0x9c,0x30,0x2a,0x94,0x34,0x26,0x61,0x04,0x45,0x24,0x26,0x28,0x13,0x46,0x09,0x4a,0x32,0x6d,0x11,0x28,0x09,0x18,0xb8,0x25,0x62,0x28,0x11,0x13,0x41,0x0d,0x41,0xb2,0x11,0x90,0x84,0x4c,0x8b,0x12,0x12,0xa2,0xc6,0x88,0xc9,0xc0,0x30,0x22,0x06,0x06,0xd2,0x18,0x8e,0x84,0x86,0x30,0x90,0x44,0x52,0x12,0x88,0x31,0xd9,0x20,0x71,0x13,0xc5,0x28,0x43,0x06,0x0e,0x03,0x30,0x60,0xcc,0xa6,0x84,0x58,0x26,0x52,0x4c,0x88,0x01,0x1e,0xf7,0x25,0x62,0xc8,0x5f,0xfa,0x43,0xac,0xfa,0x49,0x21,0x7f,0x2b,0x17,0x2d,0x7b,0xbc,0x14,0x62,0x0e,0x6d,0x98,0x0a,0x71,0xaa,0xbb,0xdf,0x0c,0x45,0xe9,0xa2,0x06,0xec,0xb1,0x42,0x3f,0xee,0x15,0xde,0xcc,0x17,0x60,0x13,0x00,0x14,0x9d,0x92,0x23,0xcd,0x6e,0x6c,0x6e,0x1f,0xa8,0xe4,0x1f,0xc7,0xc6,0x49,0x38,0xab,0x68,0x90,0x5f,0xd3,0xdc,0xda,0x50,0xd8,0x70,0x82,0xe7,0xd0,0xd7,0x1d,0x1b,0xc9,0xb2,0xb8,0x4c,0x85,0x52,0x3c,0xa8,0xfe,0x6c,0xad,0x29,0x4a,0xdf,0x83,0xbe,0x15,0xb1,0x08,0xff,0x72,0x1d,0x0c,0xc8,0x7b,0xc3,0xdd,0x3a,0x75,0x90,0x18,0x4b,0x0e,0x84,0x56,0x63,0xa9,0x1f,0xc9,0xe1,0xc3,0xc5,0x3a,0x61,0xd8,0x67,0x42,0x0b,0x04,0xf0,0x92,0x35,0x57,0x53,0xbc,0x65,0xa0,0x63,0x68,0xfd,0x41,0x29,0x5f,0xd0,0x99,0x24,0x13,0x2c,0x6f,0x91,0xf6,0x79,0x64,0xc1,0x42,0x67,0x4a,0x72,0x5c,0x34,0x39,0x14,0xc4,0xce,0xcf,0x58,0xc0,0x74,0xbc,0xaf,0x45,0x58,0xc9,0x7b,0xf7,0x91,0x1e,0x07,0xaa,0x6d,0x09,0x38,0xf2,0xee,0x2b,0xb3,0xc1,0xa8,0xc5,0x95,0xd6,0x35,0xe8,0x43,0x42,0xfd,0xea,0x01,0xdc,0x24,0xb2,0x11,0xad,0x2f,0xc2,0x81,0xcf,0x77,0xe5,0x91,0x10,0xc7,0xab,0xc5,0x4b,0xf0,0xc8,0x6d,0x48,0x0b,0x9b,0xe2,0x76,0x47,0x1d,0xc9,0xd6,0x03,0xce,0xe9,0x8c,0xfd,0xab,0x3e,0x9f,0xcf,0xb7,0x03,0x79,0x35,0x60,0x54,0x9e,0xa4,0x45,0x0f,0xa7,0xb3,0x3f,0xb9,0x16,0x9c,0x44,0xb4,0xd2,0x5f,0xb9,0xc4,0x57,0xf4,0x97,0x91,0xcd,0x3d,0xa0,0x3e,0xac,0x96,0x09,0x58,0x13,0xc1,0x05,0x13,0x2c,0xcd,0xa4,0xe6,0x3e,0x49,0x22,0x8c,0xd2,0x3d,0x8a,0x1f,0x37,0x85,0x6f,0x14,0x2d,0x93,0xb9,0x0d,0xb0,0x9f,0x82,0xaf,0x89,0x25,0x8c,0x63,0xaa,0xb8,0x04,0x7a,0x80,0xc0,0x36,0xc9,0x35,0x7e,0xa2,0x04,0x6f,0x8d,0xc6,0x35,0x4f,0x0c,0x52,0x95,0xf3,0x42,0xbb,0x41,0x7d,0x3c,0xfe,0xb0,0xb1,0xfd,0x33,0x62,0x2c,0x29,0xe1,0x4c,0xbb,0xd9,0x2e,0x13,0x63,0xc6,0x5e,0xbd,0x45,0x04,0xb7,0x51,0x23,0x29,0xb9,0x67,0x0e,0x32,0xe1,0xb2,0xc6,0x7a,0x54,0xe7,0xf1,0xa5,0x5f,0x8b,0x9f,0x9e,0xa0,0x4e,0x8c,0xa3,0xa7,0x05,0xe6,0x2a,0x3c,0x5e,0x63,0x73,0x74,0xaf,0xb7,0xae,0xb6,0xdd,0xea,0x61,0x2c,0xde,0x28,0xf0,0x1a,0x20,0x2d,0x7a,0xa4,0xe3,0x47,0x22,0xd2,0x7d,0xd3,0xf9,0xb8,0x98,0x94,0xd0,0x19,0xfd,0x5d,0x4d,0x71,0x19,0xef,0xe3,0x72,0x3b,0xba,0x10,0x4c,0xb8,0xbb,0x09,0x81,0xe0,0x74,0xde,0x3a,0xfe,0x20,0x0d,0xaa,0xae,0xad,0x82,0x6c,0xc4,0x5f,0x24,0x4d,0xbf,0x43,0x1a,0xfa,0xb3,0x4e,0xfb,0xdf,0x78,0x24,0x74,0xd2,0xfd,0x57,0x11,0x8f,0x64,0x62,0x14,0x93,0x4e,0xd9,0x9c,0xba,0x3b,0x00,0x3e,0x8d,0x67,0xa3,0x83,0x6f,0x6f,0x19,0xfc,0x41,0x91,0x0c,0xe5,0x16,0x3e,0xe3,0xae,0x99,0xeb,0x84,0xd5,0x14,0xeb,0x76,0x1e,0x63,0x68,0x4e,0xa5,0x6f,0x97,0x91,0xd2,0xdd,0x4a,0xac,0x6e,0x61,0x68,0xb9,0x48,0xc8,0x17,0xf7,0x5a,0x22,0x2a,0xcb,0x0e,0x8c,0xdc,0x03,0xcc,0x4a,0xfe,0x8f,0x67,0x15,0x7e,0x1a,0x36,0x3b,0x7f,0xae,0xff,0x9f,0x17,0x2b,0x98,0x91,0x36,0x77,0xc5,0xa1,0xdd,0x08,0x5e,0x9e,0xe4,0xc2,0x20,0x52,0xc1,0xaf,0x58,0x19,0x31,0x16,0x67,0x3d,0xcd,0x3b,0xfc,0x5f,0x34,0xb8,0x55,0xdc,0xc6,0xc7,0x78,0x85,0x64,0x9e,0x9e,0x71,0xf4,0x3d,0x4a,0xea,0x0f,0x4b,0x72,0xca,0x7e,0xda,0x05,0x78,0xba,0x13,0xd3,0x1a,0x65,0x8d,0x2d,0x06,0x0a,0x9a,0x66,0xff,0x69,0xed,0x1b,0xe7,0x99,0x7a,0x2f,0xb1,0xd2,0x72,0x3d,0x38,0xf9,0xbf,0xab,0xe1,0x8f,0x8e,0x7b,0x3c,0xda,0x90,0x6e,0x4e,0x9b,0x5e,0x94,0x2c,0x8e,0xae,0xb2,0x96,0x07,0x0e,0xbf,0xd3,0x64,0x94,0x7a,0x94,0x0c,0xc9,0x78,0xbe,0xd6,0x6b,0x37,0x74,0x9e,0x6d,0x5d,0xcd,0x7b,0xe8,0xc4,0x94,0x44,0x0e,0x2b,0x84,0xce,0xcf,0xef,0xb9,0x8c,0x0b,0xed,0xfb,0x3c,0x41,0xe3,0x35,0x9d,0x2c,0xd7,0x19,0x7f,0xbe,0x72,0x0c,0x48,0xaa,0x6c,0x6b,0x64,0x65,0xc1,0xee,0x63,0xe3,0x56,0x9c,0x2a,0xdc,0x74,0x44,0x91,0x37,0x0b,0x7f,0x78,0x26,0xfe,0x0b,0x77,0xa1,0xd1,0x9d,0x64,0x10,0x1d,0x03,0x2b,0x91,0x81,0x06,0xb4,0x2d,0x2e,0xf7,0x37,0x47,0xe5,0x60,0x1f,0xe4,0xba,0x50,0xf2,0x3e,0xde,0x52,0x1f,0x03,0x1a,0x81,0x7d,0x15,0x29,0x4a,0x43,0x72,0x2e,0x83,0x78,0x78,0x4b,0x6d,0xb0,0xcf,0x1b,0xa9,0xe8,0xae,0x91,0x1d,0x92,0x01,0xb9,0xce,0x9c,0xc3,0x01,0x9c,0x6f,0x5c,0x27,0xcb,0x98,0xda,0x26,0x14,0x4b,0x64,0x22,0x5a,0x7c,0x93,0x2b,0x30,0xf7,0x61,0xe7,0x8a,0x2d,0x59,0xa1,0xd8,0xb8,0x3e,0xc6,0x34,0x4a,0x2f,0x6d,0xd4,0x7e,0x76,0x57,0x06,0xd0,0x0b,0xf4,0xa7,0x9a,0x6a,0x92,0x6c,0x3b,0xa9,0x1d,0x81,0x2c,0x8f,0x2c,0x79,0x7a,0xb1,0x79,0x67,0x09,0xe5,0xd1,0x68,0x56,0x77,0x82,0x93,0x52,0x9f,0x02,0x86,0xd0,0x15,0xc3,0xb5,0x39,0x96,0x19,0x64,0x2a,0x33,0x3e,0x9e,0x59,0x3d,0x6e,0x3f,0x53,0x53,0x99,0x42,0x08,0xe9,0xe6,0xa3,0x32,0x85,0x1d,0x7f,0x65,0x25,0x22,0xa9,0x28,0xb9,0x17,0xe2,0x7e,0x2d,0x6d,0x42,0x13,0x7d,0xfe,0x2e,0xbf,0xa6,0xfb,0x1c,0x67,0xb2,0x6c,0x02,0x54,0x52,0x86,0x85,0xf7,0xeb,0xdb,0xe3,0x15,0xa6,0x8e,0xaa,0x2d,0xa7,0x69,0xe8,0xa9,0xf4,0x2d,0x3e,0x60,0x00,0x7c,0x71,0x33,0x09,0x26,0xb2,0xc0,0x01,0x2d,0x83,0xea,0xd4,0xe4,0xfd,0x1e,0xd8,0x72,0xcc,0xd1,0x97,0x22,0x01,0xd2,0xb0,0x27,0xf3,0x54,0x5a,0xc2,0xd3,0x0c,0xd7,0x8b,0xc1,0xd7,0x40,0xfe,0xcc,0xbc,0x6f,0xc2,0xa0,0x44,0x6c,0x6e,0x30,0xea,0xc5,0x1f,0x5a,0x69,0x09,0x8a,0xa2,0xd4,0x47,0xf2,0x08,0x5b,0x4e,0x4e,0x4b,0x92,0xcc,0xc2,0x69,0x21,0xd2,0xde,0x47,0x85,0x18,0xcd,0x09,0x0c,0xe2,0x67,0xae,0xa2,0xd2,0x7a,0xda,0x57,0xfd,0x88,0xb4,0x97,0x6d,0x89,0xfb,0x84,0x3c,0xdc,0xcf,0x49,0xa7,0x6c,0xa2,0x67,0x9e,0x68,0x01,0xbf,0xa7,0xfb,0x03,0x18,0x96,0xfb,0x50,0x62,0x97,0x04,0xb9,0x92,0x39,0x36,0xbb,0x5d,0xd3,0x85,0x31,0x11,0x21,0xca,0xdf,0xb1,0x19,0x95,0xe5,0x9b,0x73,0x03,0x4c,0xf6,0x7e,0xd0,0x3a,0xb8,0x13,0x86,0x76,0x48,0xd0,0x25,0x82,0x80,0x87,0xe9,0x49,0xa9,0xaf,0xd1,0x6b,0x95,0xd7,0x2d,0x99,0xb1,0xed,0xca,0x25,0x7a,0xac,0x13,0x2f,0xfb,0x7a,0x07,0x09,0xae,0xd5,0xa9,0xc0,0xff,0x05,0xfb,0x0f,0x2b,0xbf,0x28,0x40,0x9e,0xed,0x7b,0x5f,0x58,0x01,0xbe,0x96,0x4c,0xed,0x01,0x9e,0x1c,0xb7,0x85,0x1d,0x38,0x51,0xf1,0x02,0x90,0x67,0x4e,0x19,0xff,0xb0,0x08,0xb3,0x01,0xc4,0xac,0xf6,0x41,0xa2,0xbb,0x14,0x21,0x6e,0x1d,0x69,0xca,0xbf,0x52,0xb5,0xef,0x22,0x74,0x96,0xb0,0xf3,0x07,0x99,0xa8,0x55,0xd1,0x17,0xfa,0xd3,0x74,0x4a,0x6f,0xa3,0x35,0x03,0xea,0x79,0x8b,0x52,0xdd,0xd7,0xee,0x54,0x26,0x60,0x9d,0xbf,0xcd,0x3f,0x0c,0x13,0xb1,0x64,0xd6,0xc0,0x51,0xf7,0xed,0x4a,0x11,0x97,0x19,0xa7,0x12,0xe3,0x88,0xd3,0x28,0x40,0x20,0x81,0xff,0x13,0x54,0xb5,0x54,0xd2,0xc2,0x37,0xaf,0xed,0x3b,0x15,0x1c,0x4b,0xa8,0xe9,0xf4,0xbd,0xeb,0x84,0x99,0xa3,0x06,0x6e,0x26,0xbb,0xc6,0x9e,0x8a,0xf0,0x89,0xde,0xc7,0x17,0x31,0xd1,0xdc,0x52,0x9e,0xab,0x17,0xef,0x73,0x74,0x73,0x4c,0x0f,0xe4,0x75,0x49,0x4c,0x83,0x83,0x6b,0xdd,0x34,0xa0,0x3b,0x9b,0xc8,0x99,0x14,0x71,0x60,0x61,0xbf,0xb9,0x8e,0xc6,0xe6,0x1c,0x3e,0xd4,0x43,0x8e,0xdc,0xaf,0x25,0x24,0x3c,0x64,0x70,0x86,0xb9,0xea,0x70,0x18,0xb0,0xd9,0xa8,0xa0,0xb0,0x0c,0xec,0xb0,0x0a,0xbd,0xe2,0x49,0x8d,0x69,0xc2,0x33,0x61,0x01,0xa7,0x72,0xcb,0xe4,0xf5,0x71,0x52,0x3f,0x51,0xbd,0x05,0x88,0x2c,0xdf,0x35,0x8b,0x84,0x9c,0xc1,0x40,0xaa,0x1f,0xaf,0x22,0x42,0x3a,0x12,0x85,0x1c,0xe0,0xe3,0x3f,0xd4,0x89,0x75,0xa4,0x95,0x9f,0xa5,0xc5,0xfe,0x41,0x8c,0x93,0x90,0x81,0x91,0xab,0x6e,0x74,0x1b,0x77,0xbf,0xe0,0x2c,0xbd,0x69,0x8e,0xe7,0x95,0xc4,0x66,0xd6,0x15,0x61,0x9e,0x64,0x41,0x38,0x2c,0x6e,0xac,0x01,0x83,0x4e,0xe9,0xab,0x73,0xce,0xa8,0x0b,0xbe,0x23,0x5c,0x78,0xda,0x91,0xbd,0x79,0xb6,0xf8,0x2f,0x89,0x97,0x85,0xd6,0x87,0x00,0xd3,0x93,0xe6,0x75,0xc2,0x22,0x4d,0x6b,0x7a,0x1a,0xd2,0x13,0x20,0x49,0x56,0x79,0xad,0xae,0xd7,0x01,0x67,0xb5,0x08,0x66,0x71,0x3a,0x53,0x10,0x9d,0xb7,0xb6,0xf7,0xd8,0x13,0x04,0xec,0xdf,0xd8,0x3b,0x31,0x9b,0x1e,0xf2,0x48,0x30,0x6b,0x45,0xad,0x29,0xe7,0xdd,0xcc,0x86,0x3d,0xac,0x56,0x04,0x8b,0x5d,0x69,0xea,0x17,0x50,0x11,0xf7,0x61,0x4c,0x00,0xa8,0x6a,0x86,0x3c,0xde,0x18,0x72,0xa8,0x93,0x28,0x78,0xb9,0xac,0x7e,0x1a,0xc5,0xbd,0xa4,0x99,0x7b,0x72,0x06,0x4f,0x0c,0xd7,0x5f,0x4c,0x81,0x4e,0x03,0x4d,0xe1,0x1a,0xcb,0x90,0x13,0xcf,0x7e,0xa9,0x26,0xb4,0xe7,0xea,0xac,0xe0,0x70,0xc7,0xba,0x21,0x88,0xef,0xad,0x2e,0x43,0x1e,0x12,0x23,0xd4,0x5d,0xd0,0x5c,0x4d,0x84,0x03,0xc2,0xe4,0x5c,0xee,0x64,0x13,0xec,0xbe,0x75,0x27,0xe8,0x73,0xe4,0x55,0xc4,0xe6,0x10,0xa6,0x18,0x39,0xaa,0xcc,0x0b,0xd5,0x6d,0x24,0x83,0xe7,0x8f,0x29,0x8b,0x66,0xa4,0x78,0xeb,0x2f,0x55,0x8c,0xba,0xfc,0xa8,0x6b,0xe8,0x47,0xba,0xeb,0x02,0xc5,0xb2,0x16,0xc8,0xcd,0x88,0xfe,0xa4,0xdf,0x24,0x9b,0x09,0xe6,0x70,0xa2,0x07,0x03,0xab,0xac,0x24,0xb0,0xa9,0x1a,0xbc,0x4a,0x56,0x46,0x60,0x14,0x42,0xba,0x10,0xbe,0xcf,0xd3,0x09,0x93,0x88,0x00,0x51,0xd0,0x7f,0x56,0xa0,0x5a,0x93,0x79,0xe7,0xa8,0xe6,0xbe,0xfe,0xe3,0xf2,0x2f,0xaa,0x10,0x63,0x98,0xf7,0x70,0x60,0x06,0xe4,0x2e,0x9b,0xe1,0xef,0x89,0xd2,0x5c,0x27,0x2f,0x11,0xa9,0x50,0x95,0xc5,0x87,0xd7,0x13,0x73,0x22,0x84,0xde,0x9d,0xbd,0x3c,0x72,0x17,0xb0,0x68,0x9e,0x21,0xd8,0xeb,0x0f,0xf6,0x96,0x68]).unwrap(); + let sk = MLDSA44PrivateKey::from_bytes(&[ + 0xD7, 0xB2, 0xB4, 0x72, 0x54, 0xAA, 0xE0, 0xDB, 0x45, 0xE7, 0x93, 0x0D, 0x4A, 0x98, 0xD2, + 0xC9, 0x7D, 0x8F, 0x13, 0x97, 0xD1, 0x78, 0x9D, 0xAF, 0xA1, 0x70, 0x24, 0xB3, 0x16, 0xE9, + 0xBE, 0xC9, 0x39, 0xCE, 0x0F, 0x7F, 0x77, 0xF8, 0xDB, 0x56, 0x44, 0xDC, 0xDA, 0x36, 0x6B, + 0xFE, 0x47, 0x34, 0xBD, 0x95, 0xF4, 0x35, 0xFF, 0x9A, 0x61, 0x3A, 0xA5, 0x4A, 0xA4, 0x1C, + 0x2C, 0x69, 0x4C, 0x04, 0x32, 0x9A, 0x07, 0xB1, 0xFA, 0xBB, 0x48, 0xF5, 0x2A, 0x30, 0x9F, + 0x11, 0xA1, 0x89, 0x8F, 0x84, 0x8E, 0x23, 0x22, 0xFF, 0xE6, 0x23, 0xEC, 0x81, 0x0D, 0xB3, + 0xBE, 0xE3, 0x36, 0x85, 0x85, 0x4A, 0x88, 0x26, 0x9D, 0xA3, 0x20, 0xD5, 0x12, 0x0B, 0xFC, + 0xFE, 0x89, 0xA1, 0x8E, 0x30, 0xF7, 0x11, 0x4D, 0x83, 0xAA, 0x40, 0x4A, 0x64, 0x6B, 0x6C, + 0x99, 0x73, 0x89, 0x86, 0x0D, 0x12, 0x52, 0x2E, 0xE0, 0x00, 0x6E, 0x23, 0x84, 0x81, 0x91, + 0x86, 0x61, 0x9B, 0x26, 0x0D, 0x11, 0x86, 0x64, 0xD4, 0xA6, 0x28, 0x22, 0x18, 0x44, 0x82, + 0x40, 0x28, 0x98, 0x14, 0x61, 0x48, 0xA6, 0x61, 0x4C, 0x42, 0x48, 0xA1, 0x92, 0x08, 0xC2, + 0x38, 0x29, 0x51, 0x24, 0x48, 0x08, 0xA1, 0x25, 0xC2, 0x08, 0x31, 0x08, 0xC4, 0x71, 0x20, + 0x14, 0x09, 0x14, 0x83, 0x6C, 0x18, 0xA7, 0x80, 0x84, 0x10, 0x6E, 0xC9, 0xC0, 0x70, 0x22, + 0xB5, 0x64, 0x08, 0xB0, 0x61, 0x0C, 0x07, 0x04, 0x98, 0x12, 0x44, 0x51, 0x88, 0x69, 0x59, + 0x00, 0x46, 0x22, 0x93, 0x20, 0x41, 0x06, 0x2E, 0x42, 0xB6, 0x4C, 0x01, 0x16, 0x49, 0x14, + 0x28, 0x4C, 0x41, 0xA8, 0x51, 0x80, 0x46, 0x0A, 0x51, 0x16, 0x51, 0x5A, 0x08, 0x20, 0x02, + 0x22, 0x44, 0xDC, 0x98, 0x49, 0xD1, 0x32, 0x51, 0xE1, 0x30, 0x65, 0xD3, 0xC0, 0x85, 0x92, + 0xA8, 0x51, 0x12, 0xA1, 0x64, 0x00, 0x39, 0x22, 0x09, 0x46, 0x62, 0x1C, 0xC7, 0x0C, 0xD9, + 0x08, 0x6D, 0xD0, 0x06, 0x26, 0x52, 0x40, 0x85, 0x80, 0x44, 0x30, 0x91, 0x06, 0x2C, 0x50, + 0xC8, 0x09, 0x24, 0xC5, 0x84, 0x1A, 0x96, 0x6D, 0x4A, 0x98, 0x2C, 0x99, 0x06, 0x6D, 0xA4, + 0x44, 0x32, 0x20, 0xA7, 0x64, 0x5A, 0x32, 0x6E, 0x11, 0xB5, 0x70, 0x20, 0x92, 0x61, 0x24, + 0x13, 0x8E, 0x04, 0x85, 0x2C, 0x0A, 0x48, 0x72, 0xC8, 0xA0, 0x51, 0xD3, 0x08, 0x2A, 0x99, + 0x20, 0x80, 0x58, 0x24, 0x20, 0x24, 0x07, 0x4E, 0x59, 0x14, 0x88, 0x10, 0xA4, 0x64, 0x60, + 0xC0, 0x6D, 0xE0, 0xB2, 0x8D, 0x1B, 0x19, 0x09, 0x20, 0x34, 0x22, 0xC0, 0x24, 0x41, 0x09, + 0x43, 0x71, 0x0A, 0x21, 0x20, 0x61, 0xA2, 0x01, 0x52, 0x22, 0x52, 0x1B, 0x80, 0x80, 0x9A, + 0x34, 0x00, 0x13, 0x93, 0x4D, 0xD3, 0x32, 0x29, 0x22, 0x17, 0x0A, 0x98, 0x92, 0x69, 0x1A, + 0x14, 0x51, 0x20, 0x27, 0x21, 0x9C, 0xC0, 0x20, 0x62, 0xA2, 0x81, 0x48, 0x18, 0x69, 0x1A, + 0x85, 0x4D, 0x83, 0x44, 0x69, 0x5B, 0x20, 0x41, 0x03, 0x12, 0x42, 0xCB, 0x18, 0x46, 0x01, + 0xA9, 0x0D, 0x0C, 0x02, 0x31, 0x83, 0xB0, 0x21, 0x5A, 0x22, 0x4A, 0xC8, 0x92, 0x05, 0xD9, + 0x90, 0x69, 0x04, 0x30, 0x6A, 0x4B, 0x06, 0x4A, 0xD2, 0xB2, 0x01, 0x1C, 0x40, 0x40, 0x81, + 0x42, 0x32, 0x52, 0x32, 0x72, 0x54, 0xA6, 0x40, 0x5A, 0x18, 0x10, 0x0C, 0x32, 0x12, 0x92, + 0xC2, 0x80, 0x52, 0x12, 0x62, 0x5C, 0x82, 0x28, 0x0B, 0xB4, 0x6C, 0x03, 0x42, 0x8D, 0x53, + 0x10, 0x0C, 0x14, 0x01, 0x0E, 0xE1, 0x36, 0x52, 0x88, 0x84, 0x24, 0x91, 0x02, 0x0A, 0x63, + 0x46, 0x26, 0x20, 0x06, 0x29, 0x11, 0xC2, 0x28, 0xD0, 0x20, 0x48, 0x02, 0xB3, 0x6C, 0xA2, + 0x36, 0x09, 0x5A, 0x86, 0x48, 0xCB, 0xB4, 0x61, 0x8B, 0x46, 0x62, 0xC4, 0x40, 0x82, 0x1A, + 0x89, 0x09, 0x10, 0x02, 0x4D, 0x24, 0xB2, 0x45, 0x20, 0x12, 0x25, 0x24, 0xC9, 0x05, 0x88, + 0x28, 0x8C, 0xC9, 0xC0, 0x4D, 0x59, 0x48, 0x22, 0x0A, 0x27, 0x6E, 0xC1, 0x34, 0x64, 0x4C, + 0x90, 0x60, 0x5B, 0x44, 0x50, 0x82, 0x86, 0x49, 0x43, 0x88, 0x04, 0x43, 0xB2, 0x8C, 0x60, + 0x30, 0x80, 0xA2, 0x88, 0x2D, 0x84, 0xA4, 0x6D, 0x8C, 0xA6, 0x29, 0xD0, 0xC6, 0x84, 0x42, + 0x06, 0x46, 0x89, 0x88, 0x51, 0x00, 0xA9, 0x8D, 0x01, 0x49, 0x8D, 0xE4, 0x38, 0x0D, 0xA4, + 0x06, 0x8D, 0xD3, 0x94, 0x71, 0x42, 0xB2, 0x6C, 0x1A, 0x84, 0x61, 0x1B, 0xA3, 0x28, 0x42, + 0xB4, 0x28, 0x08, 0xA0, 0x71, 0x1A, 0xC5, 0x31, 0xE0, 0xA0, 0x4C, 0x01, 0x37, 0x65, 0x24, + 0x28, 0x62, 0x14, 0x28, 0x90, 0x09, 0x10, 0x61, 0xD9, 0x40, 0x22, 0x1B, 0x33, 0x60, 0x09, + 0x02, 0x92, 0xD0, 0x24, 0x81, 0x20, 0x04, 0x08, 0x49, 0x18, 0x44, 0xA3, 0x22, 0x2D, 0x5C, + 0x88, 0x44, 0x14, 0x98, 0x08, 0xA4, 0x46, 0x61, 0x01, 0x95, 0x64, 0x0B, 0x39, 0x0A, 0x0C, + 0x94, 0x50, 0xCA, 0x40, 0x6A, 0xD2, 0xB2, 0x20, 0xC0, 0x38, 0x01, 0x82, 0x30, 0x8E, 0x13, + 0xB9, 0x08, 0x91, 0x80, 0x84, 0x14, 0x88, 0x29, 0xC0, 0x18, 0x91, 0x12, 0x35, 0x0D, 0xA0, + 0x24, 0x22, 0xE2, 0x04, 0x06, 0xD9, 0xC2, 0x85, 0x04, 0x28, 0x12, 0x1C, 0xC9, 0x89, 0x18, + 0x02, 0x72, 0xD2, 0x40, 0x29, 0xC2, 0x08, 0x12, 0xD8, 0x06, 0x2A, 0x99, 0x94, 0x71, 0x9B, + 0xB8, 0x68, 0x23, 0x84, 0x29, 0x1A, 0x22, 0x89, 0x14, 0x45, 0x11, 0xDC, 0x82, 0x44, 0x50, + 0x96, 0x45, 0x0C, 0x44, 0x84, 0xC0, 0xB2, 0x04, 0x9A, 0xA6, 0x05, 0x43, 0x86, 0x2C, 0x44, + 0x32, 0x6E, 0x88, 0x44, 0x21, 0x20, 0xA8, 0x4C, 0x9A, 0x30, 0x70, 0xE3, 0xB8, 0x2D, 0x63, + 0x26, 0x88, 0x03, 0x25, 0x49, 0x03, 0x43, 0x8C, 0x48, 0xA8, 0x09, 0xCA, 0x14, 0x72, 0x53, + 0x34, 0x4E, 0x12, 0x43, 0x08, 0x1B, 0xA7, 0x04, 0x59, 0x30, 0x22, 0xD9, 0x94, 0x80, 0xE2, + 0x34, 0x22, 0x81, 0x42, 0x12, 0x9C, 0x30, 0x2A, 0x94, 0x34, 0x26, 0x61, 0x04, 0x45, 0x24, + 0x26, 0x28, 0x13, 0x46, 0x09, 0x4A, 0x32, 0x6D, 0x11, 0x28, 0x09, 0x18, 0xB8, 0x25, 0x62, + 0x28, 0x11, 0x13, 0x41, 0x0D, 0x41, 0xB2, 0x11, 0x90, 0x84, 0x4C, 0x8B, 0x12, 0x12, 0xA2, + 0xC6, 0x88, 0xC9, 0xC0, 0x30, 0x22, 0x06, 0x06, 0xD2, 0x18, 0x8E, 0x84, 0x86, 0x30, 0x90, + 0x44, 0x52, 0x12, 0x88, 0x31, 0xD9, 0x20, 0x71, 0x13, 0xC5, 0x28, 0x43, 0x06, 0x0E, 0x03, + 0x30, 0x60, 0xCC, 0xA6, 0x84, 0x58, 0x26, 0x52, 0x4C, 0x88, 0x01, 0x1E, 0xF7, 0x25, 0x62, + 0xC8, 0x5F, 0xFA, 0x43, 0xAC, 0xFA, 0x49, 0x21, 0x7F, 0x2B, 0x17, 0x2D, 0x7B, 0xBC, 0x14, + 0x62, 0x0E, 0x6D, 0x98, 0x0A, 0x71, 0xAA, 0xBB, 0xDF, 0x0C, 0x45, 0xE9, 0xA2, 0x06, 0xEC, + 0xB1, 0x42, 0x3F, 0xEE, 0x15, 0xDE, 0xCC, 0x17, 0x60, 0x13, 0x00, 0x14, 0x9D, 0x92, 0x23, + 0xCD, 0x6E, 0x6C, 0x6E, 0x1F, 0xA8, 0xE4, 0x1F, 0xC7, 0xC6, 0x49, 0x38, 0xAB, 0x68, 0x90, + 0x5F, 0xD3, 0xDC, 0xDA, 0x50, 0xD8, 0x70, 0x82, 0xE7, 0xD0, 0xD7, 0x1D, 0x1B, 0xC9, 0xB2, + 0xB8, 0x4C, 0x85, 0x52, 0x3C, 0xA8, 0xFE, 0x6C, 0xAD, 0x29, 0x4A, 0xDF, 0x83, 0xBE, 0x15, + 0xB1, 0x08, 0xFF, 0x72, 0x1D, 0x0C, 0xC8, 0x7B, 0xC3, 0xDD, 0x3A, 0x75, 0x90, 0x18, 0x4B, + 0x0E, 0x84, 0x56, 0x63, 0xA9, 0x1F, 0xC9, 0xE1, 0xC3, 0xC5, 0x3A, 0x61, 0xD8, 0x67, 0x42, + 0x0B, 0x04, 0xF0, 0x92, 0x35, 0x57, 0x53, 0xBC, 0x65, 0xA0, 0x63, 0x68, 0xFD, 0x41, 0x29, + 0x5F, 0xD0, 0x99, 0x24, 0x13, 0x2C, 0x6F, 0x91, 0xF6, 0x79, 0x64, 0xC1, 0x42, 0x67, 0x4A, + 0x72, 0x5C, 0x34, 0x39, 0x14, 0xC4, 0xCE, 0xCF, 0x58, 0xC0, 0x74, 0xBC, 0xAF, 0x45, 0x58, + 0xC9, 0x7B, 0xF7, 0x91, 0x1E, 0x07, 0xAA, 0x6D, 0x09, 0x38, 0xF2, 0xEE, 0x2B, 0xB3, 0xC1, + 0xA8, 0xC5, 0x95, 0xD6, 0x35, 0xE8, 0x43, 0x42, 0xFD, 0xEA, 0x01, 0xDC, 0x24, 0xB2, 0x11, + 0xAD, 0x2F, 0xC2, 0x81, 0xCF, 0x77, 0xE5, 0x91, 0x10, 0xC7, 0xAB, 0xC5, 0x4B, 0xF0, 0xC8, + 0x6D, 0x48, 0x0B, 0x9B, 0xE2, 0x76, 0x47, 0x1D, 0xC9, 0xD6, 0x03, 0xCE, 0xE9, 0x8C, 0xFD, + 0xAB, 0x3E, 0x9F, 0xCF, 0xB7, 0x03, 0x79, 0x35, 0x60, 0x54, 0x9E, 0xA4, 0x45, 0x0F, 0xA7, + 0xB3, 0x3F, 0xB9, 0x16, 0x9C, 0x44, 0xB4, 0xD2, 0x5F, 0xB9, 0xC4, 0x57, 0xF4, 0x97, 0x91, + 0xCD, 0x3D, 0xA0, 0x3E, 0xAC, 0x96, 0x09, 0x58, 0x13, 0xC1, 0x05, 0x13, 0x2C, 0xCD, 0xA4, + 0xE6, 0x3E, 0x49, 0x22, 0x8C, 0xD2, 0x3D, 0x8A, 0x1F, 0x37, 0x85, 0x6F, 0x14, 0x2D, 0x93, + 0xB9, 0x0D, 0xB0, 0x9F, 0x82, 0xAF, 0x89, 0x25, 0x8C, 0x63, 0xAA, 0xB8, 0x04, 0x7A, 0x80, + 0xC0, 0x36, 0xC9, 0x35, 0x7E, 0xA2, 0x04, 0x6F, 0x8D, 0xC6, 0x35, 0x4F, 0x0C, 0x52, 0x95, + 0xF3, 0x42, 0xBB, 0x41, 0x7D, 0x3C, 0xFE, 0xB0, 0xB1, 0xFD, 0x33, 0x62, 0x2C, 0x29, 0xE1, + 0x4C, 0xBB, 0xD9, 0x2E, 0x13, 0x63, 0xC6, 0x5E, 0xBD, 0x45, 0x04, 0xB7, 0x51, 0x23, 0x29, + 0xB9, 0x67, 0x0E, 0x32, 0xE1, 0xB2, 0xC6, 0x7A, 0x54, 0xE7, 0xF1, 0xA5, 0x5F, 0x8B, 0x9F, + 0x9E, 0xA0, 0x4E, 0x8C, 0xA3, 0xA7, 0x05, 0xE6, 0x2A, 0x3C, 0x5E, 0x63, 0x73, 0x74, 0xAF, + 0xB7, 0xAE, 0xB6, 0xDD, 0xEA, 0x61, 0x2C, 0xDE, 0x28, 0xF0, 0x1A, 0x20, 0x2D, 0x7A, 0xA4, + 0xE3, 0x47, 0x22, 0xD2, 0x7D, 0xD3, 0xF9, 0xB8, 0x98, 0x94, 0xD0, 0x19, 0xFD, 0x5D, 0x4D, + 0x71, 0x19, 0xEF, 0xE3, 0x72, 0x3B, 0xBA, 0x10, 0x4C, 0xB8, 0xBB, 0x09, 0x81, 0xE0, 0x74, + 0xDE, 0x3A, 0xFE, 0x20, 0x0D, 0xAA, 0xAE, 0xAD, 0x82, 0x6C, 0xC4, 0x5F, 0x24, 0x4D, 0xBF, + 0x43, 0x1A, 0xFA, 0xB3, 0x4E, 0xFB, 0xDF, 0x78, 0x24, 0x74, 0xD2, 0xFD, 0x57, 0x11, 0x8F, + 0x64, 0x62, 0x14, 0x93, 0x4E, 0xD9, 0x9C, 0xBA, 0x3B, 0x00, 0x3E, 0x8D, 0x67, 0xA3, 0x83, + 0x6F, 0x6F, 0x19, 0xFC, 0x41, 0x91, 0x0C, 0xE5, 0x16, 0x3E, 0xE3, 0xAE, 0x99, 0xEB, 0x84, + 0xD5, 0x14, 0xEB, 0x76, 0x1E, 0x63, 0x68, 0x4E, 0xA5, 0x6F, 0x97, 0x91, 0xD2, 0xDD, 0x4A, + 0xAC, 0x6E, 0x61, 0x68, 0xB9, 0x48, 0xC8, 0x17, 0xF7, 0x5A, 0x22, 0x2A, 0xCB, 0x0E, 0x8C, + 0xDC, 0x03, 0xCC, 0x4A, 0xFE, 0x8F, 0x67, 0x15, 0x7E, 0x1A, 0x36, 0x3B, 0x7F, 0xAE, 0xFF, + 0x9F, 0x17, 0x2B, 0x98, 0x91, 0x36, 0x77, 0xC5, 0xA1, 0xDD, 0x08, 0x5E, 0x9E, 0xE4, 0xC2, + 0x20, 0x52, 0xC1, 0xAF, 0x58, 0x19, 0x31, 0x16, 0x67, 0x3D, 0xCD, 0x3B, 0xFC, 0x5F, 0x34, + 0xB8, 0x55, 0xDC, 0xC6, 0xC7, 0x78, 0x85, 0x64, 0x9E, 0x9E, 0x71, 0xF4, 0x3D, 0x4A, 0xEA, + 0x0F, 0x4B, 0x72, 0xCA, 0x7E, 0xDA, 0x05, 0x78, 0xBA, 0x13, 0xD3, 0x1A, 0x65, 0x8D, 0x2D, + 0x06, 0x0A, 0x9A, 0x66, 0xFF, 0x69, 0xED, 0x1B, 0xE7, 0x99, 0x7A, 0x2F, 0xB1, 0xD2, 0x72, + 0x3D, 0x38, 0xF9, 0xBF, 0xAB, 0xE1, 0x8F, 0x8E, 0x7B, 0x3C, 0xDA, 0x90, 0x6E, 0x4E, 0x9B, + 0x5E, 0x94, 0x2C, 0x8E, 0xAE, 0xB2, 0x96, 0x07, 0x0E, 0xBF, 0xD3, 0x64, 0x94, 0x7A, 0x94, + 0x0C, 0xC9, 0x78, 0xBE, 0xD6, 0x6B, 0x37, 0x74, 0x9E, 0x6D, 0x5D, 0xCD, 0x7B, 0xE8, 0xC4, + 0x94, 0x44, 0x0E, 0x2B, 0x84, 0xCE, 0xCF, 0xEF, 0xB9, 0x8C, 0x0B, 0xED, 0xFB, 0x3C, 0x41, + 0xE3, 0x35, 0x9D, 0x2C, 0xD7, 0x19, 0x7F, 0xBE, 0x72, 0x0C, 0x48, 0xAA, 0x6C, 0x6B, 0x64, + 0x65, 0xC1, 0xEE, 0x63, 0xE3, 0x56, 0x9C, 0x2A, 0xDC, 0x74, 0x44, 0x91, 0x37, 0x0B, 0x7F, + 0x78, 0x26, 0xFE, 0x0B, 0x77, 0xA1, 0xD1, 0x9D, 0x64, 0x10, 0x1D, 0x03, 0x2B, 0x91, 0x81, + 0x06, 0xB4, 0x2D, 0x2E, 0xF7, 0x37, 0x47, 0xE5, 0x60, 0x1F, 0xE4, 0xBA, 0x50, 0xF2, 0x3E, + 0xDE, 0x52, 0x1F, 0x03, 0x1A, 0x81, 0x7D, 0x15, 0x29, 0x4A, 0x43, 0x72, 0x2E, 0x83, 0x78, + 0x78, 0x4B, 0x6D, 0xB0, 0xCF, 0x1B, 0xA9, 0xE8, 0xAE, 0x91, 0x1D, 0x92, 0x01, 0xB9, 0xCE, + 0x9C, 0xC3, 0x01, 0x9C, 0x6F, 0x5C, 0x27, 0xCB, 0x98, 0xDA, 0x26, 0x14, 0x4B, 0x64, 0x22, + 0x5A, 0x7C, 0x93, 0x2B, 0x30, 0xF7, 0x61, 0xE7, 0x8A, 0x2D, 0x59, 0xA1, 0xD8, 0xB8, 0x3E, + 0xC6, 0x34, 0x4A, 0x2F, 0x6D, 0xD4, 0x7E, 0x76, 0x57, 0x06, 0xD0, 0x0B, 0xF4, 0xA7, 0x9A, + 0x6A, 0x92, 0x6C, 0x3B, 0xA9, 0x1D, 0x81, 0x2C, 0x8F, 0x2C, 0x79, 0x7A, 0xB1, 0x79, 0x67, + 0x09, 0xE5, 0xD1, 0x68, 0x56, 0x77, 0x82, 0x93, 0x52, 0x9F, 0x02, 0x86, 0xD0, 0x15, 0xC3, + 0xB5, 0x39, 0x96, 0x19, 0x64, 0x2A, 0x33, 0x3E, 0x9E, 0x59, 0x3D, 0x6E, 0x3F, 0x53, 0x53, + 0x99, 0x42, 0x08, 0xE9, 0xE6, 0xA3, 0x32, 0x85, 0x1D, 0x7F, 0x65, 0x25, 0x22, 0xA9, 0x28, + 0xB9, 0x17, 0xE2, 0x7E, 0x2D, 0x6D, 0x42, 0x13, 0x7D, 0xFE, 0x2E, 0xBF, 0xA6, 0xFB, 0x1C, + 0x67, 0xB2, 0x6C, 0x02, 0x54, 0x52, 0x86, 0x85, 0xF7, 0xEB, 0xDB, 0xE3, 0x15, 0xA6, 0x8E, + 0xAA, 0x2D, 0xA7, 0x69, 0xE8, 0xA9, 0xF4, 0x2D, 0x3E, 0x60, 0x00, 0x7C, 0x71, 0x33, 0x09, + 0x26, 0xB2, 0xC0, 0x01, 0x2D, 0x83, 0xEA, 0xD4, 0xE4, 0xFD, 0x1E, 0xD8, 0x72, 0xCC, 0xD1, + 0x97, 0x22, 0x01, 0xD2, 0xB0, 0x27, 0xF3, 0x54, 0x5A, 0xC2, 0xD3, 0x0C, 0xD7, 0x8B, 0xC1, + 0xD7, 0x40, 0xFE, 0xCC, 0xBC, 0x6F, 0xC2, 0xA0, 0x44, 0x6C, 0x6E, 0x30, 0xEA, 0xC5, 0x1F, + 0x5A, 0x69, 0x09, 0x8A, 0xA2, 0xD4, 0x47, 0xF2, 0x08, 0x5B, 0x4E, 0x4E, 0x4B, 0x92, 0xCC, + 0xC2, 0x69, 0x21, 0xD2, 0xDE, 0x47, 0x85, 0x18, 0xCD, 0x09, 0x0C, 0xE2, 0x67, 0xAE, 0xA2, + 0xD2, 0x7A, 0xDA, 0x57, 0xFD, 0x88, 0xB4, 0x97, 0x6D, 0x89, 0xFB, 0x84, 0x3C, 0xDC, 0xCF, + 0x49, 0xA7, 0x6C, 0xA2, 0x67, 0x9E, 0x68, 0x01, 0xBF, 0xA7, 0xFB, 0x03, 0x18, 0x96, 0xFB, + 0x50, 0x62, 0x97, 0x04, 0xB9, 0x92, 0x39, 0x36, 0xBB, 0x5D, 0xD3, 0x85, 0x31, 0x11, 0x21, + 0xCA, 0xDF, 0xB1, 0x19, 0x95, 0xE5, 0x9B, 0x73, 0x03, 0x4C, 0xF6, 0x7E, 0xD0, 0x3A, 0xB8, + 0x13, 0x86, 0x76, 0x48, 0xD0, 0x25, 0x82, 0x80, 0x87, 0xE9, 0x49, 0xA9, 0xAF, 0xD1, 0x6B, + 0x95, 0xD7, 0x2D, 0x99, 0xB1, 0xED, 0xCA, 0x25, 0x7A, 0xAC, 0x13, 0x2F, 0xFB, 0x7A, 0x07, + 0x09, 0xAE, 0xD5, 0xA9, 0xC0, 0xFF, 0x05, 0xFB, 0x0F, 0x2B, 0xBF, 0x28, 0x40, 0x9E, 0xED, + 0x7B, 0x5F, 0x58, 0x01, 0xBE, 0x96, 0x4C, 0xED, 0x01, 0x9E, 0x1C, 0xB7, 0x85, 0x1D, 0x38, + 0x51, 0xF1, 0x02, 0x90, 0x67, 0x4E, 0x19, 0xFF, 0xB0, 0x08, 0xB3, 0x01, 0xC4, 0xAC, 0xF6, + 0x41, 0xA2, 0xBB, 0x14, 0x21, 0x6E, 0x1D, 0x69, 0xCA, 0xBF, 0x52, 0xB5, 0xEF, 0x22, 0x74, + 0x96, 0xB0, 0xF3, 0x07, 0x99, 0xA8, 0x55, 0xD1, 0x17, 0xFA, 0xD3, 0x74, 0x4A, 0x6F, 0xA3, + 0x35, 0x03, 0xEA, 0x79, 0x8B, 0x52, 0xDD, 0xD7, 0xEE, 0x54, 0x26, 0x60, 0x9D, 0xBF, 0xCD, + 0x3F, 0x0C, 0x13, 0xB1, 0x64, 0xD6, 0xC0, 0x51, 0xF7, 0xED, 0x4A, 0x11, 0x97, 0x19, 0xA7, + 0x12, 0xE3, 0x88, 0xD3, 0x28, 0x40, 0x20, 0x81, 0xFF, 0x13, 0x54, 0xB5, 0x54, 0xD2, 0xC2, + 0x37, 0xAF, 0xED, 0x3B, 0x15, 0x1C, 0x4B, 0xA8, 0xE9, 0xF4, 0xBD, 0xEB, 0x84, 0x99, 0xA3, + 0x06, 0x6E, 0x26, 0xBB, 0xC6, 0x9E, 0x8A, 0xF0, 0x89, 0xDE, 0xC7, 0x17, 0x31, 0xD1, 0xDC, + 0x52, 0x9E, 0xAB, 0x17, 0xEF, 0x73, 0x74, 0x73, 0x4C, 0x0F, 0xE4, 0x75, 0x49, 0x4C, 0x83, + 0x83, 0x6B, 0xDD, 0x34, 0xA0, 0x3B, 0x9B, 0xC8, 0x99, 0x14, 0x71, 0x60, 0x61, 0xBF, 0xB9, + 0x8E, 0xC6, 0xE6, 0x1C, 0x3E, 0xD4, 0x43, 0x8E, 0xDC, 0xAF, 0x25, 0x24, 0x3C, 0x64, 0x70, + 0x86, 0xB9, 0xEA, 0x70, 0x18, 0xB0, 0xD9, 0xA8, 0xA0, 0xB0, 0x0C, 0xEC, 0xB0, 0x0A, 0xBD, + 0xE2, 0x49, 0x8D, 0x69, 0xC2, 0x33, 0x61, 0x01, 0xA7, 0x72, 0xCB, 0xE4, 0xF5, 0x71, 0x52, + 0x3F, 0x51, 0xBD, 0x05, 0x88, 0x2C, 0xDF, 0x35, 0x8B, 0x84, 0x9C, 0xC1, 0x40, 0xAA, 0x1F, + 0xAF, 0x22, 0x42, 0x3A, 0x12, 0x85, 0x1C, 0xE0, 0xE3, 0x3F, 0xD4, 0x89, 0x75, 0xA4, 0x95, + 0x9F, 0xA5, 0xC5, 0xFE, 0x41, 0x8C, 0x93, 0x90, 0x81, 0x91, 0xAB, 0x6E, 0x74, 0x1B, 0x77, + 0xBF, 0xE0, 0x2C, 0xBD, 0x69, 0x8E, 0xE7, 0x95, 0xC4, 0x66, 0xD6, 0x15, 0x61, 0x9E, 0x64, + 0x41, 0x38, 0x2C, 0x6E, 0xAC, 0x01, 0x83, 0x4E, 0xE9, 0xAB, 0x73, 0xCE, 0xA8, 0x0B, 0xBE, + 0x23, 0x5C, 0x78, 0xDA, 0x91, 0xBD, 0x79, 0xB6, 0xF8, 0x2F, 0x89, 0x97, 0x85, 0xD6, 0x87, + 0x00, 0xD3, 0x93, 0xE6, 0x75, 0xC2, 0x22, 0x4D, 0x6B, 0x7A, 0x1A, 0xD2, 0x13, 0x20, 0x49, + 0x56, 0x79, 0xAD, 0xAE, 0xD7, 0x01, 0x67, 0xB5, 0x08, 0x66, 0x71, 0x3A, 0x53, 0x10, 0x9D, + 0xB7, 0xB6, 0xF7, 0xD8, 0x13, 0x04, 0xEC, 0xDF, 0xD8, 0x3B, 0x31, 0x9B, 0x1E, 0xF2, 0x48, + 0x30, 0x6B, 0x45, 0xAD, 0x29, 0xE7, 0xDD, 0xCC, 0x86, 0x3D, 0xAC, 0x56, 0x04, 0x8B, 0x5D, + 0x69, 0xEA, 0x17, 0x50, 0x11, 0xF7, 0x61, 0x4C, 0x00, 0xA8, 0x6A, 0x86, 0x3C, 0xDE, 0x18, + 0x72, 0xA8, 0x93, 0x28, 0x78, 0xB9, 0xAC, 0x7E, 0x1A, 0xC5, 0xBD, 0xA4, 0x99, 0x7B, 0x72, + 0x06, 0x4F, 0x0C, 0xD7, 0x5F, 0x4C, 0x81, 0x4E, 0x03, 0x4D, 0xE1, 0x1A, 0xCB, 0x90, 0x13, + 0xCF, 0x7E, 0xA9, 0x26, 0xB4, 0xE7, 0xEA, 0xAC, 0xE0, 0x70, 0xC7, 0xBA, 0x21, 0x88, 0xEF, + 0xAD, 0x2E, 0x43, 0x1E, 0x12, 0x23, 0xD4, 0x5D, 0xD0, 0x5C, 0x4D, 0x84, 0x03, 0xC2, 0xE4, + 0x5C, 0xEE, 0x64, 0x13, 0xEC, 0xBE, 0x75, 0x27, 0xE8, 0x73, 0xE4, 0x55, 0xC4, 0xE6, 0x10, + 0xA6, 0x18, 0x39, 0xAA, 0xCC, 0x0B, 0xD5, 0x6D, 0x24, 0x83, 0xE7, 0x8F, 0x29, 0x8B, 0x66, + 0xA4, 0x78, 0xEB, 0x2F, 0x55, 0x8C, 0xBA, 0xFC, 0xA8, 0x6B, 0xE8, 0x47, 0xBA, 0xEB, 0x02, + 0xC5, 0xB2, 0x16, 0xC8, 0xCD, 0x88, 0xFE, 0xA4, 0xDF, 0x24, 0x9B, 0x09, 0xE6, 0x70, 0xA2, + 0x07, 0x03, 0xAB, 0xAC, 0x24, 0xB0, 0xA9, 0x1A, 0xBC, 0x4A, 0x56, 0x46, 0x60, 0x14, 0x42, + 0xBA, 0x10, 0xBE, 0xCF, 0xD3, 0x09, 0x93, 0x88, 0x00, 0x51, 0xD0, 0x7F, 0x56, 0xA0, 0x5A, + 0x93, 0x79, 0xE7, 0xA8, 0xE6, 0xBE, 0xFE, 0xE3, 0xF2, 0x2F, 0xAA, 0x10, 0x63, 0x98, 0xF7, + 0x70, 0x60, 0x06, 0xE4, 0x2E, 0x9B, 0xE1, 0xEF, 0x89, 0xD2, 0x5C, 0x27, 0x2F, 0x11, 0xA9, + 0x50, 0x95, 0xC5, 0x87, 0xD7, 0x13, 0x73, 0x22, 0x84, 0xDE, 0x9D, 0xBD, 0x3C, 0x72, 0x17, + 0xB0, 0x68, 0x9E, 0x21, 0xD8, 0xEB, 0x0F, 0xF6, 0x96, 0x68, + ]) + .unwrap(); let msg = b"The quick brown fox jumped over the lazy dog"; @@ -182,7 +418,7 @@ fn bench_mldsa44_sign() { } fn bench_mldsa44_lowmemory_sign() { - use bouncycastle::mldsa_lowmemory::{MLDSATrait, MLDSA44, MLDSA44PrivateKey, MLDSA44_SK_LEN}; + use bouncycastle::mldsa_lowmemory::{MLDSA44, MLDSA44_SK_LEN, MLDSA44PrivateKey, MLDSATrait}; eprintln!("MLDSA44_lowmemory/Sign"); @@ -195,7 +431,12 @@ fn bench_mldsa44_lowmemory_sign() { // use bouncycastle_hex as hex; // eprintln!("sk:\n{}", &hex::encode(sk.encode())); - let sk = MLDSA44PrivateKey::from_bytes(&[0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f]).unwrap(); + let sk = MLDSA44PrivateKey::from_bytes(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, + 0x1E, 0x1F, + ]) + .unwrap(); let msg = b"The quick brown fox jumped over the lazy dog"; @@ -205,7 +446,7 @@ fn bench_mldsa44_lowmemory_sign() { } fn bench_mldsa65_sign() { - use bouncycastle::mldsa::{MLDSATrait, MLDSA65, MLDSA65PrivateKey, MLDSA65_SK_LEN}; + use bouncycastle::mldsa::{MLDSA65, MLDSA65_SK_LEN, MLDSA65PrivateKey, MLDSATrait}; eprintln!("MLDSA65/Sign"); @@ -219,7 +460,278 @@ fn bench_mldsa65_sign() { // use bouncycastle_hex as hex; // eprintln!("sk:\n{}", &hex::encode(sk.encode())); - let sk = MLDSA65PrivateKey::from_bytes(&[0x48,0x68,0x3d,0x91,0x97,0x8e,0x31,0xeb,0x3d,0xdd,0xb8,0xb0,0x47,0x34,0x82,0xd2,0xb8,0x8a,0x5f,0x62,0x59,0x49,0xfd,0x8f,0x58,0xa5,0x61,0xe6,0x96,0xbd,0x4c,0x27,0xd8,0x53,0xfa,0x69,0xb8,0x19,0x90,0x23,0xe8,0xcd,0x67,0x8d,0xd9,0xfa,0xbf,0x90,0x47,0x64,0x6f,0xfd,0x0c,0xb3,0xcc,0x7f,0x79,0x58,0x05,0xa7,0x1e,0x70,0xd2,0x37,0x1b,0x05,0x63,0xe3,0xcd,0x33,0x46,0x14,0x9c,0x8c,0x9e,0xbc,0xf2,0x3b,0x0a,0x4e,0x5a,0x90,0x0e,0xea,0x9c,0x65,0x62,0x79,0x0a,0x7c,0x63,0xe3,0x86,0x63,0xda,0xa2,0xdd,0xdb,0x6e,0x48,0x0d,0xc4,0x05,0xa1,0xe7,0x01,0x94,0x8b,0x74,0x84,0x1e,0xf5,0xcc,0x1c,0x3f,0x2b,0xf3,0x27,0x97,0x2e,0x95,0x10,0x51,0x0c,0xd5,0x37,0x5e,0xcc,0x08,0x55,0x71,0x77,0x11,0x87,0x22,0x21,0x86,0x23,0x81,0x00,0x04,0x24,0x77,0x80,0x61,0x47,0x50,0x07,0x50,0x17,0x17,0x03,0x55,0x04,0x51,0x51,0x25,0x47,0x18,0x38,0x04,0x61,0x75,0x72,0x22,0x44,0x10,0x88,0x68,0x60,0x86,0x46,0x01,0x27,0x47,0x56,0x71,0x80,0x87,0x06,0x66,0x86,0x43,0x32,0x44,0x41,0x22,0x04,0x36,0x38,0x66,0x75,0x02,0x82,0x36,0x34,0x24,0x43,0x22,0x05,0x73,0x64,0x10,0x64,0x55,0x54,0x77,0x22,0x75,0x56,0x81,0x43,0x36,0x14,0x62,0x55,0x08,0x20,0x64,0x37,0x68,0x54,0x68,0x75,0x43,0x53,0x75,0x10,0x68,0x71,0x83,0x33,0x80,0x54,0x75,0x05,0x25,0x80,0x75,0x28,0x18,0x84,0x38,0x11,0x08,0x72,0x60,0x20,0x20,0x08,0x58,0x83,0x01,0x83,0x61,0x13,0x82,0x82,0x12,0x06,0x17,0x11,0x57,0x87,0x68,0x78,0x88,0x78,0x64,0x37,0x54,0x60,0x16,0x57,0x15,0x50,0x84,0x71,0x88,0x66,0x07,0x27,0x32,0x88,0x06,0x64,0x74,0x18,0x56,0x76,0x21,0x80,0x31,0x82,0x76,0x64,0x15,0x78,0x24,0x50,0x25,0x64,0x66,0x43,0x11,0x35,0x04,0x36,0x47,0x80,0x12,0x66,0x73,0x14,0x30,0x11,0x66,0x06,0x55,0x86,0x47,0x18,0x36,0x88,0x63,0x50,0x38,0x47,0x86,0x11,0x01,0x20,0x23,0x56,0x11,0x61,0x37,0x86,0x07,0x85,0x32,0x12,0x40,0x07,0x54,0x78,0x82,0x30,0x43,0x66,0x61,0x16,0x60,0x42,0x55,0x41,0x82,0x85,0x60,0x53,0x67,0x78,0x56,0x38,0x43,0x44,0x30,0x63,0x26,0x10,0x77,0x07,0x31,0x78,0x42,0x72,0x14,0x11,0x16,0x53,0x03,0x85,0x27,0x68,0x67,0x46,0x01,0x50,0x82,0x37,0x35,0x32,0x07,0x66,0x10,0x75,0x04,0x68,0x12,0x48,0x06,0x66,0x03,0x03,0x26,0x52,0x31,0x24,0x45,0x40,0x88,0x00,0x31,0x80,0x88,0x76,0x72,0x17,0x30,0x71,0x82,0x47,0x21,0x51,0x27,0x80,0x11,0x65,0x44,0x74,0x86,0x61,0x72,0x23,0x33,0x80,0x86,0x60,0x64,0x46,0x83,0x52,0x15,0x84,0x20,0x36,0x80,0x11,0x80,0x21,0x18,0x18,0x33,0x17,0x73,0x54,0x53,0x48,0x81,0x00,0x44,0x86,0x53,0x67,0x43,0x70,0x57,0x72,0x58,0x83,0x34,0x60,0x38,0x42,0x32,0x85,0x68,0x10,0x06,0x04,0x26,0x04,0x25,0x84,0x56,0x02,0x35,0x68,0x20,0x51,0x83,0x86,0x38,0x43,0x24,0x21,0x22,0x42,0x45,0x64,0x58,0x58,0x67,0x71,0x45,0x72,0x85,0x04,0x78,0x87,0x17,0x18,0x06,0x18,0x83,0x60,0x86,0x86,0x41,0x56,0x50,0x81,0x16,0x50,0x26,0x46,0x70,0x06,0x08,0x26,0x62,0x27,0x38,0x31,0x72,0x40,0x72,0x57,0x30,0x07,0x27,0x28,0x86,0x20,0x66,0x75,0x88,0x68,0x26,0x07,0x06,0x40,0x20,0x33,0x03,0x43,0x66,0x31,0x55,0x46,0x42,0x45,0x34,0x56,0x67,0x18,0x73,0x45,0x65,0x83,0x70,0x22,0x50,0x84,0x68,0x56,0x28,0x80,0x70,0x36,0x70,0x84,0x62,0x37,0x17,0x10,0x06,0x57,0x17,0x58,0x47,0x78,0x70,0x86,0x55,0x53,0x78,0x22,0x35,0x14,0x46,0x77,0x28,0x56,0x73,0x03,0x22,0x87,0x00,0x14,0x33,0x20,0x61,0x71,0x58,0x45,0x52,0x66,0x32,0x50,0x26,0x51,0x33,0x47,0x77,0x38,0x03,0x55,0x16,0x43,0x13,0x47,0x35,0x10,0x66,0x27,0x51,0x75,0x74,0x02,0x46,0x88,0x81,0x70,0x67,0x43,0x46,0x81,0x86,0x01,0x76,0x52,0x45,0x33,0x30,0x87,0x21,0x04,0x34,0x34,0x01,0x03,0x22,0x87,0x63,0x51,0x55,0x26,0x50,0x81,0x30,0x77,0x45,0x44,0x41,0x68,0x15,0x41,0x83,0x63,0x64,0x11,0x20,0x40,0x26,0x87,0x30,0x43,0x67,0x77,0x12,0x80,0x88,0x46,0x35,0x54,0x53,0x00,0x62,0x45,0x81,0x04,0x58,0x36,0x51,0x24,0x84,0x27,0x80,0x34,0x51,0x66,0x63,0x58,0x43,0x78,0x56,0x01,0x46,0x51,0x15,0x74,0x23,0x21,0x43,0x66,0x85,0x22,0x47,0x77,0x31,0x34,0x50,0x17,0x83,0x62,0x42,0x05,0x50,0x00,0x64,0x84,0x47,0x12,0x34,0x40,0x88,0x00,0x60,0x47,0x35,0x40,0x57,0x83,0x33,0x63,0x08,0x21,0x06,0x15,0x22,0x52,0x07,0x24,0x88,0x51,0x34,0x86,0x37,0x06,0x76,0x22,0x58,0x85,0x71,0x26,0x56,0x73,0x47,0x68,0x16,0x46,0x46,0x84,0x25,0x87,0x08,0x12,0x27,0x05,0x50,0x08,0x38,0x32,0x00,0x23,0x20,0x80,0x66,0x34,0x53,0x36,0x00,0x33,0x46,0x85,0x72,0x47,0x06,0x35,0x54,0x00,0x35,0x77,0x12,0x27,0x52,0x30,0x71,0x42,0x53,0x68,0x74,0x37,0x45,0x70,0x05,0x66,0x43,0x22,0x44,0x82,0x85,0x20,0x72,0x18,0x33,0x30,0x20,0x53,0x37,0x33,0x40,0x77,0x27,0x80,0x55,0x25,0x30,0x63,0x52,0x50,0x40,0x67,0x33,0x46,0x13,0x18,0x07,0x28,0x07,0x17,0x24,0x83,0x77,0x63,0x45,0x73,0x18,0x58,0x51,0x60,0x23,0x33,0x44,0x36,0x25,0x16,0x43,0x38,0x16,0x08,0x58,0x77,0x34,0x62,0x42,0x88,0x30,0x07,0x03,0x65,0x85,0x37,0x55,0x00,0x75,0x52,0x31,0x50,0x37,0x02,0x13,0x24,0x63,0x04,0x37,0x08,0x68,0x06,0x36,0x15,0x03,0x03,0x00,0x43,0x58,0x63,0x57,0x08,0x02,0x11,0x06,0x64,0x73,0x46,0x35,0x22,0x62,0x03,0x30,0x43,0x80,0x21,0x08,0x52,0x87,0x57,0x83,0x21,0x07,0x88,0x67,0x48,0x08,0x56,0x34,0x74,0x36,0x73,0x42,0x84,0x05,0x84,0x66,0x84,0x14,0x37,0x00,0x55,0x10,0x87,0x34,0x26,0x44,0x77,0x21,0x12,0x73,0x84,0x73,0x65,0x26,0x47,0x25,0x77,0x14,0x47,0x04,0x17,0x86,0x44,0x26,0x02,0x47,0x11,0x87,0x40,0x81,0x22,0x16,0x60,0x58,0x47,0x17,0x81,0x37,0x06,0x76,0x80,0x81,0x70,0x58,0x18,0x55,0x85,0x47,0x13,0x63,0x42,0x10,0x75,0x58,0x01,0x63,0x58,0x35,0x85,0x18,0x44,0x03,0x84,0x71,0x10,0x33,0x87,0x42,0x62,0x82,0x47,0x74,0x13,0x65,0x54,0x42,0x70,0x73,0x46,0x35,0x77,0x75,0x00,0x66,0x25,0x62,0x68,0x42,0x02,0x12,0x46,0x83,0x86,0x46,0x16,0x64,0x60,0x31,0x22,0x53,0x88,0x84,0x54,0x00,0x84,0x57,0x34,0x46,0x47,0x54,0x47,0x25,0x60,0x54,0x61,0x66,0x84,0x66,0x30,0x88,0x06,0x38,0x27,0x15,0x63,0x28,0x71,0x83,0x84,0x06,0x52,0x24,0x76,0x81,0x16,0x06,0x62,0x13,0x03,0x30,0x18,0x68,0x02,0x80,0x13,0x84,0x63,0x05,0x05,0x65,0x72,0x38,0x75,0x83,0x65,0x72,0x32,0x30,0x68,0x80,0x46,0x12,0x26,0x06,0x65,0x16,0x75,0x57,0x05,0x32,0x41,0x32,0x27,0x67,0x35,0x17,0x08,0x01,0x53,0x00,0x16,0x28,0x46,0x01,0x34,0x88,0x77,0x01,0x11,0x88,0x15,0x57,0x13,0x15,0x46,0x43,0x11,0x70,0x47,0x32,0x88,0x28,0x56,0x36,0x82,0x34,0x55,0x50,0x41,0x86,0x27,0x65,0x63,0x11,0x11,0x68,0x75,0x05,0x10,0x42,0x54,0x41,0x44,0x27,0x85,0x22,0x11,0x17,0x17,0x88,0x15,0x36,0x85,0x15,0x74,0x47,0x16,0x62,0x55,0x36,0x55,0x83,0x63,0x02,0x50,0x28,0x55,0x76,0x87,0x53,0x27,0x13,0x71,0x03,0x72,0x37,0x05,0x71,0x47,0x61,0x71,0x36,0x51,0x84,0x12,0x42,0x36,0x64,0x44,0x66,0x41,0x43,0x52,0x05,0x21,0x08,0x51,0x57,0x03,0x33,0x63,0x86,0x02,0x58,0x42,0x66,0x28,0x14,0x81,0x10,0x54,0x62,0x68,0x17,0x30,0x38,0x75,0x64,0x33,0x21,0x65,0x88,0x56,0x86,0x63,0x63,0x28,0x13,0x40,0x62,0x54,0x01,0x20,0x40,0x88,0x65,0x47,0x88,0x61,0x71,0x65,0x76,0x23,0x72,0x62,0x34,0x86,0x70,0x30,0x11,0x51,0x15,0x63,0x20,0x50,0x75,0x35,0x02,0x12,0x21,0x08,0x42,0x65,0x31,0x43,0x55,0x67,0x11,0x15,0x25,0x72,0x01,0x06,0x85,0x36,0x30,0x15,0x05,0x57,0x58,0x60,0x58,0x78,0x43,0x14,0x31,0x32,0x78,0x78,0x80,0x87,0x38,0x47,0x88,0x63,0x78,0x81,0x81,0x38,0x73,0x42,0x61,0x78,0x38,0x85,0x24,0x66,0x77,0x33,0x50,0x60,0x21,0x15,0x14,0x64,0x23,0x82,0x32,0x68,0x01,0x35,0x44,0x07,0x83,0x47,0x53,0x85,0x53,0x57,0x52,0x83,0x23,0x35,0x18,0x76,0x01,0x15,0x21,0x34,0x32,0x57,0x73,0x33,0x36,0x55,0x18,0x86,0x15,0x81,0x61,0x68,0x24,0x18,0x42,0x21,0x22,0x30,0x84,0x14,0x48,0x15,0x12,0x01,0x10,0x30,0x24,0x77,0x72,0x42,0x54,0x43,0x66,0x06,0x77,0x17,0x70,0x76,0x03,0x01,0x45,0x25,0x40,0x35,0x00,0x18,0x38,0x73,0x23,0x77,0x35,0x26,0x50,0x86,0x35,0x71,0x13,0x73,0x44,0x81,0x60,0x52,0x77,0x45,0x65,0x53,0x73,0x00,0x85,0x83,0x77,0x85,0x03,0x51,0x21,0x11,0x54,0x80,0x62,0x88,0x50,0x18,0x02,0x68,0x13,0x86,0x52,0x05,0x34,0x68,0x01,0x32,0x07,0x24,0x18,0x03,0x21,0x30,0x05,0x72,0x38,0x64,0x07,0x64,0x27,0x11,0x41,0x01,0x83,0x85,0x25,0x51,0x06,0x32,0x60,0x71,0x04,0x86,0x51,0x76,0x83,0x38,0x28,0x57,0x27,0x62,0x35,0x45,0x18,0x73,0x50,0x83,0x13,0x28,0x86,0x37,0x66,0x61,0x42,0x63,0x11,0x67,0x50,0x33,0x11,0x25,0x53,0x76,0x41,0x76,0x03,0x14,0x33,0x17,0x72,0x12,0x23,0x44,0x18,0xa8,0x2e,0x4f,0x5c,0x9e,0xa0,0xfa,0xf9,0x9e,0xb0,0x4d,0x78,0xa7,0x33,0x27,0x11,0x11,0x7c,0x33,0xf1,0x8e,0xca,0x21,0xf8,0x74,0x33,0x76,0xad,0xa5,0x21,0x98,0x04,0xa7,0xed,0x9a,0x55,0x57,0xfc,0xd6,0x7a,0x35,0x50,0xb3,0xa4,0xb8,0xc5,0x88,0x62,0x9c,0x02,0x14,0x75,0xfa,0x3d,0x56,0xd5,0xd6,0xcf,0xbb,0x1a,0x09,0xbd,0xa8,0xd1,0x4d,0xe6,0x22,0xdd,0xff,0x16,0xd8,0xbc,0x99,0xb1,0x42,0x78,0xa8,0xaf,0x1d,0x76,0xbe,0xd1,0x57,0x67,0x2d,0xd9,0xc3,0x23,0x16,0xf9,0x7e,0x8d,0xaa,0xde,0xf8,0xd9,0xda,0x69,0x58,0x67,0x25,0x56,0x7f,0xb9,0x6b,0x59,0x99,0x0d,0x4b,0xf0,0xbc,0x9c,0x19,0x5b,0x90,0xb7,0x42,0x95,0xf5,0x67,0x5b,0x24,0x25,0x7c,0x27,0x10,0xc1,0x75,0xb0,0x15,0x3f,0x29,0x11,0x32,0x8c,0x2e,0xb7,0xab,0xb9,0xad,0x46,0xe7,0x0a,0x8b,0x53,0xc3,0x9e,0xa6,0x42,0xce,0xe4,0xb3,0xcb,0x42,0x62,0x0e,0x86,0x3c,0xe8,0xb6,0x50,0xce,0x8a,0xdc,0xd9,0x23,0x72,0x1a,0x16,0x87,0x02,0x3c,0x67,0x3a,0x8c,0xbb,0x6b,0x03,0xd5,0x1c,0xd1,0x97,0xe8,0xc3,0x46,0xeb,0xad,0xce,0x93,0x95,0x0f,0x88,0xce,0xe2,0x01,0xdb,0x9e,0x32,0x08,0x43,0xe2,0x9f,0x30,0x0d,0x9a,0x19,0x50,0x0d,0x70,0xa4,0xca,0xf2,0x72,0xc6,0x9e,0x4e,0xef,0x69,0xfb,0xb8,0xa5,0x5e,0xfd,0x7c,0xa2,0xbe,0xd9,0x90,0xd2,0xd3,0xb5,0x82,0x84,0x8f,0x9c,0x45,0xc2,0xab,0xc5,0x4c,0xfc,0x47,0xd3,0x4f,0x06,0xc0,0xff,0xa5,0x6f,0xcd,0x76,0x2a,0xb9,0xcb,0xa9,0x14,0x6d,0x77,0x25,0x21,0x89,0x63,0xb2,0x40,0xd7,0x2b,0x6d,0x22,0xc9,0x31,0x71,0xfb,0xd4,0x77,0x88,0xb7,0x6e,0x72,0x04,0x2d,0xef,0x08,0x78,0xd2,0x3d,0xf6,0x31,0xa1,0xa1,0xe5,0xa6,0x02,0x76,0x86,0xde,0x5b,0x4a,0x10,0xe9,0x10,0x69,0xc8,0xf2,0xba,0x02,0x59,0xb0,0x4d,0x64,0x09,0xda,0x96,0x56,0x7c,0xa5,0x2d,0xa4,0x97,0x02,0x6e,0x58,0x3a,0x0e,0xce,0xfc,0x1f,0x01,0xe6,0xb9,0x88,0xe2,0x1f,0x97,0x67,0xa2,0xb7,0xe1,0x67,0x2d,0xeb,0x9a,0x1e,0x2a,0x3f,0xcc,0x86,0x3a,0xa9,0x15,0x17,0xc3,0x34,0x62,0x06,0x01,0xb4,0xfe,0x79,0x73,0x0e,0x93,0x49,0x35,0xf4,0xb6,0xfb,0xc4,0xe3,0x26,0x95,0x14,0x5c,0x2b,0x5f,0x6a,0x12,0x7f,0xec,0xc0,0xa2,0x77,0x45,0x1e,0xbc,0x3f,0xd5,0x23,0x44,0x4f,0x9e,0xe7,0xc9,0xc3,0x45,0x34,0xf3,0x56,0xdb,0x54,0x4f,0xc3,0x1c,0x1b,0xfd,0xe5,0xf6,0x5c,0x77,0xea,0x2f,0x7c,0x2e,0xae,0x4c,0x55,0xeb,0xaf,0x10,0x42,0x71,0xc5,0x66,0xfd,0x4e,0xba,0xc7,0x1c,0x7a,0x62,0xc7,0x49,0x52,0x81,0x7a,0xe6,0x75,0x50,0x4d,0x95,0x99,0xb1,0xb7,0x62,0xb6,0xac,0xa1,0x68,0xa8,0x32,0x48,0xc9,0xd9,0xad,0xb0,0xce,0xb1,0x55,0x6e,0x57,0x59,0x49,0x0b,0xbc,0x0c,0x79,0x00,0x79,0x5a,0xd7,0x21,0x23,0x03,0x8b,0x66,0x2f,0x64,0xf1,0x06,0xa9,0x99,0x36,0x81,0xa2,0x5d,0x59,0xaf,0x7b,0xc9,0x7a,0x23,0x5b,0xe9,0x28,0x4c,0x5b,0xc4,0x5a,0x6c,0x90,0xcb,0x1c,0x29,0x99,0xc6,0x63,0xd9,0x6b,0x47,0x8e,0x23,0x07,0xf8,0x55,0x48,0x95,0x7d,0x65,0x74,0x0e,0x26,0x73,0xe9,0xeb,0xd1,0x35,0x28,0x29,0x03,0x8f,0x46,0x2b,0x8f,0xd3,0xb5,0x68,0x1d,0xa5,0x5c,0x02,0x52,0x52,0x38,0x53,0x52,0x5e,0xa0,0xad,0x64,0x7e,0x71,0xac,0x2c,0x5a,0x88,0x93,0xe6,0x03,0xac,0x97,0xe5,0x6c,0x04,0xce,0xb2,0xf2,0x6f,0x5c,0x5b,0x4b,0x6d,0x94,0xab,0x81,0x13,0x80,0xfd,0x00,0xf2,0x20,0x8f,0xe8,0x65,0x35,0x08,0x6a,0xeb,0xfd,0x35,0xc2,0x91,0x20,0x62,0x4c,0x04,0xfb,0xb6,0x11,0x39,0x29,0xd9,0xc5,0x56,0x35,0x02,0x53,0x76,0x6c,0x20,0x9f,0xdb,0xa8,0x3c,0x95,0xfc,0xcd,0x34,0x2a,0x28,0x09,0x93,0x55,0xd0,0x0b,0xc8,0x63,0xf4,0xee,0xf5,0x96,0xeb,0x0b,0x42,0xeb,0xcc,0x7c,0x79,0x49,0x1c,0xce,0xae,0x20,0x5e,0xa0,0xb8,0x05,0x9f,0xbb,0x8a,0x57,0x26,0xc5,0x94,0x9d,0x2b,0x15,0xe7,0xe2,0x9c,0x51,0xfc,0x9b,0x02,0xee,0x1a,0x4f,0xc3,0x57,0xb5,0xf1,0xbe,0xf9,0xc4,0xad,0xd4,0x6a,0x2a,0x92,0x0c,0x2f,0xbf,0x08,0xa3,0x7e,0xb1,0x51,0x4b,0xfa,0x15,0x11,0x0a,0x43,0x92,0xa7,0x4c,0x6f,0x13,0xc5,0x0c,0x5c,0xff,0xd9,0x75,0x31,0x09,0x8d,0x7c,0xd2,0x3b,0x60,0xeb,0x35,0xc4,0xa4,0x28,0xb4,0x6c,0x55,0x38,0x6e,0x10,0x10,0xc4,0xba,0x7f,0x70,0xe4,0xc7,0xec,0xb7,0x57,0x5f,0x30,0x63,0xa7,0x1e,0x84,0xdf,0xdc,0xf0,0x9a,0x58,0xb2,0xcd,0xb0,0xf9,0x9f,0x27,0xed,0x37,0x86,0x10,0xd2,0x5c,0xba,0xd7,0xbf,0xa6,0xba,0x0d,0x59,0x18,0x9c,0xfe,0x88,0xea,0xb9,0xb4,0x6d,0x7e,0x6d,0xb0,0x30,0x7e,0xab,0xe4,0x19,0x8e,0x99,0xbd,0x71,0xf7,0x79,0xab,0x66,0x58,0x1e,0x09,0x12,0xfc,0x7b,0x1d,0x25,0x85,0x24,0x5e,0x9a,0x12,0x68,0x7a,0x97,0x5c,0xd5,0xe8,0xe1,0xdc,0xc0,0x45,0xd5,0xf8,0x91,0xc4,0xc6,0x85,0xdb,0x07,0xcf,0x81,0xe7,0x73,0x89,0xb3,0x63,0xeb,0x6b,0xdf,0xe3,0x9b,0x27,0xff,0x84,0xc9,0x7e,0xef,0xee,0x16,0x2e,0x3b,0x45,0x1f,0xe6,0x91,0x47,0x19,0xcb,0x64,0x36,0xd8,0x55,0x96,0x0f,0xf9,0x15,0xd7,0xce,0xa6,0xad,0xea,0xfd,0xfc,0x1c,0x05,0x78,0x6c,0x49,0xf9,0x23,0xa4,0x74,0xff,0xdf,0xc3,0x15,0x3a,0x06,0xe6,0xed,0x0b,0x0a,0xd2,0x20,0xd7,0x25,0x24,0x43,0x4d,0x52,0x73,0xc0,0xaa,0xb6,0xdd,0xe4,0xe9,0x14,0x76,0xd5,0x81,0xa2,0x69,0x5a,0x60,0xde,0x6d,0x9f,0x44,0xd7,0x7a,0xa0,0x82,0x66,0xe9,0x38,0xee,0xb4,0xa9,0x59,0x7c,0x9b,0x64,0x98,0x60,0x59,0xe4,0x92,0x62,0xa4,0xea,0xb2,0x45,0x4e,0x14,0x01,0x5a,0xd0,0x53,0x6c,0x42,0x73,0x3a,0x5d,0x77,0xd7,0x99,0x5c,0x2a,0x20,0x44,0x60,0x09,0xeb,0xfe,0x56,0x32,0xc8,0x0c,0x08,0xed,0x2b,0x97,0xaf,0x35,0x06,0x64,0x89,0xf5,0x97,0xeb,0x1b,0x1f,0x11,0xf0,0x4f,0x60,0xe0,0xc9,0x04,0x01,0x59,0xc4,0x4a,0xb3,0xe6,0x0e,0x0a,0x15,0x22,0x9d,0x19,0x12,0x28,0xbe,0xd1,0x7b,0xbc,0x3a,0xc9,0x39,0xb3,0xc6,0x7c,0xee,0x13,0x5f,0x35,0x2c,0x27,0x21,0x6c,0x9c,0x31,0xf7,0x2a,0x3e,0x87,0x04,0x0c,0x5f,0x61,0x93,0x06,0xeb,0x0b,0x6c,0xca,0x2a,0x9c,0xe7,0xb2,0x2a,0x16,0x94,0xd0,0x0c,0xa9,0xc0,0x5e,0x31,0x51,0x26,0x45,0x7f,0x26,0xce,0x84,0xf9,0x61,0x72,0x41,0x86,0x07,0x82,0xf8,0x64,0xb4,0x73,0xd8,0x40,0x17,0x49,0x19,0x02,0xb1,0xbd,0xc8,0xcd,0xc5,0x80,0x0d,0xd4,0x61,0x27,0xfb,0x80,0xa7,0x1c,0x09,0x5b,0x47,0x3a,0x56,0x25,0x29,0xb3,0xb1,0xe7,0xe4,0x37,0xe1,0x58,0xa5,0xf6,0x66,0x6e,0x99,0x74,0xd0,0x05,0xb0,0x62,0xc2,0x30,0x9e,0x6d,0xce,0x98,0xf9,0xb6,0x58,0xc6,0xe3,0xf9,0xa2,0x16,0xd5,0x8c,0x8c,0x91,0x42,0xbd,0x1c,0x8c,0x85,0xa9,0xda,0x87,0x2e,0xbb,0xfa,0xd3,0xfe,0xa9,0xd9,0xab,0xa2,0xb6,0x8c,0x0e,0x8f,0x19,0xc6,0xff,0x5f,0x00,0x58,0x4d,0x45,0xda,0xf9,0xd6,0xc9,0xd6,0x9e,0xd0,0x4b,0x8d,0xa8,0xd6,0x87,0x25,0x8b,0x77,0x80,0x79,0x27,0x61,0x2c,0x53,0x04,0x46,0xfe,0xa7,0x69,0x7a,0xe3,0xf9,0x26,0x69,0x89,0x29,0xbc,0x6a,0x5a,0x8c,0xf3,0xe2,0x02,0x4c,0x0f,0x0c,0x5e,0xe5,0x7b,0x58,0x69,0xbf,0x98,0x18,0x81,0xca,0xf9,0xe3,0x66,0x5f,0xc7,0xf7,0xef,0xc6,0x78,0x92,0x9f,0x87,0xa5,0x6e,0xaa,0x42,0xea,0x4d,0x1f,0xf6,0x69,0x18,0x22,0xdd,0x79,0xa4,0x70,0x96,0xb7,0x76,0xd1,0xd8,0xf0,0x14,0x56,0xe5,0x87,0x3b,0x07,0x38,0x40,0x6c,0x38,0x2c,0x57,0x3a,0xe9,0xcd,0xe2,0xd9,0xe7,0xf2,0x31,0xb6,0xcc,0x5c,0x67,0x6e,0x7c,0xf4,0x39,0x63,0x37,0x30,0x13,0xa5,0x80,0x75,0x38,0x1f,0xf0,0x94,0x9b,0xe0,0x84,0x54,0x6d,0x72,0xe4,0xf8,0xa3,0xe5,0xfe,0x4a,0xa5,0x09,0x1a,0xdd,0x23,0x4e,0x2a,0xfe,0x00,0x30,0xb1,0xb6,0x63,0xae,0x9d,0x2d,0x32,0x41,0x09,0x86,0xb9,0x40,0x2a,0xaa,0xf2,0x46,0x5b,0x74,0xa5,0xe2,0xd0,0xbc,0x38,0xe3,0xa9,0x2b,0xbd,0xdd,0x8a,0x1f,0xed,0x7b,0x94,0x8c,0x23,0xcc,0xe6,0xf8,0xc0,0x8f,0xe3,0x56,0x83,0x5b,0xa6,0x5b,0x0f,0x98,0x40,0x68,0x61,0x6e,0xf4,0x81,0x38,0xef,0xd8,0x9b,0xf3,0x57,0xa5,0x4d,0x2e,0xbb,0xf3,0x76,0xcb,0xdc,0xc6,0x9c,0x5f,0x1f,0x61,0xc6,0x4d,0x27,0x94,0xbc,0x06,0xcc,0xb9,0xab,0xdf,0x66,0xe2,0x50,0x85,0xd8,0xc8,0x30,0xe2,0xae,0x3b,0x0f,0xe0,0xf0,0x7a,0x7a,0xf8,0xb9,0x32,0x0b,0xf3,0x42,0x97,0x09,0x97,0xd6,0x7d,0x7c,0x12,0x59,0x3a,0x8f,0xbf,0xad,0xe6,0x35,0xaa,0xc5,0x30,0x83,0xa7,0x02,0x2c,0x47,0xd5,0xf7,0x7a,0x52,0xb5,0x7b,0x59,0x8d,0xa9,0x39,0x2a,0xe6,0xd8,0x6a,0xfc,0x46,0xfc,0x06,0x45,0x51,0x81,0xb9,0xc7,0x5a,0x64,0x6d,0xc2,0x1f,0x81,0xe4,0xbf,0x21,0x37,0x53,0xde,0x73,0x7f,0xd2,0xa1,0x40,0x02,0x79,0x20,0xad,0xd3,0x5a,0x22,0x3f,0x9f,0x5f,0x44,0x65,0xce,0xb6,0x0c,0x03,0xed,0x04,0x55,0xa3,0x33,0xa5,0xcc,0x83,0xad,0xbf,0x43,0xf1,0xf4,0x2c,0x2c,0xcb,0x83,0x28,0xc2,0x1c,0x7a,0xb7,0xfa,0xed,0x2b,0x21,0xcf,0xad,0xe2,0xda,0x55,0x22,0x3a,0xaa,0xb2,0xaf,0x9b,0x41,0xc7,0x33,0x23,0x41,0x74,0x63,0x41,0xb3,0x9a,0xa2,0xf4,0x38,0x15,0x65,0x0f,0x54,0x80,0x51,0x14,0x24,0xcf,0xa6,0x90,0x17,0x79,0xc4,0xd1,0x8b,0x63,0x8c,0xc0,0x28,0x7a,0xaa,0xf3,0x16,0x80,0x33,0x8d,0x20,0xb1,0x7c,0x74,0x49,0xfd,0xc6,0xa2,0x78,0xa8,0xd9,0x6a,0x82,0xee,0x4c,0x4e,0xca,0x40,0x12,0x5e,0x2d,0x65,0x29,0x00,0x71,0xc7,0xae,0xf1,0xbe,0x6a,0x99,0x15,0x98,0xfb,0x9d,0x59,0x51,0x25,0x23,0xbc,0xd4,0xb3,0x8c,0x56,0x6b,0x8e,0x80,0xa7,0x3a,0xe3,0x33,0xe1,0x34,0x41,0x43,0x27,0xef,0x1d,0x83,0xc4,0x7c,0x49,0xdf,0xe7,0x93,0x6d,0xf1,0x33,0x8a,0x5e,0x24,0x77,0x87,0x86,0x8f,0xc8,0x4f,0xdc,0xb9,0x5a,0xc8,0x9c,0x18,0x5c,0x4b,0xb5,0xfd,0x57,0xb2,0x33,0x8a,0xc4,0x2b,0x41,0xc1,0x0a,0x82,0x3d,0xf3,0x96,0x24,0xf3,0x6b,0x15,0xa2,0xf0,0x67,0x58,0x4e,0x06,0xca,0x2e,0x08,0xcc,0xaf,0xf1,0x61,0x8f,0xe0,0x1d,0xd0,0x6d,0xf3,0x51,0x2e,0x0b,0x72,0x4d,0xec,0x85,0x06,0xda,0x24,0x21,0x5a,0xca,0xcc,0x2c,0x51,0xb8,0x2a,0xd8,0xd3,0x02,0x00,0x2f,0xb4,0x10,0x68,0xb1,0xda,0x4f,0x8b,0xb1,0x47,0x98,0x7b,0x35,0x16,0xba,0xd5,0xdb,0xdd,0xf0,0x13,0x18,0xfd,0x3f,0xa9,0xbc,0x43,0x70,0x2a,0xc4,0x98,0xc7,0x19,0xd9,0x5f,0x2e,0x84,0x1b,0x62,0x2a,0x5e,0x48,0x48,0xa3,0xc5,0xc2,0x62,0x95,0x99,0x92,0xea,0x7a,0x7d,0x72,0xca,0x8a,0x36,0x80,0x28,0xf4,0x97,0xdf,0xad,0x93,0x35,0x5c,0xbb,0x1b,0xb9,0x78,0x6d,0x14,0xff,0x2c,0xf5,0x90,0x31,0x78,0x48,0xf9,0x58,0x56,0x42,0x71,0x10,0xdd,0xa3,0x6f,0x51,0x92,0xa8,0x16,0xce,0x9c,0x88,0x16,0xcc,0x7b,0xbf,0xc8,0x04,0xef,0xc4,0x00,0x85,0xa3,0x85,0x0b,0x89,0xf1,0xe7,0xfe,0x56,0x56,0xdb,0xa4,0x10,0xf9,0x06,0xa9,0x7c,0x32,0x33,0x6c,0x1a,0xe7,0xe8,0x17,0x37,0xa8,0x3e,0x08,0x73,0x54,0xe4,0x28,0xda,0x85,0x38,0xd9,0x48,0xdb,0xf5,0xdf,0xac,0xb5,0x9d,0xd2,0xb5,0xfd,0x3b,0xc8,0x03,0xf4,0xba,0x43,0x2c,0x9a,0x73,0x9d,0xf2,0xcf,0xa9,0xed,0x94,0x84,0x32,0x0f,0x97,0xed,0xff,0x1a,0x48,0xc6,0xb8,0x6b,0x30,0x02,0xcf,0xb7,0x72,0xdd,0x5e,0x56,0x2b,0xc4,0xc3,0xd6,0x83,0xed,0x96,0x4b,0x61,0x99,0xfa,0x05,0x14,0xb0,0x79,0x0d,0x95,0x80,0x95,0xb7,0xb8,0x5c,0x6b,0xe8,0x75,0xfb,0xb5,0x59,0xe1,0x93,0x01,0x46,0xcc,0xea,0x63,0xa3,0x88,0xa1,0x94,0xfe,0x09,0xc3,0xde,0xa0,0x3b,0xe5,0x2d,0xe2,0x7e,0x90,0x10,0x17,0xaf,0xe8,0x09,0xaf,0x63,0x0a,0x73,0x82,0xbf,0x5c,0x4c,0xd4,0xd1,0xb8,0xf4,0x15,0x79,0xfb,0x43,0x48,0xed,0xe4,0xca,0x05,0xf4,0xcd,0x3f,0x13,0x9a,0x31,0xb2,0x54,0x4e,0x51,0x6d,0xbe,0x40,0x86,0xb9,0xbb,0x4b,0x2b,0xed,0x47,0xe2,0xd2,0x30,0x98,0x2d,0xd5,0x19,0x24,0x29,0xd3,0x77,0xb7,0xc0,0x74,0x5c,0xc0,0x68,0xe2,0xf5,0xa4,0xaa,0x04,0xc7,0xff,0x87,0x20,0x9e,0xd1,0x25,0x99,0x76,0xa0,0xfc,0x9b,0x25,0xe9,0xe8,0x51,0xd4,0xe3,0x50,0x2c,0x02,0xc8,0x5d,0x6d,0xff,0x02,0x9e,0x21,0x1d,0x01,0xeb,0xf0,0xe9,0xe7,0x18,0x8d,0x56,0x8f,0x84,0x37,0xd8,0x13,0xb0,0xf1,0x22,0xf2,0xfb,0x17,0x60,0x3b,0x69,0x3e,0xd9,0xc3,0x8f,0x17,0xcf,0xd5,0x0b,0x81,0x5e,0x6d,0x9d,0xfc,0x0e,0xd2,0xcc,0xf1,0x9f,0x63,0x99,0x27,0x4a,0x14,0x20,0xf2,0x35,0xa5,0x9d,0x8b,0xf7,0x24,0x34,0x5e,0x14,0xe4,0x5d,0x9e,0x4b,0xe8,0x93,0x4d,0xfc,0x3f,0xa9,0x26,0x78,0xdb,0x61,0xd7,0x11,0x8b,0xf5,0x3c,0xb8,0xa2,0x22,0x5b,0x33,0x5f,0x7e,0xae,0x50,0xe3,0xf9,0x41,0x23,0x76,0x28,0xdb,0x76,0xd8,0xea,0x38,0xf7,0x7a,0x72,0xaf,0x3a,0x26,0xc8,0x1f,0xe4,0x35,0x23,0xb3,0x35,0x53,0x5a,0x5d,0x1d,0xb7,0xc3,0x8f,0x34,0x10,0x82,0xbb,0x57,0x34,0xd0,0x89,0xe8,0xae,0x30,0x9c,0xfd,0xa3,0xa0,0xbc,0xb5,0xcd,0x5b,0x09,0x71,0x13,0xc8,0xed,0xf9,0x61,0x6a,0xa4,0xf6,0xe6,0x63,0x1b,0x91,0x25,0x27,0x6f,0xb3,0xf6,0x80,0xa3,0x43,0x41,0xc3,0xdb,0x66,0x8d,0xc6,0xca,0xd4,0x5f,0xc9,0x3b,0x27,0x08,0xca,0x2a,0xf7,0x5c,0xcc,0xe7,0x34,0xfd,0x19,0x1c,0x50,0x08,0x9d,0xad,0x53,0x98,0x2f,0xdd,0xae,0x02,0x53,0x1f,0xf9,0x3e,0x1f,0x21,0xff,0x39,0x5f,0xc0,0xa1,0x28,0x74,0xed,0xf0,0x6b,0x6f,0x96,0x47,0xe9,0x5a,0x73,0x24,0x58,0x6c,0x71,0xdf,0xd9,0x1d,0x90,0x1d,0x62,0x18,0x58,0x19,0x0f,0xec,0xd0,0x0c,0xcd,0x11,0x0b,0xba,0xc5,0x9f,0x96,0xcb,0x88,0x4c,0x3c,0x93,0x99,0x47,0x48,0xa5,0x6f,0x41,0x28,0x3b,0xfc,0x41,0xfb,0x89,0x05,0x21,0x53,0xa8,0x94,0x58,0x8c,0x3c,0xb9,0x01,0x7f,0x3d,0x66,0x32,0x6c,0x98,0x56,0x37,0xe5,0x75,0xac,0xb8,0x12,0x34,0x63,0x42,0x65,0x40,0x25,0xd6,0x02,0xde,0x3b,0xa9,0x40,0xc1,0x9a,0xc1,0xa6,0x33,0xdf,0xfd,0xa9,0x77,0xb5,0x29,0xb8,0x01,0x3e,0x19,0xc1,0xd6,0xd0,0x68,0x0f,0x4d,0xae,0x62,0xc9,0x24,0x45,0x0a,0xe6,0x6a,0xab,0x82,0xf2,0x14,0x73,0x06,0x1d,0xab,0x3d,0x62,0xb2,0x47,0xf9,0x07,0xe3,0x55,0x19,0x39,0xad,0x3f,0x54,0x65,0xe9,0xd0,0x8a,0x82,0xbf,0xea,0x17,0xee,0xa1,0xb6,0xb2,0xb9,0x23,0x75,0x74,0x77,0xf9,0x93,0x00,0x0b,0x2f,0x43,0xb7,0x0f,0x28,0xaa,0xab,0x1f,0xe9,0xa2,0x6a,0xd1,0xfd,0x33,0x61,0x61,0x6c,0x0b,0x0e,0x24,0x2f,0xe7,0x66,0x04,0xb7,0x03,0x3a,0x1f,0x30,0xe9,0x7e,0x28,0xf5,0x26,0xca,0x3c,0x88,0x0f,0xe2,0xb8,0xd9,0xd1,0xb0,0xc9,0xff,0x18,0x8b,0x31,0xcb,0x9d,0x97,0x42,0x5a,0xca,0xb9,0xb2,0x16,0xd9,0x8a,0x6a,0xe3,0x55,0xe5,0x83,0xda,0x71,0xe8,0x86,0x4e,0xe3,0xd1,0x6b,0x07,0x59,0x79,0x61,0x90,0xef,0x54,0x5c,0x1e,0x62,0xbf,0xef,0x92,0xaf,0x6c,0xa1,0x47,0xb1,0x32,0x44,0xd6,0xc8,0x92,0xfc,0x8e,0xf2,0x23,0xab,0x3f,0x43,0xf9,0x24,0xc2,0xf4,0x66,0x09,0x7e,0xe8]).unwrap(); + let sk = MLDSA65PrivateKey::from_bytes(&[ + 0x48, 0x68, 0x3D, 0x91, 0x97, 0x8E, 0x31, 0xEB, 0x3D, 0xDD, 0xB8, 0xB0, 0x47, 0x34, 0x82, + 0xD2, 0xB8, 0x8A, 0x5F, 0x62, 0x59, 0x49, 0xFD, 0x8F, 0x58, 0xA5, 0x61, 0xE6, 0x96, 0xBD, + 0x4C, 0x27, 0xD8, 0x53, 0xFA, 0x69, 0xB8, 0x19, 0x90, 0x23, 0xE8, 0xCD, 0x67, 0x8D, 0xD9, + 0xFA, 0xBF, 0x90, 0x47, 0x64, 0x6F, 0xFD, 0x0C, 0xB3, 0xCC, 0x7F, 0x79, 0x58, 0x05, 0xA7, + 0x1E, 0x70, 0xD2, 0x37, 0x1B, 0x05, 0x63, 0xE3, 0xCD, 0x33, 0x46, 0x14, 0x9C, 0x8C, 0x9E, + 0xBC, 0xF2, 0x3B, 0x0A, 0x4E, 0x5A, 0x90, 0x0E, 0xEA, 0x9C, 0x65, 0x62, 0x79, 0x0A, 0x7C, + 0x63, 0xE3, 0x86, 0x63, 0xDA, 0xA2, 0xDD, 0xDB, 0x6E, 0x48, 0x0D, 0xC4, 0x05, 0xA1, 0xE7, + 0x01, 0x94, 0x8B, 0x74, 0x84, 0x1E, 0xF5, 0xCC, 0x1C, 0x3F, 0x2B, 0xF3, 0x27, 0x97, 0x2E, + 0x95, 0x10, 0x51, 0x0C, 0xD5, 0x37, 0x5E, 0xCC, 0x08, 0x55, 0x71, 0x77, 0x11, 0x87, 0x22, + 0x21, 0x86, 0x23, 0x81, 0x00, 0x04, 0x24, 0x77, 0x80, 0x61, 0x47, 0x50, 0x07, 0x50, 0x17, + 0x17, 0x03, 0x55, 0x04, 0x51, 0x51, 0x25, 0x47, 0x18, 0x38, 0x04, 0x61, 0x75, 0x72, 0x22, + 0x44, 0x10, 0x88, 0x68, 0x60, 0x86, 0x46, 0x01, 0x27, 0x47, 0x56, 0x71, 0x80, 0x87, 0x06, + 0x66, 0x86, 0x43, 0x32, 0x44, 0x41, 0x22, 0x04, 0x36, 0x38, 0x66, 0x75, 0x02, 0x82, 0x36, + 0x34, 0x24, 0x43, 0x22, 0x05, 0x73, 0x64, 0x10, 0x64, 0x55, 0x54, 0x77, 0x22, 0x75, 0x56, + 0x81, 0x43, 0x36, 0x14, 0x62, 0x55, 0x08, 0x20, 0x64, 0x37, 0x68, 0x54, 0x68, 0x75, 0x43, + 0x53, 0x75, 0x10, 0x68, 0x71, 0x83, 0x33, 0x80, 0x54, 0x75, 0x05, 0x25, 0x80, 0x75, 0x28, + 0x18, 0x84, 0x38, 0x11, 0x08, 0x72, 0x60, 0x20, 0x20, 0x08, 0x58, 0x83, 0x01, 0x83, 0x61, + 0x13, 0x82, 0x82, 0x12, 0x06, 0x17, 0x11, 0x57, 0x87, 0x68, 0x78, 0x88, 0x78, 0x64, 0x37, + 0x54, 0x60, 0x16, 0x57, 0x15, 0x50, 0x84, 0x71, 0x88, 0x66, 0x07, 0x27, 0x32, 0x88, 0x06, + 0x64, 0x74, 0x18, 0x56, 0x76, 0x21, 0x80, 0x31, 0x82, 0x76, 0x64, 0x15, 0x78, 0x24, 0x50, + 0x25, 0x64, 0x66, 0x43, 0x11, 0x35, 0x04, 0x36, 0x47, 0x80, 0x12, 0x66, 0x73, 0x14, 0x30, + 0x11, 0x66, 0x06, 0x55, 0x86, 0x47, 0x18, 0x36, 0x88, 0x63, 0x50, 0x38, 0x47, 0x86, 0x11, + 0x01, 0x20, 0x23, 0x56, 0x11, 0x61, 0x37, 0x86, 0x07, 0x85, 0x32, 0x12, 0x40, 0x07, 0x54, + 0x78, 0x82, 0x30, 0x43, 0x66, 0x61, 0x16, 0x60, 0x42, 0x55, 0x41, 0x82, 0x85, 0x60, 0x53, + 0x67, 0x78, 0x56, 0x38, 0x43, 0x44, 0x30, 0x63, 0x26, 0x10, 0x77, 0x07, 0x31, 0x78, 0x42, + 0x72, 0x14, 0x11, 0x16, 0x53, 0x03, 0x85, 0x27, 0x68, 0x67, 0x46, 0x01, 0x50, 0x82, 0x37, + 0x35, 0x32, 0x07, 0x66, 0x10, 0x75, 0x04, 0x68, 0x12, 0x48, 0x06, 0x66, 0x03, 0x03, 0x26, + 0x52, 0x31, 0x24, 0x45, 0x40, 0x88, 0x00, 0x31, 0x80, 0x88, 0x76, 0x72, 0x17, 0x30, 0x71, + 0x82, 0x47, 0x21, 0x51, 0x27, 0x80, 0x11, 0x65, 0x44, 0x74, 0x86, 0x61, 0x72, 0x23, 0x33, + 0x80, 0x86, 0x60, 0x64, 0x46, 0x83, 0x52, 0x15, 0x84, 0x20, 0x36, 0x80, 0x11, 0x80, 0x21, + 0x18, 0x18, 0x33, 0x17, 0x73, 0x54, 0x53, 0x48, 0x81, 0x00, 0x44, 0x86, 0x53, 0x67, 0x43, + 0x70, 0x57, 0x72, 0x58, 0x83, 0x34, 0x60, 0x38, 0x42, 0x32, 0x85, 0x68, 0x10, 0x06, 0x04, + 0x26, 0x04, 0x25, 0x84, 0x56, 0x02, 0x35, 0x68, 0x20, 0x51, 0x83, 0x86, 0x38, 0x43, 0x24, + 0x21, 0x22, 0x42, 0x45, 0x64, 0x58, 0x58, 0x67, 0x71, 0x45, 0x72, 0x85, 0x04, 0x78, 0x87, + 0x17, 0x18, 0x06, 0x18, 0x83, 0x60, 0x86, 0x86, 0x41, 0x56, 0x50, 0x81, 0x16, 0x50, 0x26, + 0x46, 0x70, 0x06, 0x08, 0x26, 0x62, 0x27, 0x38, 0x31, 0x72, 0x40, 0x72, 0x57, 0x30, 0x07, + 0x27, 0x28, 0x86, 0x20, 0x66, 0x75, 0x88, 0x68, 0x26, 0x07, 0x06, 0x40, 0x20, 0x33, 0x03, + 0x43, 0x66, 0x31, 0x55, 0x46, 0x42, 0x45, 0x34, 0x56, 0x67, 0x18, 0x73, 0x45, 0x65, 0x83, + 0x70, 0x22, 0x50, 0x84, 0x68, 0x56, 0x28, 0x80, 0x70, 0x36, 0x70, 0x84, 0x62, 0x37, 0x17, + 0x10, 0x06, 0x57, 0x17, 0x58, 0x47, 0x78, 0x70, 0x86, 0x55, 0x53, 0x78, 0x22, 0x35, 0x14, + 0x46, 0x77, 0x28, 0x56, 0x73, 0x03, 0x22, 0x87, 0x00, 0x14, 0x33, 0x20, 0x61, 0x71, 0x58, + 0x45, 0x52, 0x66, 0x32, 0x50, 0x26, 0x51, 0x33, 0x47, 0x77, 0x38, 0x03, 0x55, 0x16, 0x43, + 0x13, 0x47, 0x35, 0x10, 0x66, 0x27, 0x51, 0x75, 0x74, 0x02, 0x46, 0x88, 0x81, 0x70, 0x67, + 0x43, 0x46, 0x81, 0x86, 0x01, 0x76, 0x52, 0x45, 0x33, 0x30, 0x87, 0x21, 0x04, 0x34, 0x34, + 0x01, 0x03, 0x22, 0x87, 0x63, 0x51, 0x55, 0x26, 0x50, 0x81, 0x30, 0x77, 0x45, 0x44, 0x41, + 0x68, 0x15, 0x41, 0x83, 0x63, 0x64, 0x11, 0x20, 0x40, 0x26, 0x87, 0x30, 0x43, 0x67, 0x77, + 0x12, 0x80, 0x88, 0x46, 0x35, 0x54, 0x53, 0x00, 0x62, 0x45, 0x81, 0x04, 0x58, 0x36, 0x51, + 0x24, 0x84, 0x27, 0x80, 0x34, 0x51, 0x66, 0x63, 0x58, 0x43, 0x78, 0x56, 0x01, 0x46, 0x51, + 0x15, 0x74, 0x23, 0x21, 0x43, 0x66, 0x85, 0x22, 0x47, 0x77, 0x31, 0x34, 0x50, 0x17, 0x83, + 0x62, 0x42, 0x05, 0x50, 0x00, 0x64, 0x84, 0x47, 0x12, 0x34, 0x40, 0x88, 0x00, 0x60, 0x47, + 0x35, 0x40, 0x57, 0x83, 0x33, 0x63, 0x08, 0x21, 0x06, 0x15, 0x22, 0x52, 0x07, 0x24, 0x88, + 0x51, 0x34, 0x86, 0x37, 0x06, 0x76, 0x22, 0x58, 0x85, 0x71, 0x26, 0x56, 0x73, 0x47, 0x68, + 0x16, 0x46, 0x46, 0x84, 0x25, 0x87, 0x08, 0x12, 0x27, 0x05, 0x50, 0x08, 0x38, 0x32, 0x00, + 0x23, 0x20, 0x80, 0x66, 0x34, 0x53, 0x36, 0x00, 0x33, 0x46, 0x85, 0x72, 0x47, 0x06, 0x35, + 0x54, 0x00, 0x35, 0x77, 0x12, 0x27, 0x52, 0x30, 0x71, 0x42, 0x53, 0x68, 0x74, 0x37, 0x45, + 0x70, 0x05, 0x66, 0x43, 0x22, 0x44, 0x82, 0x85, 0x20, 0x72, 0x18, 0x33, 0x30, 0x20, 0x53, + 0x37, 0x33, 0x40, 0x77, 0x27, 0x80, 0x55, 0x25, 0x30, 0x63, 0x52, 0x50, 0x40, 0x67, 0x33, + 0x46, 0x13, 0x18, 0x07, 0x28, 0x07, 0x17, 0x24, 0x83, 0x77, 0x63, 0x45, 0x73, 0x18, 0x58, + 0x51, 0x60, 0x23, 0x33, 0x44, 0x36, 0x25, 0x16, 0x43, 0x38, 0x16, 0x08, 0x58, 0x77, 0x34, + 0x62, 0x42, 0x88, 0x30, 0x07, 0x03, 0x65, 0x85, 0x37, 0x55, 0x00, 0x75, 0x52, 0x31, 0x50, + 0x37, 0x02, 0x13, 0x24, 0x63, 0x04, 0x37, 0x08, 0x68, 0x06, 0x36, 0x15, 0x03, 0x03, 0x00, + 0x43, 0x58, 0x63, 0x57, 0x08, 0x02, 0x11, 0x06, 0x64, 0x73, 0x46, 0x35, 0x22, 0x62, 0x03, + 0x30, 0x43, 0x80, 0x21, 0x08, 0x52, 0x87, 0x57, 0x83, 0x21, 0x07, 0x88, 0x67, 0x48, 0x08, + 0x56, 0x34, 0x74, 0x36, 0x73, 0x42, 0x84, 0x05, 0x84, 0x66, 0x84, 0x14, 0x37, 0x00, 0x55, + 0x10, 0x87, 0x34, 0x26, 0x44, 0x77, 0x21, 0x12, 0x73, 0x84, 0x73, 0x65, 0x26, 0x47, 0x25, + 0x77, 0x14, 0x47, 0x04, 0x17, 0x86, 0x44, 0x26, 0x02, 0x47, 0x11, 0x87, 0x40, 0x81, 0x22, + 0x16, 0x60, 0x58, 0x47, 0x17, 0x81, 0x37, 0x06, 0x76, 0x80, 0x81, 0x70, 0x58, 0x18, 0x55, + 0x85, 0x47, 0x13, 0x63, 0x42, 0x10, 0x75, 0x58, 0x01, 0x63, 0x58, 0x35, 0x85, 0x18, 0x44, + 0x03, 0x84, 0x71, 0x10, 0x33, 0x87, 0x42, 0x62, 0x82, 0x47, 0x74, 0x13, 0x65, 0x54, 0x42, + 0x70, 0x73, 0x46, 0x35, 0x77, 0x75, 0x00, 0x66, 0x25, 0x62, 0x68, 0x42, 0x02, 0x12, 0x46, + 0x83, 0x86, 0x46, 0x16, 0x64, 0x60, 0x31, 0x22, 0x53, 0x88, 0x84, 0x54, 0x00, 0x84, 0x57, + 0x34, 0x46, 0x47, 0x54, 0x47, 0x25, 0x60, 0x54, 0x61, 0x66, 0x84, 0x66, 0x30, 0x88, 0x06, + 0x38, 0x27, 0x15, 0x63, 0x28, 0x71, 0x83, 0x84, 0x06, 0x52, 0x24, 0x76, 0x81, 0x16, 0x06, + 0x62, 0x13, 0x03, 0x30, 0x18, 0x68, 0x02, 0x80, 0x13, 0x84, 0x63, 0x05, 0x05, 0x65, 0x72, + 0x38, 0x75, 0x83, 0x65, 0x72, 0x32, 0x30, 0x68, 0x80, 0x46, 0x12, 0x26, 0x06, 0x65, 0x16, + 0x75, 0x57, 0x05, 0x32, 0x41, 0x32, 0x27, 0x67, 0x35, 0x17, 0x08, 0x01, 0x53, 0x00, 0x16, + 0x28, 0x46, 0x01, 0x34, 0x88, 0x77, 0x01, 0x11, 0x88, 0x15, 0x57, 0x13, 0x15, 0x46, 0x43, + 0x11, 0x70, 0x47, 0x32, 0x88, 0x28, 0x56, 0x36, 0x82, 0x34, 0x55, 0x50, 0x41, 0x86, 0x27, + 0x65, 0x63, 0x11, 0x11, 0x68, 0x75, 0x05, 0x10, 0x42, 0x54, 0x41, 0x44, 0x27, 0x85, 0x22, + 0x11, 0x17, 0x17, 0x88, 0x15, 0x36, 0x85, 0x15, 0x74, 0x47, 0x16, 0x62, 0x55, 0x36, 0x55, + 0x83, 0x63, 0x02, 0x50, 0x28, 0x55, 0x76, 0x87, 0x53, 0x27, 0x13, 0x71, 0x03, 0x72, 0x37, + 0x05, 0x71, 0x47, 0x61, 0x71, 0x36, 0x51, 0x84, 0x12, 0x42, 0x36, 0x64, 0x44, 0x66, 0x41, + 0x43, 0x52, 0x05, 0x21, 0x08, 0x51, 0x57, 0x03, 0x33, 0x63, 0x86, 0x02, 0x58, 0x42, 0x66, + 0x28, 0x14, 0x81, 0x10, 0x54, 0x62, 0x68, 0x17, 0x30, 0x38, 0x75, 0x64, 0x33, 0x21, 0x65, + 0x88, 0x56, 0x86, 0x63, 0x63, 0x28, 0x13, 0x40, 0x62, 0x54, 0x01, 0x20, 0x40, 0x88, 0x65, + 0x47, 0x88, 0x61, 0x71, 0x65, 0x76, 0x23, 0x72, 0x62, 0x34, 0x86, 0x70, 0x30, 0x11, 0x51, + 0x15, 0x63, 0x20, 0x50, 0x75, 0x35, 0x02, 0x12, 0x21, 0x08, 0x42, 0x65, 0x31, 0x43, 0x55, + 0x67, 0x11, 0x15, 0x25, 0x72, 0x01, 0x06, 0x85, 0x36, 0x30, 0x15, 0x05, 0x57, 0x58, 0x60, + 0x58, 0x78, 0x43, 0x14, 0x31, 0x32, 0x78, 0x78, 0x80, 0x87, 0x38, 0x47, 0x88, 0x63, 0x78, + 0x81, 0x81, 0x38, 0x73, 0x42, 0x61, 0x78, 0x38, 0x85, 0x24, 0x66, 0x77, 0x33, 0x50, 0x60, + 0x21, 0x15, 0x14, 0x64, 0x23, 0x82, 0x32, 0x68, 0x01, 0x35, 0x44, 0x07, 0x83, 0x47, 0x53, + 0x85, 0x53, 0x57, 0x52, 0x83, 0x23, 0x35, 0x18, 0x76, 0x01, 0x15, 0x21, 0x34, 0x32, 0x57, + 0x73, 0x33, 0x36, 0x55, 0x18, 0x86, 0x15, 0x81, 0x61, 0x68, 0x24, 0x18, 0x42, 0x21, 0x22, + 0x30, 0x84, 0x14, 0x48, 0x15, 0x12, 0x01, 0x10, 0x30, 0x24, 0x77, 0x72, 0x42, 0x54, 0x43, + 0x66, 0x06, 0x77, 0x17, 0x70, 0x76, 0x03, 0x01, 0x45, 0x25, 0x40, 0x35, 0x00, 0x18, 0x38, + 0x73, 0x23, 0x77, 0x35, 0x26, 0x50, 0x86, 0x35, 0x71, 0x13, 0x73, 0x44, 0x81, 0x60, 0x52, + 0x77, 0x45, 0x65, 0x53, 0x73, 0x00, 0x85, 0x83, 0x77, 0x85, 0x03, 0x51, 0x21, 0x11, 0x54, + 0x80, 0x62, 0x88, 0x50, 0x18, 0x02, 0x68, 0x13, 0x86, 0x52, 0x05, 0x34, 0x68, 0x01, 0x32, + 0x07, 0x24, 0x18, 0x03, 0x21, 0x30, 0x05, 0x72, 0x38, 0x64, 0x07, 0x64, 0x27, 0x11, 0x41, + 0x01, 0x83, 0x85, 0x25, 0x51, 0x06, 0x32, 0x60, 0x71, 0x04, 0x86, 0x51, 0x76, 0x83, 0x38, + 0x28, 0x57, 0x27, 0x62, 0x35, 0x45, 0x18, 0x73, 0x50, 0x83, 0x13, 0x28, 0x86, 0x37, 0x66, + 0x61, 0x42, 0x63, 0x11, 0x67, 0x50, 0x33, 0x11, 0x25, 0x53, 0x76, 0x41, 0x76, 0x03, 0x14, + 0x33, 0x17, 0x72, 0x12, 0x23, 0x44, 0x18, 0xA8, 0x2E, 0x4F, 0x5C, 0x9E, 0xA0, 0xFA, 0xF9, + 0x9E, 0xB0, 0x4D, 0x78, 0xA7, 0x33, 0x27, 0x11, 0x11, 0x7C, 0x33, 0xF1, 0x8E, 0xCA, 0x21, + 0xF8, 0x74, 0x33, 0x76, 0xAD, 0xA5, 0x21, 0x98, 0x04, 0xA7, 0xED, 0x9A, 0x55, 0x57, 0xFC, + 0xD6, 0x7A, 0x35, 0x50, 0xB3, 0xA4, 0xB8, 0xC5, 0x88, 0x62, 0x9C, 0x02, 0x14, 0x75, 0xFA, + 0x3D, 0x56, 0xD5, 0xD6, 0xCF, 0xBB, 0x1A, 0x09, 0xBD, 0xA8, 0xD1, 0x4D, 0xE6, 0x22, 0xDD, + 0xFF, 0x16, 0xD8, 0xBC, 0x99, 0xB1, 0x42, 0x78, 0xA8, 0xAF, 0x1D, 0x76, 0xBE, 0xD1, 0x57, + 0x67, 0x2D, 0xD9, 0xC3, 0x23, 0x16, 0xF9, 0x7E, 0x8D, 0xAA, 0xDE, 0xF8, 0xD9, 0xDA, 0x69, + 0x58, 0x67, 0x25, 0x56, 0x7F, 0xB9, 0x6B, 0x59, 0x99, 0x0D, 0x4B, 0xF0, 0xBC, 0x9C, 0x19, + 0x5B, 0x90, 0xB7, 0x42, 0x95, 0xF5, 0x67, 0x5B, 0x24, 0x25, 0x7C, 0x27, 0x10, 0xC1, 0x75, + 0xB0, 0x15, 0x3F, 0x29, 0x11, 0x32, 0x8C, 0x2E, 0xB7, 0xAB, 0xB9, 0xAD, 0x46, 0xE7, 0x0A, + 0x8B, 0x53, 0xC3, 0x9E, 0xA6, 0x42, 0xCE, 0xE4, 0xB3, 0xCB, 0x42, 0x62, 0x0E, 0x86, 0x3C, + 0xE8, 0xB6, 0x50, 0xCE, 0x8A, 0xDC, 0xD9, 0x23, 0x72, 0x1A, 0x16, 0x87, 0x02, 0x3C, 0x67, + 0x3A, 0x8C, 0xBB, 0x6B, 0x03, 0xD5, 0x1C, 0xD1, 0x97, 0xE8, 0xC3, 0x46, 0xEB, 0xAD, 0xCE, + 0x93, 0x95, 0x0F, 0x88, 0xCE, 0xE2, 0x01, 0xDB, 0x9E, 0x32, 0x08, 0x43, 0xE2, 0x9F, 0x30, + 0x0D, 0x9A, 0x19, 0x50, 0x0D, 0x70, 0xA4, 0xCA, 0xF2, 0x72, 0xC6, 0x9E, 0x4E, 0xEF, 0x69, + 0xFB, 0xB8, 0xA5, 0x5E, 0xFD, 0x7C, 0xA2, 0xBE, 0xD9, 0x90, 0xD2, 0xD3, 0xB5, 0x82, 0x84, + 0x8F, 0x9C, 0x45, 0xC2, 0xAB, 0xC5, 0x4C, 0xFC, 0x47, 0xD3, 0x4F, 0x06, 0xC0, 0xFF, 0xA5, + 0x6F, 0xCD, 0x76, 0x2A, 0xB9, 0xCB, 0xA9, 0x14, 0x6D, 0x77, 0x25, 0x21, 0x89, 0x63, 0xB2, + 0x40, 0xD7, 0x2B, 0x6D, 0x22, 0xC9, 0x31, 0x71, 0xFB, 0xD4, 0x77, 0x88, 0xB7, 0x6E, 0x72, + 0x04, 0x2D, 0xEF, 0x08, 0x78, 0xD2, 0x3D, 0xF6, 0x31, 0xA1, 0xA1, 0xE5, 0xA6, 0x02, 0x76, + 0x86, 0xDE, 0x5B, 0x4A, 0x10, 0xE9, 0x10, 0x69, 0xC8, 0xF2, 0xBA, 0x02, 0x59, 0xB0, 0x4D, + 0x64, 0x09, 0xDA, 0x96, 0x56, 0x7C, 0xA5, 0x2D, 0xA4, 0x97, 0x02, 0x6E, 0x58, 0x3A, 0x0E, + 0xCE, 0xFC, 0x1F, 0x01, 0xE6, 0xB9, 0x88, 0xE2, 0x1F, 0x97, 0x67, 0xA2, 0xB7, 0xE1, 0x67, + 0x2D, 0xEB, 0x9A, 0x1E, 0x2A, 0x3F, 0xCC, 0x86, 0x3A, 0xA9, 0x15, 0x17, 0xC3, 0x34, 0x62, + 0x06, 0x01, 0xB4, 0xFE, 0x79, 0x73, 0x0E, 0x93, 0x49, 0x35, 0xF4, 0xB6, 0xFB, 0xC4, 0xE3, + 0x26, 0x95, 0x14, 0x5C, 0x2B, 0x5F, 0x6A, 0x12, 0x7F, 0xEC, 0xC0, 0xA2, 0x77, 0x45, 0x1E, + 0xBC, 0x3F, 0xD5, 0x23, 0x44, 0x4F, 0x9E, 0xE7, 0xC9, 0xC3, 0x45, 0x34, 0xF3, 0x56, 0xDB, + 0x54, 0x4F, 0xC3, 0x1C, 0x1B, 0xFD, 0xE5, 0xF6, 0x5C, 0x77, 0xEA, 0x2F, 0x7C, 0x2E, 0xAE, + 0x4C, 0x55, 0xEB, 0xAF, 0x10, 0x42, 0x71, 0xC5, 0x66, 0xFD, 0x4E, 0xBA, 0xC7, 0x1C, 0x7A, + 0x62, 0xC7, 0x49, 0x52, 0x81, 0x7A, 0xE6, 0x75, 0x50, 0x4D, 0x95, 0x99, 0xB1, 0xB7, 0x62, + 0xB6, 0xAC, 0xA1, 0x68, 0xA8, 0x32, 0x48, 0xC9, 0xD9, 0xAD, 0xB0, 0xCE, 0xB1, 0x55, 0x6E, + 0x57, 0x59, 0x49, 0x0B, 0xBC, 0x0C, 0x79, 0x00, 0x79, 0x5A, 0xD7, 0x21, 0x23, 0x03, 0x8B, + 0x66, 0x2F, 0x64, 0xF1, 0x06, 0xA9, 0x99, 0x36, 0x81, 0xA2, 0x5D, 0x59, 0xAF, 0x7B, 0xC9, + 0x7A, 0x23, 0x5B, 0xE9, 0x28, 0x4C, 0x5B, 0xC4, 0x5A, 0x6C, 0x90, 0xCB, 0x1C, 0x29, 0x99, + 0xC6, 0x63, 0xD9, 0x6B, 0x47, 0x8E, 0x23, 0x07, 0xF8, 0x55, 0x48, 0x95, 0x7D, 0x65, 0x74, + 0x0E, 0x26, 0x73, 0xE9, 0xEB, 0xD1, 0x35, 0x28, 0x29, 0x03, 0x8F, 0x46, 0x2B, 0x8F, 0xD3, + 0xB5, 0x68, 0x1D, 0xA5, 0x5C, 0x02, 0x52, 0x52, 0x38, 0x53, 0x52, 0x5E, 0xA0, 0xAD, 0x64, + 0x7E, 0x71, 0xAC, 0x2C, 0x5A, 0x88, 0x93, 0xE6, 0x03, 0xAC, 0x97, 0xE5, 0x6C, 0x04, 0xCE, + 0xB2, 0xF2, 0x6F, 0x5C, 0x5B, 0x4B, 0x6D, 0x94, 0xAB, 0x81, 0x13, 0x80, 0xFD, 0x00, 0xF2, + 0x20, 0x8F, 0xE8, 0x65, 0x35, 0x08, 0x6A, 0xEB, 0xFD, 0x35, 0xC2, 0x91, 0x20, 0x62, 0x4C, + 0x04, 0xFB, 0xB6, 0x11, 0x39, 0x29, 0xD9, 0xC5, 0x56, 0x35, 0x02, 0x53, 0x76, 0x6C, 0x20, + 0x9F, 0xDB, 0xA8, 0x3C, 0x95, 0xFC, 0xCD, 0x34, 0x2A, 0x28, 0x09, 0x93, 0x55, 0xD0, 0x0B, + 0xC8, 0x63, 0xF4, 0xEE, 0xF5, 0x96, 0xEB, 0x0B, 0x42, 0xEB, 0xCC, 0x7C, 0x79, 0x49, 0x1C, + 0xCE, 0xAE, 0x20, 0x5E, 0xA0, 0xB8, 0x05, 0x9F, 0xBB, 0x8A, 0x57, 0x26, 0xC5, 0x94, 0x9D, + 0x2B, 0x15, 0xE7, 0xE2, 0x9C, 0x51, 0xFC, 0x9B, 0x02, 0xEE, 0x1A, 0x4F, 0xC3, 0x57, 0xB5, + 0xF1, 0xBE, 0xF9, 0xC4, 0xAD, 0xD4, 0x6A, 0x2A, 0x92, 0x0C, 0x2F, 0xBF, 0x08, 0xA3, 0x7E, + 0xB1, 0x51, 0x4B, 0xFA, 0x15, 0x11, 0x0A, 0x43, 0x92, 0xA7, 0x4C, 0x6F, 0x13, 0xC5, 0x0C, + 0x5C, 0xFF, 0xD9, 0x75, 0x31, 0x09, 0x8D, 0x7C, 0xD2, 0x3B, 0x60, 0xEB, 0x35, 0xC4, 0xA4, + 0x28, 0xB4, 0x6C, 0x55, 0x38, 0x6E, 0x10, 0x10, 0xC4, 0xBA, 0x7F, 0x70, 0xE4, 0xC7, 0xEC, + 0xB7, 0x57, 0x5F, 0x30, 0x63, 0xA7, 0x1E, 0x84, 0xDF, 0xDC, 0xF0, 0x9A, 0x58, 0xB2, 0xCD, + 0xB0, 0xF9, 0x9F, 0x27, 0xED, 0x37, 0x86, 0x10, 0xD2, 0x5C, 0xBA, 0xD7, 0xBF, 0xA6, 0xBA, + 0x0D, 0x59, 0x18, 0x9C, 0xFE, 0x88, 0xEA, 0xB9, 0xB4, 0x6D, 0x7E, 0x6D, 0xB0, 0x30, 0x7E, + 0xAB, 0xE4, 0x19, 0x8E, 0x99, 0xBD, 0x71, 0xF7, 0x79, 0xAB, 0x66, 0x58, 0x1E, 0x09, 0x12, + 0xFC, 0x7B, 0x1D, 0x25, 0x85, 0x24, 0x5E, 0x9A, 0x12, 0x68, 0x7A, 0x97, 0x5C, 0xD5, 0xE8, + 0xE1, 0xDC, 0xC0, 0x45, 0xD5, 0xF8, 0x91, 0xC4, 0xC6, 0x85, 0xDB, 0x07, 0xCF, 0x81, 0xE7, + 0x73, 0x89, 0xB3, 0x63, 0xEB, 0x6B, 0xDF, 0xE3, 0x9B, 0x27, 0xFF, 0x84, 0xC9, 0x7E, 0xEF, + 0xEE, 0x16, 0x2E, 0x3B, 0x45, 0x1F, 0xE6, 0x91, 0x47, 0x19, 0xCB, 0x64, 0x36, 0xD8, 0x55, + 0x96, 0x0F, 0xF9, 0x15, 0xD7, 0xCE, 0xA6, 0xAD, 0xEA, 0xFD, 0xFC, 0x1C, 0x05, 0x78, 0x6C, + 0x49, 0xF9, 0x23, 0xA4, 0x74, 0xFF, 0xDF, 0xC3, 0x15, 0x3A, 0x06, 0xE6, 0xED, 0x0B, 0x0A, + 0xD2, 0x20, 0xD7, 0x25, 0x24, 0x43, 0x4D, 0x52, 0x73, 0xC0, 0xAA, 0xB6, 0xDD, 0xE4, 0xE9, + 0x14, 0x76, 0xD5, 0x81, 0xA2, 0x69, 0x5A, 0x60, 0xDE, 0x6D, 0x9F, 0x44, 0xD7, 0x7A, 0xA0, + 0x82, 0x66, 0xE9, 0x38, 0xEE, 0xB4, 0xA9, 0x59, 0x7C, 0x9B, 0x64, 0x98, 0x60, 0x59, 0xE4, + 0x92, 0x62, 0xA4, 0xEA, 0xB2, 0x45, 0x4E, 0x14, 0x01, 0x5A, 0xD0, 0x53, 0x6C, 0x42, 0x73, + 0x3A, 0x5D, 0x77, 0xD7, 0x99, 0x5C, 0x2A, 0x20, 0x44, 0x60, 0x09, 0xEB, 0xFE, 0x56, 0x32, + 0xC8, 0x0C, 0x08, 0xED, 0x2B, 0x97, 0xAF, 0x35, 0x06, 0x64, 0x89, 0xF5, 0x97, 0xEB, 0x1B, + 0x1F, 0x11, 0xF0, 0x4F, 0x60, 0xE0, 0xC9, 0x04, 0x01, 0x59, 0xC4, 0x4A, 0xB3, 0xE6, 0x0E, + 0x0A, 0x15, 0x22, 0x9D, 0x19, 0x12, 0x28, 0xBE, 0xD1, 0x7B, 0xBC, 0x3A, 0xC9, 0x39, 0xB3, + 0xC6, 0x7C, 0xEE, 0x13, 0x5F, 0x35, 0x2C, 0x27, 0x21, 0x6C, 0x9C, 0x31, 0xF7, 0x2A, 0x3E, + 0x87, 0x04, 0x0C, 0x5F, 0x61, 0x93, 0x06, 0xEB, 0x0B, 0x6C, 0xCA, 0x2A, 0x9C, 0xE7, 0xB2, + 0x2A, 0x16, 0x94, 0xD0, 0x0C, 0xA9, 0xC0, 0x5E, 0x31, 0x51, 0x26, 0x45, 0x7F, 0x26, 0xCE, + 0x84, 0xF9, 0x61, 0x72, 0x41, 0x86, 0x07, 0x82, 0xF8, 0x64, 0xB4, 0x73, 0xD8, 0x40, 0x17, + 0x49, 0x19, 0x02, 0xB1, 0xBD, 0xC8, 0xCD, 0xC5, 0x80, 0x0D, 0xD4, 0x61, 0x27, 0xFB, 0x80, + 0xA7, 0x1C, 0x09, 0x5B, 0x47, 0x3A, 0x56, 0x25, 0x29, 0xB3, 0xB1, 0xE7, 0xE4, 0x37, 0xE1, + 0x58, 0xA5, 0xF6, 0x66, 0x6E, 0x99, 0x74, 0xD0, 0x05, 0xB0, 0x62, 0xC2, 0x30, 0x9E, 0x6D, + 0xCE, 0x98, 0xF9, 0xB6, 0x58, 0xC6, 0xE3, 0xF9, 0xA2, 0x16, 0xD5, 0x8C, 0x8C, 0x91, 0x42, + 0xBD, 0x1C, 0x8C, 0x85, 0xA9, 0xDA, 0x87, 0x2E, 0xBB, 0xFA, 0xD3, 0xFE, 0xA9, 0xD9, 0xAB, + 0xA2, 0xB6, 0x8C, 0x0E, 0x8F, 0x19, 0xC6, 0xFF, 0x5F, 0x00, 0x58, 0x4D, 0x45, 0xDA, 0xF9, + 0xD6, 0xC9, 0xD6, 0x9E, 0xD0, 0x4B, 0x8D, 0xA8, 0xD6, 0x87, 0x25, 0x8B, 0x77, 0x80, 0x79, + 0x27, 0x61, 0x2C, 0x53, 0x04, 0x46, 0xFE, 0xA7, 0x69, 0x7A, 0xE3, 0xF9, 0x26, 0x69, 0x89, + 0x29, 0xBC, 0x6A, 0x5A, 0x8C, 0xF3, 0xE2, 0x02, 0x4C, 0x0F, 0x0C, 0x5E, 0xE5, 0x7B, 0x58, + 0x69, 0xBF, 0x98, 0x18, 0x81, 0xCA, 0xF9, 0xE3, 0x66, 0x5F, 0xC7, 0xF7, 0xEF, 0xC6, 0x78, + 0x92, 0x9F, 0x87, 0xA5, 0x6E, 0xAA, 0x42, 0xEA, 0x4D, 0x1F, 0xF6, 0x69, 0x18, 0x22, 0xDD, + 0x79, 0xA4, 0x70, 0x96, 0xB7, 0x76, 0xD1, 0xD8, 0xF0, 0x14, 0x56, 0xE5, 0x87, 0x3B, 0x07, + 0x38, 0x40, 0x6C, 0x38, 0x2C, 0x57, 0x3A, 0xE9, 0xCD, 0xE2, 0xD9, 0xE7, 0xF2, 0x31, 0xB6, + 0xCC, 0x5C, 0x67, 0x6E, 0x7C, 0xF4, 0x39, 0x63, 0x37, 0x30, 0x13, 0xA5, 0x80, 0x75, 0x38, + 0x1F, 0xF0, 0x94, 0x9B, 0xE0, 0x84, 0x54, 0x6D, 0x72, 0xE4, 0xF8, 0xA3, 0xE5, 0xFE, 0x4A, + 0xA5, 0x09, 0x1A, 0xDD, 0x23, 0x4E, 0x2A, 0xFE, 0x00, 0x30, 0xB1, 0xB6, 0x63, 0xAE, 0x9D, + 0x2D, 0x32, 0x41, 0x09, 0x86, 0xB9, 0x40, 0x2A, 0xAA, 0xF2, 0x46, 0x5B, 0x74, 0xA5, 0xE2, + 0xD0, 0xBC, 0x38, 0xE3, 0xA9, 0x2B, 0xBD, 0xDD, 0x8A, 0x1F, 0xED, 0x7B, 0x94, 0x8C, 0x23, + 0xCC, 0xE6, 0xF8, 0xC0, 0x8F, 0xE3, 0x56, 0x83, 0x5B, 0xA6, 0x5B, 0x0F, 0x98, 0x40, 0x68, + 0x61, 0x6E, 0xF4, 0x81, 0x38, 0xEF, 0xD8, 0x9B, 0xF3, 0x57, 0xA5, 0x4D, 0x2E, 0xBB, 0xF3, + 0x76, 0xCB, 0xDC, 0xC6, 0x9C, 0x5F, 0x1F, 0x61, 0xC6, 0x4D, 0x27, 0x94, 0xBC, 0x06, 0xCC, + 0xB9, 0xAB, 0xDF, 0x66, 0xE2, 0x50, 0x85, 0xD8, 0xC8, 0x30, 0xE2, 0xAE, 0x3B, 0x0F, 0xE0, + 0xF0, 0x7A, 0x7A, 0xF8, 0xB9, 0x32, 0x0B, 0xF3, 0x42, 0x97, 0x09, 0x97, 0xD6, 0x7D, 0x7C, + 0x12, 0x59, 0x3A, 0x8F, 0xBF, 0xAD, 0xE6, 0x35, 0xAA, 0xC5, 0x30, 0x83, 0xA7, 0x02, 0x2C, + 0x47, 0xD5, 0xF7, 0x7A, 0x52, 0xB5, 0x7B, 0x59, 0x8D, 0xA9, 0x39, 0x2A, 0xE6, 0xD8, 0x6A, + 0xFC, 0x46, 0xFC, 0x06, 0x45, 0x51, 0x81, 0xB9, 0xC7, 0x5A, 0x64, 0x6D, 0xC2, 0x1F, 0x81, + 0xE4, 0xBF, 0x21, 0x37, 0x53, 0xDE, 0x73, 0x7F, 0xD2, 0xA1, 0x40, 0x02, 0x79, 0x20, 0xAD, + 0xD3, 0x5A, 0x22, 0x3F, 0x9F, 0x5F, 0x44, 0x65, 0xCE, 0xB6, 0x0C, 0x03, 0xED, 0x04, 0x55, + 0xA3, 0x33, 0xA5, 0xCC, 0x83, 0xAD, 0xBF, 0x43, 0xF1, 0xF4, 0x2C, 0x2C, 0xCB, 0x83, 0x28, + 0xC2, 0x1C, 0x7A, 0xB7, 0xFA, 0xED, 0x2B, 0x21, 0xCF, 0xAD, 0xE2, 0xDA, 0x55, 0x22, 0x3A, + 0xAA, 0xB2, 0xAF, 0x9B, 0x41, 0xC7, 0x33, 0x23, 0x41, 0x74, 0x63, 0x41, 0xB3, 0x9A, 0xA2, + 0xF4, 0x38, 0x15, 0x65, 0x0F, 0x54, 0x80, 0x51, 0x14, 0x24, 0xCF, 0xA6, 0x90, 0x17, 0x79, + 0xC4, 0xD1, 0x8B, 0x63, 0x8C, 0xC0, 0x28, 0x7A, 0xAA, 0xF3, 0x16, 0x80, 0x33, 0x8D, 0x20, + 0xB1, 0x7C, 0x74, 0x49, 0xFD, 0xC6, 0xA2, 0x78, 0xA8, 0xD9, 0x6A, 0x82, 0xEE, 0x4C, 0x4E, + 0xCA, 0x40, 0x12, 0x5E, 0x2D, 0x65, 0x29, 0x00, 0x71, 0xC7, 0xAE, 0xF1, 0xBE, 0x6A, 0x99, + 0x15, 0x98, 0xFB, 0x9D, 0x59, 0x51, 0x25, 0x23, 0xBC, 0xD4, 0xB3, 0x8C, 0x56, 0x6B, 0x8E, + 0x80, 0xA7, 0x3A, 0xE3, 0x33, 0xE1, 0x34, 0x41, 0x43, 0x27, 0xEF, 0x1D, 0x83, 0xC4, 0x7C, + 0x49, 0xDF, 0xE7, 0x93, 0x6D, 0xF1, 0x33, 0x8A, 0x5E, 0x24, 0x77, 0x87, 0x86, 0x8F, 0xC8, + 0x4F, 0xDC, 0xB9, 0x5A, 0xC8, 0x9C, 0x18, 0x5C, 0x4B, 0xB5, 0xFD, 0x57, 0xB2, 0x33, 0x8A, + 0xC4, 0x2B, 0x41, 0xC1, 0x0A, 0x82, 0x3D, 0xF3, 0x96, 0x24, 0xF3, 0x6B, 0x15, 0xA2, 0xF0, + 0x67, 0x58, 0x4E, 0x06, 0xCA, 0x2E, 0x08, 0xCC, 0xAF, 0xF1, 0x61, 0x8F, 0xE0, 0x1D, 0xD0, + 0x6D, 0xF3, 0x51, 0x2E, 0x0B, 0x72, 0x4D, 0xEC, 0x85, 0x06, 0xDA, 0x24, 0x21, 0x5A, 0xCA, + 0xCC, 0x2C, 0x51, 0xB8, 0x2A, 0xD8, 0xD3, 0x02, 0x00, 0x2F, 0xB4, 0x10, 0x68, 0xB1, 0xDA, + 0x4F, 0x8B, 0xB1, 0x47, 0x98, 0x7B, 0x35, 0x16, 0xBA, 0xD5, 0xDB, 0xDD, 0xF0, 0x13, 0x18, + 0xFD, 0x3F, 0xA9, 0xBC, 0x43, 0x70, 0x2A, 0xC4, 0x98, 0xC7, 0x19, 0xD9, 0x5F, 0x2E, 0x84, + 0x1B, 0x62, 0x2A, 0x5E, 0x48, 0x48, 0xA3, 0xC5, 0xC2, 0x62, 0x95, 0x99, 0x92, 0xEA, 0x7A, + 0x7D, 0x72, 0xCA, 0x8A, 0x36, 0x80, 0x28, 0xF4, 0x97, 0xDF, 0xAD, 0x93, 0x35, 0x5C, 0xBB, + 0x1B, 0xB9, 0x78, 0x6D, 0x14, 0xFF, 0x2C, 0xF5, 0x90, 0x31, 0x78, 0x48, 0xF9, 0x58, 0x56, + 0x42, 0x71, 0x10, 0xDD, 0xA3, 0x6F, 0x51, 0x92, 0xA8, 0x16, 0xCE, 0x9C, 0x88, 0x16, 0xCC, + 0x7B, 0xBF, 0xC8, 0x04, 0xEF, 0xC4, 0x00, 0x85, 0xA3, 0x85, 0x0B, 0x89, 0xF1, 0xE7, 0xFE, + 0x56, 0x56, 0xDB, 0xA4, 0x10, 0xF9, 0x06, 0xA9, 0x7C, 0x32, 0x33, 0x6C, 0x1A, 0xE7, 0xE8, + 0x17, 0x37, 0xA8, 0x3E, 0x08, 0x73, 0x54, 0xE4, 0x28, 0xDA, 0x85, 0x38, 0xD9, 0x48, 0xDB, + 0xF5, 0xDF, 0xAC, 0xB5, 0x9D, 0xD2, 0xB5, 0xFD, 0x3B, 0xC8, 0x03, 0xF4, 0xBA, 0x43, 0x2C, + 0x9A, 0x73, 0x9D, 0xF2, 0xCF, 0xA9, 0xED, 0x94, 0x84, 0x32, 0x0F, 0x97, 0xED, 0xFF, 0x1A, + 0x48, 0xC6, 0xB8, 0x6B, 0x30, 0x02, 0xCF, 0xB7, 0x72, 0xDD, 0x5E, 0x56, 0x2B, 0xC4, 0xC3, + 0xD6, 0x83, 0xED, 0x96, 0x4B, 0x61, 0x99, 0xFA, 0x05, 0x14, 0xB0, 0x79, 0x0D, 0x95, 0x80, + 0x95, 0xB7, 0xB8, 0x5C, 0x6B, 0xE8, 0x75, 0xFB, 0xB5, 0x59, 0xE1, 0x93, 0x01, 0x46, 0xCC, + 0xEA, 0x63, 0xA3, 0x88, 0xA1, 0x94, 0xFE, 0x09, 0xC3, 0xDE, 0xA0, 0x3B, 0xE5, 0x2D, 0xE2, + 0x7E, 0x90, 0x10, 0x17, 0xAF, 0xE8, 0x09, 0xAF, 0x63, 0x0A, 0x73, 0x82, 0xBF, 0x5C, 0x4C, + 0xD4, 0xD1, 0xB8, 0xF4, 0x15, 0x79, 0xFB, 0x43, 0x48, 0xED, 0xE4, 0xCA, 0x05, 0xF4, 0xCD, + 0x3F, 0x13, 0x9A, 0x31, 0xB2, 0x54, 0x4E, 0x51, 0x6D, 0xBE, 0x40, 0x86, 0xB9, 0xBB, 0x4B, + 0x2B, 0xED, 0x47, 0xE2, 0xD2, 0x30, 0x98, 0x2D, 0xD5, 0x19, 0x24, 0x29, 0xD3, 0x77, 0xB7, + 0xC0, 0x74, 0x5C, 0xC0, 0x68, 0xE2, 0xF5, 0xA4, 0xAA, 0x04, 0xC7, 0xFF, 0x87, 0x20, 0x9E, + 0xD1, 0x25, 0x99, 0x76, 0xA0, 0xFC, 0x9B, 0x25, 0xE9, 0xE8, 0x51, 0xD4, 0xE3, 0x50, 0x2C, + 0x02, 0xC8, 0x5D, 0x6D, 0xFF, 0x02, 0x9E, 0x21, 0x1D, 0x01, 0xEB, 0xF0, 0xE9, 0xE7, 0x18, + 0x8D, 0x56, 0x8F, 0x84, 0x37, 0xD8, 0x13, 0xB0, 0xF1, 0x22, 0xF2, 0xFB, 0x17, 0x60, 0x3B, + 0x69, 0x3E, 0xD9, 0xC3, 0x8F, 0x17, 0xCF, 0xD5, 0x0B, 0x81, 0x5E, 0x6D, 0x9D, 0xFC, 0x0E, + 0xD2, 0xCC, 0xF1, 0x9F, 0x63, 0x99, 0x27, 0x4A, 0x14, 0x20, 0xF2, 0x35, 0xA5, 0x9D, 0x8B, + 0xF7, 0x24, 0x34, 0x5E, 0x14, 0xE4, 0x5D, 0x9E, 0x4B, 0xE8, 0x93, 0x4D, 0xFC, 0x3F, 0xA9, + 0x26, 0x78, 0xDB, 0x61, 0xD7, 0x11, 0x8B, 0xF5, 0x3C, 0xB8, 0xA2, 0x22, 0x5B, 0x33, 0x5F, + 0x7E, 0xAE, 0x50, 0xE3, 0xF9, 0x41, 0x23, 0x76, 0x28, 0xDB, 0x76, 0xD8, 0xEA, 0x38, 0xF7, + 0x7A, 0x72, 0xAF, 0x3A, 0x26, 0xC8, 0x1F, 0xE4, 0x35, 0x23, 0xB3, 0x35, 0x53, 0x5A, 0x5D, + 0x1D, 0xB7, 0xC3, 0x8F, 0x34, 0x10, 0x82, 0xBB, 0x57, 0x34, 0xD0, 0x89, 0xE8, 0xAE, 0x30, + 0x9C, 0xFD, 0xA3, 0xA0, 0xBC, 0xB5, 0xCD, 0x5B, 0x09, 0x71, 0x13, 0xC8, 0xED, 0xF9, 0x61, + 0x6A, 0xA4, 0xF6, 0xE6, 0x63, 0x1B, 0x91, 0x25, 0x27, 0x6F, 0xB3, 0xF6, 0x80, 0xA3, 0x43, + 0x41, 0xC3, 0xDB, 0x66, 0x8D, 0xC6, 0xCA, 0xD4, 0x5F, 0xC9, 0x3B, 0x27, 0x08, 0xCA, 0x2A, + 0xF7, 0x5C, 0xCC, 0xE7, 0x34, 0xFD, 0x19, 0x1C, 0x50, 0x08, 0x9D, 0xAD, 0x53, 0x98, 0x2F, + 0xDD, 0xAE, 0x02, 0x53, 0x1F, 0xF9, 0x3E, 0x1F, 0x21, 0xFF, 0x39, 0x5F, 0xC0, 0xA1, 0x28, + 0x74, 0xED, 0xF0, 0x6B, 0x6F, 0x96, 0x47, 0xE9, 0x5A, 0x73, 0x24, 0x58, 0x6C, 0x71, 0xDF, + 0xD9, 0x1D, 0x90, 0x1D, 0x62, 0x18, 0x58, 0x19, 0x0F, 0xEC, 0xD0, 0x0C, 0xCD, 0x11, 0x0B, + 0xBA, 0xC5, 0x9F, 0x96, 0xCB, 0x88, 0x4C, 0x3C, 0x93, 0x99, 0x47, 0x48, 0xA5, 0x6F, 0x41, + 0x28, 0x3B, 0xFC, 0x41, 0xFB, 0x89, 0x05, 0x21, 0x53, 0xA8, 0x94, 0x58, 0x8C, 0x3C, 0xB9, + 0x01, 0x7F, 0x3D, 0x66, 0x32, 0x6C, 0x98, 0x56, 0x37, 0xE5, 0x75, 0xAC, 0xB8, 0x12, 0x34, + 0x63, 0x42, 0x65, 0x40, 0x25, 0xD6, 0x02, 0xDE, 0x3B, 0xA9, 0x40, 0xC1, 0x9A, 0xC1, 0xA6, + 0x33, 0xDF, 0xFD, 0xA9, 0x77, 0xB5, 0x29, 0xB8, 0x01, 0x3E, 0x19, 0xC1, 0xD6, 0xD0, 0x68, + 0x0F, 0x4D, 0xAE, 0x62, 0xC9, 0x24, 0x45, 0x0A, 0xE6, 0x6A, 0xAB, 0x82, 0xF2, 0x14, 0x73, + 0x06, 0x1D, 0xAB, 0x3D, 0x62, 0xB2, 0x47, 0xF9, 0x07, 0xE3, 0x55, 0x19, 0x39, 0xAD, 0x3F, + 0x54, 0x65, 0xE9, 0xD0, 0x8A, 0x82, 0xBF, 0xEA, 0x17, 0xEE, 0xA1, 0xB6, 0xB2, 0xB9, 0x23, + 0x75, 0x74, 0x77, 0xF9, 0x93, 0x00, 0x0B, 0x2F, 0x43, 0xB7, 0x0F, 0x28, 0xAA, 0xAB, 0x1F, + 0xE9, 0xA2, 0x6A, 0xD1, 0xFD, 0x33, 0x61, 0x61, 0x6C, 0x0B, 0x0E, 0x24, 0x2F, 0xE7, 0x66, + 0x04, 0xB7, 0x03, 0x3A, 0x1F, 0x30, 0xE9, 0x7E, 0x28, 0xF5, 0x26, 0xCA, 0x3C, 0x88, 0x0F, + 0xE2, 0xB8, 0xD9, 0xD1, 0xB0, 0xC9, 0xFF, 0x18, 0x8B, 0x31, 0xCB, 0x9D, 0x97, 0x42, 0x5A, + 0xCA, 0xB9, 0xB2, 0x16, 0xD9, 0x8A, 0x6A, 0xE3, 0x55, 0xE5, 0x83, 0xDA, 0x71, 0xE8, 0x86, + 0x4E, 0xE3, 0xD1, 0x6B, 0x07, 0x59, 0x79, 0x61, 0x90, 0xEF, 0x54, 0x5C, 0x1E, 0x62, 0xBF, + 0xEF, 0x92, 0xAF, 0x6C, 0xA1, 0x47, 0xB1, 0x32, 0x44, 0xD6, 0xC8, 0x92, 0xFC, 0x8E, 0xF2, + 0x23, 0xAB, 0x3F, 0x43, 0xF9, 0x24, 0xC2, 0xF4, 0x66, 0x09, 0x7E, 0xE8, + ]) + .unwrap(); let msg = b"The quick brown fox jumped over the lazy dog"; @@ -229,7 +741,7 @@ fn bench_mldsa65_sign() { } fn bench_mldsa65_lowmemory_sign() { - use bouncycastle::mldsa_lowmemory::{MLDSATrait, MLDSA65, MLDSA65PrivateKey, MLDSA65_SK_LEN}; + use bouncycastle::mldsa_lowmemory::{MLDSA65, MLDSA65_SK_LEN, MLDSA65PrivateKey, MLDSATrait}; eprintln!("MLDSA65_lowmemory/Sign"); @@ -242,7 +754,12 @@ fn bench_mldsa65_lowmemory_sign() { // use bouncycastle_hex as hex; // eprintln!("sk:\n{}", &hex::encode(sk.encode())); - let sk = MLDSA65PrivateKey::from_bytes(&[0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f]).unwrap(); + let sk = MLDSA65PrivateKey::from_bytes(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, + 0x1E, 0x1F, + ]) + .unwrap(); let msg = b"The quick brown fox jumped over the lazy dog"; @@ -252,7 +769,7 @@ fn bench_mldsa65_lowmemory_sign() { } fn bench_mldsa87_sign() { - use bouncycastle::mldsa::{MLDSATrait, MLDSA87, MLDSA87PrivateKey}; + use bouncycastle::mldsa::{MLDSA87, MLDSA87PrivateKey, MLDSATrait}; eprintln!("MLDSA87/Sign"); @@ -268,7 +785,336 @@ fn bench_mldsa87_sign() { let msg = b"The quick brown fox jumped over the lazy dog"; // let (_pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); - let sk = MLDSA87PrivateKey::from_bytes(&[0x97,0x92,0xbc,0xec,0x2f,0x24,0x30,0x68,0x6a,0x82,0xfc,0xcf,0x3c,0x2f,0x5f,0xf6,0x65,0xe7,0x71,0xd7,0xab,0x41,0xb9,0x02,0x58,0xcf,0xa7,0xe9,0x0e,0xc9,0x71,0x24,0xd8,0xe9,0xee,0x4e,0x90,0xa1,0x6c,0x60,0x2f,0x5e,0xc9,0xbc,0x38,0x51,0x7d,0xc3,0x0e,0x32,0x9d,0x5a,0xb2,0x76,0x73,0xbd,0x85,0xf4,0xc9,0xb0,0x30,0x0f,0x77,0x63,0x89,0x88,0x67,0x50,0xb5,0x7c,0x24,0xdb,0x3f,0xc0,0x12,0xe6,0x1e,0xde,0x59,0x75,0x33,0x37,0x37,0x4f,0xa7,0x12,0x49,0x91,0x54,0x9a,0xf2,0x43,0x49,0x6d,0x06,0x37,0xcb,0x3b,0xe0,0x5a,0x59,0x48,0x23,0x5b,0xf7,0x98,0x75,0xf8,0x96,0xd8,0xfe,0x0c,0xab,0x30,0xc8,0x49,0x48,0xdb,0x4d,0x63,0x15,0xaa,0xaf,0x16,0x0a,0xc6,0x24,0x36,0x64,0x22,0x01,0x48,0x16,0x11,0x09,0x11,0x2c,0x94,0x02,0x89,0x22,0x45,0x2c,0x62,0xb8,0x45,0x00,0x45,0x2a,0x08,0x96,0x70,0x90,0x12,0x6e,0x14,0x93,0x70,0xd4,0x46,0x10,0x84,0x44,0x51,0x58,0x96,0x91,0x0c,0xa9,0x29,0x82,0xb2,0x41,0xc9,0x08,0x71,0xc4,0x28,0x68,0x04,0x96,0x89,0x48,0x40,0x85,0x9b,0x22,0x6d,0x1c,0x28,0x64,0x59,0x12,0x41,0x9c,0xb8,0x91,0x84,0x04,0x89,0x44,0x90,0x05,0xcb,0x34,0x62,0xa0,0x86,0x90,0x40,0x26,0x92,0x20,0x99,0x29,0x13,0x05,0x69,0x5c,0x34,0x68,0xa4,0x32,0x8e,0x19,0x26,0x92,0x59,0x46,0x10,0x09,0xa4,0x49,0x23,0x42,0x4d,0x12,0x36,0x61,0x58,0x10,0x65,0x01,0x28,0x90,0x1a,0x33,0x4c,0x99,0x86,0x31,0xd3,0xa2,0x49,0x09,0x82,0x25,0x43,0x14,0x28,0xc0,0x38,0x81,0x03,0x15,0x4d,0x5b,0x28,0x86,0x08,0x87,0x48,0x23,0x31,0x52,0x94,0x22,0x25,0xc3,0xc0,0x4d,0xa4,0x98,0x21,0x98,0x40,0x20,0xd1,0x42,0x86,0xcb,0x40,0x70,0x5b,0xb0,0x71,0x9c,0x96,0x2c,0xc1,0x12,0x06,0x53,0x46,0x09,0x0c,0x45,0x02,0x14,0x46,0x6e,0x91,0xb4,0x21,0x54,0xb0,0x8c,0xe4,0x46,0x42,0x9a,0x20,0x8c,0x01,0x21,0x25,0x13,0x41,0x05,0x5a,0x40,0x22,0x13,0xc9,0x0c,0xa0,0x18,0x40,0x52,0xc2,0x30,0xcb,0x34,0x2c,0x4b,0xc8,0x68,0x1b,0xa4,0x60,0x49,0x84,0x84,0x63,0x30,0x29,0x4a,0xa0,0x69,0x5b,0x80,0x04,0xd2,0x38,0x0a,0x14,0x26,0x4c,0xe2,0xb2,0x44,0x8b,0xa2,0x11,0x24,0x46,0x49,0xc4,0x14,0x52,0x0b,0x42,0x71,0x03,0xb2,0x10,0x92,0x28,0x80,0x01,0x24,0x88,0xe3,0x08,0x11,0x0a,0x05,0x28,0x19,0xc4,0x81,0x00,0x20,0x22,0xdc,0x44,0x68,0x42,0x12,0x22,0x44,0x00,0x2a,0xc9,0x26,0x6a,0x0c,0x87,0x31,0xe0,0xc0,0x44,0x99,0x14,0x84,0x18,0x36,0x0d,0x11,0x37,0x42,0x22,0x18,0x8c,0x63,0xb2,0x91,0x0c,0x98,0x08,0xa1,0xa0,0x01,0x08,0x92,0x44,0x04,0x13,0x24,0x5c,0x98,0x71,0x82,0x84,0x71,0x84,0x32,0x52,0x51,0xb0,0x31,0x9c,0x42,0x2e,0x1a,0xa8,0x28,0x02,0x08,0x91,0x01,0xc0,0x89,0x0b,0xc7,0x05,0x8a,0x24,0x65,0x22,0xc2,0x64,0x4c,0x88,0x91,0x5c,0x82,0x68,0x13,0xa5,0x64,0x50,0x20,0x86,0x21,0x04,0x02,0x91,0x94,0x44,0x48,0x22,0x30,0x22,0x39,0x4a,0x02,0x98,0x84,0x09,0xa2,0x88,0x19,0x19,0x29,0x44,0x48,0x8c,0x22,0x95,0x0c,0xa1,0x04,0x72,0x04,0x87,0x70,0x12,0x21,0x10,0x42,0x06,0x84,0x1b,0x49,0x85,0x89,0x06,0x4a,0xd3,0x36,0x08,0xdb,0xc0,0x40,0x58,0xb6,0x51,0x0c,0xa7,0x09,0x8c,0x24,0x61,0x99,0x90,0x64,0x8c,0xc2,0x90,0x5b,0x24,0x90,0x10,0xa3,0x49,0x03,0x25,0x61,0x43,0x32,0x8a,0x11,0x98,0x44,0xc8,0xb2,0x20,0x04,0x38,0x41,0x10,0x47,0x2c,0x19,0xc6,0x44,0x1c,0x25,0x2c,0x04,0x88,0x30,0xd9,0x46,0x69,0x9b,0x20,0x00,0x1b,0x46,0x82,0x5a,0xa4,0x80,0x5b,0xa0,0x49,0x18,0x90,0x25,0x00,0x26,0x80,0x0b,0xc2,0x31,0x5a,0x40,0x72,0x54,0xc6,0x20,0xc1,0xb0,0x31,0x24,0xb1,0x4d,0x10,0x95,0x28,0x14,0x00,0x0a,0xa0,0xc8,0x4d,0x54,0xa2,0x88,0x23,0x98,0x81,0x60,0x90,0x04,0x02,0x16,0x2c,0x13,0x21,0x40,0x91,0x86,0x8d,0x08,0xc2,0x91,0x91,0x14,0x26,0xd0,0xb4,0x0c,0x09,0xc6,0x60,0x51,0x44,0x2e,0x04,0x11,0x26,0x00,0x29,0x11,0x93,0xc2,0x08,0x63,0xa4,0x31,0x22,0x00,0x28,0xc1,0x14,0x08,0x0c,0x40,0x2c,0x41,0x14,0x06,0x9c,0x20,0x72,0x54,0x22,0x06,0x8b,0x08,0x4d,0xa1,0x48,0x69,0x10,0x30,0x41,0x1c,0x28,0x49,0x44,0x18,0x8e,0xcc,0x96,0x48,0xd9,0x42,0x50,0x1b,0x06,0x49,0x0c,0x45,0x88,0x23,0x04,0x0d,0x20,0x30,0x2e,0x23,0x85,0x2c,0x14,0x07,0x30,0x40,0xb6,0x85,0x4c,0xc0,0x20,0x44,0x86,0x20,0x19,0x00,0x6c,0x54,0x02,0x48,0xcc,0x88,0x6c,0x59,0x06,0x32,0x49,0x14,0x84,0x04,0xc7,0x50,0x13,0x49,0x28,0xe4,0x06,0x09,0xd3,0xc6,0x10,0xc8,0x28,0x4c,0x23,0x39,0x44,0x52,0xa4,0x64,0xcc,0xa8,0x49,0x44,0x38,0x32,0x0a,0x89,0x84,0x00,0x34,0x2c,0x22,0x85,0x8d,0x10,0x31,0x09,0x09,0x32,0x65,0x1c,0x89,0x8c,0x40,0x40,0x29,0x21,0x85,0x00,0x09,0xa1,0x6d,0x84,0xc0,0x64,0xe2,0x02,0x2d,0x48,0x04,0x40,0x12,0x09,0x8e,0xe0,0x42,0x2e,0x93,0x44,0x08,0x12,0x10,0x6a,0x01,0x84,0x05,0x92,0x30,0x8a,0xcb,0x34,0x8e,0xa2,0x26,0x2e,0x5c,0x86,0x11,0x0b,0x35,0x08,0x18,0x10,0x00,0x02,0x34,0x26,0x24,0x23,0x89,0xd1,0x84,0x00,0x24,0x46,0x60,0x13,0xb2,0x49,0x24,0x28,0x46,0x18,0x02,0x71,0xa0,0x38,0x90,0x89,0x14,0x44,0xd3,0x96,0x2d,0xa3,0x18,0x40,0x23,0x27,0x21,0xc0,0x18,0x50,0x43,0xc8,0x04,0x41,0x42,0x8d,0x5c,0x26,0x41,0x44,0xa2,0x6d,0x48,0x12,0x0e,0x40,0x32,0x25,0x0b,0x14,0x82,0x0a,0x48,0x2e,0xcb,0x82,0x88,0x03,0xa3,0x60,0x1b,0x25,0x26,0x8c,0xb8,0x20,0x24,0xb0,0x85,0x98,0x04,0x21,0x08,0xa7,0x2c,0x83,0x38,0x64,0x54,0x32,0x89,0x01,0x04,0x01,0x23,0x49,0x84,0x02,0x95,0x69,0xd1,0xa4,0x4d,0x13,0xa4,0x0c,0x91,0x46,0x0d,0x61,0x94,0x80,0x90,0x38,0x45,0x5c,0xc6,0x50,0x01,0x17,0x20,0x53,0xc6,0x28,0x9b,0x18,0x10,0x41,0x12,0x68,0x90,0x12,0x21,0xc0,0x10,0x84,0x42,0x16,0x92,0x53,0x82,0x29,0x81,0x26,0x49,0xd8,0xa4,0x05,0x9a,0x26,0x24,0x24,0x03,0x29,0xe0,0x40,0x26,0xd2,0x02,0x48,0x11,0x24,0x68,0x11,0x99,0x89,0x98,0x14,0x85,0xc9,0x20,0x0d,0x50,0x12,0x8c,0x1c,0x08,0x10,0x02,0x10,0x00,0x00,0x95,0x28,0xc1,0x28,0x90,0x59,0xb4,0x85,0xd3,0x14,0x65,0x0a,0x40,0x6e,0x11,0x29,0x65,0x18,0xc2,0x4c,0x21,0x34,0x65,0xd8,0x30,0x69,0x10,0x30,0x52,0x19,0x31,0x66,0x8c,0x18,0x8a,0xc8,0xc0,0x08,0x4c,0x98,0x30,0xa3,0xa6,0x20,0x41,0x16,0x22,0x18,0x05,0x2e,0x22,0x25,0x2a,0x64,0xb8,0x25,0x0a,0xb3,0x01,0x63,0x20,0x84,0x09,0x80,0x09,0x19,0x28,0x0e,0x02,0x11,0x01,0xa3,0x94,0x11,0xe3,0x98,0x6c,0x58,0x20,0x21,0x09,0x41,0x10,0x60,0x36,0x22,0x08,0x06,0x71,0x12,0xc2,0x85,0x5a,0xa0,0x85,0xc0,0xc6,0x84,0x5c,0x38,0x06,0xcb,0xb6,0x69,0x14,0x84,0x84,0x53,0x22,0x82,0xa1,0xa6,0x40,0xcc,0x86,0x00,0xc4,0x26,0x22,0xa0,0xa8,0x08,0x98,0x34,0x72,0xd4,0x20,0x41,0x43,0xc4,0x90,0x48,0x16,0x68,0x1b,0x16,0x52,0x1a,0x37,0x02,0x50,0x20,0x42,0x48,0x48,0x8a,0x20,0x11,0x41,0xe2,0x00,0x6c,0xc0,0xc2,0x0c,0x14,0x06,0x49,0x11,0x31,0x4d,0x19,0x06,0x0a,0x89,0x46,0x09,0x1b,0x81,0x65,0x44,0xc8,0x00,0x82,0x06,0x70,0x00,0x16,0x72,0xcc,0x24,0x50,0x8a,0x42,0x89,0x9c,0x96,0x90,0x64,0x28,0x70,0x92,0xb2,0x68,0x98,0x26,0x62,0x61,0x94,0x40,0xc1,0x16,0x89,0xd8,0x42,0x64,0x1a,0x21,0x4e,0x62,0x90,0x64,0x21,0xc8,0x24,0x8b,0x28,0x6d,0x5c,0x42,0x92,0xa0,0xc6,0x4d,0x0c,0x85,0x80,0xcc,0x88,0x4d,0xd4,0x42,0x8d,0x42,0x34,0x8a,0x0b,0x04,0x51,0xc3,0x26,0x86,0x24,0x25,0x81,0x12,0x35,0x06,0xa0,0x44,0x04,0xc8,0x94,0x81,0x5b,0xb4,0x31,0x1c,0x08,0x06,0x5c,0x24,0x08,0x03,0x27,0x6a,0x20,0xc2,0x25,0xe1,0x80,0x90,0x19,0xb4,0x6d,0xa3,0x46,0x0c,0x4b,0x18,0x60,0x50,0xc6,0x2c,0x1b,0x92,0x2d,0x11,0x15,0x04,0xa2,0x00,0x04,0x21,0x48,0x2e,0xd8,0x16,0x06,0xd2,0x10,0x8a,0x83,0xa2,0x25,0x08,0x31,0x0d,0x09,0x38,0x51,0xd9,0x48,0x49,0x0b,0x16,0x4c,0x23,0x32,0x25,0x19,0x19,0x02,0x4a,0x44,0x09,0xd1,0xb2,0x21,0x0b,0x83,0x2c,0x23,0x25,0x85,0x93,0x16,0x85,0x44,0xa0,0x44,0x1b,0x83,0x50,0x02,0x22,0x72,0x4b,0x04,0x80,0x9b,0x14,0x65,0x21,0x93,0x60,0x18,0x13,0x0a,0xd9,0x46,0x0d,0x22,0x45,0x61,0xc8,0xb4,0x40,0xa1,0x42,0x2d,0x02,0xb8,0x09,0x00,0x14,0x44,0x9b,0xb6,0x11,0x0b,0x97,0x8c,0x40,0x10,0x4a,0x82,0x14,0x6a,0xda,0x90,0x05,0x1c,0x02,0x8e,0x0c,0x19,0x72,0xa3,0xb4,0x8d,0x24,0x30,0x50,0x11,0x87,0x09,0x64,0xc6,0x28,0xe4,0x18,0x92,0x98,0xb4,0x6c,0x61,0x16,0x51,0x40,0x46,0x0e,0x1c,0x32,0x48,0xda,0x20,0x51,0x88,0x36,0x8a,0x23,0xb1,0x21,0x82,0x90,0x28,0x1a,0x15,0x32,0xe2,0x18,0x61,0x92,0x04,0x8e,0x13,0xb6,0x90,0x13,0x13,0x68,0xc9,0x84,0x68,0x4c,0x40,0x6d,0x0b,0x33,0x00,0x81,0x46,0x4d,0xd2,0x38,0x0c,0x04,0x96,0x81,0xa4,0x88,0x50,0x02,0x90,0x85,0x22,0xb0,0x04,0xd3,0xa4,0x71,0xd2,0x80,0x10,0xca,0x96,0x40,0x51,0xa6,0x41,0xa4,0x84,0x28,0xe0,0x08,0x52,0x0b,0x30,0x8c,0xd2,0x38,0x0a,0x0c,0x29,0x51,0xc3,0x82,0x09,0xca,0x20,0x91,0xd8,0x36,0x92,0xa3,0xa6,0x28,0x92,0x42,0x22,0xa2,0x16,0x01,0x1a,0x34,0x86,0x37,0xd9,0xa6,0x59,0x16,0x98,0x81,0xec,0x21,0xcf,0x48,0x11,0x86,0x9d,0x1d,0x7f,0x13,0x9f,0x05,0x37,0xe9,0x6f,0x11,0x84,0x58,0x54,0x05,0xfd,0x17,0x80,0x8a,0xf1,0xe0,0x62,0x39,0xd3,0xb3,0x4e,0x5a,0xca,0x8b,0xf1,0x36,0x96,0x77,0xb4,0x47,0xac,0x71,0x8a,0xc4,0x7d,0x85,0x0c,0x4d,0x77,0xb0,0xbe,0x31,0xdc,0x9f,0x50,0x8e,0x39,0x78,0xf2,0x42,0x74,0xab,0x01,0x85,0xf7,0x27,0xab,0xdf,0xf5,0x9f,0x44,0x90,0x37,0x1b,0xf0,0x46,0x10,0xe3,0x64,0xe6,0x4e,0xc8,0x75,0xef,0x9d,0x20,0xdc,0x94,0x07,0x7e,0x1e,0x16,0x63,0x27,0xa8,0x79,0xb8,0xab,0x51,0x61,0x60,0xb2,0xa3,0xf7,0x74,0x37,0xb9,0xb3,0xcc,0x7d,0x17,0xae,0xad,0xdc,0x84,0xdb,0x62,0x74,0x6a,0x35,0xac,0x09,0x6f,0x78,0x2f,0x62,0xa7,0xf0,0x1a,0xa6,0xd6,0x69,0x3d,0xee,0xc9,0x0b,0x23,0xc6,0x69,0x85,0xa0,0x23,0x07,0xe0,0xa1,0xca,0xe5,0x98,0xa6,0x73,0x24,0xdb,0xa0,0xf5,0x2f,0x22,0x43,0x22,0x75,0xe9,0x32,0x57,0x06,0x5c,0x3b,0x7e,0x5e,0x1c,0xfe,0x1d,0xfd,0x4d,0x0d,0xf0,0x86,0xdf,0x21,0x24,0x34,0x14,0xa2,0xd2,0x7e,0x20,0x23,0x0a,0x82,0x9b,0xe4,0xeb,0x4c,0x82,0xc1,0x6d,0x35,0xf7,0x8b,0x0e,0x5e,0x19,0x83,0x32,0xe0,0x00,0x74,0xbb,0x64,0x61,0x2f,0xab,0x17,0xd4,0xc8,0x97,0x1c,0xb6,0x8e,0x5e,0xda,0xb0,0x36,0x9f,0x11,0x57,0xb3,0x46,0x9a,0xbd,0x83,0x84,0xe2,0xd9,0x55,0x3f,0x1b,0x78,0xe7,0x86,0xe1,0xee,0x9d,0x0b,0x98,0xd3,0x9f,0x83,0xcc,0xec,0xf3,0x7d,0x1e,0xbd,0x3a,0x9d,0x63,0xae,0xc7,0x66,0x16,0x4a,0x10,0x17,0x1a,0x4f,0xd8,0xc6,0x3d,0xaf,0x18,0x2c,0x42,0x12,0x58,0xc5,0xf5,0x29,0xaa,0x55,0xcb,0x7e,0xba,0xe2,0xe1,0x65,0x23,0x15,0xe1,0xf7,0x1e,0x8a,0x74,0x13,0x14,0x10,0xd0,0x32,0x47,0xed,0xe1,0x1d,0x34,0xdb,0x91,0xf6,0xf0,0x8a,0xa2,0x47,0x8f,0xd7,0x89,0x67,0x9c,0x04,0x94,0x9f,0x71,0xbc,0x01,0x71,0xe0,0x7e,0x3a,0x8b,0xb5,0x75,0x3d,0xbb,0xda,0xa4,0x11,0xa6,0x35,0x0a,0xb4,0x6e,0xef,0xbf,0x86,0xfc,0x55,0x1c,0x29,0xef,0xe4,0xcd,0xd7,0x66,0x1d,0x5c,0xf6,0xc3,0xdb,0x22,0xd0,0xce,0xdd,0xe5,0x99,0x85,0x44,0x59,0xd9,0x7f,0x20,0xdf,0x74,0x55,0xbd,0xf3,0x56,0xa1,0x98,0xd0,0xf7,0xeb,0x6d,0x34,0x11,0x1f,0xc9,0x40,0xb2,0x5c,0x05,0x43,0xb7,0x88,0xed,0xda,0x9d,0x26,0x81,0x0e,0xac,0x3d,0x6c,0xc9,0xc5,0x13,0x27,0xc2,0xcf,0x83,0xe8,0x87,0xd4,0x08,0x9e,0x19,0x69,0x5e,0x11,0xad,0xd8,0x37,0xf6,0xf4,0x40,0xcc,0x36,0x0f,0x93,0xf3,0x2f,0xee,0x8a,0x96,0x63,0x71,0x2c,0x6b,0xbd,0x38,0xc8,0x4a,0xb7,0xb5,0x48,0x23,0xec,0x36,0x3e,0xb7,0xe4,0x2e,0xb5,0x9f,0xc1,0xfc,0xe6,0x0f,0xbd,0x55,0x30,0x7b,0x3e,0xc8,0x5f,0xd9,0xda,0xf3,0x20,0x6d,0x7b,0x4b,0x39,0x17,0xf1,0xc8,0xb7,0xa9,0x2e,0x3c,0x67,0xd8,0x98,0x80,0xfd,0xf2,0xe4,0x7f,0x5a,0x0c,0x99,0x45,0x95,0xdb,0x17,0x0a,0xf4,0x1b,0xab,0xf5,0xa2,0x5b,0x4d,0xc1,0xc4,0x2d,0xd6,0xa9,0xdb,0x27,0x1e,0x76,0x4d,0xe2,0xfb,0x01,0x5a,0x49,0xa8,0x50,0xc7,0x91,0x9b,0xe4,0x70,0x06,0xa3,0x36,0xe2,0xe3,0x25,0xfd,0xe5,0x3a,0xc5,0x99,0x55,0x4d,0x0a,0x7d,0xe4,0xef,0x45,0xec,0x40,0xc3,0x9d,0x6b,0xaf,0xf3,0x11,0xbe,0xee,0x75,0xd8,0x9e,0x02,0xad,0x31,0xf4,0xbe,0x4b,0xd2,0x0a,0xe9,0x19,0x4f,0x5e,0xdd,0xda,0xa6,0x65,0x07,0x76,0x11,0x6e,0x9f,0x27,0x0f,0x77,0x71,0x4a,0xd7,0xa8,0xe8,0x9a,0xce,0xf7,0x4b,0x7f,0xf7,0xd8,0xdb,0xec,0x27,0xf8,0x02,0x0a,0x98,0x52,0x47,0xe2,0xcd,0xac,0xef,0x48,0x94,0xa4,0xd6,0x8b,0xa3,0x7c,0xa9,0x12,0xd6,0xbe,0x73,0x50,0x1c,0x99,0x51,0x81,0xe5,0xb7,0x77,0x23,0x35,0x0b,0x36,0x31,0xda,0x37,0x00,0xe1,0x3f,0xd3,0x66,0xe1,0x31,0xbf,0x06,0xb3,0x6e,0xb6,0xb0,0x34,0x50,0x93,0x20,0x9f,0x0a,0x7b,0xef,0xfa,0xe1,0xfd,0xd8,0x75,0xb0,0x06,0x87,0xc1,0x16,0x3c,0x35,0x3d,0x7d,0x2a,0xc9,0x09,0x37,0xb3,0x4e,0x97,0x8e,0x92,0xf8,0x21,0xad,0xc9,0x66,0x22,0x02,0xec,0xe8,0x9a,0x17,0xe7,0xbb,0x65,0xae,0x17,0xd8,0x3b,0x90,0xdb,0xbe,0x6a,0x50,0x1a,0x4e,0x13,0x45,0xbe,0xe4,0xe5,0xa5,0xb5,0x3a,0xf2,0xe5,0xba,0x3d,0x1e,0xf3,0xf4,0xe0,0x5a,0xdf,0x0b,0x3a,0x4c,0xf2,0xe5,0x30,0x36,0x0f,0xee,0x64,0x92,0x99,0x02,0xb5,0x71,0xf6,0xfd,0x2e,0x30,0x56,0x52,0xa4,0xcb,0x01,0x0f,0x79,0xf8,0x15,0xe1,0x8f,0x2b,0xbb,0x8c,0xc8,0x9f,0xa6,0xfc,0x76,0xf7,0x7c,0x89,0xe2,0x93,0xcf,0x17,0x5a,0x0b,0x19,0x58,0x00,0xfe,0x72,0xd2,0xcc,0xdd,0x7d,0x75,0xe5,0xbd,0x90,0xbc,0x6a,0xc4,0x35,0xd6,0xa4,0x40,0xef,0x85,0x2e,0x9a,0x1c,0x8c,0x53,0xde,0x03,0xbf,0x19,0x33,0x65,0xd7,0x35,0xaa,0xf2,0x9c,0x51,0x62,0xa6,0x17,0xe3,0x64,0xe7,0xf9,0x44,0x16,0x8d,0x0f,0xb4,0x8f,0xef,0x40,0x55,0x8f,0x45,0x42,0x97,0xcc,0x3d,0xd5,0x08,0x66,0x2c,0xf2,0x3f,0xb8,0x8e,0x19,0x54,0xaa,0x45,0xd1,0xc5,0xe1,0x15,0xbc,0xc3,0x6f,0x05,0xb3,0xe0,0x98,0xd5,0x55,0x22,0x0f,0x40,0xbe,0x26,0x29,0xb3,0x45,0x07,0xb8,0x46,0x4c,0x54,0xc2,0x7b,0x5d,0xec,0x78,0xda,0x8f,0x22,0x65,0x05,0x14,0x79,0x7a,0xf8,0x6a,0x25,0x12,0xbc,0xb7,0xe2,0x92,0x33,0x79,0xef,0x6d,0x73,0xc1,0x37,0x00,0x6c,0x1b,0x38,0xf5,0x1e,0x37,0xf9,0x35,0x85,0xe2,0x90,0x41,0xa3,0xe4,0xe3,0xaf,0x46,0x00,0x7c,0xe1,0x3b,0x8b,0x5f,0x7b,0x17,0xd5,0xd6,0x5d,0x7d,0x56,0x68,0xe4,0x27,0xbc,0xbe,0x7e,0xc1,0xd7,0xc4,0x08,0xc0,0x54,0xa4,0x8c,0x1a,0xe7,0x97,0xbf,0x99,0xac,0xbc,0x8d,0x26,0x07,0x52,0x29,0x35,0xfd,0x66,0x5e,0xa7,0x82,0x2d,0x93,0x0f,0x23,0xea,0xbf,0xf7,0x83,0xbb,0x23,0x69,0x75,0x69,0xe2,0x04,0xb9,0x43,0x14,0x1e,0x00,0xc0,0x88,0x10,0x95,0x6b,0xe0,0x52,0x53,0x65,0xdb,0xab,0x54,0xed,0x48,0xcb,0x76,0x96,0x4c,0xcd,0xf5,0xcb,0xd3,0xae,0xe7,0x28,0x2d,0x4a,0x00,0x00,0xd2,0x78,0x4d,0x7b,0x8f,0xab,0x16,0xb2,0xf7,0xf0,0xd5,0x22,0x57,0x32,0xb1,0xef,0xbc,0x4e,0xb1,0xcf,0xed,0xeb,0x43,0xfd,0xe7,0x9b,0x69,0xec,0xc0,0xfb,0xea,0xa1,0xe6,0xb4,0x07,0x28,0x67,0x3b,0xd4,0xb2,0xe9,0x8a,0x0d,0x4a,0x8f,0x02,0xf8,0x53,0x95,0x07,0x30,0xf2,0x8d,0x35,0xeb,0x12,0xfc,0xc7,0x97,0x68,0xb8,0xe1,0x8e,0x4b,0xda,0x0e,0x58,0xa3,0x31,0xa2,0xf7,0x1d,0x7c,0xcc,0x2d,0x45,0x1b,0x32,0xb1,0xc6,0x5c,0x31,0x2a,0xcf,0x47,0xee,0x51,0x3b,0x21,0x95,0x4c,0x41,0xc0,0x0c,0x87,0x38,0x72,0xee,0x94,0xcf,0x14,0xf4,0x60,0x37,0x42,0x53,0x61,0xf4,0xbd,0xb5,0x48,0x21,0xf7,0x11,0x46,0x0c,0xeb,0xae,0x8c,0x07,0x50,0x8a,0x92,0x19,0xf8,0x8f,0xa6,0xbe,0xda,0xa6,0x78,0xee,0xd5,0x01,0x94,0x4a,0x16,0xae,0x6f,0x7b,0x5b,0xb7,0xa2,0xe1,0xe3,0x57,0xe7,0x0d,0x7b,0x98,0x46,0x1a,0x2c,0x71,0xcb,0x0f,0xa7,0x62,0xd6,0xad,0x98,0x24,0x08,0x1d,0x37,0xf2,0x92,0xfd,0x4b,0xe8,0xb8,0x4c,0x36,0x11,0x0d,0xc7,0x44,0x36,0x02,0x01,0xbe,0xeb,0xe0,0xbd,0x6c,0x9d,0x05,0xe8,0x69,0x25,0x6d,0x2f,0xf3,0xf9,0x95,0x17,0xb7,0xef,0xd2,0xa3,0x37,0x74,0x05,0x6c,0xb5,0x67,0x16,0x75,0xa8,0xb4,0x92,0xe9,0xf5,0xf2,0x62,0x0e,0xb8,0xef,0x93,0x81,0xd3,0xd1,0xdf,0x19,0x93,0x8b,0x7b,0x5f,0xfa,0xac,0x59,0xbc,0x81,0x10,0xfa,0x87,0xba,0x8d,0x7a,0x3d,0x01,0x65,0xf8,0xe4,0x1d,0xd0,0xf8,0x04,0xf1,0x1b,0x9d,0xed,0x0f,0x35,0x2a,0x59,0x78,0x35,0xd0,0x63,0x07,0xa8,0xe0,0xc6,0xef,0x4d,0x21,0x90,0x43,0x39,0xe1,0xcf,0x45,0x89,0x23,0xa3,0xe8,0x9e,0x02,0x5d,0x94,0x53,0x47,0x36,0x6c,0x02,0xf3,0xdd,0x63,0x68,0xd4,0xe4,0x7e,0x85,0xd3,0xd2,0xa9,0x70,0x5b,0xd5,0x79,0x61,0x85,0x2e,0x5a,0x57,0x9f,0x93,0xb1,0xc5,0x14,0xc5,0x39,0xf4,0x9e,0xa1,0x16,0x3a,0x2a,0x49,0x3b,0x0e,0xfc,0xb4,0x7f,0x47,0x48,0xf6,0xa9,0x9e,0x10,0xbf,0x70,0x78,0x28,0x2e,0x4a,0xce,0x18,0x13,0x6e,0x2a,0x8b,0x3e,0xe0,0xa3,0x80,0xdc,0xd3,0xb3,0xef,0x3e,0x65,0xe1,0xb8,0x15,0x72,0x89,0xd6,0x24,0x67,0xad,0x48,0x8b,0xa0,0x39,0x2b,0x2e,0x90,0xa1,0xed,0xed,0xcb,0xdc,0x93,0x1d,0xc1,0x72,0x98,0xcc,0xef,0x76,0x64,0x5c,0x7d,0x33,0x0a,0x05,0xc2,0xce,0x40,0xf8,0x9b,0x85,0x46,0x8f,0x35,0x7a,0x21,0x77,0x51,0xe1,0x54,0x63,0x13,0x04,0xec,0x4e,0x04,0xbb,0x45,0xb3,0x67,0x89,0x09,0xc7,0x4a,0xf5,0x1c,0xe3,0x70,0x36,0x4d,0x8f,0x4f,0x7e,0xb1,0xe6,0x1e,0x00,0x28,0x74,0x29,0xc9,0x96,0x1d,0xe8,0x32,0x2c,0xa9,0xa2,0x62,0x9b,0x13,0x09,0xd8,0x00,0xe9,0x2b,0xc1,0xdc,0x50,0x55,0xdc,0xc7,0x97,0xf3,0x38,0x66,0xeb,0x0c,0xfd,0x8d,0x49,0x02,0x50,0xd4,0x8f,0xfc,0xa8,0x02,0x2f,0x49,0x29,0x0e,0x2d,0x53,0x76,0x16,0x2f,0xba,0xa9,0x82,0xd1,0x64,0x53,0xc8,0x25,0xb3,0x5f,0x65,0x15,0x63,0x5e,0xa9,0x2b,0xea,0x72,0x36,0x7b,0xaa,0x54,0xde,0x3f,0x9e,0xae,0xa6,0x95,0x42,0xa8,0x1a,0x41,0x27,0xf7,0x1c,0xba,0xa2,0x57,0xf3,0x24,0xfe,0xfe,0xf1,0x4f,0x08,0xfb,0xd6,0x5a,0x04,0x9c,0xd2,0xfb,0x36,0x25,0x94,0xa8,0xe2,0x3f,0xf1,0xa2,0x61,0x7d,0xb5,0xb1,0x58,0xf6,0xf0,0x1c,0xf5,0x0a,0xb0,0xed,0x95,0xc6,0xe7,0x09,0x84,0x11,0x64,0x10,0x8b,0x06,0xe1,0xb4,0x0a,0xb0,0xab,0x11,0xc4,0x08,0x30,0x1d,0x3d,0x9d,0x8e,0xa6,0x9e,0x96,0x8a,0x96,0x00,0xb3,0xd1,0x7f,0x38,0x01,0x1c,0xe2,0x80,0x74,0xe2,0xc2,0xe1,0x0b,0xf6,0x19,0x7c,0x60,0x2d,0x8d,0x0c,0xe7,0xd3,0xa3,0xef,0x2d,0x89,0x62,0x3b,0xc9,0xf1,0x2e,0xa3,0x38,0x79,0x1e,0x92,0x66,0xbb,0x8c,0xe0,0x2b,0x12,0x4c,0x6c,0x79,0x29,0xba,0xea,0x69,0x32,0x44,0x09,0x84,0x54,0xa0,0x80,0xeb,0x75,0x23,0xe1,0x3b,0xb1,0xb7,0xc5,0xb6,0x77,0x5f,0xab,0xab,0xab,0xbe,0x90,0x75,0xfe,0x56,0x87,0xaa,0x45,0x13,0x97,0xbb,0x9c,0xfc,0xcd,0x05,0x12,0x43,0xe9,0xbf,0x5a,0xef,0x24,0x06,0x2d,0x33,0x5d,0xe5,0xfc,0xe2,0x4e,0x9d,0xdb,0xde,0x11,0x91,0x05,0x2d,0x80,0xc3,0x6d,0xf9,0xf8,0x43,0x48,0x72,0xf2,0x77,0xed,0x4f,0x5a,0x1c,0xe8,0xeb,0xd3,0xb9,0x60,0x82,0x4a,0x4e,0x4f,0x10,0x01,0xb0,0x4c,0xb6,0x85,0xf9,0xbe,0xe4,0xd0,0xdd,0xb0,0xc5,0x71,0x59,0x8a,0xc2,0x02,0x1a,0x66,0x06,0xfd,0x23,0x34,0x5c,0x6f,0xbb,0x84,0xf0,0xce,0x05,0xfe,0x52,0x73,0x45,0x21,0xb7,0xb0,0x7c,0x63,0x88,0xd3,0xa3,0xb9,0x93,0x18,0xbf,0x01,0x31,0x50,0x4a,0xa9,0xdf,0xba,0xf5,0x48,0xf9,0xd3,0x2a,0x9c,0xd4,0xc6,0x89,0x35,0x24,0xb1,0x13,0x30,0xa2,0xd3,0xaa,0xd3,0xed,0x2a,0x58,0x96,0x6e,0xbb,0x01,0x34,0x46,0x5d,0x54,0x3f,0xd7,0x79,0x7a,0xf5,0x49,0xf5,0x68,0xea,0xeb,0xe9,0x57,0xf6,0x4f,0xec,0x85,0x46,0x74,0x90,0x2b,0x97,0x55,0x87,0x56,0x98,0x69,0x46,0xea,0x3a,0xb7,0xa2,0x51,0xcb,0xbe,0xa1,0x1a,0x68,0x7b,0xd4,0x3f,0x5d,0x0b,0xd8,0x9c,0xd2,0xca,0xba,0x61,0xd5,0x21,0x83,0x74,0x99,0x0e,0xe8,0xb9,0x22,0x19,0xed,0x25,0xdc,0xa0,0x11,0xc6,0x8a,0x97,0x57,0xc0,0x13,0xbd,0x83,0x7b,0x2d,0xd7,0x34,0xe3,0x75,0x1f,0x64,0xfc,0xb4,0xb2,0x3d,0xcd,0x6b,0xc5,0x7e,0xa5,0x67,0xf5,0x71,0x6e,0x17,0x36,0x72,0x44,0x75,0x1e,0x23,0x03,0xb2,0x2a,0x95,0x3e,0x77,0x27,0x56,0x95,0x6c,0xdc,0xc0,0x13,0xff,0xd2,0xc3,0x24,0x90,0x75,0x44,0x22,0xa5,0x72,0x52,0x9d,0x4c,0x92,0xf1,0xeb,0xb1,0x9f,0x1d,0xad,0x4d,0x03,0x6f,0x2f,0xdf,0x31,0xca,0x91,0x01,0xbd,0xf8,0x1a,0xea,0x94,0x8a,0xed,0xcf,0x21,0x7a,0xa8,0xfc,0xcd,0x7a,0x07,0x71,0xaa,0x27,0x53,0xe1,0xa8,0x23,0xbf,0x41,0xc9,0x53,0x77,0xa2,0xff,0xa6,0x1b,0x22,0x65,0x13,0x81,0x53,0xce,0x86,0xd2,0xc8,0x7d,0xd0,0x7a,0x4b,0x32,0xd2,0x7f,0x5f,0x28,0x72,0x64,0x14,0x31,0xce,0x9a,0x18,0xa5,0x02,0xaa,0xef,0xd9,0xaf,0xc5,0xb0,0xd1,0x3c,0xd4,0x6c,0x35,0x7e,0x38,0xe6,0x9e,0x1e,0xe9,0x45,0xad,0xd1,0x99,0x29,0x32,0xa5,0xb1,0xe5,0xc5,0x62,0x9c,0x9f,0x48,0xf7,0x66,0x18,0x53,0xda,0x00,0x78,0x7c,0x9d,0x78,0xfb,0x92,0x55,0x53,0xbf,0x07,0xa5,0x0d,0xd5,0xb9,0xd9,0x35,0x85,0x34,0x20,0xe4,0xd1,0xa7,0x1a,0xe6,0x2f,0xf9,0x0c,0xa1,0x93,0xcd,0xd6,0xc2,0xf4,0xbe,0xd2,0x63,0x41,0x5a,0xaf,0x9a,0x35,0x09,0x4b,0xc2,0xa2,0x2e,0x2a,0x66,0x3c,0x76,0x45,0x00,0x1c,0xd1,0x90,0xb7,0xbc,0x17,0xc7,0x5f,0xea,0xdf,0x8e,0x87,0xce,0x5c,0x24,0xb7,0x63,0xb6,0x58,0x4e,0xd3,0x2e,0x71,0xb0,0x26,0x81,0x42,0xea,0x3e,0xd6,0x89,0x81,0x57,0xbf,0x92,0x3b,0xeb,0xf0,0x19,0x2d,0x1b,0xf5,0xee,0x30,0xa7,0xd3,0x51,0x63,0x4a,0x60,0xb5,0x04,0xdd,0xe3,0x8a,0x2e,0x11,0x4f,0x7a,0xe9,0xbf,0x17,0x6d,0x4a,0x18,0xba,0x28,0x95,0xa7,0xbb,0x4b,0x47,0x44,0x4a,0x9b,0xa8,0xdb,0xb4,0xc1,0x24,0xcd,0x41,0xbb,0xb3,0x2f,0x4b,0xcb,0x1d,0xe4,0x8c,0x4a,0xbb,0x51,0x06,0x07,0xa0,0x01,0xb5,0xa0,0x00,0xbb,0xa4,0x36,0x18,0xb6,0xc1,0x9e,0x43,0x51,0x7b,0x45,0xb4,0x24,0x05,0x92,0x8b,0x67,0xc7,0x13,0x88,0x18,0x58,0xba,0xd3,0xa4,0x25,0x11,0xc2,0x71,0x6f,0xf9,0xcd,0x33,0x20,0x34,0xb6,0x72,0xb5,0x2f,0xf1,0x66,0x10,0x80,0x5c,0xdb,0xe7,0x54,0x4a,0x8a,0x84,0xb6,0x6e,0x1c,0x74,0x5a,0x73,0xc1,0xb6,0xbc,0xda,0x5b,0x77,0xb9,0x51,0xf3,0x6c,0x0f,0x7a,0x53,0x72,0xde,0x9e,0x5d,0x1f,0x9b,0xbc,0xde,0x88,0x43,0xc6,0x90,0x90,0x02,0xdd,0xa4,0x87,0x5e,0x67,0x57,0x1a,0xf0,0xbe,0xc5,0x81,0x85,0x6c,0x32,0xc0,0x9c,0x24,0x0e,0x66,0x4e,0x76,0x1e,0x57,0xcd,0x0d,0x8d,0xc8,0xa7,0x1c,0xb9,0x18,0xa5,0x76,0x2d,0x11,0x12,0x85,0xcd,0x8b,0x56,0x13,0xdd,0xbd,0x0c,0xa0,0x8a,0xc0,0x34,0x2b,0x2b,0xde,0xe3,0x8f,0x96,0xfa,0x75,0x4b,0xb2,0xb0,0x87,0x17,0x9c,0x11,0x3c,0x93,0x98,0x6a,0x81,0x03,0x56,0xeb,0x94,0x54,0x0b,0x93,0xcb,0x9d,0xec,0x4a,0xa9,0x29,0x0f,0xf1,0x2e,0xc1,0xaa,0x2e,0x65,0x6c,0x9b,0xe3,0xd5,0x90,0x75,0x3c,0x36,0x6c,0x60,0x14,0x06,0xc0,0x61,0xbc,0x22,0x03,0x3a,0x1f,0xd1,0xf4,0xe1,0x11,0x1d,0x03,0x9b,0x88,0x13,0xb9,0x83,0xcb,0x50,0x6c,0x3e,0xa7,0xff,0x30,0x57,0x98,0x3e,0x8b,0xf0,0x16,0x82,0xfb,0xb0,0x0f,0x43,0x00,0x53,0x13,0xc8,0x2c,0x13,0x92,0x91,0x8a,0x61,0x65,0xa1,0x33,0x38,0xff,0xe1,0x1a,0x99,0x2c,0x1f,0xb3,0xd1,0x03,0x2a,0xa6,0x79,0xa4,0x18,0xc8,0xba,0x4f,0x8a,0x0b,0xc1,0x99,0xe1,0x0c,0xf6,0xbd,0x77,0xa1,0x4f,0xdd,0x6a,0x06,0x09,0x35,0x14,0x34,0x8e,0x3a,0x89,0x74,0x43,0x4a,0xe8,0xa3,0x67,0x63,0x69,0xc6,0xbe,0x2c,0xf9,0x0e,0x67,0x2b,0x34,0x3f,0xce,0x04,0xac,0x6b,0x22,0xe0,0xcf,0x47,0x56,0x8b,0xc4,0x5d,0x70,0xa6,0x8e,0x68,0xc6,0x49,0xa4,0x83,0x0a,0xe2,0x18,0x59,0x0c,0x1a,0x43,0x7e,0x7a,0x23,0xa5,0x4e,0xfe,0x44,0xf6,0x70,0x86,0xeb,0x69,0x7b,0x9f,0xa5,0x78,0x35,0xf0,0xb8,0xf7,0x0f,0x0a,0x92,0x92,0x26,0xef,0xb3,0x36,0xc0,0xe2,0x18,0x33,0xa0,0x28,0x21,0x8c,0xd6,0x37,0x32,0xc8,0x0a,0xa4,0x77,0xe6,0x2d,0x14,0x1d,0xba,0x81,0x85,0x4f,0x70,0xda,0x68,0xda,0xff,0x4a,0x84,0xcb,0x6d,0xe7,0x79,0x25,0x4e,0x8a,0x97,0xe7,0x35,0x65,0x37,0x4a,0xf4,0x09,0x2a,0xf0,0x5c,0xbd,0x66,0x54,0xaf,0xc3,0xfd,0x72,0xf0,0xae,0x23,0x26,0x95,0xcb,0x66,0x68,0xea,0xfe,0xcc,0x40,0x69,0xbd,0x90,0xbb,0x52,0x8b,0x83,0xef,0xa2,0xfb,0xcd,0xbd,0x93,0xb2,0x89,0x92,0x96,0x21,0xed,0x74,0xd8,0x08,0x73,0x8f,0xc1,0x03,0xee,0xb1,0x05,0x51,0x08,0x51,0xfc,0x93,0x19,0xf1,0x71,0xea,0x0c,0xed,0x0b,0x97,0xb5,0xb9,0xfb,0x5e,0xf9,0x85,0x18,0x6b,0xc5,0x20,0x98,0xf9,0xeb,0x47,0x6f,0x67,0xb7,0xcc,0x76,0x65,0xd4,0x75,0x87,0x97,0x5c,0xb4,0x5a,0x50,0xfc,0x64,0x10,0x07,0x19,0xbf,0x76,0x34,0x5f,0x0f,0xdf,0x1e,0x09,0xef,0xe9,0xfb,0x80,0x0d,0xc1,0x14,0xe4,0x6b,0xe0,0x87,0x9a,0x19,0x5c,0xc0,0x68,0x70,0xe2,0x3d,0x26,0x31,0xda,0xe7,0x1c,0x39,0x94,0x48,0x1c,0x87,0x61,0xc4,0x0d,0x07,0xc5,0xbf,0xca,0x95,0xe7,0x18,0xb7,0xb2,0x25,0x85,0xaf,0x03,0xed,0x34,0x17,0x5a,0x46,0xd5,0x7a,0xf3,0x51,0x8e,0x32,0xa7,0xfc,0x1a,0xa4,0x48,0x27,0x32,0xa8,0x1a,0x87,0xf7,0x24,0xf8,0xd2,0xe7,0x80,0xb3,0xa3,0x9d,0x45,0x1a,0x38,0x0f,0x75,0xc2,0xd6,0x80,0xcc,0x72,0x13,0xea,0xb1,0xd4,0xa5,0x9d,0x39,0x4a,0xe3,0x81,0x0a,0x1c,0x90,0x81,0x8d,0x52,0xf9,0x3f,0xb2,0x03,0xe2,0xd8,0xb1,0xb5,0xfa,0x8f,0x60,0xb2,0xd5,0x85,0xd9,0x13,0x5d,0x64,0x88,0x46,0xf1,0x38,0xb8,0x69,0x53,0x24,0x2d,0x2b,0xb1,0xf2,0xec,0xdf,0x38,0x9b,0x4d,0xe7,0x65,0x18,0x17,0xb8,0xe4,0xe6,0x4b,0x33,0x3f,0x1a,0xac,0x52,0x3a,0x93,0xf2,0x74,0x8a,0x9c,0x38,0xff,0xbc,0x29,0xce,0xd4,0x57,0xb6,0xf9,0x78,0x1b,0x08,0xa6,0x7a,0x19,0x75,0xd0,0x31,0xcc,0xd7,0x15,0x45,0xc0,0x03,0x74,0x34,0x05,0x6c,0x24,0x34,0xd1,0x3e,0x6c,0x4b,0xee,0xbf,0x46,0xfc,0x12,0x22,0x2c,0x0b,0x2e,0xcc,0xd6,0x15,0x9d,0x5a,0xea,0x8e,0x55,0x4d,0x7a,0x09,0x65,0x2b,0x06,0xbf,0x7c,0xa6,0x99,0xa7,0x19,0x9e,0x71,0x6d,0x05,0xdd,0x55,0x30,0x41,0xa8,0xf2,0xb3,0x03,0xd2,0x36,0xa9,0xba,0xba,0xaf,0xb9,0xfa,0x52,0x8f,0x28,0xa2,0xca,0x2a,0xa7,0x80,0xb9,0x40,0x38,0x3c,0x09,0x9a,0xa6,0x5a,0x00,0x74,0xb8,0x3f,0xd1,0xf0,0xbc,0x5b,0x7b,0x5e,0x46,0xc2,0x5e,0x54,0x83,0x8b,0x3c,0xbc,0xfc,0x95,0xf8,0x7f,0x1d,0x47,0x1b,0x3b,0xa8,0x94,0x43,0x4f,0xa5,0x89,0x52,0xfd,0xcb,0x77,0xf1,0x61,0x37,0x26,0x93,0x30,0x6d,0xba,0x4e,0x8f,0x21,0x6d,0x1c,0x8e,0x5c,0xaf,0xf0,0xfe,0x83,0x60,0xa5,0x1c,0x60,0x76,0x36,0x44,0x16,0x9f,0xdc,0x6a,0x82,0x67,0xf2,0xe3,0xf9,0x09,0xa6,0x1b,0x2a,0x67,0x8b,0xce,0x6a,0xe9,0x04,0x03,0xa8,0x36,0xb1,0xa7,0xb7,0xe8,0xcd,0x8b,0x54,0xc3,0x70,0x87,0xa9,0xe1,0x44,0x46,0xd9,0x5e,0x69,0x08,0xd2,0xee,0xdb,0xfc,0xc6,0x53,0xe0,0x2f,0xdf,0x77,0x1f,0x70,0x1a,0x79,0xb9,0xe5,0xa2,0x6e,0xd0,0xa9,0x47,0x84,0x20,0x70,0xf3,0xb5,0x70,0x17,0x42,0x21,0x12,0x19,0xe7,0x61,0x76,0x2c,0x37,0xf0,0xd0,0xa1,0xd1,0xb9,0x75,0x0f,0xee,0x57,0x7e,0x12,0x08,0x11,0x5c,0x66,0xac,0x07,0xec,0x09,0x1e,0x6a,0x3f,0xc4,0xaa,0x6a,0x25,0x3b,0xcb,0xa8,0x68,0xed,0xd3,0x15,0x4d,0xca,0xf5,0x16,0x2f,0x61,0x5e,0x85,0x49,0x0a,0x6c,0xa3,0x42,0xf3,0x4c,0x43,0xac,0x61,0xa3,0xea,0x6b,0xfe,0xef,0xd8,0x50,0xe1,0x90,0xeb,0x1d,0x8d,0xa4,0xd2,0x8b,0x5e,0xce,0xeb,0x16,0x78,0xc0,0x24,0x33,0xec,0xd5,0xd4,0x8b,0x25,0x36,0x40,0x42,0x57,0xe8,0xca,0x7b,0xef,0x58,0x55,0xf2,0xb8,0x13,0xed,0x2f,0x4c,0x40,0x94,0x45,0xa3,0x31,0x7c,0x9b,0xe1,0xa3,0x5a,0xe2,0xfb,0x4d,0x2b,0x87,0x92,0x1b,0x90,0x4b,0xf2,0xc1,0x4d,0xb5,0x14,0xce,0xe0,0x45,0x25,0x1c,0xfc,0x27,0x63,0x74,0xdb,0x15,0xc9,0x9d,0xea,0x15,0xac,0xde,0x19,0x7c,0x6e,0xb5,0x24,0x98,0x8e,0x39,0xb6,0x32,0x87,0xbe,0xb8,0x67,0x68,0x65,0xaa,0xa3,0xba,0xd1,0xb4,0x3b,0x8c,0xab,0x15,0xcb,0xf2,0x7a,0x49,0x87,0x59,0xe3,0x20,0x3a,0xbf,0x36,0x9e,0x97,0x24,0x2f,0x0b,0x01,0x54,0x14,0x9f,0x14,0xac,0x23,0x3c,0xdb,0x73,0xa2,0x2b,0x7f,0xb8,0xf0,0x93,0x25,0xbf,0x2a,0xce,0x83,0xbb,0x6b,0x5d,0xb8,0xa1,0x21,0xa2,0xb6,0x82,0x14,0x9a,0x69,0x13,0x1c,0xcc,0xe5,0x22,0x29,0x84,0x0b,0x11,0x3f,0xc7,0xb0,0xbc,0xc5,0x84,0x05,0xbf,0xe8,0x7f,0x1f,0x95,0xff,0xc2,0xe9,0x6f,0xc5,0x59,0x65,0x67,0xe9,0x43,0x64,0xdf,0xaa,0x6d,0x9d,0x5a,0x6e,0xb9,0x9a,0xe4,0xdd,0xf4,0x24]).unwrap(); + let sk = MLDSA87PrivateKey::from_bytes(&[ + 0x97, 0x92, 0xBC, 0xEC, 0x2F, 0x24, 0x30, 0x68, 0x6A, 0x82, 0xFC, 0xCF, 0x3C, 0x2F, 0x5F, + 0xF6, 0x65, 0xE7, 0x71, 0xD7, 0xAB, 0x41, 0xB9, 0x02, 0x58, 0xCF, 0xA7, 0xE9, 0x0E, 0xC9, + 0x71, 0x24, 0xD8, 0xE9, 0xEE, 0x4E, 0x90, 0xA1, 0x6C, 0x60, 0x2F, 0x5E, 0xC9, 0xBC, 0x38, + 0x51, 0x7D, 0xC3, 0x0E, 0x32, 0x9D, 0x5A, 0xB2, 0x76, 0x73, 0xBD, 0x85, 0xF4, 0xC9, 0xB0, + 0x30, 0x0F, 0x77, 0x63, 0x89, 0x88, 0x67, 0x50, 0xB5, 0x7C, 0x24, 0xDB, 0x3F, 0xC0, 0x12, + 0xE6, 0x1E, 0xDE, 0x59, 0x75, 0x33, 0x37, 0x37, 0x4F, 0xA7, 0x12, 0x49, 0x91, 0x54, 0x9A, + 0xF2, 0x43, 0x49, 0x6D, 0x06, 0x37, 0xCB, 0x3B, 0xE0, 0x5A, 0x59, 0x48, 0x23, 0x5B, 0xF7, + 0x98, 0x75, 0xF8, 0x96, 0xD8, 0xFE, 0x0C, 0xAB, 0x30, 0xC8, 0x49, 0x48, 0xDB, 0x4D, 0x63, + 0x15, 0xAA, 0xAF, 0x16, 0x0A, 0xC6, 0x24, 0x36, 0x64, 0x22, 0x01, 0x48, 0x16, 0x11, 0x09, + 0x11, 0x2C, 0x94, 0x02, 0x89, 0x22, 0x45, 0x2C, 0x62, 0xB8, 0x45, 0x00, 0x45, 0x2A, 0x08, + 0x96, 0x70, 0x90, 0x12, 0x6E, 0x14, 0x93, 0x70, 0xD4, 0x46, 0x10, 0x84, 0x44, 0x51, 0x58, + 0x96, 0x91, 0x0C, 0xA9, 0x29, 0x82, 0xB2, 0x41, 0xC9, 0x08, 0x71, 0xC4, 0x28, 0x68, 0x04, + 0x96, 0x89, 0x48, 0x40, 0x85, 0x9B, 0x22, 0x6D, 0x1C, 0x28, 0x64, 0x59, 0x12, 0x41, 0x9C, + 0xB8, 0x91, 0x84, 0x04, 0x89, 0x44, 0x90, 0x05, 0xCB, 0x34, 0x62, 0xA0, 0x86, 0x90, 0x40, + 0x26, 0x92, 0x20, 0x99, 0x29, 0x13, 0x05, 0x69, 0x5C, 0x34, 0x68, 0xA4, 0x32, 0x8E, 0x19, + 0x26, 0x92, 0x59, 0x46, 0x10, 0x09, 0xA4, 0x49, 0x23, 0x42, 0x4D, 0x12, 0x36, 0x61, 0x58, + 0x10, 0x65, 0x01, 0x28, 0x90, 0x1A, 0x33, 0x4C, 0x99, 0x86, 0x31, 0xD3, 0xA2, 0x49, 0x09, + 0x82, 0x25, 0x43, 0x14, 0x28, 0xC0, 0x38, 0x81, 0x03, 0x15, 0x4D, 0x5B, 0x28, 0x86, 0x08, + 0x87, 0x48, 0x23, 0x31, 0x52, 0x94, 0x22, 0x25, 0xC3, 0xC0, 0x4D, 0xA4, 0x98, 0x21, 0x98, + 0x40, 0x20, 0xD1, 0x42, 0x86, 0xCB, 0x40, 0x70, 0x5B, 0xB0, 0x71, 0x9C, 0x96, 0x2C, 0xC1, + 0x12, 0x06, 0x53, 0x46, 0x09, 0x0C, 0x45, 0x02, 0x14, 0x46, 0x6E, 0x91, 0xB4, 0x21, 0x54, + 0xB0, 0x8C, 0xE4, 0x46, 0x42, 0x9A, 0x20, 0x8C, 0x01, 0x21, 0x25, 0x13, 0x41, 0x05, 0x5A, + 0x40, 0x22, 0x13, 0xC9, 0x0C, 0xA0, 0x18, 0x40, 0x52, 0xC2, 0x30, 0xCB, 0x34, 0x2C, 0x4B, + 0xC8, 0x68, 0x1B, 0xA4, 0x60, 0x49, 0x84, 0x84, 0x63, 0x30, 0x29, 0x4A, 0xA0, 0x69, 0x5B, + 0x80, 0x04, 0xD2, 0x38, 0x0A, 0x14, 0x26, 0x4C, 0xE2, 0xB2, 0x44, 0x8B, 0xA2, 0x11, 0x24, + 0x46, 0x49, 0xC4, 0x14, 0x52, 0x0B, 0x42, 0x71, 0x03, 0xB2, 0x10, 0x92, 0x28, 0x80, 0x01, + 0x24, 0x88, 0xE3, 0x08, 0x11, 0x0A, 0x05, 0x28, 0x19, 0xC4, 0x81, 0x00, 0x20, 0x22, 0xDC, + 0x44, 0x68, 0x42, 0x12, 0x22, 0x44, 0x00, 0x2A, 0xC9, 0x26, 0x6A, 0x0C, 0x87, 0x31, 0xE0, + 0xC0, 0x44, 0x99, 0x14, 0x84, 0x18, 0x36, 0x0D, 0x11, 0x37, 0x42, 0x22, 0x18, 0x8C, 0x63, + 0xB2, 0x91, 0x0C, 0x98, 0x08, 0xA1, 0xA0, 0x01, 0x08, 0x92, 0x44, 0x04, 0x13, 0x24, 0x5C, + 0x98, 0x71, 0x82, 0x84, 0x71, 0x84, 0x32, 0x52, 0x51, 0xB0, 0x31, 0x9C, 0x42, 0x2E, 0x1A, + 0xA8, 0x28, 0x02, 0x08, 0x91, 0x01, 0xC0, 0x89, 0x0B, 0xC7, 0x05, 0x8A, 0x24, 0x65, 0x22, + 0xC2, 0x64, 0x4C, 0x88, 0x91, 0x5C, 0x82, 0x68, 0x13, 0xA5, 0x64, 0x50, 0x20, 0x86, 0x21, + 0x04, 0x02, 0x91, 0x94, 0x44, 0x48, 0x22, 0x30, 0x22, 0x39, 0x4A, 0x02, 0x98, 0x84, 0x09, + 0xA2, 0x88, 0x19, 0x19, 0x29, 0x44, 0x48, 0x8C, 0x22, 0x95, 0x0C, 0xA1, 0x04, 0x72, 0x04, + 0x87, 0x70, 0x12, 0x21, 0x10, 0x42, 0x06, 0x84, 0x1B, 0x49, 0x85, 0x89, 0x06, 0x4A, 0xD3, + 0x36, 0x08, 0xDB, 0xC0, 0x40, 0x58, 0xB6, 0x51, 0x0C, 0xA7, 0x09, 0x8C, 0x24, 0x61, 0x99, + 0x90, 0x64, 0x8C, 0xC2, 0x90, 0x5B, 0x24, 0x90, 0x10, 0xA3, 0x49, 0x03, 0x25, 0x61, 0x43, + 0x32, 0x8A, 0x11, 0x98, 0x44, 0xC8, 0xB2, 0x20, 0x04, 0x38, 0x41, 0x10, 0x47, 0x2C, 0x19, + 0xC6, 0x44, 0x1C, 0x25, 0x2C, 0x04, 0x88, 0x30, 0xD9, 0x46, 0x69, 0x9B, 0x20, 0x00, 0x1B, + 0x46, 0x82, 0x5A, 0xA4, 0x80, 0x5B, 0xA0, 0x49, 0x18, 0x90, 0x25, 0x00, 0x26, 0x80, 0x0B, + 0xC2, 0x31, 0x5A, 0x40, 0x72, 0x54, 0xC6, 0x20, 0xC1, 0xB0, 0x31, 0x24, 0xB1, 0x4D, 0x10, + 0x95, 0x28, 0x14, 0x00, 0x0A, 0xA0, 0xC8, 0x4D, 0x54, 0xA2, 0x88, 0x23, 0x98, 0x81, 0x60, + 0x90, 0x04, 0x02, 0x16, 0x2C, 0x13, 0x21, 0x40, 0x91, 0x86, 0x8D, 0x08, 0xC2, 0x91, 0x91, + 0x14, 0x26, 0xD0, 0xB4, 0x0C, 0x09, 0xC6, 0x60, 0x51, 0x44, 0x2E, 0x04, 0x11, 0x26, 0x00, + 0x29, 0x11, 0x93, 0xC2, 0x08, 0x63, 0xA4, 0x31, 0x22, 0x00, 0x28, 0xC1, 0x14, 0x08, 0x0C, + 0x40, 0x2C, 0x41, 0x14, 0x06, 0x9C, 0x20, 0x72, 0x54, 0x22, 0x06, 0x8B, 0x08, 0x4D, 0xA1, + 0x48, 0x69, 0x10, 0x30, 0x41, 0x1C, 0x28, 0x49, 0x44, 0x18, 0x8E, 0xCC, 0x96, 0x48, 0xD9, + 0x42, 0x50, 0x1B, 0x06, 0x49, 0x0C, 0x45, 0x88, 0x23, 0x04, 0x0D, 0x20, 0x30, 0x2E, 0x23, + 0x85, 0x2C, 0x14, 0x07, 0x30, 0x40, 0xB6, 0x85, 0x4C, 0xC0, 0x20, 0x44, 0x86, 0x20, 0x19, + 0x00, 0x6C, 0x54, 0x02, 0x48, 0xCC, 0x88, 0x6C, 0x59, 0x06, 0x32, 0x49, 0x14, 0x84, 0x04, + 0xC7, 0x50, 0x13, 0x49, 0x28, 0xE4, 0x06, 0x09, 0xD3, 0xC6, 0x10, 0xC8, 0x28, 0x4C, 0x23, + 0x39, 0x44, 0x52, 0xA4, 0x64, 0xCC, 0xA8, 0x49, 0x44, 0x38, 0x32, 0x0A, 0x89, 0x84, 0x00, + 0x34, 0x2C, 0x22, 0x85, 0x8D, 0x10, 0x31, 0x09, 0x09, 0x32, 0x65, 0x1C, 0x89, 0x8C, 0x40, + 0x40, 0x29, 0x21, 0x85, 0x00, 0x09, 0xA1, 0x6D, 0x84, 0xC0, 0x64, 0xE2, 0x02, 0x2D, 0x48, + 0x04, 0x40, 0x12, 0x09, 0x8E, 0xE0, 0x42, 0x2E, 0x93, 0x44, 0x08, 0x12, 0x10, 0x6A, 0x01, + 0x84, 0x05, 0x92, 0x30, 0x8A, 0xCB, 0x34, 0x8E, 0xA2, 0x26, 0x2E, 0x5C, 0x86, 0x11, 0x0B, + 0x35, 0x08, 0x18, 0x10, 0x00, 0x02, 0x34, 0x26, 0x24, 0x23, 0x89, 0xD1, 0x84, 0x00, 0x24, + 0x46, 0x60, 0x13, 0xB2, 0x49, 0x24, 0x28, 0x46, 0x18, 0x02, 0x71, 0xA0, 0x38, 0x90, 0x89, + 0x14, 0x44, 0xD3, 0x96, 0x2D, 0xA3, 0x18, 0x40, 0x23, 0x27, 0x21, 0xC0, 0x18, 0x50, 0x43, + 0xC8, 0x04, 0x41, 0x42, 0x8D, 0x5C, 0x26, 0x41, 0x44, 0xA2, 0x6D, 0x48, 0x12, 0x0E, 0x40, + 0x32, 0x25, 0x0B, 0x14, 0x82, 0x0A, 0x48, 0x2E, 0xCB, 0x82, 0x88, 0x03, 0xA3, 0x60, 0x1B, + 0x25, 0x26, 0x8C, 0xB8, 0x20, 0x24, 0xB0, 0x85, 0x98, 0x04, 0x21, 0x08, 0xA7, 0x2C, 0x83, + 0x38, 0x64, 0x54, 0x32, 0x89, 0x01, 0x04, 0x01, 0x23, 0x49, 0x84, 0x02, 0x95, 0x69, 0xD1, + 0xA4, 0x4D, 0x13, 0xA4, 0x0C, 0x91, 0x46, 0x0D, 0x61, 0x94, 0x80, 0x90, 0x38, 0x45, 0x5C, + 0xC6, 0x50, 0x01, 0x17, 0x20, 0x53, 0xC6, 0x28, 0x9B, 0x18, 0x10, 0x41, 0x12, 0x68, 0x90, + 0x12, 0x21, 0xC0, 0x10, 0x84, 0x42, 0x16, 0x92, 0x53, 0x82, 0x29, 0x81, 0x26, 0x49, 0xD8, + 0xA4, 0x05, 0x9A, 0x26, 0x24, 0x24, 0x03, 0x29, 0xE0, 0x40, 0x26, 0xD2, 0x02, 0x48, 0x11, + 0x24, 0x68, 0x11, 0x99, 0x89, 0x98, 0x14, 0x85, 0xC9, 0x20, 0x0D, 0x50, 0x12, 0x8C, 0x1C, + 0x08, 0x10, 0x02, 0x10, 0x00, 0x00, 0x95, 0x28, 0xC1, 0x28, 0x90, 0x59, 0xB4, 0x85, 0xD3, + 0x14, 0x65, 0x0A, 0x40, 0x6E, 0x11, 0x29, 0x65, 0x18, 0xC2, 0x4C, 0x21, 0x34, 0x65, 0xD8, + 0x30, 0x69, 0x10, 0x30, 0x52, 0x19, 0x31, 0x66, 0x8C, 0x18, 0x8A, 0xC8, 0xC0, 0x08, 0x4C, + 0x98, 0x30, 0xA3, 0xA6, 0x20, 0x41, 0x16, 0x22, 0x18, 0x05, 0x2E, 0x22, 0x25, 0x2A, 0x64, + 0xB8, 0x25, 0x0A, 0xB3, 0x01, 0x63, 0x20, 0x84, 0x09, 0x80, 0x09, 0x19, 0x28, 0x0E, 0x02, + 0x11, 0x01, 0xA3, 0x94, 0x11, 0xE3, 0x98, 0x6C, 0x58, 0x20, 0x21, 0x09, 0x41, 0x10, 0x60, + 0x36, 0x22, 0x08, 0x06, 0x71, 0x12, 0xC2, 0x85, 0x5A, 0xA0, 0x85, 0xC0, 0xC6, 0x84, 0x5C, + 0x38, 0x06, 0xCB, 0xB6, 0x69, 0x14, 0x84, 0x84, 0x53, 0x22, 0x82, 0xA1, 0xA6, 0x40, 0xCC, + 0x86, 0x00, 0xC4, 0x26, 0x22, 0xA0, 0xA8, 0x08, 0x98, 0x34, 0x72, 0xD4, 0x20, 0x41, 0x43, + 0xC4, 0x90, 0x48, 0x16, 0x68, 0x1B, 0x16, 0x52, 0x1A, 0x37, 0x02, 0x50, 0x20, 0x42, 0x48, + 0x48, 0x8A, 0x20, 0x11, 0x41, 0xE2, 0x00, 0x6C, 0xC0, 0xC2, 0x0C, 0x14, 0x06, 0x49, 0x11, + 0x31, 0x4D, 0x19, 0x06, 0x0A, 0x89, 0x46, 0x09, 0x1B, 0x81, 0x65, 0x44, 0xC8, 0x00, 0x82, + 0x06, 0x70, 0x00, 0x16, 0x72, 0xCC, 0x24, 0x50, 0x8A, 0x42, 0x89, 0x9C, 0x96, 0x90, 0x64, + 0x28, 0x70, 0x92, 0xB2, 0x68, 0x98, 0x26, 0x62, 0x61, 0x94, 0x40, 0xC1, 0x16, 0x89, 0xD8, + 0x42, 0x64, 0x1A, 0x21, 0x4E, 0x62, 0x90, 0x64, 0x21, 0xC8, 0x24, 0x8B, 0x28, 0x6D, 0x5C, + 0x42, 0x92, 0xA0, 0xC6, 0x4D, 0x0C, 0x85, 0x80, 0xCC, 0x88, 0x4D, 0xD4, 0x42, 0x8D, 0x42, + 0x34, 0x8A, 0x0B, 0x04, 0x51, 0xC3, 0x26, 0x86, 0x24, 0x25, 0x81, 0x12, 0x35, 0x06, 0xA0, + 0x44, 0x04, 0xC8, 0x94, 0x81, 0x5B, 0xB4, 0x31, 0x1C, 0x08, 0x06, 0x5C, 0x24, 0x08, 0x03, + 0x27, 0x6A, 0x20, 0xC2, 0x25, 0xE1, 0x80, 0x90, 0x19, 0xB4, 0x6D, 0xA3, 0x46, 0x0C, 0x4B, + 0x18, 0x60, 0x50, 0xC6, 0x2C, 0x1B, 0x92, 0x2D, 0x11, 0x15, 0x04, 0xA2, 0x00, 0x04, 0x21, + 0x48, 0x2E, 0xD8, 0x16, 0x06, 0xD2, 0x10, 0x8A, 0x83, 0xA2, 0x25, 0x08, 0x31, 0x0D, 0x09, + 0x38, 0x51, 0xD9, 0x48, 0x49, 0x0B, 0x16, 0x4C, 0x23, 0x32, 0x25, 0x19, 0x19, 0x02, 0x4A, + 0x44, 0x09, 0xD1, 0xB2, 0x21, 0x0B, 0x83, 0x2C, 0x23, 0x25, 0x85, 0x93, 0x16, 0x85, 0x44, + 0xA0, 0x44, 0x1B, 0x83, 0x50, 0x02, 0x22, 0x72, 0x4B, 0x04, 0x80, 0x9B, 0x14, 0x65, 0x21, + 0x93, 0x60, 0x18, 0x13, 0x0A, 0xD9, 0x46, 0x0D, 0x22, 0x45, 0x61, 0xC8, 0xB4, 0x40, 0xA1, + 0x42, 0x2D, 0x02, 0xB8, 0x09, 0x00, 0x14, 0x44, 0x9B, 0xB6, 0x11, 0x0B, 0x97, 0x8C, 0x40, + 0x10, 0x4A, 0x82, 0x14, 0x6A, 0xDA, 0x90, 0x05, 0x1C, 0x02, 0x8E, 0x0C, 0x19, 0x72, 0xA3, + 0xB4, 0x8D, 0x24, 0x30, 0x50, 0x11, 0x87, 0x09, 0x64, 0xC6, 0x28, 0xE4, 0x18, 0x92, 0x98, + 0xB4, 0x6C, 0x61, 0x16, 0x51, 0x40, 0x46, 0x0E, 0x1C, 0x32, 0x48, 0xDA, 0x20, 0x51, 0x88, + 0x36, 0x8A, 0x23, 0xB1, 0x21, 0x82, 0x90, 0x28, 0x1A, 0x15, 0x32, 0xE2, 0x18, 0x61, 0x92, + 0x04, 0x8E, 0x13, 0xB6, 0x90, 0x13, 0x13, 0x68, 0xC9, 0x84, 0x68, 0x4C, 0x40, 0x6D, 0x0B, + 0x33, 0x00, 0x81, 0x46, 0x4D, 0xD2, 0x38, 0x0C, 0x04, 0x96, 0x81, 0xA4, 0x88, 0x50, 0x02, + 0x90, 0x85, 0x22, 0xB0, 0x04, 0xD3, 0xA4, 0x71, 0xD2, 0x80, 0x10, 0xCA, 0x96, 0x40, 0x51, + 0xA6, 0x41, 0xA4, 0x84, 0x28, 0xE0, 0x08, 0x52, 0x0B, 0x30, 0x8C, 0xD2, 0x38, 0x0A, 0x0C, + 0x29, 0x51, 0xC3, 0x82, 0x09, 0xCA, 0x20, 0x91, 0xD8, 0x36, 0x92, 0xA3, 0xA6, 0x28, 0x92, + 0x42, 0x22, 0xA2, 0x16, 0x01, 0x1A, 0x34, 0x86, 0x37, 0xD9, 0xA6, 0x59, 0x16, 0x98, 0x81, + 0xEC, 0x21, 0xCF, 0x48, 0x11, 0x86, 0x9D, 0x1D, 0x7F, 0x13, 0x9F, 0x05, 0x37, 0xE9, 0x6F, + 0x11, 0x84, 0x58, 0x54, 0x05, 0xFD, 0x17, 0x80, 0x8A, 0xF1, 0xE0, 0x62, 0x39, 0xD3, 0xB3, + 0x4E, 0x5A, 0xCA, 0x8B, 0xF1, 0x36, 0x96, 0x77, 0xB4, 0x47, 0xAC, 0x71, 0x8A, 0xC4, 0x7D, + 0x85, 0x0C, 0x4D, 0x77, 0xB0, 0xBE, 0x31, 0xDC, 0x9F, 0x50, 0x8E, 0x39, 0x78, 0xF2, 0x42, + 0x74, 0xAB, 0x01, 0x85, 0xF7, 0x27, 0xAB, 0xDF, 0xF5, 0x9F, 0x44, 0x90, 0x37, 0x1B, 0xF0, + 0x46, 0x10, 0xE3, 0x64, 0xE6, 0x4E, 0xC8, 0x75, 0xEF, 0x9D, 0x20, 0xDC, 0x94, 0x07, 0x7E, + 0x1E, 0x16, 0x63, 0x27, 0xA8, 0x79, 0xB8, 0xAB, 0x51, 0x61, 0x60, 0xB2, 0xA3, 0xF7, 0x74, + 0x37, 0xB9, 0xB3, 0xCC, 0x7D, 0x17, 0xAE, 0xAD, 0xDC, 0x84, 0xDB, 0x62, 0x74, 0x6A, 0x35, + 0xAC, 0x09, 0x6F, 0x78, 0x2F, 0x62, 0xA7, 0xF0, 0x1A, 0xA6, 0xD6, 0x69, 0x3D, 0xEE, 0xC9, + 0x0B, 0x23, 0xC6, 0x69, 0x85, 0xA0, 0x23, 0x07, 0xE0, 0xA1, 0xCA, 0xE5, 0x98, 0xA6, 0x73, + 0x24, 0xDB, 0xA0, 0xF5, 0x2F, 0x22, 0x43, 0x22, 0x75, 0xE9, 0x32, 0x57, 0x06, 0x5C, 0x3B, + 0x7E, 0x5E, 0x1C, 0xFE, 0x1D, 0xFD, 0x4D, 0x0D, 0xF0, 0x86, 0xDF, 0x21, 0x24, 0x34, 0x14, + 0xA2, 0xD2, 0x7E, 0x20, 0x23, 0x0A, 0x82, 0x9B, 0xE4, 0xEB, 0x4C, 0x82, 0xC1, 0x6D, 0x35, + 0xF7, 0x8B, 0x0E, 0x5E, 0x19, 0x83, 0x32, 0xE0, 0x00, 0x74, 0xBB, 0x64, 0x61, 0x2F, 0xAB, + 0x17, 0xD4, 0xC8, 0x97, 0x1C, 0xB6, 0x8E, 0x5E, 0xDA, 0xB0, 0x36, 0x9F, 0x11, 0x57, 0xB3, + 0x46, 0x9A, 0xBD, 0x83, 0x84, 0xE2, 0xD9, 0x55, 0x3F, 0x1B, 0x78, 0xE7, 0x86, 0xE1, 0xEE, + 0x9D, 0x0B, 0x98, 0xD3, 0x9F, 0x83, 0xCC, 0xEC, 0xF3, 0x7D, 0x1E, 0xBD, 0x3A, 0x9D, 0x63, + 0xAE, 0xC7, 0x66, 0x16, 0x4A, 0x10, 0x17, 0x1A, 0x4F, 0xD8, 0xC6, 0x3D, 0xAF, 0x18, 0x2C, + 0x42, 0x12, 0x58, 0xC5, 0xF5, 0x29, 0xAA, 0x55, 0xCB, 0x7E, 0xBA, 0xE2, 0xE1, 0x65, 0x23, + 0x15, 0xE1, 0xF7, 0x1E, 0x8A, 0x74, 0x13, 0x14, 0x10, 0xD0, 0x32, 0x47, 0xED, 0xE1, 0x1D, + 0x34, 0xDB, 0x91, 0xF6, 0xF0, 0x8A, 0xA2, 0x47, 0x8F, 0xD7, 0x89, 0x67, 0x9C, 0x04, 0x94, + 0x9F, 0x71, 0xBC, 0x01, 0x71, 0xE0, 0x7E, 0x3A, 0x8B, 0xB5, 0x75, 0x3D, 0xBB, 0xDA, 0xA4, + 0x11, 0xA6, 0x35, 0x0A, 0xB4, 0x6E, 0xEF, 0xBF, 0x86, 0xFC, 0x55, 0x1C, 0x29, 0xEF, 0xE4, + 0xCD, 0xD7, 0x66, 0x1D, 0x5C, 0xF6, 0xC3, 0xDB, 0x22, 0xD0, 0xCE, 0xDD, 0xE5, 0x99, 0x85, + 0x44, 0x59, 0xD9, 0x7F, 0x20, 0xDF, 0x74, 0x55, 0xBD, 0xF3, 0x56, 0xA1, 0x98, 0xD0, 0xF7, + 0xEB, 0x6D, 0x34, 0x11, 0x1F, 0xC9, 0x40, 0xB2, 0x5C, 0x05, 0x43, 0xB7, 0x88, 0xED, 0xDA, + 0x9D, 0x26, 0x81, 0x0E, 0xAC, 0x3D, 0x6C, 0xC9, 0xC5, 0x13, 0x27, 0xC2, 0xCF, 0x83, 0xE8, + 0x87, 0xD4, 0x08, 0x9E, 0x19, 0x69, 0x5E, 0x11, 0xAD, 0xD8, 0x37, 0xF6, 0xF4, 0x40, 0xCC, + 0x36, 0x0F, 0x93, 0xF3, 0x2F, 0xEE, 0x8A, 0x96, 0x63, 0x71, 0x2C, 0x6B, 0xBD, 0x38, 0xC8, + 0x4A, 0xB7, 0xB5, 0x48, 0x23, 0xEC, 0x36, 0x3E, 0xB7, 0xE4, 0x2E, 0xB5, 0x9F, 0xC1, 0xFC, + 0xE6, 0x0F, 0xBD, 0x55, 0x30, 0x7B, 0x3E, 0xC8, 0x5F, 0xD9, 0xDA, 0xF3, 0x20, 0x6D, 0x7B, + 0x4B, 0x39, 0x17, 0xF1, 0xC8, 0xB7, 0xA9, 0x2E, 0x3C, 0x67, 0xD8, 0x98, 0x80, 0xFD, 0xF2, + 0xE4, 0x7F, 0x5A, 0x0C, 0x99, 0x45, 0x95, 0xDB, 0x17, 0x0A, 0xF4, 0x1B, 0xAB, 0xF5, 0xA2, + 0x5B, 0x4D, 0xC1, 0xC4, 0x2D, 0xD6, 0xA9, 0xDB, 0x27, 0x1E, 0x76, 0x4D, 0xE2, 0xFB, 0x01, + 0x5A, 0x49, 0xA8, 0x50, 0xC7, 0x91, 0x9B, 0xE4, 0x70, 0x06, 0xA3, 0x36, 0xE2, 0xE3, 0x25, + 0xFD, 0xE5, 0x3A, 0xC5, 0x99, 0x55, 0x4D, 0x0A, 0x7D, 0xE4, 0xEF, 0x45, 0xEC, 0x40, 0xC3, + 0x9D, 0x6B, 0xAF, 0xF3, 0x11, 0xBE, 0xEE, 0x75, 0xD8, 0x9E, 0x02, 0xAD, 0x31, 0xF4, 0xBE, + 0x4B, 0xD2, 0x0A, 0xE9, 0x19, 0x4F, 0x5E, 0xDD, 0xDA, 0xA6, 0x65, 0x07, 0x76, 0x11, 0x6E, + 0x9F, 0x27, 0x0F, 0x77, 0x71, 0x4A, 0xD7, 0xA8, 0xE8, 0x9A, 0xCE, 0xF7, 0x4B, 0x7F, 0xF7, + 0xD8, 0xDB, 0xEC, 0x27, 0xF8, 0x02, 0x0A, 0x98, 0x52, 0x47, 0xE2, 0xCD, 0xAC, 0xEF, 0x48, + 0x94, 0xA4, 0xD6, 0x8B, 0xA3, 0x7C, 0xA9, 0x12, 0xD6, 0xBE, 0x73, 0x50, 0x1C, 0x99, 0x51, + 0x81, 0xE5, 0xB7, 0x77, 0x23, 0x35, 0x0B, 0x36, 0x31, 0xDA, 0x37, 0x00, 0xE1, 0x3F, 0xD3, + 0x66, 0xE1, 0x31, 0xBF, 0x06, 0xB3, 0x6E, 0xB6, 0xB0, 0x34, 0x50, 0x93, 0x20, 0x9F, 0x0A, + 0x7B, 0xEF, 0xFA, 0xE1, 0xFD, 0xD8, 0x75, 0xB0, 0x06, 0x87, 0xC1, 0x16, 0x3C, 0x35, 0x3D, + 0x7D, 0x2A, 0xC9, 0x09, 0x37, 0xB3, 0x4E, 0x97, 0x8E, 0x92, 0xF8, 0x21, 0xAD, 0xC9, 0x66, + 0x22, 0x02, 0xEC, 0xE8, 0x9A, 0x17, 0xE7, 0xBB, 0x65, 0xAE, 0x17, 0xD8, 0x3B, 0x90, 0xDB, + 0xBE, 0x6A, 0x50, 0x1A, 0x4E, 0x13, 0x45, 0xBE, 0xE4, 0xE5, 0xA5, 0xB5, 0x3A, 0xF2, 0xE5, + 0xBA, 0x3D, 0x1E, 0xF3, 0xF4, 0xE0, 0x5A, 0xDF, 0x0B, 0x3A, 0x4C, 0xF2, 0xE5, 0x30, 0x36, + 0x0F, 0xEE, 0x64, 0x92, 0x99, 0x02, 0xB5, 0x71, 0xF6, 0xFD, 0x2E, 0x30, 0x56, 0x52, 0xA4, + 0xCB, 0x01, 0x0F, 0x79, 0xF8, 0x15, 0xE1, 0x8F, 0x2B, 0xBB, 0x8C, 0xC8, 0x9F, 0xA6, 0xFC, + 0x76, 0xF7, 0x7C, 0x89, 0xE2, 0x93, 0xCF, 0x17, 0x5A, 0x0B, 0x19, 0x58, 0x00, 0xFE, 0x72, + 0xD2, 0xCC, 0xDD, 0x7D, 0x75, 0xE5, 0xBD, 0x90, 0xBC, 0x6A, 0xC4, 0x35, 0xD6, 0xA4, 0x40, + 0xEF, 0x85, 0x2E, 0x9A, 0x1C, 0x8C, 0x53, 0xDE, 0x03, 0xBF, 0x19, 0x33, 0x65, 0xD7, 0x35, + 0xAA, 0xF2, 0x9C, 0x51, 0x62, 0xA6, 0x17, 0xE3, 0x64, 0xE7, 0xF9, 0x44, 0x16, 0x8D, 0x0F, + 0xB4, 0x8F, 0xEF, 0x40, 0x55, 0x8F, 0x45, 0x42, 0x97, 0xCC, 0x3D, 0xD5, 0x08, 0x66, 0x2C, + 0xF2, 0x3F, 0xB8, 0x8E, 0x19, 0x54, 0xAA, 0x45, 0xD1, 0xC5, 0xE1, 0x15, 0xBC, 0xC3, 0x6F, + 0x05, 0xB3, 0xE0, 0x98, 0xD5, 0x55, 0x22, 0x0F, 0x40, 0xBE, 0x26, 0x29, 0xB3, 0x45, 0x07, + 0xB8, 0x46, 0x4C, 0x54, 0xC2, 0x7B, 0x5D, 0xEC, 0x78, 0xDA, 0x8F, 0x22, 0x65, 0x05, 0x14, + 0x79, 0x7A, 0xF8, 0x6A, 0x25, 0x12, 0xBC, 0xB7, 0xE2, 0x92, 0x33, 0x79, 0xEF, 0x6D, 0x73, + 0xC1, 0x37, 0x00, 0x6C, 0x1B, 0x38, 0xF5, 0x1E, 0x37, 0xF9, 0x35, 0x85, 0xE2, 0x90, 0x41, + 0xA3, 0xE4, 0xE3, 0xAF, 0x46, 0x00, 0x7C, 0xE1, 0x3B, 0x8B, 0x5F, 0x7B, 0x17, 0xD5, 0xD6, + 0x5D, 0x7D, 0x56, 0x68, 0xE4, 0x27, 0xBC, 0xBE, 0x7E, 0xC1, 0xD7, 0xC4, 0x08, 0xC0, 0x54, + 0xA4, 0x8C, 0x1A, 0xE7, 0x97, 0xBF, 0x99, 0xAC, 0xBC, 0x8D, 0x26, 0x07, 0x52, 0x29, 0x35, + 0xFD, 0x66, 0x5E, 0xA7, 0x82, 0x2D, 0x93, 0x0F, 0x23, 0xEA, 0xBF, 0xF7, 0x83, 0xBB, 0x23, + 0x69, 0x75, 0x69, 0xE2, 0x04, 0xB9, 0x43, 0x14, 0x1E, 0x00, 0xC0, 0x88, 0x10, 0x95, 0x6B, + 0xE0, 0x52, 0x53, 0x65, 0xDB, 0xAB, 0x54, 0xED, 0x48, 0xCB, 0x76, 0x96, 0x4C, 0xCD, 0xF5, + 0xCB, 0xD3, 0xAE, 0xE7, 0x28, 0x2D, 0x4A, 0x00, 0x00, 0xD2, 0x78, 0x4D, 0x7B, 0x8F, 0xAB, + 0x16, 0xB2, 0xF7, 0xF0, 0xD5, 0x22, 0x57, 0x32, 0xB1, 0xEF, 0xBC, 0x4E, 0xB1, 0xCF, 0xED, + 0xEB, 0x43, 0xFD, 0xE7, 0x9B, 0x69, 0xEC, 0xC0, 0xFB, 0xEA, 0xA1, 0xE6, 0xB4, 0x07, 0x28, + 0x67, 0x3B, 0xD4, 0xB2, 0xE9, 0x8A, 0x0D, 0x4A, 0x8F, 0x02, 0xF8, 0x53, 0x95, 0x07, 0x30, + 0xF2, 0x8D, 0x35, 0xEB, 0x12, 0xFC, 0xC7, 0x97, 0x68, 0xB8, 0xE1, 0x8E, 0x4B, 0xDA, 0x0E, + 0x58, 0xA3, 0x31, 0xA2, 0xF7, 0x1D, 0x7C, 0xCC, 0x2D, 0x45, 0x1B, 0x32, 0xB1, 0xC6, 0x5C, + 0x31, 0x2A, 0xCF, 0x47, 0xEE, 0x51, 0x3B, 0x21, 0x95, 0x4C, 0x41, 0xC0, 0x0C, 0x87, 0x38, + 0x72, 0xEE, 0x94, 0xCF, 0x14, 0xF4, 0x60, 0x37, 0x42, 0x53, 0x61, 0xF4, 0xBD, 0xB5, 0x48, + 0x21, 0xF7, 0x11, 0x46, 0x0C, 0xEB, 0xAE, 0x8C, 0x07, 0x50, 0x8A, 0x92, 0x19, 0xF8, 0x8F, + 0xA6, 0xBE, 0xDA, 0xA6, 0x78, 0xEE, 0xD5, 0x01, 0x94, 0x4A, 0x16, 0xAE, 0x6F, 0x7B, 0x5B, + 0xB7, 0xA2, 0xE1, 0xE3, 0x57, 0xE7, 0x0D, 0x7B, 0x98, 0x46, 0x1A, 0x2C, 0x71, 0xCB, 0x0F, + 0xA7, 0x62, 0xD6, 0xAD, 0x98, 0x24, 0x08, 0x1D, 0x37, 0xF2, 0x92, 0xFD, 0x4B, 0xE8, 0xB8, + 0x4C, 0x36, 0x11, 0x0D, 0xC7, 0x44, 0x36, 0x02, 0x01, 0xBE, 0xEB, 0xE0, 0xBD, 0x6C, 0x9D, + 0x05, 0xE8, 0x69, 0x25, 0x6D, 0x2F, 0xF3, 0xF9, 0x95, 0x17, 0xB7, 0xEF, 0xD2, 0xA3, 0x37, + 0x74, 0x05, 0x6C, 0xB5, 0x67, 0x16, 0x75, 0xA8, 0xB4, 0x92, 0xE9, 0xF5, 0xF2, 0x62, 0x0E, + 0xB8, 0xEF, 0x93, 0x81, 0xD3, 0xD1, 0xDF, 0x19, 0x93, 0x8B, 0x7B, 0x5F, 0xFA, 0xAC, 0x59, + 0xBC, 0x81, 0x10, 0xFA, 0x87, 0xBA, 0x8D, 0x7A, 0x3D, 0x01, 0x65, 0xF8, 0xE4, 0x1D, 0xD0, + 0xF8, 0x04, 0xF1, 0x1B, 0x9D, 0xED, 0x0F, 0x35, 0x2A, 0x59, 0x78, 0x35, 0xD0, 0x63, 0x07, + 0xA8, 0xE0, 0xC6, 0xEF, 0x4D, 0x21, 0x90, 0x43, 0x39, 0xE1, 0xCF, 0x45, 0x89, 0x23, 0xA3, + 0xE8, 0x9E, 0x02, 0x5D, 0x94, 0x53, 0x47, 0x36, 0x6C, 0x02, 0xF3, 0xDD, 0x63, 0x68, 0xD4, + 0xE4, 0x7E, 0x85, 0xD3, 0xD2, 0xA9, 0x70, 0x5B, 0xD5, 0x79, 0x61, 0x85, 0x2E, 0x5A, 0x57, + 0x9F, 0x93, 0xB1, 0xC5, 0x14, 0xC5, 0x39, 0xF4, 0x9E, 0xA1, 0x16, 0x3A, 0x2A, 0x49, 0x3B, + 0x0E, 0xFC, 0xB4, 0x7F, 0x47, 0x48, 0xF6, 0xA9, 0x9E, 0x10, 0xBF, 0x70, 0x78, 0x28, 0x2E, + 0x4A, 0xCE, 0x18, 0x13, 0x6E, 0x2A, 0x8B, 0x3E, 0xE0, 0xA3, 0x80, 0xDC, 0xD3, 0xB3, 0xEF, + 0x3E, 0x65, 0xE1, 0xB8, 0x15, 0x72, 0x89, 0xD6, 0x24, 0x67, 0xAD, 0x48, 0x8B, 0xA0, 0x39, + 0x2B, 0x2E, 0x90, 0xA1, 0xED, 0xED, 0xCB, 0xDC, 0x93, 0x1D, 0xC1, 0x72, 0x98, 0xCC, 0xEF, + 0x76, 0x64, 0x5C, 0x7D, 0x33, 0x0A, 0x05, 0xC2, 0xCE, 0x40, 0xF8, 0x9B, 0x85, 0x46, 0x8F, + 0x35, 0x7A, 0x21, 0x77, 0x51, 0xE1, 0x54, 0x63, 0x13, 0x04, 0xEC, 0x4E, 0x04, 0xBB, 0x45, + 0xB3, 0x67, 0x89, 0x09, 0xC7, 0x4A, 0xF5, 0x1C, 0xE3, 0x70, 0x36, 0x4D, 0x8F, 0x4F, 0x7E, + 0xB1, 0xE6, 0x1E, 0x00, 0x28, 0x74, 0x29, 0xC9, 0x96, 0x1D, 0xE8, 0x32, 0x2C, 0xA9, 0xA2, + 0x62, 0x9B, 0x13, 0x09, 0xD8, 0x00, 0xE9, 0x2B, 0xC1, 0xDC, 0x50, 0x55, 0xDC, 0xC7, 0x97, + 0xF3, 0x38, 0x66, 0xEB, 0x0C, 0xFD, 0x8D, 0x49, 0x02, 0x50, 0xD4, 0x8F, 0xFC, 0xA8, 0x02, + 0x2F, 0x49, 0x29, 0x0E, 0x2D, 0x53, 0x76, 0x16, 0x2F, 0xBA, 0xA9, 0x82, 0xD1, 0x64, 0x53, + 0xC8, 0x25, 0xB3, 0x5F, 0x65, 0x15, 0x63, 0x5E, 0xA9, 0x2B, 0xEA, 0x72, 0x36, 0x7B, 0xAA, + 0x54, 0xDE, 0x3F, 0x9E, 0xAE, 0xA6, 0x95, 0x42, 0xA8, 0x1A, 0x41, 0x27, 0xF7, 0x1C, 0xBA, + 0xA2, 0x57, 0xF3, 0x24, 0xFE, 0xFE, 0xF1, 0x4F, 0x08, 0xFB, 0xD6, 0x5A, 0x04, 0x9C, 0xD2, + 0xFB, 0x36, 0x25, 0x94, 0xA8, 0xE2, 0x3F, 0xF1, 0xA2, 0x61, 0x7D, 0xB5, 0xB1, 0x58, 0xF6, + 0xF0, 0x1C, 0xF5, 0x0A, 0xB0, 0xED, 0x95, 0xC6, 0xE7, 0x09, 0x84, 0x11, 0x64, 0x10, 0x8B, + 0x06, 0xE1, 0xB4, 0x0A, 0xB0, 0xAB, 0x11, 0xC4, 0x08, 0x30, 0x1D, 0x3D, 0x9D, 0x8E, 0xA6, + 0x9E, 0x96, 0x8A, 0x96, 0x00, 0xB3, 0xD1, 0x7F, 0x38, 0x01, 0x1C, 0xE2, 0x80, 0x74, 0xE2, + 0xC2, 0xE1, 0x0B, 0xF6, 0x19, 0x7C, 0x60, 0x2D, 0x8D, 0x0C, 0xE7, 0xD3, 0xA3, 0xEF, 0x2D, + 0x89, 0x62, 0x3B, 0xC9, 0xF1, 0x2E, 0xA3, 0x38, 0x79, 0x1E, 0x92, 0x66, 0xBB, 0x8C, 0xE0, + 0x2B, 0x12, 0x4C, 0x6C, 0x79, 0x29, 0xBA, 0xEA, 0x69, 0x32, 0x44, 0x09, 0x84, 0x54, 0xA0, + 0x80, 0xEB, 0x75, 0x23, 0xE1, 0x3B, 0xB1, 0xB7, 0xC5, 0xB6, 0x77, 0x5F, 0xAB, 0xAB, 0xAB, + 0xBE, 0x90, 0x75, 0xFE, 0x56, 0x87, 0xAA, 0x45, 0x13, 0x97, 0xBB, 0x9C, 0xFC, 0xCD, 0x05, + 0x12, 0x43, 0xE9, 0xBF, 0x5A, 0xEF, 0x24, 0x06, 0x2D, 0x33, 0x5D, 0xE5, 0xFC, 0xE2, 0x4E, + 0x9D, 0xDB, 0xDE, 0x11, 0x91, 0x05, 0x2D, 0x80, 0xC3, 0x6D, 0xF9, 0xF8, 0x43, 0x48, 0x72, + 0xF2, 0x77, 0xED, 0x4F, 0x5A, 0x1C, 0xE8, 0xEB, 0xD3, 0xB9, 0x60, 0x82, 0x4A, 0x4E, 0x4F, + 0x10, 0x01, 0xB0, 0x4C, 0xB6, 0x85, 0xF9, 0xBE, 0xE4, 0xD0, 0xDD, 0xB0, 0xC5, 0x71, 0x59, + 0x8A, 0xC2, 0x02, 0x1A, 0x66, 0x06, 0xFD, 0x23, 0x34, 0x5C, 0x6F, 0xBB, 0x84, 0xF0, 0xCE, + 0x05, 0xFE, 0x52, 0x73, 0x45, 0x21, 0xB7, 0xB0, 0x7C, 0x63, 0x88, 0xD3, 0xA3, 0xB9, 0x93, + 0x18, 0xBF, 0x01, 0x31, 0x50, 0x4A, 0xA9, 0xDF, 0xBA, 0xF5, 0x48, 0xF9, 0xD3, 0x2A, 0x9C, + 0xD4, 0xC6, 0x89, 0x35, 0x24, 0xB1, 0x13, 0x30, 0xA2, 0xD3, 0xAA, 0xD3, 0xED, 0x2A, 0x58, + 0x96, 0x6E, 0xBB, 0x01, 0x34, 0x46, 0x5D, 0x54, 0x3F, 0xD7, 0x79, 0x7A, 0xF5, 0x49, 0xF5, + 0x68, 0xEA, 0xEB, 0xE9, 0x57, 0xF6, 0x4F, 0xEC, 0x85, 0x46, 0x74, 0x90, 0x2B, 0x97, 0x55, + 0x87, 0x56, 0x98, 0x69, 0x46, 0xEA, 0x3A, 0xB7, 0xA2, 0x51, 0xCB, 0xBE, 0xA1, 0x1A, 0x68, + 0x7B, 0xD4, 0x3F, 0x5D, 0x0B, 0xD8, 0x9C, 0xD2, 0xCA, 0xBA, 0x61, 0xD5, 0x21, 0x83, 0x74, + 0x99, 0x0E, 0xE8, 0xB9, 0x22, 0x19, 0xED, 0x25, 0xDC, 0xA0, 0x11, 0xC6, 0x8A, 0x97, 0x57, + 0xC0, 0x13, 0xBD, 0x83, 0x7B, 0x2D, 0xD7, 0x34, 0xE3, 0x75, 0x1F, 0x64, 0xFC, 0xB4, 0xB2, + 0x3D, 0xCD, 0x6B, 0xC5, 0x7E, 0xA5, 0x67, 0xF5, 0x71, 0x6E, 0x17, 0x36, 0x72, 0x44, 0x75, + 0x1E, 0x23, 0x03, 0xB2, 0x2A, 0x95, 0x3E, 0x77, 0x27, 0x56, 0x95, 0x6C, 0xDC, 0xC0, 0x13, + 0xFF, 0xD2, 0xC3, 0x24, 0x90, 0x75, 0x44, 0x22, 0xA5, 0x72, 0x52, 0x9D, 0x4C, 0x92, 0xF1, + 0xEB, 0xB1, 0x9F, 0x1D, 0xAD, 0x4D, 0x03, 0x6F, 0x2F, 0xDF, 0x31, 0xCA, 0x91, 0x01, 0xBD, + 0xF8, 0x1A, 0xEA, 0x94, 0x8A, 0xED, 0xCF, 0x21, 0x7A, 0xA8, 0xFC, 0xCD, 0x7A, 0x07, 0x71, + 0xAA, 0x27, 0x53, 0xE1, 0xA8, 0x23, 0xBF, 0x41, 0xC9, 0x53, 0x77, 0xA2, 0xFF, 0xA6, 0x1B, + 0x22, 0x65, 0x13, 0x81, 0x53, 0xCE, 0x86, 0xD2, 0xC8, 0x7D, 0xD0, 0x7A, 0x4B, 0x32, 0xD2, + 0x7F, 0x5F, 0x28, 0x72, 0x64, 0x14, 0x31, 0xCE, 0x9A, 0x18, 0xA5, 0x02, 0xAA, 0xEF, 0xD9, + 0xAF, 0xC5, 0xB0, 0xD1, 0x3C, 0xD4, 0x6C, 0x35, 0x7E, 0x38, 0xE6, 0x9E, 0x1E, 0xE9, 0x45, + 0xAD, 0xD1, 0x99, 0x29, 0x32, 0xA5, 0xB1, 0xE5, 0xC5, 0x62, 0x9C, 0x9F, 0x48, 0xF7, 0x66, + 0x18, 0x53, 0xDA, 0x00, 0x78, 0x7C, 0x9D, 0x78, 0xFB, 0x92, 0x55, 0x53, 0xBF, 0x07, 0xA5, + 0x0D, 0xD5, 0xB9, 0xD9, 0x35, 0x85, 0x34, 0x20, 0xE4, 0xD1, 0xA7, 0x1A, 0xE6, 0x2F, 0xF9, + 0x0C, 0xA1, 0x93, 0xCD, 0xD6, 0xC2, 0xF4, 0xBE, 0xD2, 0x63, 0x41, 0x5A, 0xAF, 0x9A, 0x35, + 0x09, 0x4B, 0xC2, 0xA2, 0x2E, 0x2A, 0x66, 0x3C, 0x76, 0x45, 0x00, 0x1C, 0xD1, 0x90, 0xB7, + 0xBC, 0x17, 0xC7, 0x5F, 0xEA, 0xDF, 0x8E, 0x87, 0xCE, 0x5C, 0x24, 0xB7, 0x63, 0xB6, 0x58, + 0x4E, 0xD3, 0x2E, 0x71, 0xB0, 0x26, 0x81, 0x42, 0xEA, 0x3E, 0xD6, 0x89, 0x81, 0x57, 0xBF, + 0x92, 0x3B, 0xEB, 0xF0, 0x19, 0x2D, 0x1B, 0xF5, 0xEE, 0x30, 0xA7, 0xD3, 0x51, 0x63, 0x4A, + 0x60, 0xB5, 0x04, 0xDD, 0xE3, 0x8A, 0x2E, 0x11, 0x4F, 0x7A, 0xE9, 0xBF, 0x17, 0x6D, 0x4A, + 0x18, 0xBA, 0x28, 0x95, 0xA7, 0xBB, 0x4B, 0x47, 0x44, 0x4A, 0x9B, 0xA8, 0xDB, 0xB4, 0xC1, + 0x24, 0xCD, 0x41, 0xBB, 0xB3, 0x2F, 0x4B, 0xCB, 0x1D, 0xE4, 0x8C, 0x4A, 0xBB, 0x51, 0x06, + 0x07, 0xA0, 0x01, 0xB5, 0xA0, 0x00, 0xBB, 0xA4, 0x36, 0x18, 0xB6, 0xC1, 0x9E, 0x43, 0x51, + 0x7B, 0x45, 0xB4, 0x24, 0x05, 0x92, 0x8B, 0x67, 0xC7, 0x13, 0x88, 0x18, 0x58, 0xBA, 0xD3, + 0xA4, 0x25, 0x11, 0xC2, 0x71, 0x6F, 0xF9, 0xCD, 0x33, 0x20, 0x34, 0xB6, 0x72, 0xB5, 0x2F, + 0xF1, 0x66, 0x10, 0x80, 0x5C, 0xDB, 0xE7, 0x54, 0x4A, 0x8A, 0x84, 0xB6, 0x6E, 0x1C, 0x74, + 0x5A, 0x73, 0xC1, 0xB6, 0xBC, 0xDA, 0x5B, 0x77, 0xB9, 0x51, 0xF3, 0x6C, 0x0F, 0x7A, 0x53, + 0x72, 0xDE, 0x9E, 0x5D, 0x1F, 0x9B, 0xBC, 0xDE, 0x88, 0x43, 0xC6, 0x90, 0x90, 0x02, 0xDD, + 0xA4, 0x87, 0x5E, 0x67, 0x57, 0x1A, 0xF0, 0xBE, 0xC5, 0x81, 0x85, 0x6C, 0x32, 0xC0, 0x9C, + 0x24, 0x0E, 0x66, 0x4E, 0x76, 0x1E, 0x57, 0xCD, 0x0D, 0x8D, 0xC8, 0xA7, 0x1C, 0xB9, 0x18, + 0xA5, 0x76, 0x2D, 0x11, 0x12, 0x85, 0xCD, 0x8B, 0x56, 0x13, 0xDD, 0xBD, 0x0C, 0xA0, 0x8A, + 0xC0, 0x34, 0x2B, 0x2B, 0xDE, 0xE3, 0x8F, 0x96, 0xFA, 0x75, 0x4B, 0xB2, 0xB0, 0x87, 0x17, + 0x9C, 0x11, 0x3C, 0x93, 0x98, 0x6A, 0x81, 0x03, 0x56, 0xEB, 0x94, 0x54, 0x0B, 0x93, 0xCB, + 0x9D, 0xEC, 0x4A, 0xA9, 0x29, 0x0F, 0xF1, 0x2E, 0xC1, 0xAA, 0x2E, 0x65, 0x6C, 0x9B, 0xE3, + 0xD5, 0x90, 0x75, 0x3C, 0x36, 0x6C, 0x60, 0x14, 0x06, 0xC0, 0x61, 0xBC, 0x22, 0x03, 0x3A, + 0x1F, 0xD1, 0xF4, 0xE1, 0x11, 0x1D, 0x03, 0x9B, 0x88, 0x13, 0xB9, 0x83, 0xCB, 0x50, 0x6C, + 0x3E, 0xA7, 0xFF, 0x30, 0x57, 0x98, 0x3E, 0x8B, 0xF0, 0x16, 0x82, 0xFB, 0xB0, 0x0F, 0x43, + 0x00, 0x53, 0x13, 0xC8, 0x2C, 0x13, 0x92, 0x91, 0x8A, 0x61, 0x65, 0xA1, 0x33, 0x38, 0xFF, + 0xE1, 0x1A, 0x99, 0x2C, 0x1F, 0xB3, 0xD1, 0x03, 0x2A, 0xA6, 0x79, 0xA4, 0x18, 0xC8, 0xBA, + 0x4F, 0x8A, 0x0B, 0xC1, 0x99, 0xE1, 0x0C, 0xF6, 0xBD, 0x77, 0xA1, 0x4F, 0xDD, 0x6A, 0x06, + 0x09, 0x35, 0x14, 0x34, 0x8E, 0x3A, 0x89, 0x74, 0x43, 0x4A, 0xE8, 0xA3, 0x67, 0x63, 0x69, + 0xC6, 0xBE, 0x2C, 0xF9, 0x0E, 0x67, 0x2B, 0x34, 0x3F, 0xCE, 0x04, 0xAC, 0x6B, 0x22, 0xE0, + 0xCF, 0x47, 0x56, 0x8B, 0xC4, 0x5D, 0x70, 0xA6, 0x8E, 0x68, 0xC6, 0x49, 0xA4, 0x83, 0x0A, + 0xE2, 0x18, 0x59, 0x0C, 0x1A, 0x43, 0x7E, 0x7A, 0x23, 0xA5, 0x4E, 0xFE, 0x44, 0xF6, 0x70, + 0x86, 0xEB, 0x69, 0x7B, 0x9F, 0xA5, 0x78, 0x35, 0xF0, 0xB8, 0xF7, 0x0F, 0x0A, 0x92, 0x92, + 0x26, 0xEF, 0xB3, 0x36, 0xC0, 0xE2, 0x18, 0x33, 0xA0, 0x28, 0x21, 0x8C, 0xD6, 0x37, 0x32, + 0xC8, 0x0A, 0xA4, 0x77, 0xE6, 0x2D, 0x14, 0x1D, 0xBA, 0x81, 0x85, 0x4F, 0x70, 0xDA, 0x68, + 0xDA, 0xFF, 0x4A, 0x84, 0xCB, 0x6D, 0xE7, 0x79, 0x25, 0x4E, 0x8A, 0x97, 0xE7, 0x35, 0x65, + 0x37, 0x4A, 0xF4, 0x09, 0x2A, 0xF0, 0x5C, 0xBD, 0x66, 0x54, 0xAF, 0xC3, 0xFD, 0x72, 0xF0, + 0xAE, 0x23, 0x26, 0x95, 0xCB, 0x66, 0x68, 0xEA, 0xFE, 0xCC, 0x40, 0x69, 0xBD, 0x90, 0xBB, + 0x52, 0x8B, 0x83, 0xEF, 0xA2, 0xFB, 0xCD, 0xBD, 0x93, 0xB2, 0x89, 0x92, 0x96, 0x21, 0xED, + 0x74, 0xD8, 0x08, 0x73, 0x8F, 0xC1, 0x03, 0xEE, 0xB1, 0x05, 0x51, 0x08, 0x51, 0xFC, 0x93, + 0x19, 0xF1, 0x71, 0xEA, 0x0C, 0xED, 0x0B, 0x97, 0xB5, 0xB9, 0xFB, 0x5E, 0xF9, 0x85, 0x18, + 0x6B, 0xC5, 0x20, 0x98, 0xF9, 0xEB, 0x47, 0x6F, 0x67, 0xB7, 0xCC, 0x76, 0x65, 0xD4, 0x75, + 0x87, 0x97, 0x5C, 0xB4, 0x5A, 0x50, 0xFC, 0x64, 0x10, 0x07, 0x19, 0xBF, 0x76, 0x34, 0x5F, + 0x0F, 0xDF, 0x1E, 0x09, 0xEF, 0xE9, 0xFB, 0x80, 0x0D, 0xC1, 0x14, 0xE4, 0x6B, 0xE0, 0x87, + 0x9A, 0x19, 0x5C, 0xC0, 0x68, 0x70, 0xE2, 0x3D, 0x26, 0x31, 0xDA, 0xE7, 0x1C, 0x39, 0x94, + 0x48, 0x1C, 0x87, 0x61, 0xC4, 0x0D, 0x07, 0xC5, 0xBF, 0xCA, 0x95, 0xE7, 0x18, 0xB7, 0xB2, + 0x25, 0x85, 0xAF, 0x03, 0xED, 0x34, 0x17, 0x5A, 0x46, 0xD5, 0x7A, 0xF3, 0x51, 0x8E, 0x32, + 0xA7, 0xFC, 0x1A, 0xA4, 0x48, 0x27, 0x32, 0xA8, 0x1A, 0x87, 0xF7, 0x24, 0xF8, 0xD2, 0xE7, + 0x80, 0xB3, 0xA3, 0x9D, 0x45, 0x1A, 0x38, 0x0F, 0x75, 0xC2, 0xD6, 0x80, 0xCC, 0x72, 0x13, + 0xEA, 0xB1, 0xD4, 0xA5, 0x9D, 0x39, 0x4A, 0xE3, 0x81, 0x0A, 0x1C, 0x90, 0x81, 0x8D, 0x52, + 0xF9, 0x3F, 0xB2, 0x03, 0xE2, 0xD8, 0xB1, 0xB5, 0xFA, 0x8F, 0x60, 0xB2, 0xD5, 0x85, 0xD9, + 0x13, 0x5D, 0x64, 0x88, 0x46, 0xF1, 0x38, 0xB8, 0x69, 0x53, 0x24, 0x2D, 0x2B, 0xB1, 0xF2, + 0xEC, 0xDF, 0x38, 0x9B, 0x4D, 0xE7, 0x65, 0x18, 0x17, 0xB8, 0xE4, 0xE6, 0x4B, 0x33, 0x3F, + 0x1A, 0xAC, 0x52, 0x3A, 0x93, 0xF2, 0x74, 0x8A, 0x9C, 0x38, 0xFF, 0xBC, 0x29, 0xCE, 0xD4, + 0x57, 0xB6, 0xF9, 0x78, 0x1B, 0x08, 0xA6, 0x7A, 0x19, 0x75, 0xD0, 0x31, 0xCC, 0xD7, 0x15, + 0x45, 0xC0, 0x03, 0x74, 0x34, 0x05, 0x6C, 0x24, 0x34, 0xD1, 0x3E, 0x6C, 0x4B, 0xEE, 0xBF, + 0x46, 0xFC, 0x12, 0x22, 0x2C, 0x0B, 0x2E, 0xCC, 0xD6, 0x15, 0x9D, 0x5A, 0xEA, 0x8E, 0x55, + 0x4D, 0x7A, 0x09, 0x65, 0x2B, 0x06, 0xBF, 0x7C, 0xA6, 0x99, 0xA7, 0x19, 0x9E, 0x71, 0x6D, + 0x05, 0xDD, 0x55, 0x30, 0x41, 0xA8, 0xF2, 0xB3, 0x03, 0xD2, 0x36, 0xA9, 0xBA, 0xBA, 0xAF, + 0xB9, 0xFA, 0x52, 0x8F, 0x28, 0xA2, 0xCA, 0x2A, 0xA7, 0x80, 0xB9, 0x40, 0x38, 0x3C, 0x09, + 0x9A, 0xA6, 0x5A, 0x00, 0x74, 0xB8, 0x3F, 0xD1, 0xF0, 0xBC, 0x5B, 0x7B, 0x5E, 0x46, 0xC2, + 0x5E, 0x54, 0x83, 0x8B, 0x3C, 0xBC, 0xFC, 0x95, 0xF8, 0x7F, 0x1D, 0x47, 0x1B, 0x3B, 0xA8, + 0x94, 0x43, 0x4F, 0xA5, 0x89, 0x52, 0xFD, 0xCB, 0x77, 0xF1, 0x61, 0x37, 0x26, 0x93, 0x30, + 0x6D, 0xBA, 0x4E, 0x8F, 0x21, 0x6D, 0x1C, 0x8E, 0x5C, 0xAF, 0xF0, 0xFE, 0x83, 0x60, 0xA5, + 0x1C, 0x60, 0x76, 0x36, 0x44, 0x16, 0x9F, 0xDC, 0x6A, 0x82, 0x67, 0xF2, 0xE3, 0xF9, 0x09, + 0xA6, 0x1B, 0x2A, 0x67, 0x8B, 0xCE, 0x6A, 0xE9, 0x04, 0x03, 0xA8, 0x36, 0xB1, 0xA7, 0xB7, + 0xE8, 0xCD, 0x8B, 0x54, 0xC3, 0x70, 0x87, 0xA9, 0xE1, 0x44, 0x46, 0xD9, 0x5E, 0x69, 0x08, + 0xD2, 0xEE, 0xDB, 0xFC, 0xC6, 0x53, 0xE0, 0x2F, 0xDF, 0x77, 0x1F, 0x70, 0x1A, 0x79, 0xB9, + 0xE5, 0xA2, 0x6E, 0xD0, 0xA9, 0x47, 0x84, 0x20, 0x70, 0xF3, 0xB5, 0x70, 0x17, 0x42, 0x21, + 0x12, 0x19, 0xE7, 0x61, 0x76, 0x2C, 0x37, 0xF0, 0xD0, 0xA1, 0xD1, 0xB9, 0x75, 0x0F, 0xEE, + 0x57, 0x7E, 0x12, 0x08, 0x11, 0x5C, 0x66, 0xAC, 0x07, 0xEC, 0x09, 0x1E, 0x6A, 0x3F, 0xC4, + 0xAA, 0x6A, 0x25, 0x3B, 0xCB, 0xA8, 0x68, 0xED, 0xD3, 0x15, 0x4D, 0xCA, 0xF5, 0x16, 0x2F, + 0x61, 0x5E, 0x85, 0x49, 0x0A, 0x6C, 0xA3, 0x42, 0xF3, 0x4C, 0x43, 0xAC, 0x61, 0xA3, 0xEA, + 0x6B, 0xFE, 0xEF, 0xD8, 0x50, 0xE1, 0x90, 0xEB, 0x1D, 0x8D, 0xA4, 0xD2, 0x8B, 0x5E, 0xCE, + 0xEB, 0x16, 0x78, 0xC0, 0x24, 0x33, 0xEC, 0xD5, 0xD4, 0x8B, 0x25, 0x36, 0x40, 0x42, 0x57, + 0xE8, 0xCA, 0x7B, 0xEF, 0x58, 0x55, 0xF2, 0xB8, 0x13, 0xED, 0x2F, 0x4C, 0x40, 0x94, 0x45, + 0xA3, 0x31, 0x7C, 0x9B, 0xE1, 0xA3, 0x5A, 0xE2, 0xFB, 0x4D, 0x2B, 0x87, 0x92, 0x1B, 0x90, + 0x4B, 0xF2, 0xC1, 0x4D, 0xB5, 0x14, 0xCE, 0xE0, 0x45, 0x25, 0x1C, 0xFC, 0x27, 0x63, 0x74, + 0xDB, 0x15, 0xC9, 0x9D, 0xEA, 0x15, 0xAC, 0xDE, 0x19, 0x7C, 0x6E, 0xB5, 0x24, 0x98, 0x8E, + 0x39, 0xB6, 0x32, 0x87, 0xBE, 0xB8, 0x67, 0x68, 0x65, 0xAA, 0xA3, 0xBA, 0xD1, 0xB4, 0x3B, + 0x8C, 0xAB, 0x15, 0xCB, 0xF2, 0x7A, 0x49, 0x87, 0x59, 0xE3, 0x20, 0x3A, 0xBF, 0x36, 0x9E, + 0x97, 0x24, 0x2F, 0x0B, 0x01, 0x54, 0x14, 0x9F, 0x14, 0xAC, 0x23, 0x3C, 0xDB, 0x73, 0xA2, + 0x2B, 0x7F, 0xB8, 0xF0, 0x93, 0x25, 0xBF, 0x2A, 0xCE, 0x83, 0xBB, 0x6B, 0x5D, 0xB8, 0xA1, + 0x21, 0xA2, 0xB6, 0x82, 0x14, 0x9A, 0x69, 0x13, 0x1C, 0xCC, 0xE5, 0x22, 0x29, 0x84, 0x0B, + 0x11, 0x3F, 0xC7, 0xB0, 0xBC, 0xC5, 0x84, 0x05, 0xBF, 0xE8, 0x7F, 0x1F, 0x95, 0xFF, 0xC2, + 0xE9, 0x6F, 0xC5, 0x59, 0x65, 0x67, 0xE9, 0x43, 0x64, 0xDF, 0xAA, 0x6D, 0x9D, 0x5A, 0x6E, + 0xB9, 0x9A, 0xE4, 0xDD, 0xF4, 0x24, + ]) + .unwrap(); let mu = MLDSA87::compute_mu_from_sk(&sk, msg, None).unwrap(); let sig = MLDSA87::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); @@ -276,7 +1122,7 @@ fn bench_mldsa87_sign() { } fn bench_mldsa87_lowmemory_sign() { - use bouncycastle::mldsa_lowmemory::{MLDSATrait, MLDSA87, MLDSA87PrivateKey, MLDSA87_SK_LEN}; + use bouncycastle::mldsa_lowmemory::{MLDSA87, MLDSA87_SK_LEN, MLDSA87PrivateKey, MLDSATrait}; eprintln!("MLDSA87_lowmemory/Sign"); @@ -289,7 +1135,12 @@ fn bench_mldsa87_lowmemory_sign() { // use bouncycastle_hex as hex; // eprintln!("sk:\n{}", &hex::encode(sk.encode())); - let sk = MLDSA87PrivateKey::from_bytes(&[0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f]).unwrap(); + let sk = MLDSA87PrivateKey::from_bytes(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, + 0x1E, 0x1F, + ]) + .unwrap(); let msg = b"The quick brown fox jumped over the lazy dog"; @@ -299,8 +1150,8 @@ fn bench_mldsa87_lowmemory_sign() { } fn bench_mldsa44_verify() { - use bouncycastle::mldsa::{MLDSATrait, MLDSA44, MLDSA44_SIG_LEN, MLDSA44PublicKey}; use bouncycastle::hex; + use bouncycastle::mldsa::{MLDSA44, MLDSA44_SIG_LEN, MLDSA44PublicKey, MLDSATrait}; eprintln!("MLDSA44/Verify"); @@ -319,8 +1170,261 @@ fn bench_mldsa44_verify() { // let sig = MLDSA44::sign_mu_deterministic(&mldsa44_sk, &mu, [0u8; 32]).unwrap(); // eprintln!("sig:\n{}", &*hex::encode(sig)); - let mldsa44_pk = MLDSA44PublicKey::from_bytes(&[0xd7,0xb2,0xb4,0x72,0x54,0xaa,0xe0,0xdb,0x45,0xe7,0x93,0x0d,0x4a,0x98,0xd2,0xc9,0x7d,0x8f,0x13,0x97,0xd1,0x78,0x9d,0xaf,0xa1,0x70,0x24,0xb3,0x16,0xe9,0xbe,0xc9,0x4f,0xc9,0x94,0x6d,0x42,0xf1,0x9b,0x79,0xa7,0x41,0x3b,0xba,0xa3,0x3e,0x71,0x49,0xcb,0x42,0xed,0x51,0x15,0x69,0x3a,0xc0,0x41,0xfa,0xcb,0x98,0x8a,0xde,0xb5,0xfe,0x0e,0x1d,0x86,0x31,0x18,0x49,0x95,0xb5,0x92,0xc3,0x97,0xd2,0x29,0x4e,0x2e,0x14,0xf9,0x0a,0xa4,0x14,0xba,0x38,0x26,0x89,0x9a,0xc4,0x3f,0x4c,0xcc,0xac,0xbc,0x26,0xe9,0xa8,0x32,0xb9,0x51,0x18,0xd5,0xcb,0x43,0x3c,0xbe,0xf9,0x66,0x0b,0x00,0x13,0x8e,0x08,0x17,0xf6,0x1e,0x76,0x2c,0xa2,0x74,0xc3,0x6a,0xd5,0x54,0xeb,0x22,0xaa,0xc1,0x16,0x2e,0x4a,0xb0,0x1a,0xcb,0xa1,0xe3,0x8c,0x4e,0xfd,0x8f,0x80,0xb6,0x5b,0x33,0x3d,0x0f,0x72,0xe5,0x5d,0xfe,0x71,0xce,0x9c,0x1e,0xbb,0x98,0x89,0xe7,0xc5,0x61,0x06,0xc0,0xfd,0x73,0x80,0x3a,0x2a,0xec,0xfe,0xaf,0xde,0xd7,0xaa,0x3c,0xb2,0xce,0xda,0x54,0xd1,0x2b,0xd8,0xcd,0x36,0xa7,0x8c,0xf9,0x75,0x94,0x3b,0x47,0xab,0xd2,0x5e,0x88,0x0a,0xc4,0x52,0xe5,0x74,0x2e,0xd1,0xe8,0xd1,0xa8,0x2a,0xfa,0x86,0xe5,0x90,0xc7,0x58,0xc1,0x5a,0xe4,0xd2,0x84,0x0d,0x92,0xbc,0xa1,0xa5,0x09,0x0f,0x40,0x49,0x65,0x97,0xfc,0xa7,0xd8,0xb9,0x51,0x3f,0x1a,0x1b,0xda,0x6e,0x95,0x0a,0xaa,0x98,0xde,0x46,0x75,0x07,0xd4,0xa4,0xf5,0xa4,0xf0,0x59,0x92,0x16,0x58,0x2c,0x35,0x72,0xf6,0x2e,0xda,0x89,0x05,0xab,0x35,0x81,0x67,0x0c,0x4a,0x02,0x77,0x7a,0x33,0xe0,0xca,0x72,0x95,0xfd,0x8f,0x4f,0xf6,0xd1,0xa0,0xa3,0xa7,0x68,0x3d,0x65,0xf5,0xf5,0xf7,0xfc,0x60,0xda,0x02,0x3e,0x82,0x6c,0x5f,0x92,0x14,0x4c,0x02,0xf7,0xd1,0xba,0x10,0x75,0x98,0x75,0x53,0xea,0x93,0x67,0xfc,0xd7,0x6d,0x99,0x0b,0x7f,0xa9,0x9c,0xd4,0x5a,0xfd,0xb8,0x83,0x6d,0x43,0xe4,0x59,0xf5,0x18,0x7d,0xf0,0x58,0x47,0x97,0x09,0xa0,0x1e,0xa6,0x83,0x59,0x35,0xfa,0x70,0x46,0x09,0x90,0xcd,0x3d,0xc1,0xba,0x40,0x1b,0xa9,0x4b,0xab,0x1d,0xde,0x41,0xac,0x67,0xab,0x33,0x19,0xdc,0xac,0xa0,0x60,0x48,0xd4,0xc4,0xee,0xf2,0x7e,0xe1,0x3a,0x9c,0x17,0xd0,0x53,0x8f,0x43,0x0f,0x2d,0x64,0x2d,0xc2,0x41,0x56,0x60,0xde,0x78,0x87,0x7d,0x8d,0x8a,0xbc,0x72,0x52,0x39,0x78,0xc0,0x42,0xe4,0x28,0x5f,0x43,0x19,0x84,0x6c,0x44,0x12,0x62,0x42,0x97,0x68,0x44,0xc1,0x0e,0x55,0x6b,0xa2,0x15,0xb5,0xa7,0x19,0xe5,0x9d,0x0c,0x6b,0x2a,0x96,0xd3,0x98,0x59,0x07,0x1f,0xdc,0xc2,0xcd,0xe7,0x52,0x4a,0x7b,0xed,0xae,0x54,0xe8,0x5b,0x31,0x8e,0x85,0x4e,0x8f,0xe2,0xb2,0xf3,0xed,0xfa,0xc9,0x71,0x91,0x28,0x27,0x0a,0xaf,0xd1,0xe5,0x04,0x4c,0x3a,0x4f,0xda,0xfd,0x9f,0xf3,0x1f,0x90,0x78,0x4b,0x8e,0x8e,0x45,0x96,0x14,0x4a,0x0d,0xaf,0x58,0x65,0x11,0xd3,0xd9,0x96,0x2b,0x9e,0xa9,0x5a,0xf1,0x97,0xb4,0xe5,0xfc,0x60,0xf2,0xb1,0xed,0x15,0xde,0x3a,0x5b,0xef,0x5f,0x89,0xbd,0xc7,0x9d,0x91,0x05,0x1d,0x9b,0x28,0x16,0xe7,0x4f,0xa5,0x45,0x31,0xef,0xdc,0x1c,0xbe,0x74,0xd4,0x48,0x85,0x7f,0x47,0x6b,0xcd,0x58,0xf2,0x1c,0x0b,0x65,0x3b,0x3b,0x76,0xa4,0xe0,0x76,0xa6,0x55,0x9a,0x30,0x27,0x18,0x55,0x5c,0xc6,0x3f,0x74,0x85,0x9a,0xab,0xab,0x92,0x5f,0x02,0x38,0x61,0xca,0x8c,0xd0,0xf7,0xba,0xdb,0x28,0x71,0xf6,0x7d,0x55,0x32,0x6d,0x74,0x51,0x13,0x5a,0xd4,0x5f,0x4a,0x1b,0xa6,0x91,0x18,0xfb,0xb2,0xc8,0xa3,0x0e,0xec,0x93,0x92,0xef,0x3f,0x97,0x70,0x66,0xc9,0xad,0xd5,0xc7,0x10,0xcc,0x64,0x7b,0x15,0x14,0xd2,0x17,0xd9,0x58,0xc7,0x01,0x7c,0x3e,0x90,0xfd,0x20,0xc0,0x4e,0x67,0x4b,0x90,0x48,0x6e,0x93,0x70,0xa3,0x1a,0x00,0x1d,0x32,0xf4,0x73,0x97,0x9e,0x49,0x06,0x74,0x9e,0x7e,0x47,0x7f,0xa0,0xb7,0x45,0x08,0xf8,0xa5,0xf2,0x37,0x83,0x12,0xb8,0x3c,0x25,0xbd,0x38,0x8c,0xa0,0xb0,0xff,0xf7,0x47,0x8b,0xaf,0x42,0xb7,0x16,0x67,0xed,0xaa,0xc9,0x7c,0x46,0xb1,0x29,0x64,0x3e,0x58,0x6e,0x5b,0x05,0x5a,0x0c,0x21,0x19,0x46,0xd4,0xf3,0x6e,0x67,0x5b,0xed,0x58,0x60,0xfa,0x04,0x2a,0x31,0x5d,0x98,0x26,0x16,0x4d,0x6a,0x92,0x37,0xc3,0x5a,0x5f,0xbf,0x49,0x54,0x90,0xa5,0xbd,0x4d,0xf2,0x48,0xb9,0x5c,0x4a,0xae,0x77,0x84,0xb6,0x05,0x67,0x31,0x66,0xac,0x42,0x45,0xb5,0xb4,0xb0,0x82,0xa0,0x9e,0x93,0x23,0xe6,0x2f,0x20,0x78,0xc5,0xb7,0x67,0x83,0x44,0x6d,0xef,0xd7,0x36,0xad,0x3a,0x37,0x02,0xd4,0x9b,0x08,0x98,0x44,0x90,0x0a,0x61,0x83,0x33,0x97,0xbc,0x44,0x19,0xb3,0x0d,0x7a,0x97,0xa0,0xb3,0x87,0xc1,0x91,0x14,0x74,0xc4,0xd4,0x1b,0x53,0xe3,0x2a,0x97,0x7a,0xcb,0x6f,0x0e,0xa7,0x5d,0xb6,0x5b,0xb3,0x9e,0x59,0xe7,0x01,0xe7,0x69,0x57,0xde,0xf6,0xf2,0xd4,0x45,0x59,0xc3,0x1a,0x77,0x12,0x2b,0x52,0x04,0xe3,0xb5,0xc2,0x19,0xf1,0x68,0x8b,0x14,0xed,0x0b,0xc0,0xb8,0x01,0xb3,0xe6,0xe8,0x2d,0xcd,0x43,0xe9,0xc0,0xe9,0xf4,0x17,0x44,0xcd,0x98,0x15,0xbd,0x1b,0xc8,0x82,0x0d,0x8b,0xb1,0x23,0xf0,0x4f,0xac,0xd1,0xb1,0xb6,0x85,0xdd,0x5a,0x2b,0x1b,0x8d,0xbb,0xf3,0xed,0x93,0x36,0x70,0xf0,0x95,0xa1,0x80,0xb4,0xf1,0x92,0xd0,0x8b,0x10,0xb8,0xfa,0xbb,0xdf,0xcc,0x2b,0x24,0x51,0x8e,0x32,0xee,0xa0,0xa5,0xe0,0xc9,0x04,0xca,0x84,0x47,0x80,0x08,0x3f,0x3b,0x0c,0xd2,0xd0,0xb8,0xb6,0xaf,0x67,0xbc,0x35,0x5b,0x94,0x94,0x02,0x5d,0xc7,0xb0,0xa7,0x8f,0xa8,0x0e,0x3a,0x2d,0xbf,0xeb,0x51,0x32,0x88,0x51,0xd6,0x07,0x81,0x98,0xe9,0x49,0x36,0x51,0xae,0x78,0x7e,0xc0,0x25,0x1f,0x92,0x2b,0xa3,0x0e,0x9f,0x51,0xdf,0x62,0xa6,0xd7,0x27,0x84,0xcf,0x3d,0xd2,0x05,0x39,0x31,0x76,0xdf,0xa3,0x24,0xa5,0x12,0xbd,0x94,0x97,0x0a,0x36,0xdd,0x34,0xa5,0x14,0xa8,0x67,0x91,0xf0,0xeb,0x36,0xf0,0x14,0x5b,0x09,0xab,0x64,0x65,0x1b,0x4a,0x03,0x13,0xb2,0x99,0x61,0x1a,0x2a,0x1c,0x48,0x89,0x16,0x27,0x59,0x87,0x68,0xa3,0x11,0x40,0x60,0xba,0x44,0x43,0x48,0x6d,0xf5,0x15,0x22,0xa1,0xce,0x88,0xb3,0x09,0x85,0xc2,0x16,0xf8,0xe6,0xed,0x17,0x8d,0xd5,0x67,0xb3,0x04,0xa0,0xd4,0xca,0xfb,0xa8,0x82,0xa2,0x83,0x42,0xf1,0x7a,0x9a,0xa2,0x6a,0xe5,0x8d,0xb6,0x30,0x08,0x3d,0x2c,0x35,0x8f,0xdf,0x56,0x6c,0x3f,0x5d,0x62,0xa4,0x28,0x56,0x7b,0xc9,0xea,0x8c,0xe9,0x5c,0xaa,0x0f,0x35,0x47,0x4b,0x0b,0xfa,0x8f,0x33,0x9a,0x25,0x0a,0xb4,0xdf,0xcf,0x20,0x83,0xbe,0x8e,0xef,0xbc,0x10,0x55,0xe1,0x8f,0xe1,0x53,0x70,0xee,0xcb,0x26,0x05,0x66,0xd8,0x3f,0xf0,0x6b,0x21,0x1a,0xae,0xc4,0x3c,0xa2,0x9b,0x54,0xcc,0xd0,0x0f,0x88,0x15,0xa2,0x46,0x5e,0xf0,0xb4,0x65,0x15,0xcc,0x7e,0x41,0xf3,0x12,0x4f,0x09,0xef,0xff,0x73,0x93,0x09,0xab,0x58,0xb2,0x9a,0x14,0x59,0xa0,0x0b,0xce,0x50,0x38,0xe9,0x38,0xc9,0x67,0x8f,0x72,0xeb,0x0e,0x4e,0xe5,0xfd,0xaa,0xe6,0x6d,0x9f,0x85,0x73,0xfc,0x97,0xfc,0x42,0xb4,0x95,0x9f,0x4b,0xf8,0xb6,0x1d,0x78,0x43,0x3e,0x86,0xb0,0x33,0x5d,0x6e,0x91,0x91,0xc4,0xd8,0xbf,0x48,0x7b,0x39,0x05,0xc1,0x08,0xcf,0xd6,0xac,0x24,0xb0,0xce,0xb7,0xdc,0xb7,0xcf,0x51,0xf8,0x4d,0x0e,0xd6,0x87,0xb9,0x5e,0xae,0xb1,0xc5,0x33,0xc0,0x6f,0x0d,0x97,0x02,0x3d,0x92,0xa7,0x08,0x25,0x83,0x7b,0x59,0xba,0x6c,0xb7,0xd4,0xe5,0x6b,0x0a,0x87,0xc2,0x03,0x86,0x2a,0xe8,0xf3,0x15,0xba,0x59,0x25,0xe8,0xed,0xef,0xa6,0x79,0x36,0x9a,0x22,0x02,0x76,0x61,0x51,0xf1,0x6a,0x96,0x5f,0x9f,0x81,0xec,0xe7,0x6c,0xc0,0x70,0xb5,0x58,0x69,0xe4,0xdb,0x97,0x84,0xcf,0x05,0xc8,0x30,0xb3,0x24,0x2c,0x83,0x12]).unwrap(); - let sig: [u8; MLDSA44_SIG_LEN] = [0x5e,0x93,0xb7,0x85,0xc5,0x11,0x9c,0x39,0x83,0xa2,0x91,0xb1,0x84,0x20,0xfd,0xbe,0x4b,0xca,0x53,0xd5,0xa3,0x73,0x29,0x22,0xfa,0xaa,0xcd,0x5a,0x5d,0x32,0xa7,0x45,0xc7,0x8d,0x10,0x5b,0xa1,0x0b,0xee,0x1e,0xd8,0x06,0x9f,0x19,0xe6,0xc5,0x37,0xbd,0xa1,0x6e,0x89,0xd3,0x90,0x04,0xc3,0x59,0xd1,0xfd,0x38,0x1a,0x02,0x91,0xf1,0xc5,0x1f,0x1c,0x38,0xed,0xcd,0xb3,0x15,0xc8,0xc6,0x95,0x70,0xd8,0xf2,0x5f,0x16,0x55,0xba,0x8e,0xa8,0x3a,0xff,0x24,0xb8,0xb6,0xbe,0x8d,0xe7,0x62,0x34,0x2e,0x34,0x7e,0xab,0x2c,0xaa,0x68,0x03,0xed,0x70,0x59,0x52,0xdd,0x64,0x50,0xc5,0x18,0x5e,0x9d,0x60,0xce,0x96,0xe8,0xdc,0xa4,0x23,0xa0,0x2f,0x64,0x6c,0xea,0x69,0x01,0x64,0xa2,0x26,0xe4,0xc3,0xd6,0xa5,0x15,0xce,0x16,0x29,0x0f,0x19,0xb2,0xc6,0x26,0xda,0x9b,0x45,0x0e,0xcf,0x66,0x50,0x13,0xc5,0xe2,0x26,0xb6,0xc0,0xac,0x5c,0x07,0xce,0x90,0xe2,0x78,0xf1,0xb0,0x13,0x4e,0x38,0x5d,0x13,0xe7,0x42,0x08,0xa0,0xb3,0xff,0x05,0x2a,0x36,0x25,0x79,0xf9,0x20,0x7e,0xa0,0x1f,0x18,0xa0,0x39,0xaa,0x1b,0x97,0xae,0x34,0x52,0x67,0x5b,0x62,0x07,0x71,0xf8,0x01,0x2e,0xe7,0xa4,0xe5,0x5c,0x98,0xbf,0xd2,0x01,0x9e,0xd8,0xa3,0xb0,0x0a,0xce,0xa8,0xe8,0xab,0x28,0x17,0x2f,0xaa,0x42,0xca,0x1f,0xda,0x83,0xc5,0xff,0xe8,0x1a,0x45,0xbe,0x73,0x6b,0xde,0xdd,0x5f,0xb3,0x00,0xce,0x17,0x07,0x8b,0x38,0x0f,0x62,0x0b,0xde,0xeb,0xad,0x69,0x36,0x01,0x37,0x2c,0x85,0xea,0xcf,0x79,0xbc,0x98,0xe1,0xb4,0x8f,0x2a,0xd7,0xe5,0xdc,0xe4,0x27,0x9a,0x12,0x95,0xbb,0x2b,0xa6,0x0a,0x0c,0x5e,0x37,0x26,0x64,0x2d,0x23,0x36,0xc5,0xeb,0x1d,0x37,0xc8,0x62,0x3c,0x75,0x58,0x24,0x13,0x18,0xd8,0x9b,0xc7,0x83,0xc4,0xf0,0x00,0x98,0x07,0x74,0x84,0x62,0x3c,0x21,0x75,0x60,0xa0,0xc7,0xaa,0xf7,0x5d,0xca,0xcc,0xb7,0x8e,0xe6,0x9c,0x20,0x7c,0x27,0xc8,0xbf,0x39,0x65,0xcc,0xf5,0x8a,0x80,0xc8,0x8e,0xfc,0xc7,0xe5,0xde,0xb3,0x61,0x5d,0x50,0x45,0xa7,0x41,0xc4,0xda,0xc0,0xa0,0x21,0xdd,0x06,0x0d,0x31,0x5d,0x4e,0xc2,0x85,0x7e,0xb6,0x64,0xd7,0x28,0xd0,0xaf,0x97,0x3b,0xea,0x07,0xe1,0xca,0x56,0x3f,0xaa,0x0e,0x19,0x99,0x6c,0xea,0x37,0x70,0x31,0x6c,0x11,0xa5,0x06,0x66,0x65,0x66,0x20,0x05,0xac,0xe9,0x8f,0x61,0x10,0xe8,0x83,0xba,0xe0,0x60,0xda,0xa7,0xb6,0xd8,0x33,0x79,0xe0,0x87,0x87,0x96,0x69,0x17,0x08,0xa3,0x2b,0x85,0x73,0x0d,0xe8,0xb9,0x2d,0x89,0xf9,0x0a,0x36,0x60,0xc9,0x49,0x16,0x5b,0x14,0x61,0x25,0x67,0x66,0x2e,0x16,0x22,0x32,0x29,0x6c,0xbd,0x14,0x35,0x17,0xa2,0x82,0xe2,0x2c,0x46,0xb6,0x36,0x06,0xd3,0xc1,0x4e,0xd4,0x55,0x9a,0x5a,0x1c,0x45,0x9b,0xab,0x7f,0x35,0x50,0x07,0xad,0x6f,0x7e,0x3b,0x1e,0x07,0x44,0x5d,0xfc,0x96,0xbd,0x9b,0x75,0x08,0x0b,0x3d,0x4f,0x68,0x99,0x84,0x90,0xa2,0x6b,0x5e,0x09,0x0b,0xe2,0x67,0x40,0x71,0xab,0x92,0x5b,0xb6,0x50,0x59,0x08,0x56,0xc5,0x9f,0x8b,0xa7,0x48,0x8d,0x2b,0x72,0xf8,0x40,0xac,0x3e,0xaf,0xe4,0xdd,0x91,0xf0,0xf5,0x1c,0x43,0x64,0x11,0x2c,0x1a,0x13,0x9e,0x3e,0x94,0x2a,0x59,0x7b,0x93,0xa1,0xe3,0xf4,0xfa,0xde,0xd1,0x29,0xc1,0x4b,0x59,0x78,0xb3,0x15,0xe2,0x24,0x6a,0x93,0x14,0x6a,0x79,0x36,0x5f,0x0f,0x59,0x7a,0x18,0x34,0x0c,0xca,0x86,0xbb,0x15,0xce,0xed,0x39,0xf1,0x75,0xea,0xb1,0xe5,0x46,0x53,0x5a,0xfb,0x96,0x6f,0x0a,0x65,0xa8,0xf6,0x6f,0x73,0x7a,0xb0,0x28,0x97,0xed,0xdf,0xe9,0x2c,0xf7,0x78,0x68,0x94,0x84,0x3c,0x26,0x91,0x46,0x47,0x76,0xc9,0x4b,0xd4,0x50,0xa1,0x06,0x91,0x38,0xb2,0x6d,0xf8,0x3b,0x2d,0x1d,0xd8,0x01,0x14,0x3a,0x8f,0xdf,0xdc,0x25,0x14,0xcc,0x5b,0x58,0x31,0xab,0x53,0xa7,0x5c,0x55,0xef,0x29,0xf4,0x0e,0x7c,0x63,0xd2,0xc7,0x2a,0xbe,0x97,0xe2,0xaf,0x14,0x85,0x3b,0xe4,0x9b,0xe1,0x6f,0x47,0x30,0xa1,0x59,0x97,0x49,0x70,0x95,0x14,0x39,0xe5,0x5c,0x15,0x89,0xd0,0xf4,0xa1,0x62,0xe3,0x51,0x7d,0xf9,0xd7,0xab,0xc9,0x8d,0x8a,0x30,0x72,0x16,0xe7,0xf1,0xcb,0x46,0x27,0xc9,0x17,0x5c,0x0e,0xef,0x23,0x33,0x7e,0x56,0xd5,0x28,0x1b,0x83,0x72,0x6f,0xff,0x40,0xa1,0x48,0xb0,0xc4,0x8e,0x8d,0xf3,0x49,0x6a,0x21,0x18,0xd8,0x02,0x19,0xae,0xf8,0xf4,0x0b,0x29,0xfb,0xa1,0xf2,0xf7,0x87,0x86,0xb6,0x7f,0xfb,0x7b,0x7d,0x47,0xd4,0x06,0xb7,0x65,0xbd,0x13,0x66,0x10,0xbe,0xde,0xb9,0x5c,0xd7,0x32,0x1f,0x58,0xf3,0xb8,0x36,0xc9,0x25,0x8b,0xe3,0x5d,0x78,0xb4,0x98,0xf3,0xef,0xe1,0xdb,0x2b,0x24,0x3d,0x73,0x4f,0xab,0x15,0x9b,0xae,0xd8,0x80,0x7c,0x3c,0xcc,0xf8,0x3e,0xb2,0xea,0xf8,0xa9,0xaf,0x01,0xa5,0x18,0xd4,0x8c,0x60,0xe9,0x1a,0x96,0x81,0x2a,0xd6,0x89,0xc2,0xd8,0x3c,0xc4,0xe8,0xe9,0xb3,0x65,0x04,0x22,0xbe,0xd6,0xf1,0x3c,0x24,0xad,0xaa,0xd9,0x1c,0x95,0xb3,0xe3,0xcf,0x35,0x4f,0x0f,0x6b,0xc9,0xee,0x89,0x41,0xa6,0xb1,0x5b,0x69,0x75,0x13,0x1d,0x95,0x23,0x3d,0x89,0x35,0xde,0x36,0x7e,0xfc,0x6d,0x86,0xa4,0x5d,0xac,0x7d,0x0f,0x1d,0xdd,0x9a,0xeb,0xd2,0xc5,0x9c,0x02,0x7f,0xcd,0xa4,0x48,0x80,0x1e,0x93,0xe7,0x33,0xac,0xa5,0x18,0x74,0xbe,0x9a,0xb9,0x27,0xa9,0x04,0xf9,0x6d,0xdb,0x7a,0x46,0xb2,0xda,0x13,0x26,0x1d,0x52,0x2b,0x23,0xc9,0x50,0xc0,0x1d,0x5f,0x5e,0x11,0x2b,0x76,0xf8,0x51,0xff,0x23,0x4f,0x06,0xf8,0xd5,0xe6,0x5b,0x13,0x19,0xab,0xcd,0x79,0xa1,0x80,0xae,0x06,0x3d,0x65,0xb2,0x8c,0x74,0x58,0x78,0xc0,0x6d,0xbb,0x69,0xba,0x73,0x29,0x3e,0xab,0x34,0x43,0x4b,0xf1,0xa9,0x2f,0xba,0x69,0x19,0x93,0xbd,0x0f,0xf3,0xed,0xac,0x76,0xa1,0x2f,0x80,0xc0,0xad,0xa4,0xb1,0x96,0x9c,0x76,0x65,0x58,0x9d,0x53,0x0a,0x67,0x01,0x6a,0x62,0x54,0x03,0xc5,0x37,0x03,0x29,0x04,0xf2,0xe1,0x04,0x54,0x7c,0xd3,0xea,0x40,0x62,0x60,0xdd,0x35,0x7f,0xa0,0x6e,0xa0,0x12,0xa7,0x85,0x82,0x6c,0x16,0x0e,0x99,0xff,0xd0,0x65,0xb0,0xe3,0xf3,0x3c,0x76,0x89,0xd3,0x55,0x2a,0xb9,0xe2,0xe0,0x9f,0xa7,0xe5,0x5b,0xbc,0xef,0x04,0x22,0x42,0xbc,0xac,0xad,0x8a,0x3d,0xa4,0x7b,0xcc,0x54,0xa1,0x21,0xf1,0x52,0x6c,0x8c,0xd4,0xcc,0x5a,0x89,0x2a,0x81,0x31,0xcf,0x4e,0xef,0xaf,0x42,0x48,0xdd,0xd6,0xa1,0x1e,0xc4,0x27,0xba,0x37,0x8a,0xae,0x89,0xaa,0xf5,0x82,0xce,0x1f,0x4e,0x32,0x69,0x0a,0x55,0x5e,0x74,0x07,0x61,0xd3,0x58,0xad,0x4e,0x92,0xbc,0x38,0x41,0x8a,0xa7,0x82,0xda,0x91,0x65,0x24,0xfb,0x09,0xab,0x2c,0xa6,0xb3,0xd3,0x11,0x3d,0x6f,0x2c,0x2a,0x6a,0x9b,0x9d,0x29,0xd4,0xe7,0x48,0x92,0x55,0x25,0x2a,0xf0,0x75,0xcb,0xf9,0xfe,0xac,0xed,0xae,0x6f,0x3e,0xc0,0xb0,0x70,0x82,0x46,0x89,0xdd,0x3c,0x78,0xac,0x14,0x3e,0xd6,0x77,0x6d,0x95,0xdd,0x8f,0x13,0xd4,0x35,0xa2,0x90,0xbd,0xca,0x4c,0x11,0x31,0x8e,0x5a,0xcc,0xe0,0x44,0x69,0x64,0x4e,0x13,0x74,0xa9,0x45,0x1b,0x62,0x04,0xf3,0xb3,0x96,0x1b,0x7d,0xd2,0x39,0xe3,0x06,0xfe,0xf5,0xf4,0xf4,0xe5,0x1b,0x78,0xb0,0xfb,0x9d,0xce,0xe6,0x9c,0x3e,0x79,0x0b,0x23,0x1f,0x2e,0x65,0xfd,0x1a,0xb1,0xc2,0xa7,0x5b,0x07,0x06,0x7d,0x5c,0x16,0xdd,0xe0,0x09,0x83,0xa5,0x8f,0xfc,0xda,0xaa,0xee,0x16,0xd2,0x74,0x2e,0x13,0x3e,0xd7,0x37,0xb4,0x80,0x64,0xc8,0xa3,0x8e,0xca,0x35,0xab,0x3f,0xa1,0x8f,0x6d,0x62,0xf6,0x42,0xb1,0x2c,0xfd,0xc7,0x98,0x0f,0x2a,0xb7,0xdb,0x32,0x1f,0xec,0x9d,0xcf,0xe4,0x99,0xb4,0xfc,0x1e,0xe7,0xeb,0x29,0x79,0x54,0x05,0x66,0x17,0xc6,0x0a,0x66,0x40,0xb9,0x28,0x35,0xd1,0x65,0xc3,0xc0,0x0a,0x95,0x19,0x52,0x61,0x44,0x88,0xd5,0x65,0x7b,0xa0,0xb5,0xe9,0x0a,0xe9,0xe0,0xef,0x7b,0x3b,0x9e,0xca,0xeb,0xd8,0x1b,0x85,0x51,0xb6,0xd7,0x0e,0x83,0x5b,0x27,0x34,0x76,0x16,0x39,0xd4,0x2e,0x76,0xff,0xc5,0xb3,0x27,0x2b,0x61,0xc8,0x96,0xb4,0x5b,0x4b,0xd1,0x8f,0x30,0xe5,0x8c,0x44,0x06,0x43,0xba,0x15,0x92,0x21,0xcc,0x67,0x39,0xa1,0x9a,0x65,0xf2,0x91,0x1f,0xae,0x47,0xb0,0xd4,0xca,0xc4,0x20,0x0a,0x6f,0x04,0x3b,0x17,0xa0,0x3a,0xd3,0x93,0xec,0xb8,0x23,0xed,0x03,0xc8,0xb6,0xcd,0x68,0x16,0x7e,0x6c,0x82,0x34,0xf7,0x43,0x25,0x57,0xdb,0x27,0x20,0x79,0xee,0x89,0x9a,0xed,0xe7,0x3b,0x6b,0x98,0xd6,0x00,0x3f,0x45,0x78,0x9a,0x14,0x1b,0x60,0xd6,0xdb,0x40,0xcd,0x2a,0x59,0x74,0x57,0x1a,0x4a,0xd3,0x66,0x7b,0x88,0x93,0x18,0xba,0x60,0x28,0x5d,0x90,0x3a,0x2e,0xac,0x01,0xc2,0x16,0x08,0x83,0x8c,0x40,0x90,0x7d,0xe6,0xbb,0xab,0xe0,0x42,0xcf,0x2e,0xcd,0xd9,0x7f,0x54,0x9f,0x95,0xec,0x69,0x8d,0x79,0x22,0x2c,0x65,0xba,0x27,0xc3,0x0d,0x33,0x2a,0x68,0xd0,0x57,0xae,0xcd,0xc9,0x38,0x8a,0xa3,0x43,0x20,0xe0,0xaa,0x74,0xfd,0xbd,0x4d,0x1b,0x64,0x3c,0xac,0xe2,0x16,0xb6,0xd8,0xad,0x8f,0x07,0xa9,0x99,0x55,0xbf,0xdb,0x74,0x3a,0x86,0xb4,0x0f,0xc6,0x15,0x27,0xba,0xca,0x43,0x4a,0xc2,0xa7,0xfb,0xea,0xa7,0x71,0x11,0xdc,0x80,0x98,0xb1,0x7e,0x80,0x0f,0x59,0xdd,0x77,0xcc,0xb0,0xe6,0x77,0x07,0xe6,0x01,0x23,0xd3,0x34,0xe0,0x73,0xa2,0xf5,0xa1,0x6f,0xfb,0xcd,0x70,0x13,0x89,0xad,0xd5,0x7c,0x3c,0xec,0xcb,0x88,0xb2,0x86,0xac,0x1e,0x6e,0x3e,0x64,0x85,0xaf,0x1a,0x12,0xea,0x24,0x1d,0x14,0xa1,0xb5,0x00,0x3d,0x7f,0x3b,0xc9,0xe9,0x57,0xd4,0x48,0x3c,0x0f,0x9f,0x70,0x3b,0x3a,0x18,0x7d,0x55,0xe5,0x05,0x81,0x76,0x15,0xfb,0xc4,0xae,0x08,0x37,0x61,0x61,0x84,0x24,0x5c,0xfb,0xa6,0x1c,0xe3,0xb9,0x29,0xe3,0x3f,0x52,0xb7,0x1c,0xdd,0x7b,0x6a,0x0d,0xa5,0x5c,0x1f,0x99,0x75,0x10,0xb1,0xa9,0x00,0x2c,0xa4,0xe0,0x67,0x83,0x73,0xa3,0xb1,0xab,0x28,0x97,0xe6,0xb4,0x23,0xf1,0x5a,0x44,0x0a,0x63,0x6c,0xc8,0x61,0x49,0x1e,0xf4,0x1a,0xd0,0xaa,0x62,0x7d,0x8e,0x19,0x8a,0x5e,0xe7,0xbd,0x7b,0x6c,0xb2,0xc9,0xce,0x2a,0x8c,0xc0,0x15,0xf0,0xd2,0x06,0xde,0x4c,0x49,0xe2,0xf8,0x7f,0x31,0x09,0x54,0xa1,0x0d,0x86,0xe2,0x94,0xf7,0x42,0xee,0x18,0x6f,0x4a,0xe9,0x81,0x5f,0x69,0x96,0x22,0x79,0x22,0x06,0xca,0xfb,0xa8,0xf5,0x62,0x17,0x38,0x16,0x0e,0x6c,0x5d,0x61,0x1a,0x82,0x52,0xc6,0xf3,0x50,0x85,0xb6,0x04,0xef,0x89,0x51,0x64,0xd4,0xea,0x6d,0xdd,0x31,0x0c,0x7d,0x8f,0x0c,0x87,0x9f,0xb1,0xf8,0x84,0xc5,0x74,0x1d,0x09,0x6b,0x3d,0x2d,0xa0,0xce,0x11,0x51,0x79,0x0d,0xda,0x88,0x1d,0x18,0xcb,0x6b,0x19,0xa9,0xfe,0xd6,0xf5,0x25,0x4b,0x7d,0x52,0xd5,0xd9,0x2b,0xbb,0xe2,0x4c,0x9d,0x6a,0x65,0x60,0x4a,0x0b,0x8e,0xd2,0x4a,0xd5,0xc1,0x97,0xd6,0x83,0xf5,0x98,0x74,0x3c,0x96,0xb5,0x96,0x0e,0x87,0x23,0x73,0x2b,0x5b,0xd6,0x47,0xe9,0xdb,0xea,0xa8,0x51,0xd0,0xe1,0xcf,0x6d,0x2c,0x07,0x0d,0x44,0x42,0x76,0x2c,0x28,0x09,0x8c,0x5c,0xf5,0xa5,0x4b,0x2b,0x5e,0x69,0xa9,0x9b,0x10,0x81,0x5b,0xf0,0xf4,0x77,0xbb,0x71,0xf0,0xd5,0xd3,0xa6,0x2b,0xa2,0xb3,0xe2,0x9b,0xf8,0x4d,0x4b,0x4e,0x57,0x47,0x07,0xf5,0xf7,0x4a,0xf7,0x04,0xd2,0x77,0xbd,0x6c,0xa3,0x8d,0xa2,0x1e,0x2c,0xda,0xc5,0x49,0xe5,0xea,0xe1,0xde,0x7a,0x18,0xee,0x53,0x4c,0x8c,0x22,0x91,0xc9,0x08,0xca,0xab,0xf1,0x59,0xe9,0x0e,0x65,0x49,0xdb,0x94,0xba,0x7a,0x3f,0x3d,0x97,0xdd,0x39,0x8a,0x75,0xdf,0x5b,0x1a,0x7c,0xdf,0xb2,0x54,0x10,0xb7,0xef,0xc4,0xed,0x00,0xd9,0x99,0x5b,0x37,0xb5,0x8b,0xf9,0x1e,0xd7,0xa3,0x51,0x0c,0xff,0xea,0x82,0xf9,0xe1,0xc2,0xa3,0x29,0x04,0x06,0x00,0x4d,0x09,0x05,0x7d,0x63,0xb7,0x70,0xfa,0x0e,0x53,0x10,0x31,0x99,0x54,0x4e,0xba,0x66,0x2a,0x2c,0x30,0x2c,0xf3,0x90,0x08,0xf1,0x42,0xd2,0xb1,0x69,0x63,0xe9,0x5a,0xb1,0x0b,0xe7,0xc2,0x61,0x01,0x68,0x60,0x8f,0x35,0x3a,0x2f,0x2c,0x41,0xc7,0x05,0x6d,0xec,0x1a,0x8c,0x7a,0x6b,0xfa,0x00,0x27,0xf9,0xde,0xda,0xcb,0x77,0x86,0xb6,0x7e,0xa2,0xc4,0x94,0xd4,0x3b,0xa8,0x51,0xcf,0x94,0x15,0xc1,0xbc,0xc5,0x2f,0x02,0x7e,0xc0,0x2c,0x65,0x53,0x4f,0x60,0x8e,0x9d,0x16,0x6d,0x51,0xdd,0x43,0x1c,0xdf,0x58,0x71,0xf5,0xcd,0xd1,0x57,0x9c,0xc0,0x60,0x79,0xdf,0x07,0x5a,0x25,0x06,0x2b,0xa7,0xe7,0x0d,0x96,0x66,0xc4,0xe7,0xfe,0xd3,0x4c,0xea,0x0e,0xa0,0xf1,0x1a,0xde,0x1e,0xb2,0xa9,0xb3,0x97,0xbc,0xaa,0xad,0x10,0x61,0x27,0x0e,0xcf,0x49,0x78,0x03,0xa5,0xfc,0xe7,0xf4,0x1e,0x65,0x04,0xfb,0xec,0x71,0xa7,0xde,0x7d,0x06,0x6b,0x82,0x61,0x86,0x8a,0xfc,0x49,0xb9,0xe6,0x85,0xf0,0xdc,0xce,0x75,0xe2,0xfc,0xb3,0xba,0x8c,0xf1,0x90,0x57,0xe3,0x94,0x15,0x76,0xba,0xf5,0x8f,0xb8,0x21,0xbd,0x42,0x68,0xf7,0xfa,0xe3,0x02,0x86,0x01,0xda,0x02,0x2e,0x9b,0x46,0x86,0x46,0xab,0xdb,0x4f,0xa6,0x09,0x8a,0x44,0x9b,0x42,0x67,0xd5,0x09,0xd9,0xa3,0x3f,0x4c,0x3e,0xbc,0xc3,0x2d,0xac,0x09,0x4d,0x48,0xed,0x60,0x0e,0x76,0x57,0x87,0xfb,0x92,0xb1,0x97,0x4f,0x74,0xf7,0xbb,0x4c,0x66,0xeb,0x2b,0xbd,0x02,0x89,0x5e,0x6a,0x38,0x1c,0x1c,0x45,0x2e,0xaa,0xb1,0xae,0x47,0x31,0xcf,0x63,0x2f,0x61,0xae,0x2c,0x90,0x59,0x21,0x17,0x4a,0x3b,0xc9,0xbb,0x4c,0xdc,0x89,0xd6,0x30,0x26,0x4b,0x61,0x49,0x88,0xf3,0xab,0xbe,0xa1,0xbd,0x61,0x7f,0xfa,0x53,0xd7,0x1b,0x7d,0x8a,0x37,0x14,0x62,0xb7,0x73,0x35,0x1a,0x2d,0xcc,0xae,0xdd,0x7f,0x59,0xcd,0x72,0x8f,0xad,0xee,0x05,0x90,0x67,0xbd,0x80,0xc9,0x4c,0x8c,0x9a,0x1f,0xfc,0xa2,0xdc,0x4f,0x84,0x8b,0x82,0x9c,0x05,0x61,0x38,0x5a,0xa8,0x2c,0xc9,0x85,0x03,0xd0,0xbb,0x66,0xa6,0xaa,0x4f,0xae,0x07,0x03,0xd1,0x2e,0x60,0xe1,0x46,0x0e,0xfb,0xbc,0xdf,0x24,0x12,0xc1,0x3e,0x7c,0x68,0x4d,0x1b,0x01,0x10,0x20,0x26,0x34,0x3a,0x41,0x43,0x44,0x58,0x5f,0x6e,0x70,0x72,0x74,0x8b,0xae,0xb5,0xbb,0xc6,0xd1,0xe2,0xef,0xfb,0xfe,0x06,0x0e,0x2e,0x3e,0x51,0x60,0x79,0x7c,0x9e,0xa6,0xba,0xc7,0xf1,0x10,0x24,0x40,0x4a,0x52,0x57,0x5f,0x6c,0x89,0x8c,0x97,0xaa,0xb2,0xc3,0xcc,0xea,0xf2,0x2f,0x3f,0x53,0x5f,0x7b,0x81,0x83,0x96,0xa1,0xb1,0xbc,0xe6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x25,0x36,0x42]; + let mldsa44_pk = MLDSA44PublicKey::from_bytes(&[ + 0xD7, 0xB2, 0xB4, 0x72, 0x54, 0xAA, 0xE0, 0xDB, 0x45, 0xE7, 0x93, 0x0D, 0x4A, 0x98, 0xD2, + 0xC9, 0x7D, 0x8F, 0x13, 0x97, 0xD1, 0x78, 0x9D, 0xAF, 0xA1, 0x70, 0x24, 0xB3, 0x16, 0xE9, + 0xBE, 0xC9, 0x4F, 0xC9, 0x94, 0x6D, 0x42, 0xF1, 0x9B, 0x79, 0xA7, 0x41, 0x3B, 0xBA, 0xA3, + 0x3E, 0x71, 0x49, 0xCB, 0x42, 0xED, 0x51, 0x15, 0x69, 0x3A, 0xC0, 0x41, 0xFA, 0xCB, 0x98, + 0x8A, 0xDE, 0xB5, 0xFE, 0x0E, 0x1D, 0x86, 0x31, 0x18, 0x49, 0x95, 0xB5, 0x92, 0xC3, 0x97, + 0xD2, 0x29, 0x4E, 0x2E, 0x14, 0xF9, 0x0A, 0xA4, 0x14, 0xBA, 0x38, 0x26, 0x89, 0x9A, 0xC4, + 0x3F, 0x4C, 0xCC, 0xAC, 0xBC, 0x26, 0xE9, 0xA8, 0x32, 0xB9, 0x51, 0x18, 0xD5, 0xCB, 0x43, + 0x3C, 0xBE, 0xF9, 0x66, 0x0B, 0x00, 0x13, 0x8E, 0x08, 0x17, 0xF6, 0x1E, 0x76, 0x2C, 0xA2, + 0x74, 0xC3, 0x6A, 0xD5, 0x54, 0xEB, 0x22, 0xAA, 0xC1, 0x16, 0x2E, 0x4A, 0xB0, 0x1A, 0xCB, + 0xA1, 0xE3, 0x8C, 0x4E, 0xFD, 0x8F, 0x80, 0xB6, 0x5B, 0x33, 0x3D, 0x0F, 0x72, 0xE5, 0x5D, + 0xFE, 0x71, 0xCE, 0x9C, 0x1E, 0xBB, 0x98, 0x89, 0xE7, 0xC5, 0x61, 0x06, 0xC0, 0xFD, 0x73, + 0x80, 0x3A, 0x2A, 0xEC, 0xFE, 0xAF, 0xDE, 0xD7, 0xAA, 0x3C, 0xB2, 0xCE, 0xDA, 0x54, 0xD1, + 0x2B, 0xD8, 0xCD, 0x36, 0xA7, 0x8C, 0xF9, 0x75, 0x94, 0x3B, 0x47, 0xAB, 0xD2, 0x5E, 0x88, + 0x0A, 0xC4, 0x52, 0xE5, 0x74, 0x2E, 0xD1, 0xE8, 0xD1, 0xA8, 0x2A, 0xFA, 0x86, 0xE5, 0x90, + 0xC7, 0x58, 0xC1, 0x5A, 0xE4, 0xD2, 0x84, 0x0D, 0x92, 0xBC, 0xA1, 0xA5, 0x09, 0x0F, 0x40, + 0x49, 0x65, 0x97, 0xFC, 0xA7, 0xD8, 0xB9, 0x51, 0x3F, 0x1A, 0x1B, 0xDA, 0x6E, 0x95, 0x0A, + 0xAA, 0x98, 0xDE, 0x46, 0x75, 0x07, 0xD4, 0xA4, 0xF5, 0xA4, 0xF0, 0x59, 0x92, 0x16, 0x58, + 0x2C, 0x35, 0x72, 0xF6, 0x2E, 0xDA, 0x89, 0x05, 0xAB, 0x35, 0x81, 0x67, 0x0C, 0x4A, 0x02, + 0x77, 0x7A, 0x33, 0xE0, 0xCA, 0x72, 0x95, 0xFD, 0x8F, 0x4F, 0xF6, 0xD1, 0xA0, 0xA3, 0xA7, + 0x68, 0x3D, 0x65, 0xF5, 0xF5, 0xF7, 0xFC, 0x60, 0xDA, 0x02, 0x3E, 0x82, 0x6C, 0x5F, 0x92, + 0x14, 0x4C, 0x02, 0xF7, 0xD1, 0xBA, 0x10, 0x75, 0x98, 0x75, 0x53, 0xEA, 0x93, 0x67, 0xFC, + 0xD7, 0x6D, 0x99, 0x0B, 0x7F, 0xA9, 0x9C, 0xD4, 0x5A, 0xFD, 0xB8, 0x83, 0x6D, 0x43, 0xE4, + 0x59, 0xF5, 0x18, 0x7D, 0xF0, 0x58, 0x47, 0x97, 0x09, 0xA0, 0x1E, 0xA6, 0x83, 0x59, 0x35, + 0xFA, 0x70, 0x46, 0x09, 0x90, 0xCD, 0x3D, 0xC1, 0xBA, 0x40, 0x1B, 0xA9, 0x4B, 0xAB, 0x1D, + 0xDE, 0x41, 0xAC, 0x67, 0xAB, 0x33, 0x19, 0xDC, 0xAC, 0xA0, 0x60, 0x48, 0xD4, 0xC4, 0xEE, + 0xF2, 0x7E, 0xE1, 0x3A, 0x9C, 0x17, 0xD0, 0x53, 0x8F, 0x43, 0x0F, 0x2D, 0x64, 0x2D, 0xC2, + 0x41, 0x56, 0x60, 0xDE, 0x78, 0x87, 0x7D, 0x8D, 0x8A, 0xBC, 0x72, 0x52, 0x39, 0x78, 0xC0, + 0x42, 0xE4, 0x28, 0x5F, 0x43, 0x19, 0x84, 0x6C, 0x44, 0x12, 0x62, 0x42, 0x97, 0x68, 0x44, + 0xC1, 0x0E, 0x55, 0x6B, 0xA2, 0x15, 0xB5, 0xA7, 0x19, 0xE5, 0x9D, 0x0C, 0x6B, 0x2A, 0x96, + 0xD3, 0x98, 0x59, 0x07, 0x1F, 0xDC, 0xC2, 0xCD, 0xE7, 0x52, 0x4A, 0x7B, 0xED, 0xAE, 0x54, + 0xE8, 0x5B, 0x31, 0x8E, 0x85, 0x4E, 0x8F, 0xE2, 0xB2, 0xF3, 0xED, 0xFA, 0xC9, 0x71, 0x91, + 0x28, 0x27, 0x0A, 0xAF, 0xD1, 0xE5, 0x04, 0x4C, 0x3A, 0x4F, 0xDA, 0xFD, 0x9F, 0xF3, 0x1F, + 0x90, 0x78, 0x4B, 0x8E, 0x8E, 0x45, 0x96, 0x14, 0x4A, 0x0D, 0xAF, 0x58, 0x65, 0x11, 0xD3, + 0xD9, 0x96, 0x2B, 0x9E, 0xA9, 0x5A, 0xF1, 0x97, 0xB4, 0xE5, 0xFC, 0x60, 0xF2, 0xB1, 0xED, + 0x15, 0xDE, 0x3A, 0x5B, 0xEF, 0x5F, 0x89, 0xBD, 0xC7, 0x9D, 0x91, 0x05, 0x1D, 0x9B, 0x28, + 0x16, 0xE7, 0x4F, 0xA5, 0x45, 0x31, 0xEF, 0xDC, 0x1C, 0xBE, 0x74, 0xD4, 0x48, 0x85, 0x7F, + 0x47, 0x6B, 0xCD, 0x58, 0xF2, 0x1C, 0x0B, 0x65, 0x3B, 0x3B, 0x76, 0xA4, 0xE0, 0x76, 0xA6, + 0x55, 0x9A, 0x30, 0x27, 0x18, 0x55, 0x5C, 0xC6, 0x3F, 0x74, 0x85, 0x9A, 0xAB, 0xAB, 0x92, + 0x5F, 0x02, 0x38, 0x61, 0xCA, 0x8C, 0xD0, 0xF7, 0xBA, 0xDB, 0x28, 0x71, 0xF6, 0x7D, 0x55, + 0x32, 0x6D, 0x74, 0x51, 0x13, 0x5A, 0xD4, 0x5F, 0x4A, 0x1B, 0xA6, 0x91, 0x18, 0xFB, 0xB2, + 0xC8, 0xA3, 0x0E, 0xEC, 0x93, 0x92, 0xEF, 0x3F, 0x97, 0x70, 0x66, 0xC9, 0xAD, 0xD5, 0xC7, + 0x10, 0xCC, 0x64, 0x7B, 0x15, 0x14, 0xD2, 0x17, 0xD9, 0x58, 0xC7, 0x01, 0x7C, 0x3E, 0x90, + 0xFD, 0x20, 0xC0, 0x4E, 0x67, 0x4B, 0x90, 0x48, 0x6E, 0x93, 0x70, 0xA3, 0x1A, 0x00, 0x1D, + 0x32, 0xF4, 0x73, 0x97, 0x9E, 0x49, 0x06, 0x74, 0x9E, 0x7E, 0x47, 0x7F, 0xA0, 0xB7, 0x45, + 0x08, 0xF8, 0xA5, 0xF2, 0x37, 0x83, 0x12, 0xB8, 0x3C, 0x25, 0xBD, 0x38, 0x8C, 0xA0, 0xB0, + 0xFF, 0xF7, 0x47, 0x8B, 0xAF, 0x42, 0xB7, 0x16, 0x67, 0xED, 0xAA, 0xC9, 0x7C, 0x46, 0xB1, + 0x29, 0x64, 0x3E, 0x58, 0x6E, 0x5B, 0x05, 0x5A, 0x0C, 0x21, 0x19, 0x46, 0xD4, 0xF3, 0x6E, + 0x67, 0x5B, 0xED, 0x58, 0x60, 0xFA, 0x04, 0x2A, 0x31, 0x5D, 0x98, 0x26, 0x16, 0x4D, 0x6A, + 0x92, 0x37, 0xC3, 0x5A, 0x5F, 0xBF, 0x49, 0x54, 0x90, 0xA5, 0xBD, 0x4D, 0xF2, 0x48, 0xB9, + 0x5C, 0x4A, 0xAE, 0x77, 0x84, 0xB6, 0x05, 0x67, 0x31, 0x66, 0xAC, 0x42, 0x45, 0xB5, 0xB4, + 0xB0, 0x82, 0xA0, 0x9E, 0x93, 0x23, 0xE6, 0x2F, 0x20, 0x78, 0xC5, 0xB7, 0x67, 0x83, 0x44, + 0x6D, 0xEF, 0xD7, 0x36, 0xAD, 0x3A, 0x37, 0x02, 0xD4, 0x9B, 0x08, 0x98, 0x44, 0x90, 0x0A, + 0x61, 0x83, 0x33, 0x97, 0xBC, 0x44, 0x19, 0xB3, 0x0D, 0x7A, 0x97, 0xA0, 0xB3, 0x87, 0xC1, + 0x91, 0x14, 0x74, 0xC4, 0xD4, 0x1B, 0x53, 0xE3, 0x2A, 0x97, 0x7A, 0xCB, 0x6F, 0x0E, 0xA7, + 0x5D, 0xB6, 0x5B, 0xB3, 0x9E, 0x59, 0xE7, 0x01, 0xE7, 0x69, 0x57, 0xDE, 0xF6, 0xF2, 0xD4, + 0x45, 0x59, 0xC3, 0x1A, 0x77, 0x12, 0x2B, 0x52, 0x04, 0xE3, 0xB5, 0xC2, 0x19, 0xF1, 0x68, + 0x8B, 0x14, 0xED, 0x0B, 0xC0, 0xB8, 0x01, 0xB3, 0xE6, 0xE8, 0x2D, 0xCD, 0x43, 0xE9, 0xC0, + 0xE9, 0xF4, 0x17, 0x44, 0xCD, 0x98, 0x15, 0xBD, 0x1B, 0xC8, 0x82, 0x0D, 0x8B, 0xB1, 0x23, + 0xF0, 0x4F, 0xAC, 0xD1, 0xB1, 0xB6, 0x85, 0xDD, 0x5A, 0x2B, 0x1B, 0x8D, 0xBB, 0xF3, 0xED, + 0x93, 0x36, 0x70, 0xF0, 0x95, 0xA1, 0x80, 0xB4, 0xF1, 0x92, 0xD0, 0x8B, 0x10, 0xB8, 0xFA, + 0xBB, 0xDF, 0xCC, 0x2B, 0x24, 0x51, 0x8E, 0x32, 0xEE, 0xA0, 0xA5, 0xE0, 0xC9, 0x04, 0xCA, + 0x84, 0x47, 0x80, 0x08, 0x3F, 0x3B, 0x0C, 0xD2, 0xD0, 0xB8, 0xB6, 0xAF, 0x67, 0xBC, 0x35, + 0x5B, 0x94, 0x94, 0x02, 0x5D, 0xC7, 0xB0, 0xA7, 0x8F, 0xA8, 0x0E, 0x3A, 0x2D, 0xBF, 0xEB, + 0x51, 0x32, 0x88, 0x51, 0xD6, 0x07, 0x81, 0x98, 0xE9, 0x49, 0x36, 0x51, 0xAE, 0x78, 0x7E, + 0xC0, 0x25, 0x1F, 0x92, 0x2B, 0xA3, 0x0E, 0x9F, 0x51, 0xDF, 0x62, 0xA6, 0xD7, 0x27, 0x84, + 0xCF, 0x3D, 0xD2, 0x05, 0x39, 0x31, 0x76, 0xDF, 0xA3, 0x24, 0xA5, 0x12, 0xBD, 0x94, 0x97, + 0x0A, 0x36, 0xDD, 0x34, 0xA5, 0x14, 0xA8, 0x67, 0x91, 0xF0, 0xEB, 0x36, 0xF0, 0x14, 0x5B, + 0x09, 0xAB, 0x64, 0x65, 0x1B, 0x4A, 0x03, 0x13, 0xB2, 0x99, 0x61, 0x1A, 0x2A, 0x1C, 0x48, + 0x89, 0x16, 0x27, 0x59, 0x87, 0x68, 0xA3, 0x11, 0x40, 0x60, 0xBA, 0x44, 0x43, 0x48, 0x6D, + 0xF5, 0x15, 0x22, 0xA1, 0xCE, 0x88, 0xB3, 0x09, 0x85, 0xC2, 0x16, 0xF8, 0xE6, 0xED, 0x17, + 0x8D, 0xD5, 0x67, 0xB3, 0x04, 0xA0, 0xD4, 0xCA, 0xFB, 0xA8, 0x82, 0xA2, 0x83, 0x42, 0xF1, + 0x7A, 0x9A, 0xA2, 0x6A, 0xE5, 0x8D, 0xB6, 0x30, 0x08, 0x3D, 0x2C, 0x35, 0x8F, 0xDF, 0x56, + 0x6C, 0x3F, 0x5D, 0x62, 0xA4, 0x28, 0x56, 0x7B, 0xC9, 0xEA, 0x8C, 0xE9, 0x5C, 0xAA, 0x0F, + 0x35, 0x47, 0x4B, 0x0B, 0xFA, 0x8F, 0x33, 0x9A, 0x25, 0x0A, 0xB4, 0xDF, 0xCF, 0x20, 0x83, + 0xBE, 0x8E, 0xEF, 0xBC, 0x10, 0x55, 0xE1, 0x8F, 0xE1, 0x53, 0x70, 0xEE, 0xCB, 0x26, 0x05, + 0x66, 0xD8, 0x3F, 0xF0, 0x6B, 0x21, 0x1A, 0xAE, 0xC4, 0x3C, 0xA2, 0x9B, 0x54, 0xCC, 0xD0, + 0x0F, 0x88, 0x15, 0xA2, 0x46, 0x5E, 0xF0, 0xB4, 0x65, 0x15, 0xCC, 0x7E, 0x41, 0xF3, 0x12, + 0x4F, 0x09, 0xEF, 0xFF, 0x73, 0x93, 0x09, 0xAB, 0x58, 0xB2, 0x9A, 0x14, 0x59, 0xA0, 0x0B, + 0xCE, 0x50, 0x38, 0xE9, 0x38, 0xC9, 0x67, 0x8F, 0x72, 0xEB, 0x0E, 0x4E, 0xE5, 0xFD, 0xAA, + 0xE6, 0x6D, 0x9F, 0x85, 0x73, 0xFC, 0x97, 0xFC, 0x42, 0xB4, 0x95, 0x9F, 0x4B, 0xF8, 0xB6, + 0x1D, 0x78, 0x43, 0x3E, 0x86, 0xB0, 0x33, 0x5D, 0x6E, 0x91, 0x91, 0xC4, 0xD8, 0xBF, 0x48, + 0x7B, 0x39, 0x05, 0xC1, 0x08, 0xCF, 0xD6, 0xAC, 0x24, 0xB0, 0xCE, 0xB7, 0xDC, 0xB7, 0xCF, + 0x51, 0xF8, 0x4D, 0x0E, 0xD6, 0x87, 0xB9, 0x5E, 0xAE, 0xB1, 0xC5, 0x33, 0xC0, 0x6F, 0x0D, + 0x97, 0x02, 0x3D, 0x92, 0xA7, 0x08, 0x25, 0x83, 0x7B, 0x59, 0xBA, 0x6C, 0xB7, 0xD4, 0xE5, + 0x6B, 0x0A, 0x87, 0xC2, 0x03, 0x86, 0x2A, 0xE8, 0xF3, 0x15, 0xBA, 0x59, 0x25, 0xE8, 0xED, + 0xEF, 0xA6, 0x79, 0x36, 0x9A, 0x22, 0x02, 0x76, 0x61, 0x51, 0xF1, 0x6A, 0x96, 0x5F, 0x9F, + 0x81, 0xEC, 0xE7, 0x6C, 0xC0, 0x70, 0xB5, 0x58, 0x69, 0xE4, 0xDB, 0x97, 0x84, 0xCF, 0x05, + 0xC8, 0x30, 0xB3, 0x24, 0x2C, 0x83, 0x12, + ]) + .unwrap(); + let sig: [u8; MLDSA44_SIG_LEN] = [ + 0x5E, 0x93, 0xB7, 0x85, 0xC5, 0x11, 0x9C, 0x39, 0x83, 0xA2, 0x91, 0xB1, 0x84, 0x20, 0xFD, + 0xBE, 0x4B, 0xCA, 0x53, 0xD5, 0xA3, 0x73, 0x29, 0x22, 0xFA, 0xAA, 0xCD, 0x5A, 0x5D, 0x32, + 0xA7, 0x45, 0xC7, 0x8D, 0x10, 0x5B, 0xA1, 0x0B, 0xEE, 0x1E, 0xD8, 0x06, 0x9F, 0x19, 0xE6, + 0xC5, 0x37, 0xBD, 0xA1, 0x6E, 0x89, 0xD3, 0x90, 0x04, 0xC3, 0x59, 0xD1, 0xFD, 0x38, 0x1A, + 0x02, 0x91, 0xF1, 0xC5, 0x1F, 0x1C, 0x38, 0xED, 0xCD, 0xB3, 0x15, 0xC8, 0xC6, 0x95, 0x70, + 0xD8, 0xF2, 0x5F, 0x16, 0x55, 0xBA, 0x8E, 0xA8, 0x3A, 0xFF, 0x24, 0xB8, 0xB6, 0xBE, 0x8D, + 0xE7, 0x62, 0x34, 0x2E, 0x34, 0x7E, 0xAB, 0x2C, 0xAA, 0x68, 0x03, 0xED, 0x70, 0x59, 0x52, + 0xDD, 0x64, 0x50, 0xC5, 0x18, 0x5E, 0x9D, 0x60, 0xCE, 0x96, 0xE8, 0xDC, 0xA4, 0x23, 0xA0, + 0x2F, 0x64, 0x6C, 0xEA, 0x69, 0x01, 0x64, 0xA2, 0x26, 0xE4, 0xC3, 0xD6, 0xA5, 0x15, 0xCE, + 0x16, 0x29, 0x0F, 0x19, 0xB2, 0xC6, 0x26, 0xDA, 0x9B, 0x45, 0x0E, 0xCF, 0x66, 0x50, 0x13, + 0xC5, 0xE2, 0x26, 0xB6, 0xC0, 0xAC, 0x5C, 0x07, 0xCE, 0x90, 0xE2, 0x78, 0xF1, 0xB0, 0x13, + 0x4E, 0x38, 0x5D, 0x13, 0xE7, 0x42, 0x08, 0xA0, 0xB3, 0xFF, 0x05, 0x2A, 0x36, 0x25, 0x79, + 0xF9, 0x20, 0x7E, 0xA0, 0x1F, 0x18, 0xA0, 0x39, 0xAA, 0x1B, 0x97, 0xAE, 0x34, 0x52, 0x67, + 0x5B, 0x62, 0x07, 0x71, 0xF8, 0x01, 0x2E, 0xE7, 0xA4, 0xE5, 0x5C, 0x98, 0xBF, 0xD2, 0x01, + 0x9E, 0xD8, 0xA3, 0xB0, 0x0A, 0xCE, 0xA8, 0xE8, 0xAB, 0x28, 0x17, 0x2F, 0xAA, 0x42, 0xCA, + 0x1F, 0xDA, 0x83, 0xC5, 0xFF, 0xE8, 0x1A, 0x45, 0xBE, 0x73, 0x6B, 0xDE, 0xDD, 0x5F, 0xB3, + 0x00, 0xCE, 0x17, 0x07, 0x8B, 0x38, 0x0F, 0x62, 0x0B, 0xDE, 0xEB, 0xAD, 0x69, 0x36, 0x01, + 0x37, 0x2C, 0x85, 0xEA, 0xCF, 0x79, 0xBC, 0x98, 0xE1, 0xB4, 0x8F, 0x2A, 0xD7, 0xE5, 0xDC, + 0xE4, 0x27, 0x9A, 0x12, 0x95, 0xBB, 0x2B, 0xA6, 0x0A, 0x0C, 0x5E, 0x37, 0x26, 0x64, 0x2D, + 0x23, 0x36, 0xC5, 0xEB, 0x1D, 0x37, 0xC8, 0x62, 0x3C, 0x75, 0x58, 0x24, 0x13, 0x18, 0xD8, + 0x9B, 0xC7, 0x83, 0xC4, 0xF0, 0x00, 0x98, 0x07, 0x74, 0x84, 0x62, 0x3C, 0x21, 0x75, 0x60, + 0xA0, 0xC7, 0xAA, 0xF7, 0x5D, 0xCA, 0xCC, 0xB7, 0x8E, 0xE6, 0x9C, 0x20, 0x7C, 0x27, 0xC8, + 0xBF, 0x39, 0x65, 0xCC, 0xF5, 0x8A, 0x80, 0xC8, 0x8E, 0xFC, 0xC7, 0xE5, 0xDE, 0xB3, 0x61, + 0x5D, 0x50, 0x45, 0xA7, 0x41, 0xC4, 0xDA, 0xC0, 0xA0, 0x21, 0xDD, 0x06, 0x0D, 0x31, 0x5D, + 0x4E, 0xC2, 0x85, 0x7E, 0xB6, 0x64, 0xD7, 0x28, 0xD0, 0xAF, 0x97, 0x3B, 0xEA, 0x07, 0xE1, + 0xCA, 0x56, 0x3F, 0xAA, 0x0E, 0x19, 0x99, 0x6C, 0xEA, 0x37, 0x70, 0x31, 0x6C, 0x11, 0xA5, + 0x06, 0x66, 0x65, 0x66, 0x20, 0x05, 0xAC, 0xE9, 0x8F, 0x61, 0x10, 0xE8, 0x83, 0xBA, 0xE0, + 0x60, 0xDA, 0xA7, 0xB6, 0xD8, 0x33, 0x79, 0xE0, 0x87, 0x87, 0x96, 0x69, 0x17, 0x08, 0xA3, + 0x2B, 0x85, 0x73, 0x0D, 0xE8, 0xB9, 0x2D, 0x89, 0xF9, 0x0A, 0x36, 0x60, 0xC9, 0x49, 0x16, + 0x5B, 0x14, 0x61, 0x25, 0x67, 0x66, 0x2E, 0x16, 0x22, 0x32, 0x29, 0x6C, 0xBD, 0x14, 0x35, + 0x17, 0xA2, 0x82, 0xE2, 0x2C, 0x46, 0xB6, 0x36, 0x06, 0xD3, 0xC1, 0x4E, 0xD4, 0x55, 0x9A, + 0x5A, 0x1C, 0x45, 0x9B, 0xAB, 0x7F, 0x35, 0x50, 0x07, 0xAD, 0x6F, 0x7E, 0x3B, 0x1E, 0x07, + 0x44, 0x5D, 0xFC, 0x96, 0xBD, 0x9B, 0x75, 0x08, 0x0B, 0x3D, 0x4F, 0x68, 0x99, 0x84, 0x90, + 0xA2, 0x6B, 0x5E, 0x09, 0x0B, 0xE2, 0x67, 0x40, 0x71, 0xAB, 0x92, 0x5B, 0xB6, 0x50, 0x59, + 0x08, 0x56, 0xC5, 0x9F, 0x8B, 0xA7, 0x48, 0x8D, 0x2B, 0x72, 0xF8, 0x40, 0xAC, 0x3E, 0xAF, + 0xE4, 0xDD, 0x91, 0xF0, 0xF5, 0x1C, 0x43, 0x64, 0x11, 0x2C, 0x1A, 0x13, 0x9E, 0x3E, 0x94, + 0x2A, 0x59, 0x7B, 0x93, 0xA1, 0xE3, 0xF4, 0xFA, 0xDE, 0xD1, 0x29, 0xC1, 0x4B, 0x59, 0x78, + 0xB3, 0x15, 0xE2, 0x24, 0x6A, 0x93, 0x14, 0x6A, 0x79, 0x36, 0x5F, 0x0F, 0x59, 0x7A, 0x18, + 0x34, 0x0C, 0xCA, 0x86, 0xBB, 0x15, 0xCE, 0xED, 0x39, 0xF1, 0x75, 0xEA, 0xB1, 0xE5, 0x46, + 0x53, 0x5A, 0xFB, 0x96, 0x6F, 0x0A, 0x65, 0xA8, 0xF6, 0x6F, 0x73, 0x7A, 0xB0, 0x28, 0x97, + 0xED, 0xDF, 0xE9, 0x2C, 0xF7, 0x78, 0x68, 0x94, 0x84, 0x3C, 0x26, 0x91, 0x46, 0x47, 0x76, + 0xC9, 0x4B, 0xD4, 0x50, 0xA1, 0x06, 0x91, 0x38, 0xB2, 0x6D, 0xF8, 0x3B, 0x2D, 0x1D, 0xD8, + 0x01, 0x14, 0x3A, 0x8F, 0xDF, 0xDC, 0x25, 0x14, 0xCC, 0x5B, 0x58, 0x31, 0xAB, 0x53, 0xA7, + 0x5C, 0x55, 0xEF, 0x29, 0xF4, 0x0E, 0x7C, 0x63, 0xD2, 0xC7, 0x2A, 0xBE, 0x97, 0xE2, 0xAF, + 0x14, 0x85, 0x3B, 0xE4, 0x9B, 0xE1, 0x6F, 0x47, 0x30, 0xA1, 0x59, 0x97, 0x49, 0x70, 0x95, + 0x14, 0x39, 0xE5, 0x5C, 0x15, 0x89, 0xD0, 0xF4, 0xA1, 0x62, 0xE3, 0x51, 0x7D, 0xF9, 0xD7, + 0xAB, 0xC9, 0x8D, 0x8A, 0x30, 0x72, 0x16, 0xE7, 0xF1, 0xCB, 0x46, 0x27, 0xC9, 0x17, 0x5C, + 0x0E, 0xEF, 0x23, 0x33, 0x7E, 0x56, 0xD5, 0x28, 0x1B, 0x83, 0x72, 0x6F, 0xFF, 0x40, 0xA1, + 0x48, 0xB0, 0xC4, 0x8E, 0x8D, 0xF3, 0x49, 0x6A, 0x21, 0x18, 0xD8, 0x02, 0x19, 0xAE, 0xF8, + 0xF4, 0x0B, 0x29, 0xFB, 0xA1, 0xF2, 0xF7, 0x87, 0x86, 0xB6, 0x7F, 0xFB, 0x7B, 0x7D, 0x47, + 0xD4, 0x06, 0xB7, 0x65, 0xBD, 0x13, 0x66, 0x10, 0xBE, 0xDE, 0xB9, 0x5C, 0xD7, 0x32, 0x1F, + 0x58, 0xF3, 0xB8, 0x36, 0xC9, 0x25, 0x8B, 0xE3, 0x5D, 0x78, 0xB4, 0x98, 0xF3, 0xEF, 0xE1, + 0xDB, 0x2B, 0x24, 0x3D, 0x73, 0x4F, 0xAB, 0x15, 0x9B, 0xAE, 0xD8, 0x80, 0x7C, 0x3C, 0xCC, + 0xF8, 0x3E, 0xB2, 0xEA, 0xF8, 0xA9, 0xAF, 0x01, 0xA5, 0x18, 0xD4, 0x8C, 0x60, 0xE9, 0x1A, + 0x96, 0x81, 0x2A, 0xD6, 0x89, 0xC2, 0xD8, 0x3C, 0xC4, 0xE8, 0xE9, 0xB3, 0x65, 0x04, 0x22, + 0xBE, 0xD6, 0xF1, 0x3C, 0x24, 0xAD, 0xAA, 0xD9, 0x1C, 0x95, 0xB3, 0xE3, 0xCF, 0x35, 0x4F, + 0x0F, 0x6B, 0xC9, 0xEE, 0x89, 0x41, 0xA6, 0xB1, 0x5B, 0x69, 0x75, 0x13, 0x1D, 0x95, 0x23, + 0x3D, 0x89, 0x35, 0xDE, 0x36, 0x7E, 0xFC, 0x6D, 0x86, 0xA4, 0x5D, 0xAC, 0x7D, 0x0F, 0x1D, + 0xDD, 0x9A, 0xEB, 0xD2, 0xC5, 0x9C, 0x02, 0x7F, 0xCD, 0xA4, 0x48, 0x80, 0x1E, 0x93, 0xE7, + 0x33, 0xAC, 0xA5, 0x18, 0x74, 0xBE, 0x9A, 0xB9, 0x27, 0xA9, 0x04, 0xF9, 0x6D, 0xDB, 0x7A, + 0x46, 0xB2, 0xDA, 0x13, 0x26, 0x1D, 0x52, 0x2B, 0x23, 0xC9, 0x50, 0xC0, 0x1D, 0x5F, 0x5E, + 0x11, 0x2B, 0x76, 0xF8, 0x51, 0xFF, 0x23, 0x4F, 0x06, 0xF8, 0xD5, 0xE6, 0x5B, 0x13, 0x19, + 0xAB, 0xCD, 0x79, 0xA1, 0x80, 0xAE, 0x06, 0x3D, 0x65, 0xB2, 0x8C, 0x74, 0x58, 0x78, 0xC0, + 0x6D, 0xBB, 0x69, 0xBA, 0x73, 0x29, 0x3E, 0xAB, 0x34, 0x43, 0x4B, 0xF1, 0xA9, 0x2F, 0xBA, + 0x69, 0x19, 0x93, 0xBD, 0x0F, 0xF3, 0xED, 0xAC, 0x76, 0xA1, 0x2F, 0x80, 0xC0, 0xAD, 0xA4, + 0xB1, 0x96, 0x9C, 0x76, 0x65, 0x58, 0x9D, 0x53, 0x0A, 0x67, 0x01, 0x6A, 0x62, 0x54, 0x03, + 0xC5, 0x37, 0x03, 0x29, 0x04, 0xF2, 0xE1, 0x04, 0x54, 0x7C, 0xD3, 0xEA, 0x40, 0x62, 0x60, + 0xDD, 0x35, 0x7F, 0xA0, 0x6E, 0xA0, 0x12, 0xA7, 0x85, 0x82, 0x6C, 0x16, 0x0E, 0x99, 0xFF, + 0xD0, 0x65, 0xB0, 0xE3, 0xF3, 0x3C, 0x76, 0x89, 0xD3, 0x55, 0x2A, 0xB9, 0xE2, 0xE0, 0x9F, + 0xA7, 0xE5, 0x5B, 0xBC, 0xEF, 0x04, 0x22, 0x42, 0xBC, 0xAC, 0xAD, 0x8A, 0x3D, 0xA4, 0x7B, + 0xCC, 0x54, 0xA1, 0x21, 0xF1, 0x52, 0x6C, 0x8C, 0xD4, 0xCC, 0x5A, 0x89, 0x2A, 0x81, 0x31, + 0xCF, 0x4E, 0xEF, 0xAF, 0x42, 0x48, 0xDD, 0xD6, 0xA1, 0x1E, 0xC4, 0x27, 0xBA, 0x37, 0x8A, + 0xAE, 0x89, 0xAA, 0xF5, 0x82, 0xCE, 0x1F, 0x4E, 0x32, 0x69, 0x0A, 0x55, 0x5E, 0x74, 0x07, + 0x61, 0xD3, 0x58, 0xAD, 0x4E, 0x92, 0xBC, 0x38, 0x41, 0x8A, 0xA7, 0x82, 0xDA, 0x91, 0x65, + 0x24, 0xFB, 0x09, 0xAB, 0x2C, 0xA6, 0xB3, 0xD3, 0x11, 0x3D, 0x6F, 0x2C, 0x2A, 0x6A, 0x9B, + 0x9D, 0x29, 0xD4, 0xE7, 0x48, 0x92, 0x55, 0x25, 0x2A, 0xF0, 0x75, 0xCB, 0xF9, 0xFE, 0xAC, + 0xED, 0xAE, 0x6F, 0x3E, 0xC0, 0xB0, 0x70, 0x82, 0x46, 0x89, 0xDD, 0x3C, 0x78, 0xAC, 0x14, + 0x3E, 0xD6, 0x77, 0x6D, 0x95, 0xDD, 0x8F, 0x13, 0xD4, 0x35, 0xA2, 0x90, 0xBD, 0xCA, 0x4C, + 0x11, 0x31, 0x8E, 0x5A, 0xCC, 0xE0, 0x44, 0x69, 0x64, 0x4E, 0x13, 0x74, 0xA9, 0x45, 0x1B, + 0x62, 0x04, 0xF3, 0xB3, 0x96, 0x1B, 0x7D, 0xD2, 0x39, 0xE3, 0x06, 0xFE, 0xF5, 0xF4, 0xF4, + 0xE5, 0x1B, 0x78, 0xB0, 0xFB, 0x9D, 0xCE, 0xE6, 0x9C, 0x3E, 0x79, 0x0B, 0x23, 0x1F, 0x2E, + 0x65, 0xFD, 0x1A, 0xB1, 0xC2, 0xA7, 0x5B, 0x07, 0x06, 0x7D, 0x5C, 0x16, 0xDD, 0xE0, 0x09, + 0x83, 0xA5, 0x8F, 0xFC, 0xDA, 0xAA, 0xEE, 0x16, 0xD2, 0x74, 0x2E, 0x13, 0x3E, 0xD7, 0x37, + 0xB4, 0x80, 0x64, 0xC8, 0xA3, 0x8E, 0xCA, 0x35, 0xAB, 0x3F, 0xA1, 0x8F, 0x6D, 0x62, 0xF6, + 0x42, 0xB1, 0x2C, 0xFD, 0xC7, 0x98, 0x0F, 0x2A, 0xB7, 0xDB, 0x32, 0x1F, 0xEC, 0x9D, 0xCF, + 0xE4, 0x99, 0xB4, 0xFC, 0x1E, 0xE7, 0xEB, 0x29, 0x79, 0x54, 0x05, 0x66, 0x17, 0xC6, 0x0A, + 0x66, 0x40, 0xB9, 0x28, 0x35, 0xD1, 0x65, 0xC3, 0xC0, 0x0A, 0x95, 0x19, 0x52, 0x61, 0x44, + 0x88, 0xD5, 0x65, 0x7B, 0xA0, 0xB5, 0xE9, 0x0A, 0xE9, 0xE0, 0xEF, 0x7B, 0x3B, 0x9E, 0xCA, + 0xEB, 0xD8, 0x1B, 0x85, 0x51, 0xB6, 0xD7, 0x0E, 0x83, 0x5B, 0x27, 0x34, 0x76, 0x16, 0x39, + 0xD4, 0x2E, 0x76, 0xFF, 0xC5, 0xB3, 0x27, 0x2B, 0x61, 0xC8, 0x96, 0xB4, 0x5B, 0x4B, 0xD1, + 0x8F, 0x30, 0xE5, 0x8C, 0x44, 0x06, 0x43, 0xBA, 0x15, 0x92, 0x21, 0xCC, 0x67, 0x39, 0xA1, + 0x9A, 0x65, 0xF2, 0x91, 0x1F, 0xAE, 0x47, 0xB0, 0xD4, 0xCA, 0xC4, 0x20, 0x0A, 0x6F, 0x04, + 0x3B, 0x17, 0xA0, 0x3A, 0xD3, 0x93, 0xEC, 0xB8, 0x23, 0xED, 0x03, 0xC8, 0xB6, 0xCD, 0x68, + 0x16, 0x7E, 0x6C, 0x82, 0x34, 0xF7, 0x43, 0x25, 0x57, 0xDB, 0x27, 0x20, 0x79, 0xEE, 0x89, + 0x9A, 0xED, 0xE7, 0x3B, 0x6B, 0x98, 0xD6, 0x00, 0x3F, 0x45, 0x78, 0x9A, 0x14, 0x1B, 0x60, + 0xD6, 0xDB, 0x40, 0xCD, 0x2A, 0x59, 0x74, 0x57, 0x1A, 0x4A, 0xD3, 0x66, 0x7B, 0x88, 0x93, + 0x18, 0xBA, 0x60, 0x28, 0x5D, 0x90, 0x3A, 0x2E, 0xAC, 0x01, 0xC2, 0x16, 0x08, 0x83, 0x8C, + 0x40, 0x90, 0x7D, 0xE6, 0xBB, 0xAB, 0xE0, 0x42, 0xCF, 0x2E, 0xCD, 0xD9, 0x7F, 0x54, 0x9F, + 0x95, 0xEC, 0x69, 0x8D, 0x79, 0x22, 0x2C, 0x65, 0xBA, 0x27, 0xC3, 0x0D, 0x33, 0x2A, 0x68, + 0xD0, 0x57, 0xAE, 0xCD, 0xC9, 0x38, 0x8A, 0xA3, 0x43, 0x20, 0xE0, 0xAA, 0x74, 0xFD, 0xBD, + 0x4D, 0x1B, 0x64, 0x3C, 0xAC, 0xE2, 0x16, 0xB6, 0xD8, 0xAD, 0x8F, 0x07, 0xA9, 0x99, 0x55, + 0xBF, 0xDB, 0x74, 0x3A, 0x86, 0xB4, 0x0F, 0xC6, 0x15, 0x27, 0xBA, 0xCA, 0x43, 0x4A, 0xC2, + 0xA7, 0xFB, 0xEA, 0xA7, 0x71, 0x11, 0xDC, 0x80, 0x98, 0xB1, 0x7E, 0x80, 0x0F, 0x59, 0xDD, + 0x77, 0xCC, 0xB0, 0xE6, 0x77, 0x07, 0xE6, 0x01, 0x23, 0xD3, 0x34, 0xE0, 0x73, 0xA2, 0xF5, + 0xA1, 0x6F, 0xFB, 0xCD, 0x70, 0x13, 0x89, 0xAD, 0xD5, 0x7C, 0x3C, 0xEC, 0xCB, 0x88, 0xB2, + 0x86, 0xAC, 0x1E, 0x6E, 0x3E, 0x64, 0x85, 0xAF, 0x1A, 0x12, 0xEA, 0x24, 0x1D, 0x14, 0xA1, + 0xB5, 0x00, 0x3D, 0x7F, 0x3B, 0xC9, 0xE9, 0x57, 0xD4, 0x48, 0x3C, 0x0F, 0x9F, 0x70, 0x3B, + 0x3A, 0x18, 0x7D, 0x55, 0xE5, 0x05, 0x81, 0x76, 0x15, 0xFB, 0xC4, 0xAE, 0x08, 0x37, 0x61, + 0x61, 0x84, 0x24, 0x5C, 0xFB, 0xA6, 0x1C, 0xE3, 0xB9, 0x29, 0xE3, 0x3F, 0x52, 0xB7, 0x1C, + 0xDD, 0x7B, 0x6A, 0x0D, 0xA5, 0x5C, 0x1F, 0x99, 0x75, 0x10, 0xB1, 0xA9, 0x00, 0x2C, 0xA4, + 0xE0, 0x67, 0x83, 0x73, 0xA3, 0xB1, 0xAB, 0x28, 0x97, 0xE6, 0xB4, 0x23, 0xF1, 0x5A, 0x44, + 0x0A, 0x63, 0x6C, 0xC8, 0x61, 0x49, 0x1E, 0xF4, 0x1A, 0xD0, 0xAA, 0x62, 0x7D, 0x8E, 0x19, + 0x8A, 0x5E, 0xE7, 0xBD, 0x7B, 0x6C, 0xB2, 0xC9, 0xCE, 0x2A, 0x8C, 0xC0, 0x15, 0xF0, 0xD2, + 0x06, 0xDE, 0x4C, 0x49, 0xE2, 0xF8, 0x7F, 0x31, 0x09, 0x54, 0xA1, 0x0D, 0x86, 0xE2, 0x94, + 0xF7, 0x42, 0xEE, 0x18, 0x6F, 0x4A, 0xE9, 0x81, 0x5F, 0x69, 0x96, 0x22, 0x79, 0x22, 0x06, + 0xCA, 0xFB, 0xA8, 0xF5, 0x62, 0x17, 0x38, 0x16, 0x0E, 0x6C, 0x5D, 0x61, 0x1A, 0x82, 0x52, + 0xC6, 0xF3, 0x50, 0x85, 0xB6, 0x04, 0xEF, 0x89, 0x51, 0x64, 0xD4, 0xEA, 0x6D, 0xDD, 0x31, + 0x0C, 0x7D, 0x8F, 0x0C, 0x87, 0x9F, 0xB1, 0xF8, 0x84, 0xC5, 0x74, 0x1D, 0x09, 0x6B, 0x3D, + 0x2D, 0xA0, 0xCE, 0x11, 0x51, 0x79, 0x0D, 0xDA, 0x88, 0x1D, 0x18, 0xCB, 0x6B, 0x19, 0xA9, + 0xFE, 0xD6, 0xF5, 0x25, 0x4B, 0x7D, 0x52, 0xD5, 0xD9, 0x2B, 0xBB, 0xE2, 0x4C, 0x9D, 0x6A, + 0x65, 0x60, 0x4A, 0x0B, 0x8E, 0xD2, 0x4A, 0xD5, 0xC1, 0x97, 0xD6, 0x83, 0xF5, 0x98, 0x74, + 0x3C, 0x96, 0xB5, 0x96, 0x0E, 0x87, 0x23, 0x73, 0x2B, 0x5B, 0xD6, 0x47, 0xE9, 0xDB, 0xEA, + 0xA8, 0x51, 0xD0, 0xE1, 0xCF, 0x6D, 0x2C, 0x07, 0x0D, 0x44, 0x42, 0x76, 0x2C, 0x28, 0x09, + 0x8C, 0x5C, 0xF5, 0xA5, 0x4B, 0x2B, 0x5E, 0x69, 0xA9, 0x9B, 0x10, 0x81, 0x5B, 0xF0, 0xF4, + 0x77, 0xBB, 0x71, 0xF0, 0xD5, 0xD3, 0xA6, 0x2B, 0xA2, 0xB3, 0xE2, 0x9B, 0xF8, 0x4D, 0x4B, + 0x4E, 0x57, 0x47, 0x07, 0xF5, 0xF7, 0x4A, 0xF7, 0x04, 0xD2, 0x77, 0xBD, 0x6C, 0xA3, 0x8D, + 0xA2, 0x1E, 0x2C, 0xDA, 0xC5, 0x49, 0xE5, 0xEA, 0xE1, 0xDE, 0x7A, 0x18, 0xEE, 0x53, 0x4C, + 0x8C, 0x22, 0x91, 0xC9, 0x08, 0xCA, 0xAB, 0xF1, 0x59, 0xE9, 0x0E, 0x65, 0x49, 0xDB, 0x94, + 0xBA, 0x7A, 0x3F, 0x3D, 0x97, 0xDD, 0x39, 0x8A, 0x75, 0xDF, 0x5B, 0x1A, 0x7C, 0xDF, 0xB2, + 0x54, 0x10, 0xB7, 0xEF, 0xC4, 0xED, 0x00, 0xD9, 0x99, 0x5B, 0x37, 0xB5, 0x8B, 0xF9, 0x1E, + 0xD7, 0xA3, 0x51, 0x0C, 0xFF, 0xEA, 0x82, 0xF9, 0xE1, 0xC2, 0xA3, 0x29, 0x04, 0x06, 0x00, + 0x4D, 0x09, 0x05, 0x7D, 0x63, 0xB7, 0x70, 0xFA, 0x0E, 0x53, 0x10, 0x31, 0x99, 0x54, 0x4E, + 0xBA, 0x66, 0x2A, 0x2C, 0x30, 0x2C, 0xF3, 0x90, 0x08, 0xF1, 0x42, 0xD2, 0xB1, 0x69, 0x63, + 0xE9, 0x5A, 0xB1, 0x0B, 0xE7, 0xC2, 0x61, 0x01, 0x68, 0x60, 0x8F, 0x35, 0x3A, 0x2F, 0x2C, + 0x41, 0xC7, 0x05, 0x6D, 0xEC, 0x1A, 0x8C, 0x7A, 0x6B, 0xFA, 0x00, 0x27, 0xF9, 0xDE, 0xDA, + 0xCB, 0x77, 0x86, 0xB6, 0x7E, 0xA2, 0xC4, 0x94, 0xD4, 0x3B, 0xA8, 0x51, 0xCF, 0x94, 0x15, + 0xC1, 0xBC, 0xC5, 0x2F, 0x02, 0x7E, 0xC0, 0x2C, 0x65, 0x53, 0x4F, 0x60, 0x8E, 0x9D, 0x16, + 0x6D, 0x51, 0xDD, 0x43, 0x1C, 0xDF, 0x58, 0x71, 0xF5, 0xCD, 0xD1, 0x57, 0x9C, 0xC0, 0x60, + 0x79, 0xDF, 0x07, 0x5A, 0x25, 0x06, 0x2B, 0xA7, 0xE7, 0x0D, 0x96, 0x66, 0xC4, 0xE7, 0xFE, + 0xD3, 0x4C, 0xEA, 0x0E, 0xA0, 0xF1, 0x1A, 0xDE, 0x1E, 0xB2, 0xA9, 0xB3, 0x97, 0xBC, 0xAA, + 0xAD, 0x10, 0x61, 0x27, 0x0E, 0xCF, 0x49, 0x78, 0x03, 0xA5, 0xFC, 0xE7, 0xF4, 0x1E, 0x65, + 0x04, 0xFB, 0xEC, 0x71, 0xA7, 0xDE, 0x7D, 0x06, 0x6B, 0x82, 0x61, 0x86, 0x8A, 0xFC, 0x49, + 0xB9, 0xE6, 0x85, 0xF0, 0xDC, 0xCE, 0x75, 0xE2, 0xFC, 0xB3, 0xBA, 0x8C, 0xF1, 0x90, 0x57, + 0xE3, 0x94, 0x15, 0x76, 0xBA, 0xF5, 0x8F, 0xB8, 0x21, 0xBD, 0x42, 0x68, 0xF7, 0xFA, 0xE3, + 0x02, 0x86, 0x01, 0xDA, 0x02, 0x2E, 0x9B, 0x46, 0x86, 0x46, 0xAB, 0xDB, 0x4F, 0xA6, 0x09, + 0x8A, 0x44, 0x9B, 0x42, 0x67, 0xD5, 0x09, 0xD9, 0xA3, 0x3F, 0x4C, 0x3E, 0xBC, 0xC3, 0x2D, + 0xAC, 0x09, 0x4D, 0x48, 0xED, 0x60, 0x0E, 0x76, 0x57, 0x87, 0xFB, 0x92, 0xB1, 0x97, 0x4F, + 0x74, 0xF7, 0xBB, 0x4C, 0x66, 0xEB, 0x2B, 0xBD, 0x02, 0x89, 0x5E, 0x6A, 0x38, 0x1C, 0x1C, + 0x45, 0x2E, 0xAA, 0xB1, 0xAE, 0x47, 0x31, 0xCF, 0x63, 0x2F, 0x61, 0xAE, 0x2C, 0x90, 0x59, + 0x21, 0x17, 0x4A, 0x3B, 0xC9, 0xBB, 0x4C, 0xDC, 0x89, 0xD6, 0x30, 0x26, 0x4B, 0x61, 0x49, + 0x88, 0xF3, 0xAB, 0xBE, 0xA1, 0xBD, 0x61, 0x7F, 0xFA, 0x53, 0xD7, 0x1B, 0x7D, 0x8A, 0x37, + 0x14, 0x62, 0xB7, 0x73, 0x35, 0x1A, 0x2D, 0xCC, 0xAE, 0xDD, 0x7F, 0x59, 0xCD, 0x72, 0x8F, + 0xAD, 0xEE, 0x05, 0x90, 0x67, 0xBD, 0x80, 0xC9, 0x4C, 0x8C, 0x9A, 0x1F, 0xFC, 0xA2, 0xDC, + 0x4F, 0x84, 0x8B, 0x82, 0x9C, 0x05, 0x61, 0x38, 0x5A, 0xA8, 0x2C, 0xC9, 0x85, 0x03, 0xD0, + 0xBB, 0x66, 0xA6, 0xAA, 0x4F, 0xAE, 0x07, 0x03, 0xD1, 0x2E, 0x60, 0xE1, 0x46, 0x0E, 0xFB, + 0xBC, 0xDF, 0x24, 0x12, 0xC1, 0x3E, 0x7C, 0x68, 0x4D, 0x1B, 0x01, 0x10, 0x20, 0x26, 0x34, + 0x3A, 0x41, 0x43, 0x44, 0x58, 0x5F, 0x6E, 0x70, 0x72, 0x74, 0x8B, 0xAE, 0xB5, 0xBB, 0xC6, + 0xD1, 0xE2, 0xEF, 0xFB, 0xFE, 0x06, 0x0E, 0x2E, 0x3E, 0x51, 0x60, 0x79, 0x7C, 0x9E, 0xA6, + 0xBA, 0xC7, 0xF1, 0x10, 0x24, 0x40, 0x4A, 0x52, 0x57, 0x5F, 0x6C, 0x89, 0x8C, 0x97, 0xAA, + 0xB2, 0xC3, 0xCC, 0xEA, 0xF2, 0x2F, 0x3F, 0x53, 0x5F, 0x7B, 0x81, 0x83, 0x96, 0xA1, 0xB1, + 0xBC, 0xE6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x25, 0x36, 0x42, + ]; if MLDSA44::verify(&mldsa44_pk, msg, None, &sig).is_ok() { println!("Verification succeeded!"); @@ -330,7 +1434,7 @@ fn bench_mldsa44_verify() { } fn bench_mldsa44_lowmemory_verify() { - use bouncycastle::mldsa_lowmemory::{MLDSATrait, MLDSA44, MLDSA44_SIG_LEN, MLDSA44PublicKey}; + use bouncycastle::mldsa_lowmemory::{MLDSA44, MLDSA44_SIG_LEN, MLDSA44PublicKey, MLDSATrait}; eprintln!("MLDSA44_lowmemory/Verify"); @@ -349,8 +1453,261 @@ fn bench_mldsa44_lowmemory_verify() { // let sig = MLDSA44::sign_mu_deterministic(&mldsa44_sk, &mu, [0u8; 32]).unwrap(); // eprintln!("sig:\n{}", &*hex::encode(sig)); - let pk = MLDSA44PublicKey::from_bytes(&[0xd7,0xb2,0xb4,0x72,0x54,0xaa,0xe0,0xdb,0x45,0xe7,0x93,0x0d,0x4a,0x98,0xd2,0xc9,0x7d,0x8f,0x13,0x97,0xd1,0x78,0x9d,0xaf,0xa1,0x70,0x24,0xb3,0x16,0xe9,0xbe,0xc9,0x4f,0xc9,0x94,0x6d,0x42,0xf1,0x9b,0x79,0xa7,0x41,0x3b,0xba,0xa3,0x3e,0x71,0x49,0xcb,0x42,0xed,0x51,0x15,0x69,0x3a,0xc0,0x41,0xfa,0xcb,0x98,0x8a,0xde,0xb5,0xfe,0x0e,0x1d,0x86,0x31,0x18,0x49,0x95,0xb5,0x92,0xc3,0x97,0xd2,0x29,0x4e,0x2e,0x14,0xf9,0x0a,0xa4,0x14,0xba,0x38,0x26,0x89,0x9a,0xc4,0x3f,0x4c,0xcc,0xac,0xbc,0x26,0xe9,0xa8,0x32,0xb9,0x51,0x18,0xd5,0xcb,0x43,0x3c,0xbe,0xf9,0x66,0x0b,0x00,0x13,0x8e,0x08,0x17,0xf6,0x1e,0x76,0x2c,0xa2,0x74,0xc3,0x6a,0xd5,0x54,0xeb,0x22,0xaa,0xc1,0x16,0x2e,0x4a,0xb0,0x1a,0xcb,0xa1,0xe3,0x8c,0x4e,0xfd,0x8f,0x80,0xb6,0x5b,0x33,0x3d,0x0f,0x72,0xe5,0x5d,0xfe,0x71,0xce,0x9c,0x1e,0xbb,0x98,0x89,0xe7,0xc5,0x61,0x06,0xc0,0xfd,0x73,0x80,0x3a,0x2a,0xec,0xfe,0xaf,0xde,0xd7,0xaa,0x3c,0xb2,0xce,0xda,0x54,0xd1,0x2b,0xd8,0xcd,0x36,0xa7,0x8c,0xf9,0x75,0x94,0x3b,0x47,0xab,0xd2,0x5e,0x88,0x0a,0xc4,0x52,0xe5,0x74,0x2e,0xd1,0xe8,0xd1,0xa8,0x2a,0xfa,0x86,0xe5,0x90,0xc7,0x58,0xc1,0x5a,0xe4,0xd2,0x84,0x0d,0x92,0xbc,0xa1,0xa5,0x09,0x0f,0x40,0x49,0x65,0x97,0xfc,0xa7,0xd8,0xb9,0x51,0x3f,0x1a,0x1b,0xda,0x6e,0x95,0x0a,0xaa,0x98,0xde,0x46,0x75,0x07,0xd4,0xa4,0xf5,0xa4,0xf0,0x59,0x92,0x16,0x58,0x2c,0x35,0x72,0xf6,0x2e,0xda,0x89,0x05,0xab,0x35,0x81,0x67,0x0c,0x4a,0x02,0x77,0x7a,0x33,0xe0,0xca,0x72,0x95,0xfd,0x8f,0x4f,0xf6,0xd1,0xa0,0xa3,0xa7,0x68,0x3d,0x65,0xf5,0xf5,0xf7,0xfc,0x60,0xda,0x02,0x3e,0x82,0x6c,0x5f,0x92,0x14,0x4c,0x02,0xf7,0xd1,0xba,0x10,0x75,0x98,0x75,0x53,0xea,0x93,0x67,0xfc,0xd7,0x6d,0x99,0x0b,0x7f,0xa9,0x9c,0xd4,0x5a,0xfd,0xb8,0x83,0x6d,0x43,0xe4,0x59,0xf5,0x18,0x7d,0xf0,0x58,0x47,0x97,0x09,0xa0,0x1e,0xa6,0x83,0x59,0x35,0xfa,0x70,0x46,0x09,0x90,0xcd,0x3d,0xc1,0xba,0x40,0x1b,0xa9,0x4b,0xab,0x1d,0xde,0x41,0xac,0x67,0xab,0x33,0x19,0xdc,0xac,0xa0,0x60,0x48,0xd4,0xc4,0xee,0xf2,0x7e,0xe1,0x3a,0x9c,0x17,0xd0,0x53,0x8f,0x43,0x0f,0x2d,0x64,0x2d,0xc2,0x41,0x56,0x60,0xde,0x78,0x87,0x7d,0x8d,0x8a,0xbc,0x72,0x52,0x39,0x78,0xc0,0x42,0xe4,0x28,0x5f,0x43,0x19,0x84,0x6c,0x44,0x12,0x62,0x42,0x97,0x68,0x44,0xc1,0x0e,0x55,0x6b,0xa2,0x15,0xb5,0xa7,0x19,0xe5,0x9d,0x0c,0x6b,0x2a,0x96,0xd3,0x98,0x59,0x07,0x1f,0xdc,0xc2,0xcd,0xe7,0x52,0x4a,0x7b,0xed,0xae,0x54,0xe8,0x5b,0x31,0x8e,0x85,0x4e,0x8f,0xe2,0xb2,0xf3,0xed,0xfa,0xc9,0x71,0x91,0x28,0x27,0x0a,0xaf,0xd1,0xe5,0x04,0x4c,0x3a,0x4f,0xda,0xfd,0x9f,0xf3,0x1f,0x90,0x78,0x4b,0x8e,0x8e,0x45,0x96,0x14,0x4a,0x0d,0xaf,0x58,0x65,0x11,0xd3,0xd9,0x96,0x2b,0x9e,0xa9,0x5a,0xf1,0x97,0xb4,0xe5,0xfc,0x60,0xf2,0xb1,0xed,0x15,0xde,0x3a,0x5b,0xef,0x5f,0x89,0xbd,0xc7,0x9d,0x91,0x05,0x1d,0x9b,0x28,0x16,0xe7,0x4f,0xa5,0x45,0x31,0xef,0xdc,0x1c,0xbe,0x74,0xd4,0x48,0x85,0x7f,0x47,0x6b,0xcd,0x58,0xf2,0x1c,0x0b,0x65,0x3b,0x3b,0x76,0xa4,0xe0,0x76,0xa6,0x55,0x9a,0x30,0x27,0x18,0x55,0x5c,0xc6,0x3f,0x74,0x85,0x9a,0xab,0xab,0x92,0x5f,0x02,0x38,0x61,0xca,0x8c,0xd0,0xf7,0xba,0xdb,0x28,0x71,0xf6,0x7d,0x55,0x32,0x6d,0x74,0x51,0x13,0x5a,0xd4,0x5f,0x4a,0x1b,0xa6,0x91,0x18,0xfb,0xb2,0xc8,0xa3,0x0e,0xec,0x93,0x92,0xef,0x3f,0x97,0x70,0x66,0xc9,0xad,0xd5,0xc7,0x10,0xcc,0x64,0x7b,0x15,0x14,0xd2,0x17,0xd9,0x58,0xc7,0x01,0x7c,0x3e,0x90,0xfd,0x20,0xc0,0x4e,0x67,0x4b,0x90,0x48,0x6e,0x93,0x70,0xa3,0x1a,0x00,0x1d,0x32,0xf4,0x73,0x97,0x9e,0x49,0x06,0x74,0x9e,0x7e,0x47,0x7f,0xa0,0xb7,0x45,0x08,0xf8,0xa5,0xf2,0x37,0x83,0x12,0xb8,0x3c,0x25,0xbd,0x38,0x8c,0xa0,0xb0,0xff,0xf7,0x47,0x8b,0xaf,0x42,0xb7,0x16,0x67,0xed,0xaa,0xc9,0x7c,0x46,0xb1,0x29,0x64,0x3e,0x58,0x6e,0x5b,0x05,0x5a,0x0c,0x21,0x19,0x46,0xd4,0xf3,0x6e,0x67,0x5b,0xed,0x58,0x60,0xfa,0x04,0x2a,0x31,0x5d,0x98,0x26,0x16,0x4d,0x6a,0x92,0x37,0xc3,0x5a,0x5f,0xbf,0x49,0x54,0x90,0xa5,0xbd,0x4d,0xf2,0x48,0xb9,0x5c,0x4a,0xae,0x77,0x84,0xb6,0x05,0x67,0x31,0x66,0xac,0x42,0x45,0xb5,0xb4,0xb0,0x82,0xa0,0x9e,0x93,0x23,0xe6,0x2f,0x20,0x78,0xc5,0xb7,0x67,0x83,0x44,0x6d,0xef,0xd7,0x36,0xad,0x3a,0x37,0x02,0xd4,0x9b,0x08,0x98,0x44,0x90,0x0a,0x61,0x83,0x33,0x97,0xbc,0x44,0x19,0xb3,0x0d,0x7a,0x97,0xa0,0xb3,0x87,0xc1,0x91,0x14,0x74,0xc4,0xd4,0x1b,0x53,0xe3,0x2a,0x97,0x7a,0xcb,0x6f,0x0e,0xa7,0x5d,0xb6,0x5b,0xb3,0x9e,0x59,0xe7,0x01,0xe7,0x69,0x57,0xde,0xf6,0xf2,0xd4,0x45,0x59,0xc3,0x1a,0x77,0x12,0x2b,0x52,0x04,0xe3,0xb5,0xc2,0x19,0xf1,0x68,0x8b,0x14,0xed,0x0b,0xc0,0xb8,0x01,0xb3,0xe6,0xe8,0x2d,0xcd,0x43,0xe9,0xc0,0xe9,0xf4,0x17,0x44,0xcd,0x98,0x15,0xbd,0x1b,0xc8,0x82,0x0d,0x8b,0xb1,0x23,0xf0,0x4f,0xac,0xd1,0xb1,0xb6,0x85,0xdd,0x5a,0x2b,0x1b,0x8d,0xbb,0xf3,0xed,0x93,0x36,0x70,0xf0,0x95,0xa1,0x80,0xb4,0xf1,0x92,0xd0,0x8b,0x10,0xb8,0xfa,0xbb,0xdf,0xcc,0x2b,0x24,0x51,0x8e,0x32,0xee,0xa0,0xa5,0xe0,0xc9,0x04,0xca,0x84,0x47,0x80,0x08,0x3f,0x3b,0x0c,0xd2,0xd0,0xb8,0xb6,0xaf,0x67,0xbc,0x35,0x5b,0x94,0x94,0x02,0x5d,0xc7,0xb0,0xa7,0x8f,0xa8,0x0e,0x3a,0x2d,0xbf,0xeb,0x51,0x32,0x88,0x51,0xd6,0x07,0x81,0x98,0xe9,0x49,0x36,0x51,0xae,0x78,0x7e,0xc0,0x25,0x1f,0x92,0x2b,0xa3,0x0e,0x9f,0x51,0xdf,0x62,0xa6,0xd7,0x27,0x84,0xcf,0x3d,0xd2,0x05,0x39,0x31,0x76,0xdf,0xa3,0x24,0xa5,0x12,0xbd,0x94,0x97,0x0a,0x36,0xdd,0x34,0xa5,0x14,0xa8,0x67,0x91,0xf0,0xeb,0x36,0xf0,0x14,0x5b,0x09,0xab,0x64,0x65,0x1b,0x4a,0x03,0x13,0xb2,0x99,0x61,0x1a,0x2a,0x1c,0x48,0x89,0x16,0x27,0x59,0x87,0x68,0xa3,0x11,0x40,0x60,0xba,0x44,0x43,0x48,0x6d,0xf5,0x15,0x22,0xa1,0xce,0x88,0xb3,0x09,0x85,0xc2,0x16,0xf8,0xe6,0xed,0x17,0x8d,0xd5,0x67,0xb3,0x04,0xa0,0xd4,0xca,0xfb,0xa8,0x82,0xa2,0x83,0x42,0xf1,0x7a,0x9a,0xa2,0x6a,0xe5,0x8d,0xb6,0x30,0x08,0x3d,0x2c,0x35,0x8f,0xdf,0x56,0x6c,0x3f,0x5d,0x62,0xa4,0x28,0x56,0x7b,0xc9,0xea,0x8c,0xe9,0x5c,0xaa,0x0f,0x35,0x47,0x4b,0x0b,0xfa,0x8f,0x33,0x9a,0x25,0x0a,0xb4,0xdf,0xcf,0x20,0x83,0xbe,0x8e,0xef,0xbc,0x10,0x55,0xe1,0x8f,0xe1,0x53,0x70,0xee,0xcb,0x26,0x05,0x66,0xd8,0x3f,0xf0,0x6b,0x21,0x1a,0xae,0xc4,0x3c,0xa2,0x9b,0x54,0xcc,0xd0,0x0f,0x88,0x15,0xa2,0x46,0x5e,0xf0,0xb4,0x65,0x15,0xcc,0x7e,0x41,0xf3,0x12,0x4f,0x09,0xef,0xff,0x73,0x93,0x09,0xab,0x58,0xb2,0x9a,0x14,0x59,0xa0,0x0b,0xce,0x50,0x38,0xe9,0x38,0xc9,0x67,0x8f,0x72,0xeb,0x0e,0x4e,0xe5,0xfd,0xaa,0xe6,0x6d,0x9f,0x85,0x73,0xfc,0x97,0xfc,0x42,0xb4,0x95,0x9f,0x4b,0xf8,0xb6,0x1d,0x78,0x43,0x3e,0x86,0xb0,0x33,0x5d,0x6e,0x91,0x91,0xc4,0xd8,0xbf,0x48,0x7b,0x39,0x05,0xc1,0x08,0xcf,0xd6,0xac,0x24,0xb0,0xce,0xb7,0xdc,0xb7,0xcf,0x51,0xf8,0x4d,0x0e,0xd6,0x87,0xb9,0x5e,0xae,0xb1,0xc5,0x33,0xc0,0x6f,0x0d,0x97,0x02,0x3d,0x92,0xa7,0x08,0x25,0x83,0x7b,0x59,0xba,0x6c,0xb7,0xd4,0xe5,0x6b,0x0a,0x87,0xc2,0x03,0x86,0x2a,0xe8,0xf3,0x15,0xba,0x59,0x25,0xe8,0xed,0xef,0xa6,0x79,0x36,0x9a,0x22,0x02,0x76,0x61,0x51,0xf1,0x6a,0x96,0x5f,0x9f,0x81,0xec,0xe7,0x6c,0xc0,0x70,0xb5,0x58,0x69,0xe4,0xdb,0x97,0x84,0xcf,0x05,0xc8,0x30,0xb3,0x24,0x2c,0x83,0x12]).unwrap(); - let sig: [u8; MLDSA44_SIG_LEN] = [0x5e,0x93,0xb7,0x85,0xc5,0x11,0x9c,0x39,0x83,0xa2,0x91,0xb1,0x84,0x20,0xfd,0xbe,0x4b,0xca,0x53,0xd5,0xa3,0x73,0x29,0x22,0xfa,0xaa,0xcd,0x5a,0x5d,0x32,0xa7,0x45,0xc7,0x8d,0x10,0x5b,0xa1,0x0b,0xee,0x1e,0xd8,0x06,0x9f,0x19,0xe6,0xc5,0x37,0xbd,0xa1,0x6e,0x89,0xd3,0x90,0x04,0xc3,0x59,0xd1,0xfd,0x38,0x1a,0x02,0x91,0xf1,0xc5,0x1f,0x1c,0x38,0xed,0xcd,0xb3,0x15,0xc8,0xc6,0x95,0x70,0xd8,0xf2,0x5f,0x16,0x55,0xba,0x8e,0xa8,0x3a,0xff,0x24,0xb8,0xb6,0xbe,0x8d,0xe7,0x62,0x34,0x2e,0x34,0x7e,0xab,0x2c,0xaa,0x68,0x03,0xed,0x70,0x59,0x52,0xdd,0x64,0x50,0xc5,0x18,0x5e,0x9d,0x60,0xce,0x96,0xe8,0xdc,0xa4,0x23,0xa0,0x2f,0x64,0x6c,0xea,0x69,0x01,0x64,0xa2,0x26,0xe4,0xc3,0xd6,0xa5,0x15,0xce,0x16,0x29,0x0f,0x19,0xb2,0xc6,0x26,0xda,0x9b,0x45,0x0e,0xcf,0x66,0x50,0x13,0xc5,0xe2,0x26,0xb6,0xc0,0xac,0x5c,0x07,0xce,0x90,0xe2,0x78,0xf1,0xb0,0x13,0x4e,0x38,0x5d,0x13,0xe7,0x42,0x08,0xa0,0xb3,0xff,0x05,0x2a,0x36,0x25,0x79,0xf9,0x20,0x7e,0xa0,0x1f,0x18,0xa0,0x39,0xaa,0x1b,0x97,0xae,0x34,0x52,0x67,0x5b,0x62,0x07,0x71,0xf8,0x01,0x2e,0xe7,0xa4,0xe5,0x5c,0x98,0xbf,0xd2,0x01,0x9e,0xd8,0xa3,0xb0,0x0a,0xce,0xa8,0xe8,0xab,0x28,0x17,0x2f,0xaa,0x42,0xca,0x1f,0xda,0x83,0xc5,0xff,0xe8,0x1a,0x45,0xbe,0x73,0x6b,0xde,0xdd,0x5f,0xb3,0x00,0xce,0x17,0x07,0x8b,0x38,0x0f,0x62,0x0b,0xde,0xeb,0xad,0x69,0x36,0x01,0x37,0x2c,0x85,0xea,0xcf,0x79,0xbc,0x98,0xe1,0xb4,0x8f,0x2a,0xd7,0xe5,0xdc,0xe4,0x27,0x9a,0x12,0x95,0xbb,0x2b,0xa6,0x0a,0x0c,0x5e,0x37,0x26,0x64,0x2d,0x23,0x36,0xc5,0xeb,0x1d,0x37,0xc8,0x62,0x3c,0x75,0x58,0x24,0x13,0x18,0xd8,0x9b,0xc7,0x83,0xc4,0xf0,0x00,0x98,0x07,0x74,0x84,0x62,0x3c,0x21,0x75,0x60,0xa0,0xc7,0xaa,0xf7,0x5d,0xca,0xcc,0xb7,0x8e,0xe6,0x9c,0x20,0x7c,0x27,0xc8,0xbf,0x39,0x65,0xcc,0xf5,0x8a,0x80,0xc8,0x8e,0xfc,0xc7,0xe5,0xde,0xb3,0x61,0x5d,0x50,0x45,0xa7,0x41,0xc4,0xda,0xc0,0xa0,0x21,0xdd,0x06,0x0d,0x31,0x5d,0x4e,0xc2,0x85,0x7e,0xb6,0x64,0xd7,0x28,0xd0,0xaf,0x97,0x3b,0xea,0x07,0xe1,0xca,0x56,0x3f,0xaa,0x0e,0x19,0x99,0x6c,0xea,0x37,0x70,0x31,0x6c,0x11,0xa5,0x06,0x66,0x65,0x66,0x20,0x05,0xac,0xe9,0x8f,0x61,0x10,0xe8,0x83,0xba,0xe0,0x60,0xda,0xa7,0xb6,0xd8,0x33,0x79,0xe0,0x87,0x87,0x96,0x69,0x17,0x08,0xa3,0x2b,0x85,0x73,0x0d,0xe8,0xb9,0x2d,0x89,0xf9,0x0a,0x36,0x60,0xc9,0x49,0x16,0x5b,0x14,0x61,0x25,0x67,0x66,0x2e,0x16,0x22,0x32,0x29,0x6c,0xbd,0x14,0x35,0x17,0xa2,0x82,0xe2,0x2c,0x46,0xb6,0x36,0x06,0xd3,0xc1,0x4e,0xd4,0x55,0x9a,0x5a,0x1c,0x45,0x9b,0xab,0x7f,0x35,0x50,0x07,0xad,0x6f,0x7e,0x3b,0x1e,0x07,0x44,0x5d,0xfc,0x96,0xbd,0x9b,0x75,0x08,0x0b,0x3d,0x4f,0x68,0x99,0x84,0x90,0xa2,0x6b,0x5e,0x09,0x0b,0xe2,0x67,0x40,0x71,0xab,0x92,0x5b,0xb6,0x50,0x59,0x08,0x56,0xc5,0x9f,0x8b,0xa7,0x48,0x8d,0x2b,0x72,0xf8,0x40,0xac,0x3e,0xaf,0xe4,0xdd,0x91,0xf0,0xf5,0x1c,0x43,0x64,0x11,0x2c,0x1a,0x13,0x9e,0x3e,0x94,0x2a,0x59,0x7b,0x93,0xa1,0xe3,0xf4,0xfa,0xde,0xd1,0x29,0xc1,0x4b,0x59,0x78,0xb3,0x15,0xe2,0x24,0x6a,0x93,0x14,0x6a,0x79,0x36,0x5f,0x0f,0x59,0x7a,0x18,0x34,0x0c,0xca,0x86,0xbb,0x15,0xce,0xed,0x39,0xf1,0x75,0xea,0xb1,0xe5,0x46,0x53,0x5a,0xfb,0x96,0x6f,0x0a,0x65,0xa8,0xf6,0x6f,0x73,0x7a,0xb0,0x28,0x97,0xed,0xdf,0xe9,0x2c,0xf7,0x78,0x68,0x94,0x84,0x3c,0x26,0x91,0x46,0x47,0x76,0xc9,0x4b,0xd4,0x50,0xa1,0x06,0x91,0x38,0xb2,0x6d,0xf8,0x3b,0x2d,0x1d,0xd8,0x01,0x14,0x3a,0x8f,0xdf,0xdc,0x25,0x14,0xcc,0x5b,0x58,0x31,0xab,0x53,0xa7,0x5c,0x55,0xef,0x29,0xf4,0x0e,0x7c,0x63,0xd2,0xc7,0x2a,0xbe,0x97,0xe2,0xaf,0x14,0x85,0x3b,0xe4,0x9b,0xe1,0x6f,0x47,0x30,0xa1,0x59,0x97,0x49,0x70,0x95,0x14,0x39,0xe5,0x5c,0x15,0x89,0xd0,0xf4,0xa1,0x62,0xe3,0x51,0x7d,0xf9,0xd7,0xab,0xc9,0x8d,0x8a,0x30,0x72,0x16,0xe7,0xf1,0xcb,0x46,0x27,0xc9,0x17,0x5c,0x0e,0xef,0x23,0x33,0x7e,0x56,0xd5,0x28,0x1b,0x83,0x72,0x6f,0xff,0x40,0xa1,0x48,0xb0,0xc4,0x8e,0x8d,0xf3,0x49,0x6a,0x21,0x18,0xd8,0x02,0x19,0xae,0xf8,0xf4,0x0b,0x29,0xfb,0xa1,0xf2,0xf7,0x87,0x86,0xb6,0x7f,0xfb,0x7b,0x7d,0x47,0xd4,0x06,0xb7,0x65,0xbd,0x13,0x66,0x10,0xbe,0xde,0xb9,0x5c,0xd7,0x32,0x1f,0x58,0xf3,0xb8,0x36,0xc9,0x25,0x8b,0xe3,0x5d,0x78,0xb4,0x98,0xf3,0xef,0xe1,0xdb,0x2b,0x24,0x3d,0x73,0x4f,0xab,0x15,0x9b,0xae,0xd8,0x80,0x7c,0x3c,0xcc,0xf8,0x3e,0xb2,0xea,0xf8,0xa9,0xaf,0x01,0xa5,0x18,0xd4,0x8c,0x60,0xe9,0x1a,0x96,0x81,0x2a,0xd6,0x89,0xc2,0xd8,0x3c,0xc4,0xe8,0xe9,0xb3,0x65,0x04,0x22,0xbe,0xd6,0xf1,0x3c,0x24,0xad,0xaa,0xd9,0x1c,0x95,0xb3,0xe3,0xcf,0x35,0x4f,0x0f,0x6b,0xc9,0xee,0x89,0x41,0xa6,0xb1,0x5b,0x69,0x75,0x13,0x1d,0x95,0x23,0x3d,0x89,0x35,0xde,0x36,0x7e,0xfc,0x6d,0x86,0xa4,0x5d,0xac,0x7d,0x0f,0x1d,0xdd,0x9a,0xeb,0xd2,0xc5,0x9c,0x02,0x7f,0xcd,0xa4,0x48,0x80,0x1e,0x93,0xe7,0x33,0xac,0xa5,0x18,0x74,0xbe,0x9a,0xb9,0x27,0xa9,0x04,0xf9,0x6d,0xdb,0x7a,0x46,0xb2,0xda,0x13,0x26,0x1d,0x52,0x2b,0x23,0xc9,0x50,0xc0,0x1d,0x5f,0x5e,0x11,0x2b,0x76,0xf8,0x51,0xff,0x23,0x4f,0x06,0xf8,0xd5,0xe6,0x5b,0x13,0x19,0xab,0xcd,0x79,0xa1,0x80,0xae,0x06,0x3d,0x65,0xb2,0x8c,0x74,0x58,0x78,0xc0,0x6d,0xbb,0x69,0xba,0x73,0x29,0x3e,0xab,0x34,0x43,0x4b,0xf1,0xa9,0x2f,0xba,0x69,0x19,0x93,0xbd,0x0f,0xf3,0xed,0xac,0x76,0xa1,0x2f,0x80,0xc0,0xad,0xa4,0xb1,0x96,0x9c,0x76,0x65,0x58,0x9d,0x53,0x0a,0x67,0x01,0x6a,0x62,0x54,0x03,0xc5,0x37,0x03,0x29,0x04,0xf2,0xe1,0x04,0x54,0x7c,0xd3,0xea,0x40,0x62,0x60,0xdd,0x35,0x7f,0xa0,0x6e,0xa0,0x12,0xa7,0x85,0x82,0x6c,0x16,0x0e,0x99,0xff,0xd0,0x65,0xb0,0xe3,0xf3,0x3c,0x76,0x89,0xd3,0x55,0x2a,0xb9,0xe2,0xe0,0x9f,0xa7,0xe5,0x5b,0xbc,0xef,0x04,0x22,0x42,0xbc,0xac,0xad,0x8a,0x3d,0xa4,0x7b,0xcc,0x54,0xa1,0x21,0xf1,0x52,0x6c,0x8c,0xd4,0xcc,0x5a,0x89,0x2a,0x81,0x31,0xcf,0x4e,0xef,0xaf,0x42,0x48,0xdd,0xd6,0xa1,0x1e,0xc4,0x27,0xba,0x37,0x8a,0xae,0x89,0xaa,0xf5,0x82,0xce,0x1f,0x4e,0x32,0x69,0x0a,0x55,0x5e,0x74,0x07,0x61,0xd3,0x58,0xad,0x4e,0x92,0xbc,0x38,0x41,0x8a,0xa7,0x82,0xda,0x91,0x65,0x24,0xfb,0x09,0xab,0x2c,0xa6,0xb3,0xd3,0x11,0x3d,0x6f,0x2c,0x2a,0x6a,0x9b,0x9d,0x29,0xd4,0xe7,0x48,0x92,0x55,0x25,0x2a,0xf0,0x75,0xcb,0xf9,0xfe,0xac,0xed,0xae,0x6f,0x3e,0xc0,0xb0,0x70,0x82,0x46,0x89,0xdd,0x3c,0x78,0xac,0x14,0x3e,0xd6,0x77,0x6d,0x95,0xdd,0x8f,0x13,0xd4,0x35,0xa2,0x90,0xbd,0xca,0x4c,0x11,0x31,0x8e,0x5a,0xcc,0xe0,0x44,0x69,0x64,0x4e,0x13,0x74,0xa9,0x45,0x1b,0x62,0x04,0xf3,0xb3,0x96,0x1b,0x7d,0xd2,0x39,0xe3,0x06,0xfe,0xf5,0xf4,0xf4,0xe5,0x1b,0x78,0xb0,0xfb,0x9d,0xce,0xe6,0x9c,0x3e,0x79,0x0b,0x23,0x1f,0x2e,0x65,0xfd,0x1a,0xb1,0xc2,0xa7,0x5b,0x07,0x06,0x7d,0x5c,0x16,0xdd,0xe0,0x09,0x83,0xa5,0x8f,0xfc,0xda,0xaa,0xee,0x16,0xd2,0x74,0x2e,0x13,0x3e,0xd7,0x37,0xb4,0x80,0x64,0xc8,0xa3,0x8e,0xca,0x35,0xab,0x3f,0xa1,0x8f,0x6d,0x62,0xf6,0x42,0xb1,0x2c,0xfd,0xc7,0x98,0x0f,0x2a,0xb7,0xdb,0x32,0x1f,0xec,0x9d,0xcf,0xe4,0x99,0xb4,0xfc,0x1e,0xe7,0xeb,0x29,0x79,0x54,0x05,0x66,0x17,0xc6,0x0a,0x66,0x40,0xb9,0x28,0x35,0xd1,0x65,0xc3,0xc0,0x0a,0x95,0x19,0x52,0x61,0x44,0x88,0xd5,0x65,0x7b,0xa0,0xb5,0xe9,0x0a,0xe9,0xe0,0xef,0x7b,0x3b,0x9e,0xca,0xeb,0xd8,0x1b,0x85,0x51,0xb6,0xd7,0x0e,0x83,0x5b,0x27,0x34,0x76,0x16,0x39,0xd4,0x2e,0x76,0xff,0xc5,0xb3,0x27,0x2b,0x61,0xc8,0x96,0xb4,0x5b,0x4b,0xd1,0x8f,0x30,0xe5,0x8c,0x44,0x06,0x43,0xba,0x15,0x92,0x21,0xcc,0x67,0x39,0xa1,0x9a,0x65,0xf2,0x91,0x1f,0xae,0x47,0xb0,0xd4,0xca,0xc4,0x20,0x0a,0x6f,0x04,0x3b,0x17,0xa0,0x3a,0xd3,0x93,0xec,0xb8,0x23,0xed,0x03,0xc8,0xb6,0xcd,0x68,0x16,0x7e,0x6c,0x82,0x34,0xf7,0x43,0x25,0x57,0xdb,0x27,0x20,0x79,0xee,0x89,0x9a,0xed,0xe7,0x3b,0x6b,0x98,0xd6,0x00,0x3f,0x45,0x78,0x9a,0x14,0x1b,0x60,0xd6,0xdb,0x40,0xcd,0x2a,0x59,0x74,0x57,0x1a,0x4a,0xd3,0x66,0x7b,0x88,0x93,0x18,0xba,0x60,0x28,0x5d,0x90,0x3a,0x2e,0xac,0x01,0xc2,0x16,0x08,0x83,0x8c,0x40,0x90,0x7d,0xe6,0xbb,0xab,0xe0,0x42,0xcf,0x2e,0xcd,0xd9,0x7f,0x54,0x9f,0x95,0xec,0x69,0x8d,0x79,0x22,0x2c,0x65,0xba,0x27,0xc3,0x0d,0x33,0x2a,0x68,0xd0,0x57,0xae,0xcd,0xc9,0x38,0x8a,0xa3,0x43,0x20,0xe0,0xaa,0x74,0xfd,0xbd,0x4d,0x1b,0x64,0x3c,0xac,0xe2,0x16,0xb6,0xd8,0xad,0x8f,0x07,0xa9,0x99,0x55,0xbf,0xdb,0x74,0x3a,0x86,0xb4,0x0f,0xc6,0x15,0x27,0xba,0xca,0x43,0x4a,0xc2,0xa7,0xfb,0xea,0xa7,0x71,0x11,0xdc,0x80,0x98,0xb1,0x7e,0x80,0x0f,0x59,0xdd,0x77,0xcc,0xb0,0xe6,0x77,0x07,0xe6,0x01,0x23,0xd3,0x34,0xe0,0x73,0xa2,0xf5,0xa1,0x6f,0xfb,0xcd,0x70,0x13,0x89,0xad,0xd5,0x7c,0x3c,0xec,0xcb,0x88,0xb2,0x86,0xac,0x1e,0x6e,0x3e,0x64,0x85,0xaf,0x1a,0x12,0xea,0x24,0x1d,0x14,0xa1,0xb5,0x00,0x3d,0x7f,0x3b,0xc9,0xe9,0x57,0xd4,0x48,0x3c,0x0f,0x9f,0x70,0x3b,0x3a,0x18,0x7d,0x55,0xe5,0x05,0x81,0x76,0x15,0xfb,0xc4,0xae,0x08,0x37,0x61,0x61,0x84,0x24,0x5c,0xfb,0xa6,0x1c,0xe3,0xb9,0x29,0xe3,0x3f,0x52,0xb7,0x1c,0xdd,0x7b,0x6a,0x0d,0xa5,0x5c,0x1f,0x99,0x75,0x10,0xb1,0xa9,0x00,0x2c,0xa4,0xe0,0x67,0x83,0x73,0xa3,0xb1,0xab,0x28,0x97,0xe6,0xb4,0x23,0xf1,0x5a,0x44,0x0a,0x63,0x6c,0xc8,0x61,0x49,0x1e,0xf4,0x1a,0xd0,0xaa,0x62,0x7d,0x8e,0x19,0x8a,0x5e,0xe7,0xbd,0x7b,0x6c,0xb2,0xc9,0xce,0x2a,0x8c,0xc0,0x15,0xf0,0xd2,0x06,0xde,0x4c,0x49,0xe2,0xf8,0x7f,0x31,0x09,0x54,0xa1,0x0d,0x86,0xe2,0x94,0xf7,0x42,0xee,0x18,0x6f,0x4a,0xe9,0x81,0x5f,0x69,0x96,0x22,0x79,0x22,0x06,0xca,0xfb,0xa8,0xf5,0x62,0x17,0x38,0x16,0x0e,0x6c,0x5d,0x61,0x1a,0x82,0x52,0xc6,0xf3,0x50,0x85,0xb6,0x04,0xef,0x89,0x51,0x64,0xd4,0xea,0x6d,0xdd,0x31,0x0c,0x7d,0x8f,0x0c,0x87,0x9f,0xb1,0xf8,0x84,0xc5,0x74,0x1d,0x09,0x6b,0x3d,0x2d,0xa0,0xce,0x11,0x51,0x79,0x0d,0xda,0x88,0x1d,0x18,0xcb,0x6b,0x19,0xa9,0xfe,0xd6,0xf5,0x25,0x4b,0x7d,0x52,0xd5,0xd9,0x2b,0xbb,0xe2,0x4c,0x9d,0x6a,0x65,0x60,0x4a,0x0b,0x8e,0xd2,0x4a,0xd5,0xc1,0x97,0xd6,0x83,0xf5,0x98,0x74,0x3c,0x96,0xb5,0x96,0x0e,0x87,0x23,0x73,0x2b,0x5b,0xd6,0x47,0xe9,0xdb,0xea,0xa8,0x51,0xd0,0xe1,0xcf,0x6d,0x2c,0x07,0x0d,0x44,0x42,0x76,0x2c,0x28,0x09,0x8c,0x5c,0xf5,0xa5,0x4b,0x2b,0x5e,0x69,0xa9,0x9b,0x10,0x81,0x5b,0xf0,0xf4,0x77,0xbb,0x71,0xf0,0xd5,0xd3,0xa6,0x2b,0xa2,0xb3,0xe2,0x9b,0xf8,0x4d,0x4b,0x4e,0x57,0x47,0x07,0xf5,0xf7,0x4a,0xf7,0x04,0xd2,0x77,0xbd,0x6c,0xa3,0x8d,0xa2,0x1e,0x2c,0xda,0xc5,0x49,0xe5,0xea,0xe1,0xde,0x7a,0x18,0xee,0x53,0x4c,0x8c,0x22,0x91,0xc9,0x08,0xca,0xab,0xf1,0x59,0xe9,0x0e,0x65,0x49,0xdb,0x94,0xba,0x7a,0x3f,0x3d,0x97,0xdd,0x39,0x8a,0x75,0xdf,0x5b,0x1a,0x7c,0xdf,0xb2,0x54,0x10,0xb7,0xef,0xc4,0xed,0x00,0xd9,0x99,0x5b,0x37,0xb5,0x8b,0xf9,0x1e,0xd7,0xa3,0x51,0x0c,0xff,0xea,0x82,0xf9,0xe1,0xc2,0xa3,0x29,0x04,0x06,0x00,0x4d,0x09,0x05,0x7d,0x63,0xb7,0x70,0xfa,0x0e,0x53,0x10,0x31,0x99,0x54,0x4e,0xba,0x66,0x2a,0x2c,0x30,0x2c,0xf3,0x90,0x08,0xf1,0x42,0xd2,0xb1,0x69,0x63,0xe9,0x5a,0xb1,0x0b,0xe7,0xc2,0x61,0x01,0x68,0x60,0x8f,0x35,0x3a,0x2f,0x2c,0x41,0xc7,0x05,0x6d,0xec,0x1a,0x8c,0x7a,0x6b,0xfa,0x00,0x27,0xf9,0xde,0xda,0xcb,0x77,0x86,0xb6,0x7e,0xa2,0xc4,0x94,0xd4,0x3b,0xa8,0x51,0xcf,0x94,0x15,0xc1,0xbc,0xc5,0x2f,0x02,0x7e,0xc0,0x2c,0x65,0x53,0x4f,0x60,0x8e,0x9d,0x16,0x6d,0x51,0xdd,0x43,0x1c,0xdf,0x58,0x71,0xf5,0xcd,0xd1,0x57,0x9c,0xc0,0x60,0x79,0xdf,0x07,0x5a,0x25,0x06,0x2b,0xa7,0xe7,0x0d,0x96,0x66,0xc4,0xe7,0xfe,0xd3,0x4c,0xea,0x0e,0xa0,0xf1,0x1a,0xde,0x1e,0xb2,0xa9,0xb3,0x97,0xbc,0xaa,0xad,0x10,0x61,0x27,0x0e,0xcf,0x49,0x78,0x03,0xa5,0xfc,0xe7,0xf4,0x1e,0x65,0x04,0xfb,0xec,0x71,0xa7,0xde,0x7d,0x06,0x6b,0x82,0x61,0x86,0x8a,0xfc,0x49,0xb9,0xe6,0x85,0xf0,0xdc,0xce,0x75,0xe2,0xfc,0xb3,0xba,0x8c,0xf1,0x90,0x57,0xe3,0x94,0x15,0x76,0xba,0xf5,0x8f,0xb8,0x21,0xbd,0x42,0x68,0xf7,0xfa,0xe3,0x02,0x86,0x01,0xda,0x02,0x2e,0x9b,0x46,0x86,0x46,0xab,0xdb,0x4f,0xa6,0x09,0x8a,0x44,0x9b,0x42,0x67,0xd5,0x09,0xd9,0xa3,0x3f,0x4c,0x3e,0xbc,0xc3,0x2d,0xac,0x09,0x4d,0x48,0xed,0x60,0x0e,0x76,0x57,0x87,0xfb,0x92,0xb1,0x97,0x4f,0x74,0xf7,0xbb,0x4c,0x66,0xeb,0x2b,0xbd,0x02,0x89,0x5e,0x6a,0x38,0x1c,0x1c,0x45,0x2e,0xaa,0xb1,0xae,0x47,0x31,0xcf,0x63,0x2f,0x61,0xae,0x2c,0x90,0x59,0x21,0x17,0x4a,0x3b,0xc9,0xbb,0x4c,0xdc,0x89,0xd6,0x30,0x26,0x4b,0x61,0x49,0x88,0xf3,0xab,0xbe,0xa1,0xbd,0x61,0x7f,0xfa,0x53,0xd7,0x1b,0x7d,0x8a,0x37,0x14,0x62,0xb7,0x73,0x35,0x1a,0x2d,0xcc,0xae,0xdd,0x7f,0x59,0xcd,0x72,0x8f,0xad,0xee,0x05,0x90,0x67,0xbd,0x80,0xc9,0x4c,0x8c,0x9a,0x1f,0xfc,0xa2,0xdc,0x4f,0x84,0x8b,0x82,0x9c,0x05,0x61,0x38,0x5a,0xa8,0x2c,0xc9,0x85,0x03,0xd0,0xbb,0x66,0xa6,0xaa,0x4f,0xae,0x07,0x03,0xd1,0x2e,0x60,0xe1,0x46,0x0e,0xfb,0xbc,0xdf,0x24,0x12,0xc1,0x3e,0x7c,0x68,0x4d,0x1b,0x01,0x10,0x20,0x26,0x34,0x3a,0x41,0x43,0x44,0x58,0x5f,0x6e,0x70,0x72,0x74,0x8b,0xae,0xb5,0xbb,0xc6,0xd1,0xe2,0xef,0xfb,0xfe,0x06,0x0e,0x2e,0x3e,0x51,0x60,0x79,0x7c,0x9e,0xa6,0xba,0xc7,0xf1,0x10,0x24,0x40,0x4a,0x52,0x57,0x5f,0x6c,0x89,0x8c,0x97,0xaa,0xb2,0xc3,0xcc,0xea,0xf2,0x2f,0x3f,0x53,0x5f,0x7b,0x81,0x83,0x96,0xa1,0xb1,0xbc,0xe6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x25,0x36,0x42]; + let pk = MLDSA44PublicKey::from_bytes(&[ + 0xD7, 0xB2, 0xB4, 0x72, 0x54, 0xAA, 0xE0, 0xDB, 0x45, 0xE7, 0x93, 0x0D, 0x4A, 0x98, 0xD2, + 0xC9, 0x7D, 0x8F, 0x13, 0x97, 0xD1, 0x78, 0x9D, 0xAF, 0xA1, 0x70, 0x24, 0xB3, 0x16, 0xE9, + 0xBE, 0xC9, 0x4F, 0xC9, 0x94, 0x6D, 0x42, 0xF1, 0x9B, 0x79, 0xA7, 0x41, 0x3B, 0xBA, 0xA3, + 0x3E, 0x71, 0x49, 0xCB, 0x42, 0xED, 0x51, 0x15, 0x69, 0x3A, 0xC0, 0x41, 0xFA, 0xCB, 0x98, + 0x8A, 0xDE, 0xB5, 0xFE, 0x0E, 0x1D, 0x86, 0x31, 0x18, 0x49, 0x95, 0xB5, 0x92, 0xC3, 0x97, + 0xD2, 0x29, 0x4E, 0x2E, 0x14, 0xF9, 0x0A, 0xA4, 0x14, 0xBA, 0x38, 0x26, 0x89, 0x9A, 0xC4, + 0x3F, 0x4C, 0xCC, 0xAC, 0xBC, 0x26, 0xE9, 0xA8, 0x32, 0xB9, 0x51, 0x18, 0xD5, 0xCB, 0x43, + 0x3C, 0xBE, 0xF9, 0x66, 0x0B, 0x00, 0x13, 0x8E, 0x08, 0x17, 0xF6, 0x1E, 0x76, 0x2C, 0xA2, + 0x74, 0xC3, 0x6A, 0xD5, 0x54, 0xEB, 0x22, 0xAA, 0xC1, 0x16, 0x2E, 0x4A, 0xB0, 0x1A, 0xCB, + 0xA1, 0xE3, 0x8C, 0x4E, 0xFD, 0x8F, 0x80, 0xB6, 0x5B, 0x33, 0x3D, 0x0F, 0x72, 0xE5, 0x5D, + 0xFE, 0x71, 0xCE, 0x9C, 0x1E, 0xBB, 0x98, 0x89, 0xE7, 0xC5, 0x61, 0x06, 0xC0, 0xFD, 0x73, + 0x80, 0x3A, 0x2A, 0xEC, 0xFE, 0xAF, 0xDE, 0xD7, 0xAA, 0x3C, 0xB2, 0xCE, 0xDA, 0x54, 0xD1, + 0x2B, 0xD8, 0xCD, 0x36, 0xA7, 0x8C, 0xF9, 0x75, 0x94, 0x3B, 0x47, 0xAB, 0xD2, 0x5E, 0x88, + 0x0A, 0xC4, 0x52, 0xE5, 0x74, 0x2E, 0xD1, 0xE8, 0xD1, 0xA8, 0x2A, 0xFA, 0x86, 0xE5, 0x90, + 0xC7, 0x58, 0xC1, 0x5A, 0xE4, 0xD2, 0x84, 0x0D, 0x92, 0xBC, 0xA1, 0xA5, 0x09, 0x0F, 0x40, + 0x49, 0x65, 0x97, 0xFC, 0xA7, 0xD8, 0xB9, 0x51, 0x3F, 0x1A, 0x1B, 0xDA, 0x6E, 0x95, 0x0A, + 0xAA, 0x98, 0xDE, 0x46, 0x75, 0x07, 0xD4, 0xA4, 0xF5, 0xA4, 0xF0, 0x59, 0x92, 0x16, 0x58, + 0x2C, 0x35, 0x72, 0xF6, 0x2E, 0xDA, 0x89, 0x05, 0xAB, 0x35, 0x81, 0x67, 0x0C, 0x4A, 0x02, + 0x77, 0x7A, 0x33, 0xE0, 0xCA, 0x72, 0x95, 0xFD, 0x8F, 0x4F, 0xF6, 0xD1, 0xA0, 0xA3, 0xA7, + 0x68, 0x3D, 0x65, 0xF5, 0xF5, 0xF7, 0xFC, 0x60, 0xDA, 0x02, 0x3E, 0x82, 0x6C, 0x5F, 0x92, + 0x14, 0x4C, 0x02, 0xF7, 0xD1, 0xBA, 0x10, 0x75, 0x98, 0x75, 0x53, 0xEA, 0x93, 0x67, 0xFC, + 0xD7, 0x6D, 0x99, 0x0B, 0x7F, 0xA9, 0x9C, 0xD4, 0x5A, 0xFD, 0xB8, 0x83, 0x6D, 0x43, 0xE4, + 0x59, 0xF5, 0x18, 0x7D, 0xF0, 0x58, 0x47, 0x97, 0x09, 0xA0, 0x1E, 0xA6, 0x83, 0x59, 0x35, + 0xFA, 0x70, 0x46, 0x09, 0x90, 0xCD, 0x3D, 0xC1, 0xBA, 0x40, 0x1B, 0xA9, 0x4B, 0xAB, 0x1D, + 0xDE, 0x41, 0xAC, 0x67, 0xAB, 0x33, 0x19, 0xDC, 0xAC, 0xA0, 0x60, 0x48, 0xD4, 0xC4, 0xEE, + 0xF2, 0x7E, 0xE1, 0x3A, 0x9C, 0x17, 0xD0, 0x53, 0x8F, 0x43, 0x0F, 0x2D, 0x64, 0x2D, 0xC2, + 0x41, 0x56, 0x60, 0xDE, 0x78, 0x87, 0x7D, 0x8D, 0x8A, 0xBC, 0x72, 0x52, 0x39, 0x78, 0xC0, + 0x42, 0xE4, 0x28, 0x5F, 0x43, 0x19, 0x84, 0x6C, 0x44, 0x12, 0x62, 0x42, 0x97, 0x68, 0x44, + 0xC1, 0x0E, 0x55, 0x6B, 0xA2, 0x15, 0xB5, 0xA7, 0x19, 0xE5, 0x9D, 0x0C, 0x6B, 0x2A, 0x96, + 0xD3, 0x98, 0x59, 0x07, 0x1F, 0xDC, 0xC2, 0xCD, 0xE7, 0x52, 0x4A, 0x7B, 0xED, 0xAE, 0x54, + 0xE8, 0x5B, 0x31, 0x8E, 0x85, 0x4E, 0x8F, 0xE2, 0xB2, 0xF3, 0xED, 0xFA, 0xC9, 0x71, 0x91, + 0x28, 0x27, 0x0A, 0xAF, 0xD1, 0xE5, 0x04, 0x4C, 0x3A, 0x4F, 0xDA, 0xFD, 0x9F, 0xF3, 0x1F, + 0x90, 0x78, 0x4B, 0x8E, 0x8E, 0x45, 0x96, 0x14, 0x4A, 0x0D, 0xAF, 0x58, 0x65, 0x11, 0xD3, + 0xD9, 0x96, 0x2B, 0x9E, 0xA9, 0x5A, 0xF1, 0x97, 0xB4, 0xE5, 0xFC, 0x60, 0xF2, 0xB1, 0xED, + 0x15, 0xDE, 0x3A, 0x5B, 0xEF, 0x5F, 0x89, 0xBD, 0xC7, 0x9D, 0x91, 0x05, 0x1D, 0x9B, 0x28, + 0x16, 0xE7, 0x4F, 0xA5, 0x45, 0x31, 0xEF, 0xDC, 0x1C, 0xBE, 0x74, 0xD4, 0x48, 0x85, 0x7F, + 0x47, 0x6B, 0xCD, 0x58, 0xF2, 0x1C, 0x0B, 0x65, 0x3B, 0x3B, 0x76, 0xA4, 0xE0, 0x76, 0xA6, + 0x55, 0x9A, 0x30, 0x27, 0x18, 0x55, 0x5C, 0xC6, 0x3F, 0x74, 0x85, 0x9A, 0xAB, 0xAB, 0x92, + 0x5F, 0x02, 0x38, 0x61, 0xCA, 0x8C, 0xD0, 0xF7, 0xBA, 0xDB, 0x28, 0x71, 0xF6, 0x7D, 0x55, + 0x32, 0x6D, 0x74, 0x51, 0x13, 0x5A, 0xD4, 0x5F, 0x4A, 0x1B, 0xA6, 0x91, 0x18, 0xFB, 0xB2, + 0xC8, 0xA3, 0x0E, 0xEC, 0x93, 0x92, 0xEF, 0x3F, 0x97, 0x70, 0x66, 0xC9, 0xAD, 0xD5, 0xC7, + 0x10, 0xCC, 0x64, 0x7B, 0x15, 0x14, 0xD2, 0x17, 0xD9, 0x58, 0xC7, 0x01, 0x7C, 0x3E, 0x90, + 0xFD, 0x20, 0xC0, 0x4E, 0x67, 0x4B, 0x90, 0x48, 0x6E, 0x93, 0x70, 0xA3, 0x1A, 0x00, 0x1D, + 0x32, 0xF4, 0x73, 0x97, 0x9E, 0x49, 0x06, 0x74, 0x9E, 0x7E, 0x47, 0x7F, 0xA0, 0xB7, 0x45, + 0x08, 0xF8, 0xA5, 0xF2, 0x37, 0x83, 0x12, 0xB8, 0x3C, 0x25, 0xBD, 0x38, 0x8C, 0xA0, 0xB0, + 0xFF, 0xF7, 0x47, 0x8B, 0xAF, 0x42, 0xB7, 0x16, 0x67, 0xED, 0xAA, 0xC9, 0x7C, 0x46, 0xB1, + 0x29, 0x64, 0x3E, 0x58, 0x6E, 0x5B, 0x05, 0x5A, 0x0C, 0x21, 0x19, 0x46, 0xD4, 0xF3, 0x6E, + 0x67, 0x5B, 0xED, 0x58, 0x60, 0xFA, 0x04, 0x2A, 0x31, 0x5D, 0x98, 0x26, 0x16, 0x4D, 0x6A, + 0x92, 0x37, 0xC3, 0x5A, 0x5F, 0xBF, 0x49, 0x54, 0x90, 0xA5, 0xBD, 0x4D, 0xF2, 0x48, 0xB9, + 0x5C, 0x4A, 0xAE, 0x77, 0x84, 0xB6, 0x05, 0x67, 0x31, 0x66, 0xAC, 0x42, 0x45, 0xB5, 0xB4, + 0xB0, 0x82, 0xA0, 0x9E, 0x93, 0x23, 0xE6, 0x2F, 0x20, 0x78, 0xC5, 0xB7, 0x67, 0x83, 0x44, + 0x6D, 0xEF, 0xD7, 0x36, 0xAD, 0x3A, 0x37, 0x02, 0xD4, 0x9B, 0x08, 0x98, 0x44, 0x90, 0x0A, + 0x61, 0x83, 0x33, 0x97, 0xBC, 0x44, 0x19, 0xB3, 0x0D, 0x7A, 0x97, 0xA0, 0xB3, 0x87, 0xC1, + 0x91, 0x14, 0x74, 0xC4, 0xD4, 0x1B, 0x53, 0xE3, 0x2A, 0x97, 0x7A, 0xCB, 0x6F, 0x0E, 0xA7, + 0x5D, 0xB6, 0x5B, 0xB3, 0x9E, 0x59, 0xE7, 0x01, 0xE7, 0x69, 0x57, 0xDE, 0xF6, 0xF2, 0xD4, + 0x45, 0x59, 0xC3, 0x1A, 0x77, 0x12, 0x2B, 0x52, 0x04, 0xE3, 0xB5, 0xC2, 0x19, 0xF1, 0x68, + 0x8B, 0x14, 0xED, 0x0B, 0xC0, 0xB8, 0x01, 0xB3, 0xE6, 0xE8, 0x2D, 0xCD, 0x43, 0xE9, 0xC0, + 0xE9, 0xF4, 0x17, 0x44, 0xCD, 0x98, 0x15, 0xBD, 0x1B, 0xC8, 0x82, 0x0D, 0x8B, 0xB1, 0x23, + 0xF0, 0x4F, 0xAC, 0xD1, 0xB1, 0xB6, 0x85, 0xDD, 0x5A, 0x2B, 0x1B, 0x8D, 0xBB, 0xF3, 0xED, + 0x93, 0x36, 0x70, 0xF0, 0x95, 0xA1, 0x80, 0xB4, 0xF1, 0x92, 0xD0, 0x8B, 0x10, 0xB8, 0xFA, + 0xBB, 0xDF, 0xCC, 0x2B, 0x24, 0x51, 0x8E, 0x32, 0xEE, 0xA0, 0xA5, 0xE0, 0xC9, 0x04, 0xCA, + 0x84, 0x47, 0x80, 0x08, 0x3F, 0x3B, 0x0C, 0xD2, 0xD0, 0xB8, 0xB6, 0xAF, 0x67, 0xBC, 0x35, + 0x5B, 0x94, 0x94, 0x02, 0x5D, 0xC7, 0xB0, 0xA7, 0x8F, 0xA8, 0x0E, 0x3A, 0x2D, 0xBF, 0xEB, + 0x51, 0x32, 0x88, 0x51, 0xD6, 0x07, 0x81, 0x98, 0xE9, 0x49, 0x36, 0x51, 0xAE, 0x78, 0x7E, + 0xC0, 0x25, 0x1F, 0x92, 0x2B, 0xA3, 0x0E, 0x9F, 0x51, 0xDF, 0x62, 0xA6, 0xD7, 0x27, 0x84, + 0xCF, 0x3D, 0xD2, 0x05, 0x39, 0x31, 0x76, 0xDF, 0xA3, 0x24, 0xA5, 0x12, 0xBD, 0x94, 0x97, + 0x0A, 0x36, 0xDD, 0x34, 0xA5, 0x14, 0xA8, 0x67, 0x91, 0xF0, 0xEB, 0x36, 0xF0, 0x14, 0x5B, + 0x09, 0xAB, 0x64, 0x65, 0x1B, 0x4A, 0x03, 0x13, 0xB2, 0x99, 0x61, 0x1A, 0x2A, 0x1C, 0x48, + 0x89, 0x16, 0x27, 0x59, 0x87, 0x68, 0xA3, 0x11, 0x40, 0x60, 0xBA, 0x44, 0x43, 0x48, 0x6D, + 0xF5, 0x15, 0x22, 0xA1, 0xCE, 0x88, 0xB3, 0x09, 0x85, 0xC2, 0x16, 0xF8, 0xE6, 0xED, 0x17, + 0x8D, 0xD5, 0x67, 0xB3, 0x04, 0xA0, 0xD4, 0xCA, 0xFB, 0xA8, 0x82, 0xA2, 0x83, 0x42, 0xF1, + 0x7A, 0x9A, 0xA2, 0x6A, 0xE5, 0x8D, 0xB6, 0x30, 0x08, 0x3D, 0x2C, 0x35, 0x8F, 0xDF, 0x56, + 0x6C, 0x3F, 0x5D, 0x62, 0xA4, 0x28, 0x56, 0x7B, 0xC9, 0xEA, 0x8C, 0xE9, 0x5C, 0xAA, 0x0F, + 0x35, 0x47, 0x4B, 0x0B, 0xFA, 0x8F, 0x33, 0x9A, 0x25, 0x0A, 0xB4, 0xDF, 0xCF, 0x20, 0x83, + 0xBE, 0x8E, 0xEF, 0xBC, 0x10, 0x55, 0xE1, 0x8F, 0xE1, 0x53, 0x70, 0xEE, 0xCB, 0x26, 0x05, + 0x66, 0xD8, 0x3F, 0xF0, 0x6B, 0x21, 0x1A, 0xAE, 0xC4, 0x3C, 0xA2, 0x9B, 0x54, 0xCC, 0xD0, + 0x0F, 0x88, 0x15, 0xA2, 0x46, 0x5E, 0xF0, 0xB4, 0x65, 0x15, 0xCC, 0x7E, 0x41, 0xF3, 0x12, + 0x4F, 0x09, 0xEF, 0xFF, 0x73, 0x93, 0x09, 0xAB, 0x58, 0xB2, 0x9A, 0x14, 0x59, 0xA0, 0x0B, + 0xCE, 0x50, 0x38, 0xE9, 0x38, 0xC9, 0x67, 0x8F, 0x72, 0xEB, 0x0E, 0x4E, 0xE5, 0xFD, 0xAA, + 0xE6, 0x6D, 0x9F, 0x85, 0x73, 0xFC, 0x97, 0xFC, 0x42, 0xB4, 0x95, 0x9F, 0x4B, 0xF8, 0xB6, + 0x1D, 0x78, 0x43, 0x3E, 0x86, 0xB0, 0x33, 0x5D, 0x6E, 0x91, 0x91, 0xC4, 0xD8, 0xBF, 0x48, + 0x7B, 0x39, 0x05, 0xC1, 0x08, 0xCF, 0xD6, 0xAC, 0x24, 0xB0, 0xCE, 0xB7, 0xDC, 0xB7, 0xCF, + 0x51, 0xF8, 0x4D, 0x0E, 0xD6, 0x87, 0xB9, 0x5E, 0xAE, 0xB1, 0xC5, 0x33, 0xC0, 0x6F, 0x0D, + 0x97, 0x02, 0x3D, 0x92, 0xA7, 0x08, 0x25, 0x83, 0x7B, 0x59, 0xBA, 0x6C, 0xB7, 0xD4, 0xE5, + 0x6B, 0x0A, 0x87, 0xC2, 0x03, 0x86, 0x2A, 0xE8, 0xF3, 0x15, 0xBA, 0x59, 0x25, 0xE8, 0xED, + 0xEF, 0xA6, 0x79, 0x36, 0x9A, 0x22, 0x02, 0x76, 0x61, 0x51, 0xF1, 0x6A, 0x96, 0x5F, 0x9F, + 0x81, 0xEC, 0xE7, 0x6C, 0xC0, 0x70, 0xB5, 0x58, 0x69, 0xE4, 0xDB, 0x97, 0x84, 0xCF, 0x05, + 0xC8, 0x30, 0xB3, 0x24, 0x2C, 0x83, 0x12, + ]) + .unwrap(); + let sig: [u8; MLDSA44_SIG_LEN] = [ + 0x5E, 0x93, 0xB7, 0x85, 0xC5, 0x11, 0x9C, 0x39, 0x83, 0xA2, 0x91, 0xB1, 0x84, 0x20, 0xFD, + 0xBE, 0x4B, 0xCA, 0x53, 0xD5, 0xA3, 0x73, 0x29, 0x22, 0xFA, 0xAA, 0xCD, 0x5A, 0x5D, 0x32, + 0xA7, 0x45, 0xC7, 0x8D, 0x10, 0x5B, 0xA1, 0x0B, 0xEE, 0x1E, 0xD8, 0x06, 0x9F, 0x19, 0xE6, + 0xC5, 0x37, 0xBD, 0xA1, 0x6E, 0x89, 0xD3, 0x90, 0x04, 0xC3, 0x59, 0xD1, 0xFD, 0x38, 0x1A, + 0x02, 0x91, 0xF1, 0xC5, 0x1F, 0x1C, 0x38, 0xED, 0xCD, 0xB3, 0x15, 0xC8, 0xC6, 0x95, 0x70, + 0xD8, 0xF2, 0x5F, 0x16, 0x55, 0xBA, 0x8E, 0xA8, 0x3A, 0xFF, 0x24, 0xB8, 0xB6, 0xBE, 0x8D, + 0xE7, 0x62, 0x34, 0x2E, 0x34, 0x7E, 0xAB, 0x2C, 0xAA, 0x68, 0x03, 0xED, 0x70, 0x59, 0x52, + 0xDD, 0x64, 0x50, 0xC5, 0x18, 0x5E, 0x9D, 0x60, 0xCE, 0x96, 0xE8, 0xDC, 0xA4, 0x23, 0xA0, + 0x2F, 0x64, 0x6C, 0xEA, 0x69, 0x01, 0x64, 0xA2, 0x26, 0xE4, 0xC3, 0xD6, 0xA5, 0x15, 0xCE, + 0x16, 0x29, 0x0F, 0x19, 0xB2, 0xC6, 0x26, 0xDA, 0x9B, 0x45, 0x0E, 0xCF, 0x66, 0x50, 0x13, + 0xC5, 0xE2, 0x26, 0xB6, 0xC0, 0xAC, 0x5C, 0x07, 0xCE, 0x90, 0xE2, 0x78, 0xF1, 0xB0, 0x13, + 0x4E, 0x38, 0x5D, 0x13, 0xE7, 0x42, 0x08, 0xA0, 0xB3, 0xFF, 0x05, 0x2A, 0x36, 0x25, 0x79, + 0xF9, 0x20, 0x7E, 0xA0, 0x1F, 0x18, 0xA0, 0x39, 0xAA, 0x1B, 0x97, 0xAE, 0x34, 0x52, 0x67, + 0x5B, 0x62, 0x07, 0x71, 0xF8, 0x01, 0x2E, 0xE7, 0xA4, 0xE5, 0x5C, 0x98, 0xBF, 0xD2, 0x01, + 0x9E, 0xD8, 0xA3, 0xB0, 0x0A, 0xCE, 0xA8, 0xE8, 0xAB, 0x28, 0x17, 0x2F, 0xAA, 0x42, 0xCA, + 0x1F, 0xDA, 0x83, 0xC5, 0xFF, 0xE8, 0x1A, 0x45, 0xBE, 0x73, 0x6B, 0xDE, 0xDD, 0x5F, 0xB3, + 0x00, 0xCE, 0x17, 0x07, 0x8B, 0x38, 0x0F, 0x62, 0x0B, 0xDE, 0xEB, 0xAD, 0x69, 0x36, 0x01, + 0x37, 0x2C, 0x85, 0xEA, 0xCF, 0x79, 0xBC, 0x98, 0xE1, 0xB4, 0x8F, 0x2A, 0xD7, 0xE5, 0xDC, + 0xE4, 0x27, 0x9A, 0x12, 0x95, 0xBB, 0x2B, 0xA6, 0x0A, 0x0C, 0x5E, 0x37, 0x26, 0x64, 0x2D, + 0x23, 0x36, 0xC5, 0xEB, 0x1D, 0x37, 0xC8, 0x62, 0x3C, 0x75, 0x58, 0x24, 0x13, 0x18, 0xD8, + 0x9B, 0xC7, 0x83, 0xC4, 0xF0, 0x00, 0x98, 0x07, 0x74, 0x84, 0x62, 0x3C, 0x21, 0x75, 0x60, + 0xA0, 0xC7, 0xAA, 0xF7, 0x5D, 0xCA, 0xCC, 0xB7, 0x8E, 0xE6, 0x9C, 0x20, 0x7C, 0x27, 0xC8, + 0xBF, 0x39, 0x65, 0xCC, 0xF5, 0x8A, 0x80, 0xC8, 0x8E, 0xFC, 0xC7, 0xE5, 0xDE, 0xB3, 0x61, + 0x5D, 0x50, 0x45, 0xA7, 0x41, 0xC4, 0xDA, 0xC0, 0xA0, 0x21, 0xDD, 0x06, 0x0D, 0x31, 0x5D, + 0x4E, 0xC2, 0x85, 0x7E, 0xB6, 0x64, 0xD7, 0x28, 0xD0, 0xAF, 0x97, 0x3B, 0xEA, 0x07, 0xE1, + 0xCA, 0x56, 0x3F, 0xAA, 0x0E, 0x19, 0x99, 0x6C, 0xEA, 0x37, 0x70, 0x31, 0x6C, 0x11, 0xA5, + 0x06, 0x66, 0x65, 0x66, 0x20, 0x05, 0xAC, 0xE9, 0x8F, 0x61, 0x10, 0xE8, 0x83, 0xBA, 0xE0, + 0x60, 0xDA, 0xA7, 0xB6, 0xD8, 0x33, 0x79, 0xE0, 0x87, 0x87, 0x96, 0x69, 0x17, 0x08, 0xA3, + 0x2B, 0x85, 0x73, 0x0D, 0xE8, 0xB9, 0x2D, 0x89, 0xF9, 0x0A, 0x36, 0x60, 0xC9, 0x49, 0x16, + 0x5B, 0x14, 0x61, 0x25, 0x67, 0x66, 0x2E, 0x16, 0x22, 0x32, 0x29, 0x6C, 0xBD, 0x14, 0x35, + 0x17, 0xA2, 0x82, 0xE2, 0x2C, 0x46, 0xB6, 0x36, 0x06, 0xD3, 0xC1, 0x4E, 0xD4, 0x55, 0x9A, + 0x5A, 0x1C, 0x45, 0x9B, 0xAB, 0x7F, 0x35, 0x50, 0x07, 0xAD, 0x6F, 0x7E, 0x3B, 0x1E, 0x07, + 0x44, 0x5D, 0xFC, 0x96, 0xBD, 0x9B, 0x75, 0x08, 0x0B, 0x3D, 0x4F, 0x68, 0x99, 0x84, 0x90, + 0xA2, 0x6B, 0x5E, 0x09, 0x0B, 0xE2, 0x67, 0x40, 0x71, 0xAB, 0x92, 0x5B, 0xB6, 0x50, 0x59, + 0x08, 0x56, 0xC5, 0x9F, 0x8B, 0xA7, 0x48, 0x8D, 0x2B, 0x72, 0xF8, 0x40, 0xAC, 0x3E, 0xAF, + 0xE4, 0xDD, 0x91, 0xF0, 0xF5, 0x1C, 0x43, 0x64, 0x11, 0x2C, 0x1A, 0x13, 0x9E, 0x3E, 0x94, + 0x2A, 0x59, 0x7B, 0x93, 0xA1, 0xE3, 0xF4, 0xFA, 0xDE, 0xD1, 0x29, 0xC1, 0x4B, 0x59, 0x78, + 0xB3, 0x15, 0xE2, 0x24, 0x6A, 0x93, 0x14, 0x6A, 0x79, 0x36, 0x5F, 0x0F, 0x59, 0x7A, 0x18, + 0x34, 0x0C, 0xCA, 0x86, 0xBB, 0x15, 0xCE, 0xED, 0x39, 0xF1, 0x75, 0xEA, 0xB1, 0xE5, 0x46, + 0x53, 0x5A, 0xFB, 0x96, 0x6F, 0x0A, 0x65, 0xA8, 0xF6, 0x6F, 0x73, 0x7A, 0xB0, 0x28, 0x97, + 0xED, 0xDF, 0xE9, 0x2C, 0xF7, 0x78, 0x68, 0x94, 0x84, 0x3C, 0x26, 0x91, 0x46, 0x47, 0x76, + 0xC9, 0x4B, 0xD4, 0x50, 0xA1, 0x06, 0x91, 0x38, 0xB2, 0x6D, 0xF8, 0x3B, 0x2D, 0x1D, 0xD8, + 0x01, 0x14, 0x3A, 0x8F, 0xDF, 0xDC, 0x25, 0x14, 0xCC, 0x5B, 0x58, 0x31, 0xAB, 0x53, 0xA7, + 0x5C, 0x55, 0xEF, 0x29, 0xF4, 0x0E, 0x7C, 0x63, 0xD2, 0xC7, 0x2A, 0xBE, 0x97, 0xE2, 0xAF, + 0x14, 0x85, 0x3B, 0xE4, 0x9B, 0xE1, 0x6F, 0x47, 0x30, 0xA1, 0x59, 0x97, 0x49, 0x70, 0x95, + 0x14, 0x39, 0xE5, 0x5C, 0x15, 0x89, 0xD0, 0xF4, 0xA1, 0x62, 0xE3, 0x51, 0x7D, 0xF9, 0xD7, + 0xAB, 0xC9, 0x8D, 0x8A, 0x30, 0x72, 0x16, 0xE7, 0xF1, 0xCB, 0x46, 0x27, 0xC9, 0x17, 0x5C, + 0x0E, 0xEF, 0x23, 0x33, 0x7E, 0x56, 0xD5, 0x28, 0x1B, 0x83, 0x72, 0x6F, 0xFF, 0x40, 0xA1, + 0x48, 0xB0, 0xC4, 0x8E, 0x8D, 0xF3, 0x49, 0x6A, 0x21, 0x18, 0xD8, 0x02, 0x19, 0xAE, 0xF8, + 0xF4, 0x0B, 0x29, 0xFB, 0xA1, 0xF2, 0xF7, 0x87, 0x86, 0xB6, 0x7F, 0xFB, 0x7B, 0x7D, 0x47, + 0xD4, 0x06, 0xB7, 0x65, 0xBD, 0x13, 0x66, 0x10, 0xBE, 0xDE, 0xB9, 0x5C, 0xD7, 0x32, 0x1F, + 0x58, 0xF3, 0xB8, 0x36, 0xC9, 0x25, 0x8B, 0xE3, 0x5D, 0x78, 0xB4, 0x98, 0xF3, 0xEF, 0xE1, + 0xDB, 0x2B, 0x24, 0x3D, 0x73, 0x4F, 0xAB, 0x15, 0x9B, 0xAE, 0xD8, 0x80, 0x7C, 0x3C, 0xCC, + 0xF8, 0x3E, 0xB2, 0xEA, 0xF8, 0xA9, 0xAF, 0x01, 0xA5, 0x18, 0xD4, 0x8C, 0x60, 0xE9, 0x1A, + 0x96, 0x81, 0x2A, 0xD6, 0x89, 0xC2, 0xD8, 0x3C, 0xC4, 0xE8, 0xE9, 0xB3, 0x65, 0x04, 0x22, + 0xBE, 0xD6, 0xF1, 0x3C, 0x24, 0xAD, 0xAA, 0xD9, 0x1C, 0x95, 0xB3, 0xE3, 0xCF, 0x35, 0x4F, + 0x0F, 0x6B, 0xC9, 0xEE, 0x89, 0x41, 0xA6, 0xB1, 0x5B, 0x69, 0x75, 0x13, 0x1D, 0x95, 0x23, + 0x3D, 0x89, 0x35, 0xDE, 0x36, 0x7E, 0xFC, 0x6D, 0x86, 0xA4, 0x5D, 0xAC, 0x7D, 0x0F, 0x1D, + 0xDD, 0x9A, 0xEB, 0xD2, 0xC5, 0x9C, 0x02, 0x7F, 0xCD, 0xA4, 0x48, 0x80, 0x1E, 0x93, 0xE7, + 0x33, 0xAC, 0xA5, 0x18, 0x74, 0xBE, 0x9A, 0xB9, 0x27, 0xA9, 0x04, 0xF9, 0x6D, 0xDB, 0x7A, + 0x46, 0xB2, 0xDA, 0x13, 0x26, 0x1D, 0x52, 0x2B, 0x23, 0xC9, 0x50, 0xC0, 0x1D, 0x5F, 0x5E, + 0x11, 0x2B, 0x76, 0xF8, 0x51, 0xFF, 0x23, 0x4F, 0x06, 0xF8, 0xD5, 0xE6, 0x5B, 0x13, 0x19, + 0xAB, 0xCD, 0x79, 0xA1, 0x80, 0xAE, 0x06, 0x3D, 0x65, 0xB2, 0x8C, 0x74, 0x58, 0x78, 0xC0, + 0x6D, 0xBB, 0x69, 0xBA, 0x73, 0x29, 0x3E, 0xAB, 0x34, 0x43, 0x4B, 0xF1, 0xA9, 0x2F, 0xBA, + 0x69, 0x19, 0x93, 0xBD, 0x0F, 0xF3, 0xED, 0xAC, 0x76, 0xA1, 0x2F, 0x80, 0xC0, 0xAD, 0xA4, + 0xB1, 0x96, 0x9C, 0x76, 0x65, 0x58, 0x9D, 0x53, 0x0A, 0x67, 0x01, 0x6A, 0x62, 0x54, 0x03, + 0xC5, 0x37, 0x03, 0x29, 0x04, 0xF2, 0xE1, 0x04, 0x54, 0x7C, 0xD3, 0xEA, 0x40, 0x62, 0x60, + 0xDD, 0x35, 0x7F, 0xA0, 0x6E, 0xA0, 0x12, 0xA7, 0x85, 0x82, 0x6C, 0x16, 0x0E, 0x99, 0xFF, + 0xD0, 0x65, 0xB0, 0xE3, 0xF3, 0x3C, 0x76, 0x89, 0xD3, 0x55, 0x2A, 0xB9, 0xE2, 0xE0, 0x9F, + 0xA7, 0xE5, 0x5B, 0xBC, 0xEF, 0x04, 0x22, 0x42, 0xBC, 0xAC, 0xAD, 0x8A, 0x3D, 0xA4, 0x7B, + 0xCC, 0x54, 0xA1, 0x21, 0xF1, 0x52, 0x6C, 0x8C, 0xD4, 0xCC, 0x5A, 0x89, 0x2A, 0x81, 0x31, + 0xCF, 0x4E, 0xEF, 0xAF, 0x42, 0x48, 0xDD, 0xD6, 0xA1, 0x1E, 0xC4, 0x27, 0xBA, 0x37, 0x8A, + 0xAE, 0x89, 0xAA, 0xF5, 0x82, 0xCE, 0x1F, 0x4E, 0x32, 0x69, 0x0A, 0x55, 0x5E, 0x74, 0x07, + 0x61, 0xD3, 0x58, 0xAD, 0x4E, 0x92, 0xBC, 0x38, 0x41, 0x8A, 0xA7, 0x82, 0xDA, 0x91, 0x65, + 0x24, 0xFB, 0x09, 0xAB, 0x2C, 0xA6, 0xB3, 0xD3, 0x11, 0x3D, 0x6F, 0x2C, 0x2A, 0x6A, 0x9B, + 0x9D, 0x29, 0xD4, 0xE7, 0x48, 0x92, 0x55, 0x25, 0x2A, 0xF0, 0x75, 0xCB, 0xF9, 0xFE, 0xAC, + 0xED, 0xAE, 0x6F, 0x3E, 0xC0, 0xB0, 0x70, 0x82, 0x46, 0x89, 0xDD, 0x3C, 0x78, 0xAC, 0x14, + 0x3E, 0xD6, 0x77, 0x6D, 0x95, 0xDD, 0x8F, 0x13, 0xD4, 0x35, 0xA2, 0x90, 0xBD, 0xCA, 0x4C, + 0x11, 0x31, 0x8E, 0x5A, 0xCC, 0xE0, 0x44, 0x69, 0x64, 0x4E, 0x13, 0x74, 0xA9, 0x45, 0x1B, + 0x62, 0x04, 0xF3, 0xB3, 0x96, 0x1B, 0x7D, 0xD2, 0x39, 0xE3, 0x06, 0xFE, 0xF5, 0xF4, 0xF4, + 0xE5, 0x1B, 0x78, 0xB0, 0xFB, 0x9D, 0xCE, 0xE6, 0x9C, 0x3E, 0x79, 0x0B, 0x23, 0x1F, 0x2E, + 0x65, 0xFD, 0x1A, 0xB1, 0xC2, 0xA7, 0x5B, 0x07, 0x06, 0x7D, 0x5C, 0x16, 0xDD, 0xE0, 0x09, + 0x83, 0xA5, 0x8F, 0xFC, 0xDA, 0xAA, 0xEE, 0x16, 0xD2, 0x74, 0x2E, 0x13, 0x3E, 0xD7, 0x37, + 0xB4, 0x80, 0x64, 0xC8, 0xA3, 0x8E, 0xCA, 0x35, 0xAB, 0x3F, 0xA1, 0x8F, 0x6D, 0x62, 0xF6, + 0x42, 0xB1, 0x2C, 0xFD, 0xC7, 0x98, 0x0F, 0x2A, 0xB7, 0xDB, 0x32, 0x1F, 0xEC, 0x9D, 0xCF, + 0xE4, 0x99, 0xB4, 0xFC, 0x1E, 0xE7, 0xEB, 0x29, 0x79, 0x54, 0x05, 0x66, 0x17, 0xC6, 0x0A, + 0x66, 0x40, 0xB9, 0x28, 0x35, 0xD1, 0x65, 0xC3, 0xC0, 0x0A, 0x95, 0x19, 0x52, 0x61, 0x44, + 0x88, 0xD5, 0x65, 0x7B, 0xA0, 0xB5, 0xE9, 0x0A, 0xE9, 0xE0, 0xEF, 0x7B, 0x3B, 0x9E, 0xCA, + 0xEB, 0xD8, 0x1B, 0x85, 0x51, 0xB6, 0xD7, 0x0E, 0x83, 0x5B, 0x27, 0x34, 0x76, 0x16, 0x39, + 0xD4, 0x2E, 0x76, 0xFF, 0xC5, 0xB3, 0x27, 0x2B, 0x61, 0xC8, 0x96, 0xB4, 0x5B, 0x4B, 0xD1, + 0x8F, 0x30, 0xE5, 0x8C, 0x44, 0x06, 0x43, 0xBA, 0x15, 0x92, 0x21, 0xCC, 0x67, 0x39, 0xA1, + 0x9A, 0x65, 0xF2, 0x91, 0x1F, 0xAE, 0x47, 0xB0, 0xD4, 0xCA, 0xC4, 0x20, 0x0A, 0x6F, 0x04, + 0x3B, 0x17, 0xA0, 0x3A, 0xD3, 0x93, 0xEC, 0xB8, 0x23, 0xED, 0x03, 0xC8, 0xB6, 0xCD, 0x68, + 0x16, 0x7E, 0x6C, 0x82, 0x34, 0xF7, 0x43, 0x25, 0x57, 0xDB, 0x27, 0x20, 0x79, 0xEE, 0x89, + 0x9A, 0xED, 0xE7, 0x3B, 0x6B, 0x98, 0xD6, 0x00, 0x3F, 0x45, 0x78, 0x9A, 0x14, 0x1B, 0x60, + 0xD6, 0xDB, 0x40, 0xCD, 0x2A, 0x59, 0x74, 0x57, 0x1A, 0x4A, 0xD3, 0x66, 0x7B, 0x88, 0x93, + 0x18, 0xBA, 0x60, 0x28, 0x5D, 0x90, 0x3A, 0x2E, 0xAC, 0x01, 0xC2, 0x16, 0x08, 0x83, 0x8C, + 0x40, 0x90, 0x7D, 0xE6, 0xBB, 0xAB, 0xE0, 0x42, 0xCF, 0x2E, 0xCD, 0xD9, 0x7F, 0x54, 0x9F, + 0x95, 0xEC, 0x69, 0x8D, 0x79, 0x22, 0x2C, 0x65, 0xBA, 0x27, 0xC3, 0x0D, 0x33, 0x2A, 0x68, + 0xD0, 0x57, 0xAE, 0xCD, 0xC9, 0x38, 0x8A, 0xA3, 0x43, 0x20, 0xE0, 0xAA, 0x74, 0xFD, 0xBD, + 0x4D, 0x1B, 0x64, 0x3C, 0xAC, 0xE2, 0x16, 0xB6, 0xD8, 0xAD, 0x8F, 0x07, 0xA9, 0x99, 0x55, + 0xBF, 0xDB, 0x74, 0x3A, 0x86, 0xB4, 0x0F, 0xC6, 0x15, 0x27, 0xBA, 0xCA, 0x43, 0x4A, 0xC2, + 0xA7, 0xFB, 0xEA, 0xA7, 0x71, 0x11, 0xDC, 0x80, 0x98, 0xB1, 0x7E, 0x80, 0x0F, 0x59, 0xDD, + 0x77, 0xCC, 0xB0, 0xE6, 0x77, 0x07, 0xE6, 0x01, 0x23, 0xD3, 0x34, 0xE0, 0x73, 0xA2, 0xF5, + 0xA1, 0x6F, 0xFB, 0xCD, 0x70, 0x13, 0x89, 0xAD, 0xD5, 0x7C, 0x3C, 0xEC, 0xCB, 0x88, 0xB2, + 0x86, 0xAC, 0x1E, 0x6E, 0x3E, 0x64, 0x85, 0xAF, 0x1A, 0x12, 0xEA, 0x24, 0x1D, 0x14, 0xA1, + 0xB5, 0x00, 0x3D, 0x7F, 0x3B, 0xC9, 0xE9, 0x57, 0xD4, 0x48, 0x3C, 0x0F, 0x9F, 0x70, 0x3B, + 0x3A, 0x18, 0x7D, 0x55, 0xE5, 0x05, 0x81, 0x76, 0x15, 0xFB, 0xC4, 0xAE, 0x08, 0x37, 0x61, + 0x61, 0x84, 0x24, 0x5C, 0xFB, 0xA6, 0x1C, 0xE3, 0xB9, 0x29, 0xE3, 0x3F, 0x52, 0xB7, 0x1C, + 0xDD, 0x7B, 0x6A, 0x0D, 0xA5, 0x5C, 0x1F, 0x99, 0x75, 0x10, 0xB1, 0xA9, 0x00, 0x2C, 0xA4, + 0xE0, 0x67, 0x83, 0x73, 0xA3, 0xB1, 0xAB, 0x28, 0x97, 0xE6, 0xB4, 0x23, 0xF1, 0x5A, 0x44, + 0x0A, 0x63, 0x6C, 0xC8, 0x61, 0x49, 0x1E, 0xF4, 0x1A, 0xD0, 0xAA, 0x62, 0x7D, 0x8E, 0x19, + 0x8A, 0x5E, 0xE7, 0xBD, 0x7B, 0x6C, 0xB2, 0xC9, 0xCE, 0x2A, 0x8C, 0xC0, 0x15, 0xF0, 0xD2, + 0x06, 0xDE, 0x4C, 0x49, 0xE2, 0xF8, 0x7F, 0x31, 0x09, 0x54, 0xA1, 0x0D, 0x86, 0xE2, 0x94, + 0xF7, 0x42, 0xEE, 0x18, 0x6F, 0x4A, 0xE9, 0x81, 0x5F, 0x69, 0x96, 0x22, 0x79, 0x22, 0x06, + 0xCA, 0xFB, 0xA8, 0xF5, 0x62, 0x17, 0x38, 0x16, 0x0E, 0x6C, 0x5D, 0x61, 0x1A, 0x82, 0x52, + 0xC6, 0xF3, 0x50, 0x85, 0xB6, 0x04, 0xEF, 0x89, 0x51, 0x64, 0xD4, 0xEA, 0x6D, 0xDD, 0x31, + 0x0C, 0x7D, 0x8F, 0x0C, 0x87, 0x9F, 0xB1, 0xF8, 0x84, 0xC5, 0x74, 0x1D, 0x09, 0x6B, 0x3D, + 0x2D, 0xA0, 0xCE, 0x11, 0x51, 0x79, 0x0D, 0xDA, 0x88, 0x1D, 0x18, 0xCB, 0x6B, 0x19, 0xA9, + 0xFE, 0xD6, 0xF5, 0x25, 0x4B, 0x7D, 0x52, 0xD5, 0xD9, 0x2B, 0xBB, 0xE2, 0x4C, 0x9D, 0x6A, + 0x65, 0x60, 0x4A, 0x0B, 0x8E, 0xD2, 0x4A, 0xD5, 0xC1, 0x97, 0xD6, 0x83, 0xF5, 0x98, 0x74, + 0x3C, 0x96, 0xB5, 0x96, 0x0E, 0x87, 0x23, 0x73, 0x2B, 0x5B, 0xD6, 0x47, 0xE9, 0xDB, 0xEA, + 0xA8, 0x51, 0xD0, 0xE1, 0xCF, 0x6D, 0x2C, 0x07, 0x0D, 0x44, 0x42, 0x76, 0x2C, 0x28, 0x09, + 0x8C, 0x5C, 0xF5, 0xA5, 0x4B, 0x2B, 0x5E, 0x69, 0xA9, 0x9B, 0x10, 0x81, 0x5B, 0xF0, 0xF4, + 0x77, 0xBB, 0x71, 0xF0, 0xD5, 0xD3, 0xA6, 0x2B, 0xA2, 0xB3, 0xE2, 0x9B, 0xF8, 0x4D, 0x4B, + 0x4E, 0x57, 0x47, 0x07, 0xF5, 0xF7, 0x4A, 0xF7, 0x04, 0xD2, 0x77, 0xBD, 0x6C, 0xA3, 0x8D, + 0xA2, 0x1E, 0x2C, 0xDA, 0xC5, 0x49, 0xE5, 0xEA, 0xE1, 0xDE, 0x7A, 0x18, 0xEE, 0x53, 0x4C, + 0x8C, 0x22, 0x91, 0xC9, 0x08, 0xCA, 0xAB, 0xF1, 0x59, 0xE9, 0x0E, 0x65, 0x49, 0xDB, 0x94, + 0xBA, 0x7A, 0x3F, 0x3D, 0x97, 0xDD, 0x39, 0x8A, 0x75, 0xDF, 0x5B, 0x1A, 0x7C, 0xDF, 0xB2, + 0x54, 0x10, 0xB7, 0xEF, 0xC4, 0xED, 0x00, 0xD9, 0x99, 0x5B, 0x37, 0xB5, 0x8B, 0xF9, 0x1E, + 0xD7, 0xA3, 0x51, 0x0C, 0xFF, 0xEA, 0x82, 0xF9, 0xE1, 0xC2, 0xA3, 0x29, 0x04, 0x06, 0x00, + 0x4D, 0x09, 0x05, 0x7D, 0x63, 0xB7, 0x70, 0xFA, 0x0E, 0x53, 0x10, 0x31, 0x99, 0x54, 0x4E, + 0xBA, 0x66, 0x2A, 0x2C, 0x30, 0x2C, 0xF3, 0x90, 0x08, 0xF1, 0x42, 0xD2, 0xB1, 0x69, 0x63, + 0xE9, 0x5A, 0xB1, 0x0B, 0xE7, 0xC2, 0x61, 0x01, 0x68, 0x60, 0x8F, 0x35, 0x3A, 0x2F, 0x2C, + 0x41, 0xC7, 0x05, 0x6D, 0xEC, 0x1A, 0x8C, 0x7A, 0x6B, 0xFA, 0x00, 0x27, 0xF9, 0xDE, 0xDA, + 0xCB, 0x77, 0x86, 0xB6, 0x7E, 0xA2, 0xC4, 0x94, 0xD4, 0x3B, 0xA8, 0x51, 0xCF, 0x94, 0x15, + 0xC1, 0xBC, 0xC5, 0x2F, 0x02, 0x7E, 0xC0, 0x2C, 0x65, 0x53, 0x4F, 0x60, 0x8E, 0x9D, 0x16, + 0x6D, 0x51, 0xDD, 0x43, 0x1C, 0xDF, 0x58, 0x71, 0xF5, 0xCD, 0xD1, 0x57, 0x9C, 0xC0, 0x60, + 0x79, 0xDF, 0x07, 0x5A, 0x25, 0x06, 0x2B, 0xA7, 0xE7, 0x0D, 0x96, 0x66, 0xC4, 0xE7, 0xFE, + 0xD3, 0x4C, 0xEA, 0x0E, 0xA0, 0xF1, 0x1A, 0xDE, 0x1E, 0xB2, 0xA9, 0xB3, 0x97, 0xBC, 0xAA, + 0xAD, 0x10, 0x61, 0x27, 0x0E, 0xCF, 0x49, 0x78, 0x03, 0xA5, 0xFC, 0xE7, 0xF4, 0x1E, 0x65, + 0x04, 0xFB, 0xEC, 0x71, 0xA7, 0xDE, 0x7D, 0x06, 0x6B, 0x82, 0x61, 0x86, 0x8A, 0xFC, 0x49, + 0xB9, 0xE6, 0x85, 0xF0, 0xDC, 0xCE, 0x75, 0xE2, 0xFC, 0xB3, 0xBA, 0x8C, 0xF1, 0x90, 0x57, + 0xE3, 0x94, 0x15, 0x76, 0xBA, 0xF5, 0x8F, 0xB8, 0x21, 0xBD, 0x42, 0x68, 0xF7, 0xFA, 0xE3, + 0x02, 0x86, 0x01, 0xDA, 0x02, 0x2E, 0x9B, 0x46, 0x86, 0x46, 0xAB, 0xDB, 0x4F, 0xA6, 0x09, + 0x8A, 0x44, 0x9B, 0x42, 0x67, 0xD5, 0x09, 0xD9, 0xA3, 0x3F, 0x4C, 0x3E, 0xBC, 0xC3, 0x2D, + 0xAC, 0x09, 0x4D, 0x48, 0xED, 0x60, 0x0E, 0x76, 0x57, 0x87, 0xFB, 0x92, 0xB1, 0x97, 0x4F, + 0x74, 0xF7, 0xBB, 0x4C, 0x66, 0xEB, 0x2B, 0xBD, 0x02, 0x89, 0x5E, 0x6A, 0x38, 0x1C, 0x1C, + 0x45, 0x2E, 0xAA, 0xB1, 0xAE, 0x47, 0x31, 0xCF, 0x63, 0x2F, 0x61, 0xAE, 0x2C, 0x90, 0x59, + 0x21, 0x17, 0x4A, 0x3B, 0xC9, 0xBB, 0x4C, 0xDC, 0x89, 0xD6, 0x30, 0x26, 0x4B, 0x61, 0x49, + 0x88, 0xF3, 0xAB, 0xBE, 0xA1, 0xBD, 0x61, 0x7F, 0xFA, 0x53, 0xD7, 0x1B, 0x7D, 0x8A, 0x37, + 0x14, 0x62, 0xB7, 0x73, 0x35, 0x1A, 0x2D, 0xCC, 0xAE, 0xDD, 0x7F, 0x59, 0xCD, 0x72, 0x8F, + 0xAD, 0xEE, 0x05, 0x90, 0x67, 0xBD, 0x80, 0xC9, 0x4C, 0x8C, 0x9A, 0x1F, 0xFC, 0xA2, 0xDC, + 0x4F, 0x84, 0x8B, 0x82, 0x9C, 0x05, 0x61, 0x38, 0x5A, 0xA8, 0x2C, 0xC9, 0x85, 0x03, 0xD0, + 0xBB, 0x66, 0xA6, 0xAA, 0x4F, 0xAE, 0x07, 0x03, 0xD1, 0x2E, 0x60, 0xE1, 0x46, 0x0E, 0xFB, + 0xBC, 0xDF, 0x24, 0x12, 0xC1, 0x3E, 0x7C, 0x68, 0x4D, 0x1B, 0x01, 0x10, 0x20, 0x26, 0x34, + 0x3A, 0x41, 0x43, 0x44, 0x58, 0x5F, 0x6E, 0x70, 0x72, 0x74, 0x8B, 0xAE, 0xB5, 0xBB, 0xC6, + 0xD1, 0xE2, 0xEF, 0xFB, 0xFE, 0x06, 0x0E, 0x2E, 0x3E, 0x51, 0x60, 0x79, 0x7C, 0x9E, 0xA6, + 0xBA, 0xC7, 0xF1, 0x10, 0x24, 0x40, 0x4A, 0x52, 0x57, 0x5F, 0x6C, 0x89, 0x8C, 0x97, 0xAA, + 0xB2, 0xC3, 0xCC, 0xEA, 0xF2, 0x2F, 0x3F, 0x53, 0x5F, 0x7B, 0x81, 0x83, 0x96, 0xA1, 0xB1, + 0xBC, 0xE6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x25, 0x36, 0x42, + ]; if MLDSA44::verify(&pk, msg, None, &sig).is_ok() { println!("Verification succeeded!"); @@ -360,8 +1717,8 @@ fn bench_mldsa44_lowmemory_verify() { } fn bench_mldsa65_verify() { - use bouncycastle::mldsa::{MLDSATrait, MLDSA65, MLDSA65_SIG_LEN, MLDSA65PublicKey}; use bouncycastle::hex; + use bouncycastle::mldsa::{MLDSA65, MLDSA65_SIG_LEN, MLDSA65PublicKey, MLDSATrait}; eprintln!("MLDSA65/Verify"); @@ -381,7 +1738,140 @@ fn bench_mldsa65_verify() { // let sig = MLDSA65::sign_mu_deterministic(&mldsa65_sk, &mu, [0u8; 32]).unwrap(); // eprintln!("sig:\n{}", &*hex::encode(sig)); - let mldsa65_pk = MLDSA65PublicKey::from_bytes(&[0x48,0x68,0x3d,0x91,0x97,0x8e,0x31,0xeb,0x3d,0xdd,0xb8,0xb0,0x47,0x34,0x82,0xd2,0xb8,0x8a,0x5f,0x62,0x59,0x49,0xfd,0x8f,0x58,0xa5,0x61,0xe6,0x96,0xbd,0x4c,0x27,0xd0,0x5b,0x38,0xdb,0xb2,0xed,0xf0,0x1e,0x66,0x4e,0xfd,0x81,0xbe,0x1e,0xa8,0x93,0x68,0x8c,0xe6,0x8a,0xa2,0xd5,0x1c,0x59,0x58,0xf8,0xbb,0xc6,0xeb,0x4e,0x89,0xee,0x67,0xd2,0xc0,0x32,0x09,0x54,0xd5,0x72,0x12,0xca,0xc7,0x22,0x9f,0xf1,0xd6,0xea,0xf0,0x39,0x28,0xbd,0x51,0x51,0x1f,0x8d,0x88,0xd8,0x47,0x73,0x6c,0x7d,0xe2,0x73,0x0d,0x59,0x78,0xe5,0x41,0x07,0x13,0x16,0x09,0x78,0x86,0x77,0x11,0xbf,0x55,0x39,0xa0,0xbf,0xc4,0xc3,0x50,0xc2,0xbe,0x57,0x2b,0xaf,0x0e,0xe2,0xe2,0xfb,0x16,0xcc,0xfe,0xa0,0x80,0x28,0xd9,0x9a,0xc4,0x9a,0xeb,0xb7,0x59,0x37,0xdd,0xce,0x11,0x1c,0xda,0xb6,0x2f,0xff,0x3c,0xea,0x8b,0xa2,0x23,0x3d,0x1e,0x56,0xfb,0xc5,0xc5,0xa1,0xe7,0x26,0xde,0x63,0xfa,0xdd,0x2a,0xf0,0x16,0xb1,0x19,0x17,0x7f,0xa3,0xd9,0x71,0xa2,0xd9,0x27,0x71,0x73,0xfc,0xe5,0x5b,0x67,0x74,0x5a,0xf0,0xb7,0xc2,0x1d,0x59,0x7d,0xbe,0xb9,0x3e,0x6a,0x32,0xf3,0x41,0xc4,0x9a,0x5a,0x8b,0xe9,0xe8,0x25,0x08,0x8d,0x1f,0x2a,0xa4,0x51,0x55,0xd6,0xc8,0xae,0x15,0x36,0x7e,0x4e,0xb0,0x03,0xb8,0xfd,0xf7,0x85,0x10,0x71,0x94,0x97,0x39,0xf9,0xff,0xf0,0x90,0x23,0xea,0xf4,0x51,0x04,0xd2,0xa8,0x4a,0x45,0x90,0x6e,0xed,0x46,0x71,0xa4,0x4d,0xc2,0x8d,0x27,0x98,0x7b,0xb5,0x5d,0xf6,0x9e,0x9e,0x85,0x61,0xf6,0x1a,0x80,0xa7,0x26,0x99,0x50,0x38,0x65,0xfe,0xd9,0xb7,0xee,0x72,0xa8,0xe1,0x7a,0x19,0xc4,0x08,0x14,0x4f,0x4b,0x29,0xaf,0xef,0x70,0x31,0xc3,0xa6,0xd8,0x57,0x16,0x10,0xb4,0x2c,0x9f,0x42,0x12,0x45,0xa8,0x8f,0x19,0x7e,0x16,0x81,0x2b,0x03,0x11,0x59,0xb6,0x5b,0x96,0x87,0xe5,0xb3,0xe9,0x34,0xc5,0x22,0x5a,0xe9,0x8a,0x79,0xba,0x73,0xd2,0xb3,0x99,0xd7,0x35,0x10,0xef,0xfa,0xd1,0x9e,0x53,0xb8,0x45,0x0f,0x0b,0xa8,0xfc,0xe1,0x01,0x2f,0xd9,0x8d,0x26,0x0a,0x74,0xaa,0xaa,0x13,0xfa,0xe2,0x49,0xa0,0x06,0xb1,0xc3,0x4f,0x5b,0xa0,0xb8,0x82,0xf2,0x63,0x78,0x22,0x2f,0xb3,0x6f,0x22,0x83,0xc2,0x43,0xf0,0xff,0xeb,0x5f,0x1b,0xb4,0x14,0xa0,0xa7,0x0d,0x55,0xe3,0xd4,0x0a,0x56,0xb6,0xcb,0xc8,0x8a,0xe1,0xf0,0x3b,0x7b,0x28,0x82,0xd9,0x8d,0xee,0xa2,0x8e,0x14,0x5c,0x9d,0xed,0xfd,0x8e,0xaf,0x1c,0xef,0x2e,0xd9,0x4a,0x8b,0x05,0x0f,0x89,0x64,0xf4,0x6d,0x1e,0xa0,0xd0,0xc2,0xa4,0x3e,0x0d,0xda,0x61,0x82,0xad,0xbf,0x4f,0x6e,0xd1,0x75,0xb6,0x74,0x22,0x57,0x85,0x9b,0xf2,0x2f,0x3a,0x41,0x7e,0xcf,0x1f,0x9d,0x89,0x31,0x7b,0x5e,0x53,0x9d,0x58,0x7a,0xf1,0x6b,0x9e,0x13,0x13,0xe0,0x45,0x14,0xff,0xa6,0x4b,0xa8,0xb3,0xff,0x2b,0x83,0x21,0xf8,0x81,0x1c,0xb3,0xfb,0x02,0x2c,0x8f,0x64,0x4e,0x70,0xa4,0xb8,0x0a,0x2f,0xbf,0xee,0x60,0x4a,0xbb,0x73,0x79,0x09,0x1e,0xa8,0xe6,0xc5,0xc7,0x4d,0xfc,0x02,0x83,0x66,0x6b,0x40,0xc0,0x79,0x38,0x70,0x02,0x82,0x04,0xa1,0x36,0xbf,0x5d,0xa9,0x56,0x8e,0xb7,0x98,0xd3,0x49,0x03,0x8b,0xdb,0x0c,0x11,0xe0,0x34,0x45,0xe7,0x84,0x7c,0xb5,0x06,0x9c,0x75,0xcf,0x28,0xac,0x60,0x1c,0x77,0x99,0xd9,0x58,0x21,0x0d,0xdb,0xcb,0x22,0x6e,0x51,0xaf,0xef,0x9f,0x1d,0xe4,0x7b,0x07,0x38,0x73,0xd6,0xd3,0xf9,0x74,0x56,0xbe,0xde,0x08,0x50,0x82,0xe7,0x4a,0x29,0x8b,0x2c,0xd4,0x8f,0x4b,0x30,0x93,0x15,0x5f,0x36,0x6c,0x8f,0xa6,0x01,0xc6,0xaf,0x85,0x8d,0xfa,0x32,0xc0,0x84,0x91,0xb2,0xa2,0x98,0x87,0xf9,0x03,0x35,0x94,0x9a,0x5d,0x6e,0xda,0xa6,0x79,0x88,0x2a,0x3a,0x95,0xd6,0xbf,0x6d,0x97,0x0a,0x22,0x1f,0x4b,0x9d,0x3d,0x8c,0xbf,0x38,0x4a,0xf8,0x1a,0xac,0x95,0xe2,0xb3,0x29,0x4e,0x04,0x78,0x9a,0xc8,0x37,0x27,0xa5,0xdc,0x04,0x55,0x9f,0x96,0xaf,0x41,0xd8,0xa0,0x53,0x51,0x6f,0xee,0xee,0xbc,0x52,0x74,0x6e,0xb6,0xab,0x28,0x19,0xe0,0x91,0x08,0x71,0x0d,0x83,0x5f,0x01,0x1f,0xa6,0x30,0x65,0x87,0x2a,0xd3,0x34,0xd5,0xcd,0xff,0xb2,0xb2,0x31,0x05,0x07,0xe9,0x2f,0xc9,0x93,0xae,0x31,0x7d,0xa9,0x7f,0x4f,0x30,0x9c,0xda,0xf0,0xf6,0x7e,0xd9,0x9d,0x90,0x21,0x55,0x76,0x08,0x38,0x49,0xf9,0x53,0xb2,0x46,0xd7,0xfe,0xdb,0x3f,0xdb,0x67,0x67,0x98,0x50,0xa5,0xad,0x40,0x4e,0x64,0x14,0x7f,0xb7,0xcf,0x4f,0x6a,0xed,0xdd,0x05,0xaf,0xb4,0xb8,0x34,0x96,0x8d,0x1f,0xe8,0x80,0x14,0x96,0x0d,0xce,0x5d,0x94,0x22,0x36,0x52,0x6e,0x12,0xa4,0x78,0xd6,0x9e,0x5f,0xbe,0x69,0x70,0x31,0x0b,0x30,0x8c,0x06,0x84,0x50,0x18,0xcf,0xc7,0xb2,0xab,0x43,0x0a,0x13,0xa6,0xb1,0xac,0x7b,0xb0,0x2c,0xcc,0xbb,0x3d,0x91,0x1a,0xc2,0xf1,0x10,0x68,0x61,0x3f,0xbe,0x02,0x9b,0xfd,0xce,0x02,0xcf,0x5c,0xd3,0x89,0x50,0xed,0x72,0xc8,0x39,0x44,0xed,0xfb,0xc7,0x56,0x15,0xaf,0x87,0xf8,0x64,0xc0,0x51,0xf3,0xc5,0x54,0x56,0xc5,0x41,0x28,0x63,0xa4,0x0c,0x06,0xd1,0xda,0xb5,0x62,0xbd,0xff,0x05,0x71,0xb8,0xd3,0xc3,0x91,0x7b,0xbd,0x30,0x08,0x80,0xbb,0xa5,0xe9,0x98,0x23,0x9b,0x95,0xfa,0x91,0xb7,0xd6,0x41,0x6d,0x4f,0x39,0x8b,0x3a,0xdb,0xcd,0x30,0x98,0x3e,0xd3,0x59,0x2b,0x4d,0x9e,0xf7,0xd4,0x23,0x6f,0xd0,0x0f,0x50,0xd9,0x8a,0xa5,0x3a,0x23,0x5a,0xc4,0x17,0x27,0x20,0xf7,0x7d,0x96,0x17,0x26,0x72,0x98,0x0c,0xfe,0x8f,0xf7,0xa5,0xa7,0x02,0x78,0x3e,0xdc,0x2b,0xa3,0x1b,0x22,0x59,0x01,0x5a,0x11,0x2f,0xc7,0xf4,0x68,0xa9,0xc2,0xf9,0x46,0x40,0x39,0x00,0x2d,0x30,0xef,0x67,0x8b,0x4c,0xb7,0x98,0xbc,0x11,0x62,0x16,0xbf,0x7a,0x9a,0x7c,0x18,0xba,0x03,0xb7,0xb5,0x8f,0xd0,0x75,0x15,0xd3,0x11,0x50,0x49,0xd3,0x61,0x4b,0xe7,0xa0,0x7e,0x74,0x43,0x00,0x75,0x0d,0xf1,0xd2,0xc5,0x87,0x53,0x38,0x90,0x59,0xea,0xfc,0x3d,0x78,0x5c,0xcd,0xd3,0x1c,0x07,0x64,0x8b,0xed,0xc0,0x3a,0x5c,0x3b,0x8a,0xd4,0x6d,0x06,0x4d,0x59,0xc1,0x3d,0x57,0x37,0x47,0x29,0xfc,0x4e,0x29,0x53,0x62,0xe2,0xa5,0x19,0x12,0x04,0x53,0x04,0x28,0xbc,0x15,0x22,0xaf,0xa2,0x8f,0xf5,0xfe,0x16,0x55,0xe3,0x04,0xca,0x5b,0xc8,0xc2,0x7a,0xd0,0xe0,0xc6,0xa3,0x9d,0xd4,0xdf,0x28,0x95,0x6c,0x14,0xb3,0x8c,0xc9,0x36,0x82,0xce,0xfe,0x40,0x2b,0xbd,0x5e,0x82,0xd2,0x9c,0x46,0x4e,0x44,0xeb,0x5d,0x37,0xb4,0x8f,0xc5,0x68,0xdf,0xe0,0xcc,0x6e,0x8e,0x16,0xba,0xea,0x05,0xe5,0x13,0x55,0x90,0xf1,0x92,0x94,0xe7,0x3e,0x83,0x67,0xb0,0x21,0x6d,0xbb,0x81,0x50,0x30,0xb9,0xde,0x55,0x91,0x3f,0x08,0x03,0x9c,0x42,0x35,0x1c,0x59,0xe5,0x51,0x5d,0xd5,0xaf,0x8e,0x08,0x9a,0x15,0xe6,0x25,0xe8,0xf6,0xde,0xe6,0x39,0x38,0x6c,0x46,0x49,0x7d,0x7a,0x26,0x32,0x88,0x77,0x4d,0xe5,0x81,0xa7,0xde,0x96,0x29,0xb4,0x1b,0x44,0x24,0x14,0x1f,0x97,0x8f,0xb8,0x33,0x12,0x08,0xef,0xde,0xc3,0xc6,0xe0,0xde,0x39,0xbc,0x57,0x06,0x3f,0x3d,0xcd,0x6c,0x47,0x03,0x73,0xc0,0x88,0x91,0xea,0x29,0xcb,0xc7,0xcc,0x6d,0x64,0x83,0xb8,0x88,0x90,0x83,0xac,0xe8,0x6a,0xa7,0xb5,0x1b,0x1c,0x2c,0xfe,0x6e,0x2a,0xd1,0x8d,0x97,0xce,0x36,0xfb,0xc5,0x6e,0xa4,0x2f,0xae,0x97,0xe6,0xa7,0xac,0x11,0x48,0x64,0x47,0x8c,0x36,0x6d,0xf1,0xeb,0xb1,0xe7,0xb1,0x1a,0x90,0x98,0x50,0x4f,0xd5,0x97,0x5b,0xdf,0x1f,0x49,0xdc,0x70,0x00,0x2b,0x63,0xc1,0x73,0x9a,0x9d,0x26,0x3f,0xba,0xd4,0x07,0x3f,0x6a,0x9f,0x6c,0x2b,0x8a,0xf4,0xb4,0xc3,0x32,0xa1,0x03,0xa0,0xcf,0xfa,0x5d,0xee,0xb2,0xd0,0x62,0xca,0x3c,0x21,0x5f,0xd3,0x60,0x02,0x6b,0xe7,0xc5,0x16,0x4f,0x4a,0x44,0x24,0xef,0x74,0x94,0x88,0x04,0xd6,0x6f,0x46,0x48,0x77,0x32,0xc8,0x20,0x2c,0x79,0x54,0x78,0x64,0x7b,0x4e,0xa7,0x1d,0x62,0x7c,0x08,0x60,0x24,0xcc,0xa3,0x54,0xa4,0x1f,0x08,0x77,0xb3,0x8f,0x19,0xb3,0x77,0x4a,0xd2,0x09,0x5c,0x8d,0xa5,0x3b,0x06,0x9e,0x21,0xc7,0x6a,0xe2,0xd2,0x00,0x7e,0x16,0x71,0x9e,0xd4,0x00,0x80,0xd3,0x34,0xf7,0xda,0x52,0xe9,0xf5,0xa5,0x99,0x04,0x39,0xca,0xf0,0x83,0xa9,0x5b,0x83,0x3f,0x02,0xad,0x10,0xa0,0x8c,0x1a,0x6d,0x0f,0x26,0x0c,0x00,0x72,0x85,0xbd,0x4a,0x2f,0x47,0x70,0x3a,0x5a,0xef,0x46,0x52,0x87,0xd2,0x53,0xb1,0x8a,0xc2,0x25,0x14,0x31,0x62,0x10,0xff,0x56,0x68,0x14,0xb1,0x0f,0x87,0xa2,0x93,0xd6,0xf1,0x99,0xd3,0xc3,0x95,0x99,0x90,0xd0,0xc1,0x26,0x8b,0x4f,0x50,0xd5,0xf9,0xfc,0xef,0xbb,0xf2,0x37,0xbd,0x0c,0x28,0xb8,0x01,0x82,0xd6,0x65,0x97,0x41,0xf1,0x4f,0x10,0xbf,0xbb,0x21,0xbb,0xa1,0x2a,0xb6,0x20,0xaa,0x23,0x96,0xf5,0x6c,0x06,0x86,0xb4,0xea,0x90,0x17,0x99,0x02,0x24,0x21,0x6b,0x2f,0xe8,0xad,0x76,0xc4,0xa9,0x14,0x8e,0xef,0x9a,0x86,0xa3,0x63,0x5a,0x6a,0xa7,0x7b,0xc1,0xdc,0xfb,0x6f,0xba,0x59,0xa7,0x7d,0xfd,0xa9,0xb7,0x53,0x0d,0xc0,0xca,0x86,0x48,0xc8,0xd9,0x73,0x73,0x8e,0x01,0xba,0xb8,0xf0,0x8b,0x49,0x05,0xe8,0x4a,0xa4,0x64,0x1b,0xd6,0x02,0x41,0x0c,0xd9,0x75,0x20,0x26,0x5f,0x2f,0x23,0x1f,0x2b,0x35,0xe1,0x5e,0xb2,0xfa,0x04,0xd2,0xbd,0x94,0xd5,0xa7,0x7a,0xba,0xf1,0xe0,0xe1,0x61,0x01,0x0a,0x99,0x00,0x87,0xf5,0xb4,0x6e,0xa9,0x88,0xb2,0xbc,0x05,0x12,0xfd,0xa0,0xfa,0x92,0x3d,0xad,0xd6,0xc4,0x5c,0x53,0x01,0xd0,0x94,0x83,0x67,0x32,0x65,0xb5,0xab,0x2e,0x10,0xf4,0xba,0x52,0x0f,0x6b,0xba,0xd5,0x64,0xa5,0xc3,0xd5,0xe2,0x7b,0xdb,0x08,0x0f,0x7d,0x20,0xe1,0x32,0x96,0xa3,0x18,0x19,0x54,0xc3,0x9c,0x64,0x9c,0x94,0x3e,0xbe,0x17,0xdf,0x5c,0x1f,0x7a,0xae,0x0a,0x8f,0xe1,0x26,0xc4,0x77,0x58,0x5a,0x5d,0x4d,0x64,0x8a,0x0d,0x00,0x8b,0x6a,0xf5,0xe8,0xcd,0x31,0xbe,0x69,0xa9,0x29,0x6d,0x4f,0x3f,0xd2,0x5e,0xd8,0x6f,0x22,0x1e,0x4b,0x93,0xf6,0x5f,0x59,0x29,0x96,0x75,0x33,0x62,0x4b,0x92,0x35,0x75,0x0c,0x30,0x70,0x75,0x50,0xb5,0x85,0x36,0xd1,0x09,0xa7,0x13,0x1c,0x5a,0x5b,0xbe,0x4a,0x57,0x15,0x56,0x7c,0x12,0x53,0x4a,0xec,0x76,0x60,0x76,0x1e,0xeb,0xb9,0xfa,0xe2,0x89,0x1c,0x77,0x45,0x89,0xb8,0x0e,0x56,0x6a,0xd5,0x57,0xdd,0xef,0x73,0x67,0x19,0x6b,0x72,0x27,0xea,0x98,0x70,0xef,0x09,0xdd,0xfe,0xc7,0x9d,0x6b,0x93,0x19,0xa6,0x87,0x9b,0x52,0x05,0xd7,0x6b,0xf7,0xab,0xa5,0xac,0xf3,0x3a,0xfb,0x59,0xd1,0x7f,0xc5,0x4e,0x68,0x38,0x3d,0x6b,0xe5,0xa0,0x8e,0x9b,0x66,0xda,0x53,0xdc,0xde,0x00,0x8b,0xb2,0x94,0xb8,0x58,0x2b,0xd1,0x32,0xcd,0xcc,0x49,0x95,0x9f,0xdb,0xc2,0x1e,0x52,0x72,0x18,0x80,0xc8,0xad,0x03,0x52,0xc7,0x9f,0x03,0xa4,0x3b,0xbd,0x84,0xc4,0xcd,0xfd,0xc6,0xc5,0x29,0x00,0x5e,0x1e,0x7c,0xd9,0xa3,0x49,0xa7,0x16,0x8a,0x35,0x56,0x9b,0xa5,0xde,0xa8,0x18,0x96,0x8d,0x5a,0x91,0x46,0x6b,0xd6,0xe6,0x4e,0x20,0xbf,0x62,0x41,0x71,0x98,0xaf,0xc4,0xe8,0x1c,0x28,0xdd,0x77,0xed,0x40,0x28,0x23,0x23,0x98,0xb5,0x2f,0xbd,0xe8,0x6b,0xc8,0x4f,0x47,0x5b,0x90,0x16,0x71,0x0c,0xe2,0xaa,0xbc,0x11,0xa0,0x6b,0x4d,0xba,0xc9,0x01,0xec,0x16,0xcf,0x36,0x5c,0xa3,0xf2,0xd5,0x38,0x13,0x94,0x8a,0x69,0x3a,0x0f,0x93,0xe7,0x9c,0x46,0xca,0x5d,0x5a,0x6d,0xca,0x3d,0x28,0xca,0x50,0xad,0x18,0xbd,0x13,0xfc,0xa5,0x50,0x59,0xdd,0x9b,0x18,0x5f,0x79,0xf9,0xc4,0x71,0x96,0xa4,0xe8,0x1b,0x21,0x04,0xbc,0x46,0x0a,0x05,0x1e,0x02,0xf2,0xe8,0x44,0x4f]).unwrap(); + let mldsa65_pk = MLDSA65PublicKey::from_bytes(&[ + 0x48, 0x68, 0x3D, 0x91, 0x97, 0x8E, 0x31, 0xEB, 0x3D, 0xDD, 0xB8, 0xB0, 0x47, 0x34, 0x82, + 0xD2, 0xB8, 0x8A, 0x5F, 0x62, 0x59, 0x49, 0xFD, 0x8F, 0x58, 0xA5, 0x61, 0xE6, 0x96, 0xBD, + 0x4C, 0x27, 0xD0, 0x5B, 0x38, 0xDB, 0xB2, 0xED, 0xF0, 0x1E, 0x66, 0x4E, 0xFD, 0x81, 0xBE, + 0x1E, 0xA8, 0x93, 0x68, 0x8C, 0xE6, 0x8A, 0xA2, 0xD5, 0x1C, 0x59, 0x58, 0xF8, 0xBB, 0xC6, + 0xEB, 0x4E, 0x89, 0xEE, 0x67, 0xD2, 0xC0, 0x32, 0x09, 0x54, 0xD5, 0x72, 0x12, 0xCA, 0xC7, + 0x22, 0x9F, 0xF1, 0xD6, 0xEA, 0xF0, 0x39, 0x28, 0xBD, 0x51, 0x51, 0x1F, 0x8D, 0x88, 0xD8, + 0x47, 0x73, 0x6C, 0x7D, 0xE2, 0x73, 0x0D, 0x59, 0x78, 0xE5, 0x41, 0x07, 0x13, 0x16, 0x09, + 0x78, 0x86, 0x77, 0x11, 0xBF, 0x55, 0x39, 0xA0, 0xBF, 0xC4, 0xC3, 0x50, 0xC2, 0xBE, 0x57, + 0x2B, 0xAF, 0x0E, 0xE2, 0xE2, 0xFB, 0x16, 0xCC, 0xFE, 0xA0, 0x80, 0x28, 0xD9, 0x9A, 0xC4, + 0x9A, 0xEB, 0xB7, 0x59, 0x37, 0xDD, 0xCE, 0x11, 0x1C, 0xDA, 0xB6, 0x2F, 0xFF, 0x3C, 0xEA, + 0x8B, 0xA2, 0x23, 0x3D, 0x1E, 0x56, 0xFB, 0xC5, 0xC5, 0xA1, 0xE7, 0x26, 0xDE, 0x63, 0xFA, + 0xDD, 0x2A, 0xF0, 0x16, 0xB1, 0x19, 0x17, 0x7F, 0xA3, 0xD9, 0x71, 0xA2, 0xD9, 0x27, 0x71, + 0x73, 0xFC, 0xE5, 0x5B, 0x67, 0x74, 0x5A, 0xF0, 0xB7, 0xC2, 0x1D, 0x59, 0x7D, 0xBE, 0xB9, + 0x3E, 0x6A, 0x32, 0xF3, 0x41, 0xC4, 0x9A, 0x5A, 0x8B, 0xE9, 0xE8, 0x25, 0x08, 0x8D, 0x1F, + 0x2A, 0xA4, 0x51, 0x55, 0xD6, 0xC8, 0xAE, 0x15, 0x36, 0x7E, 0x4E, 0xB0, 0x03, 0xB8, 0xFD, + 0xF7, 0x85, 0x10, 0x71, 0x94, 0x97, 0x39, 0xF9, 0xFF, 0xF0, 0x90, 0x23, 0xEA, 0xF4, 0x51, + 0x04, 0xD2, 0xA8, 0x4A, 0x45, 0x90, 0x6E, 0xED, 0x46, 0x71, 0xA4, 0x4D, 0xC2, 0x8D, 0x27, + 0x98, 0x7B, 0xB5, 0x5D, 0xF6, 0x9E, 0x9E, 0x85, 0x61, 0xF6, 0x1A, 0x80, 0xA7, 0x26, 0x99, + 0x50, 0x38, 0x65, 0xFE, 0xD9, 0xB7, 0xEE, 0x72, 0xA8, 0xE1, 0x7A, 0x19, 0xC4, 0x08, 0x14, + 0x4F, 0x4B, 0x29, 0xAF, 0xEF, 0x70, 0x31, 0xC3, 0xA6, 0xD8, 0x57, 0x16, 0x10, 0xB4, 0x2C, + 0x9F, 0x42, 0x12, 0x45, 0xA8, 0x8F, 0x19, 0x7E, 0x16, 0x81, 0x2B, 0x03, 0x11, 0x59, 0xB6, + 0x5B, 0x96, 0x87, 0xE5, 0xB3, 0xE9, 0x34, 0xC5, 0x22, 0x5A, 0xE9, 0x8A, 0x79, 0xBA, 0x73, + 0xD2, 0xB3, 0x99, 0xD7, 0x35, 0x10, 0xEF, 0xFA, 0xD1, 0x9E, 0x53, 0xB8, 0x45, 0x0F, 0x0B, + 0xA8, 0xFC, 0xE1, 0x01, 0x2F, 0xD9, 0x8D, 0x26, 0x0A, 0x74, 0xAA, 0xAA, 0x13, 0xFA, 0xE2, + 0x49, 0xA0, 0x06, 0xB1, 0xC3, 0x4F, 0x5B, 0xA0, 0xB8, 0x82, 0xF2, 0x63, 0x78, 0x22, 0x2F, + 0xB3, 0x6F, 0x22, 0x83, 0xC2, 0x43, 0xF0, 0xFF, 0xEB, 0x5F, 0x1B, 0xB4, 0x14, 0xA0, 0xA7, + 0x0D, 0x55, 0xE3, 0xD4, 0x0A, 0x56, 0xB6, 0xCB, 0xC8, 0x8A, 0xE1, 0xF0, 0x3B, 0x7B, 0x28, + 0x82, 0xD9, 0x8D, 0xEE, 0xA2, 0x8E, 0x14, 0x5C, 0x9D, 0xED, 0xFD, 0x8E, 0xAF, 0x1C, 0xEF, + 0x2E, 0xD9, 0x4A, 0x8B, 0x05, 0x0F, 0x89, 0x64, 0xF4, 0x6D, 0x1E, 0xA0, 0xD0, 0xC2, 0xA4, + 0x3E, 0x0D, 0xDA, 0x61, 0x82, 0xAD, 0xBF, 0x4F, 0x6E, 0xD1, 0x75, 0xB6, 0x74, 0x22, 0x57, + 0x85, 0x9B, 0xF2, 0x2F, 0x3A, 0x41, 0x7E, 0xCF, 0x1F, 0x9D, 0x89, 0x31, 0x7B, 0x5E, 0x53, + 0x9D, 0x58, 0x7A, 0xF1, 0x6B, 0x9E, 0x13, 0x13, 0xE0, 0x45, 0x14, 0xFF, 0xA6, 0x4B, 0xA8, + 0xB3, 0xFF, 0x2B, 0x83, 0x21, 0xF8, 0x81, 0x1C, 0xB3, 0xFB, 0x02, 0x2C, 0x8F, 0x64, 0x4E, + 0x70, 0xA4, 0xB8, 0x0A, 0x2F, 0xBF, 0xEE, 0x60, 0x4A, 0xBB, 0x73, 0x79, 0x09, 0x1E, 0xA8, + 0xE6, 0xC5, 0xC7, 0x4D, 0xFC, 0x02, 0x83, 0x66, 0x6B, 0x40, 0xC0, 0x79, 0x38, 0x70, 0x02, + 0x82, 0x04, 0xA1, 0x36, 0xBF, 0x5D, 0xA9, 0x56, 0x8E, 0xB7, 0x98, 0xD3, 0x49, 0x03, 0x8B, + 0xDB, 0x0C, 0x11, 0xE0, 0x34, 0x45, 0xE7, 0x84, 0x7C, 0xB5, 0x06, 0x9C, 0x75, 0xCF, 0x28, + 0xAC, 0x60, 0x1C, 0x77, 0x99, 0xD9, 0x58, 0x21, 0x0D, 0xDB, 0xCB, 0x22, 0x6E, 0x51, 0xAF, + 0xEF, 0x9F, 0x1D, 0xE4, 0x7B, 0x07, 0x38, 0x73, 0xD6, 0xD3, 0xF9, 0x74, 0x56, 0xBE, 0xDE, + 0x08, 0x50, 0x82, 0xE7, 0x4A, 0x29, 0x8B, 0x2C, 0xD4, 0x8F, 0x4B, 0x30, 0x93, 0x15, 0x5F, + 0x36, 0x6C, 0x8F, 0xA6, 0x01, 0xC6, 0xAF, 0x85, 0x8D, 0xFA, 0x32, 0xC0, 0x84, 0x91, 0xB2, + 0xA2, 0x98, 0x87, 0xF9, 0x03, 0x35, 0x94, 0x9A, 0x5D, 0x6E, 0xDA, 0xA6, 0x79, 0x88, 0x2A, + 0x3A, 0x95, 0xD6, 0xBF, 0x6D, 0x97, 0x0A, 0x22, 0x1F, 0x4B, 0x9D, 0x3D, 0x8C, 0xBF, 0x38, + 0x4A, 0xF8, 0x1A, 0xAC, 0x95, 0xE2, 0xB3, 0x29, 0x4E, 0x04, 0x78, 0x9A, 0xC8, 0x37, 0x27, + 0xA5, 0xDC, 0x04, 0x55, 0x9F, 0x96, 0xAF, 0x41, 0xD8, 0xA0, 0x53, 0x51, 0x6F, 0xEE, 0xEE, + 0xBC, 0x52, 0x74, 0x6E, 0xB6, 0xAB, 0x28, 0x19, 0xE0, 0x91, 0x08, 0x71, 0x0D, 0x83, 0x5F, + 0x01, 0x1F, 0xA6, 0x30, 0x65, 0x87, 0x2A, 0xD3, 0x34, 0xD5, 0xCD, 0xFF, 0xB2, 0xB2, 0x31, + 0x05, 0x07, 0xE9, 0x2F, 0xC9, 0x93, 0xAE, 0x31, 0x7D, 0xA9, 0x7F, 0x4F, 0x30, 0x9C, 0xDA, + 0xF0, 0xF6, 0x7E, 0xD9, 0x9D, 0x90, 0x21, 0x55, 0x76, 0x08, 0x38, 0x49, 0xF9, 0x53, 0xB2, + 0x46, 0xD7, 0xFE, 0xDB, 0x3F, 0xDB, 0x67, 0x67, 0x98, 0x50, 0xA5, 0xAD, 0x40, 0x4E, 0x64, + 0x14, 0x7F, 0xB7, 0xCF, 0x4F, 0x6A, 0xED, 0xDD, 0x05, 0xAF, 0xB4, 0xB8, 0x34, 0x96, 0x8D, + 0x1F, 0xE8, 0x80, 0x14, 0x96, 0x0D, 0xCE, 0x5D, 0x94, 0x22, 0x36, 0x52, 0x6E, 0x12, 0xA4, + 0x78, 0xD6, 0x9E, 0x5F, 0xBE, 0x69, 0x70, 0x31, 0x0B, 0x30, 0x8C, 0x06, 0x84, 0x50, 0x18, + 0xCF, 0xC7, 0xB2, 0xAB, 0x43, 0x0A, 0x13, 0xA6, 0xB1, 0xAC, 0x7B, 0xB0, 0x2C, 0xCC, 0xBB, + 0x3D, 0x91, 0x1A, 0xC2, 0xF1, 0x10, 0x68, 0x61, 0x3F, 0xBE, 0x02, 0x9B, 0xFD, 0xCE, 0x02, + 0xCF, 0x5C, 0xD3, 0x89, 0x50, 0xED, 0x72, 0xC8, 0x39, 0x44, 0xED, 0xFB, 0xC7, 0x56, 0x15, + 0xAF, 0x87, 0xF8, 0x64, 0xC0, 0x51, 0xF3, 0xC5, 0x54, 0x56, 0xC5, 0x41, 0x28, 0x63, 0xA4, + 0x0C, 0x06, 0xD1, 0xDA, 0xB5, 0x62, 0xBD, 0xFF, 0x05, 0x71, 0xB8, 0xD3, 0xC3, 0x91, 0x7B, + 0xBD, 0x30, 0x08, 0x80, 0xBB, 0xA5, 0xE9, 0x98, 0x23, 0x9B, 0x95, 0xFA, 0x91, 0xB7, 0xD6, + 0x41, 0x6D, 0x4F, 0x39, 0x8B, 0x3A, 0xDB, 0xCD, 0x30, 0x98, 0x3E, 0xD3, 0x59, 0x2B, 0x4D, + 0x9E, 0xF7, 0xD4, 0x23, 0x6F, 0xD0, 0x0F, 0x50, 0xD9, 0x8A, 0xA5, 0x3A, 0x23, 0x5A, 0xC4, + 0x17, 0x27, 0x20, 0xF7, 0x7D, 0x96, 0x17, 0x26, 0x72, 0x98, 0x0C, 0xFE, 0x8F, 0xF7, 0xA5, + 0xA7, 0x02, 0x78, 0x3E, 0xDC, 0x2B, 0xA3, 0x1B, 0x22, 0x59, 0x01, 0x5A, 0x11, 0x2F, 0xC7, + 0xF4, 0x68, 0xA9, 0xC2, 0xF9, 0x46, 0x40, 0x39, 0x00, 0x2D, 0x30, 0xEF, 0x67, 0x8B, 0x4C, + 0xB7, 0x98, 0xBC, 0x11, 0x62, 0x16, 0xBF, 0x7A, 0x9A, 0x7C, 0x18, 0xBA, 0x03, 0xB7, 0xB5, + 0x8F, 0xD0, 0x75, 0x15, 0xD3, 0x11, 0x50, 0x49, 0xD3, 0x61, 0x4B, 0xE7, 0xA0, 0x7E, 0x74, + 0x43, 0x00, 0x75, 0x0D, 0xF1, 0xD2, 0xC5, 0x87, 0x53, 0x38, 0x90, 0x59, 0xEA, 0xFC, 0x3D, + 0x78, 0x5C, 0xCD, 0xD3, 0x1C, 0x07, 0x64, 0x8B, 0xED, 0xC0, 0x3A, 0x5C, 0x3B, 0x8A, 0xD4, + 0x6D, 0x06, 0x4D, 0x59, 0xC1, 0x3D, 0x57, 0x37, 0x47, 0x29, 0xFC, 0x4E, 0x29, 0x53, 0x62, + 0xE2, 0xA5, 0x19, 0x12, 0x04, 0x53, 0x04, 0x28, 0xBC, 0x15, 0x22, 0xAF, 0xA2, 0x8F, 0xF5, + 0xFE, 0x16, 0x55, 0xE3, 0x04, 0xCA, 0x5B, 0xC8, 0xC2, 0x7A, 0xD0, 0xE0, 0xC6, 0xA3, 0x9D, + 0xD4, 0xDF, 0x28, 0x95, 0x6C, 0x14, 0xB3, 0x8C, 0xC9, 0x36, 0x82, 0xCE, 0xFE, 0x40, 0x2B, + 0xBD, 0x5E, 0x82, 0xD2, 0x9C, 0x46, 0x4E, 0x44, 0xEB, 0x5D, 0x37, 0xB4, 0x8F, 0xC5, 0x68, + 0xDF, 0xE0, 0xCC, 0x6E, 0x8E, 0x16, 0xBA, 0xEA, 0x05, 0xE5, 0x13, 0x55, 0x90, 0xF1, 0x92, + 0x94, 0xE7, 0x3E, 0x83, 0x67, 0xB0, 0x21, 0x6D, 0xBB, 0x81, 0x50, 0x30, 0xB9, 0xDE, 0x55, + 0x91, 0x3F, 0x08, 0x03, 0x9C, 0x42, 0x35, 0x1C, 0x59, 0xE5, 0x51, 0x5D, 0xD5, 0xAF, 0x8E, + 0x08, 0x9A, 0x15, 0xE6, 0x25, 0xE8, 0xF6, 0xDE, 0xE6, 0x39, 0x38, 0x6C, 0x46, 0x49, 0x7D, + 0x7A, 0x26, 0x32, 0x88, 0x77, 0x4D, 0xE5, 0x81, 0xA7, 0xDE, 0x96, 0x29, 0xB4, 0x1B, 0x44, + 0x24, 0x14, 0x1F, 0x97, 0x8F, 0xB8, 0x33, 0x12, 0x08, 0xEF, 0xDE, 0xC3, 0xC6, 0xE0, 0xDE, + 0x39, 0xBC, 0x57, 0x06, 0x3F, 0x3D, 0xCD, 0x6C, 0x47, 0x03, 0x73, 0xC0, 0x88, 0x91, 0xEA, + 0x29, 0xCB, 0xC7, 0xCC, 0x6D, 0x64, 0x83, 0xB8, 0x88, 0x90, 0x83, 0xAC, 0xE8, 0x6A, 0xA7, + 0xB5, 0x1B, 0x1C, 0x2C, 0xFE, 0x6E, 0x2A, 0xD1, 0x8D, 0x97, 0xCE, 0x36, 0xFB, 0xC5, 0x6E, + 0xA4, 0x2F, 0xAE, 0x97, 0xE6, 0xA7, 0xAC, 0x11, 0x48, 0x64, 0x47, 0x8C, 0x36, 0x6D, 0xF1, + 0xEB, 0xB1, 0xE7, 0xB1, 0x1A, 0x90, 0x98, 0x50, 0x4F, 0xD5, 0x97, 0x5B, 0xDF, 0x1F, 0x49, + 0xDC, 0x70, 0x00, 0x2B, 0x63, 0xC1, 0x73, 0x9A, 0x9D, 0x26, 0x3F, 0xBA, 0xD4, 0x07, 0x3F, + 0x6A, 0x9F, 0x6C, 0x2B, 0x8A, 0xF4, 0xB4, 0xC3, 0x32, 0xA1, 0x03, 0xA0, 0xCF, 0xFA, 0x5D, + 0xEE, 0xB2, 0xD0, 0x62, 0xCA, 0x3C, 0x21, 0x5F, 0xD3, 0x60, 0x02, 0x6B, 0xE7, 0xC5, 0x16, + 0x4F, 0x4A, 0x44, 0x24, 0xEF, 0x74, 0x94, 0x88, 0x04, 0xD6, 0x6F, 0x46, 0x48, 0x77, 0x32, + 0xC8, 0x20, 0x2C, 0x79, 0x54, 0x78, 0x64, 0x7B, 0x4E, 0xA7, 0x1D, 0x62, 0x7C, 0x08, 0x60, + 0x24, 0xCC, 0xA3, 0x54, 0xA4, 0x1F, 0x08, 0x77, 0xB3, 0x8F, 0x19, 0xB3, 0x77, 0x4A, 0xD2, + 0x09, 0x5C, 0x8D, 0xA5, 0x3B, 0x06, 0x9E, 0x21, 0xC7, 0x6A, 0xE2, 0xD2, 0x00, 0x7E, 0x16, + 0x71, 0x9E, 0xD4, 0x00, 0x80, 0xD3, 0x34, 0xF7, 0xDA, 0x52, 0xE9, 0xF5, 0xA5, 0x99, 0x04, + 0x39, 0xCA, 0xF0, 0x83, 0xA9, 0x5B, 0x83, 0x3F, 0x02, 0xAD, 0x10, 0xA0, 0x8C, 0x1A, 0x6D, + 0x0F, 0x26, 0x0C, 0x00, 0x72, 0x85, 0xBD, 0x4A, 0x2F, 0x47, 0x70, 0x3A, 0x5A, 0xEF, 0x46, + 0x52, 0x87, 0xD2, 0x53, 0xB1, 0x8A, 0xC2, 0x25, 0x14, 0x31, 0x62, 0x10, 0xFF, 0x56, 0x68, + 0x14, 0xB1, 0x0F, 0x87, 0xA2, 0x93, 0xD6, 0xF1, 0x99, 0xD3, 0xC3, 0x95, 0x99, 0x90, 0xD0, + 0xC1, 0x26, 0x8B, 0x4F, 0x50, 0xD5, 0xF9, 0xFC, 0xEF, 0xBB, 0xF2, 0x37, 0xBD, 0x0C, 0x28, + 0xB8, 0x01, 0x82, 0xD6, 0x65, 0x97, 0x41, 0xF1, 0x4F, 0x10, 0xBF, 0xBB, 0x21, 0xBB, 0xA1, + 0x2A, 0xB6, 0x20, 0xAA, 0x23, 0x96, 0xF5, 0x6C, 0x06, 0x86, 0xB4, 0xEA, 0x90, 0x17, 0x99, + 0x02, 0x24, 0x21, 0x6B, 0x2F, 0xE8, 0xAD, 0x76, 0xC4, 0xA9, 0x14, 0x8E, 0xEF, 0x9A, 0x86, + 0xA3, 0x63, 0x5A, 0x6A, 0xA7, 0x7B, 0xC1, 0xDC, 0xFB, 0x6F, 0xBA, 0x59, 0xA7, 0x7D, 0xFD, + 0xA9, 0xB7, 0x53, 0x0D, 0xC0, 0xCA, 0x86, 0x48, 0xC8, 0xD9, 0x73, 0x73, 0x8E, 0x01, 0xBA, + 0xB8, 0xF0, 0x8B, 0x49, 0x05, 0xE8, 0x4A, 0xA4, 0x64, 0x1B, 0xD6, 0x02, 0x41, 0x0C, 0xD9, + 0x75, 0x20, 0x26, 0x5F, 0x2F, 0x23, 0x1F, 0x2B, 0x35, 0xE1, 0x5E, 0xB2, 0xFA, 0x04, 0xD2, + 0xBD, 0x94, 0xD5, 0xA7, 0x7A, 0xBA, 0xF1, 0xE0, 0xE1, 0x61, 0x01, 0x0A, 0x99, 0x00, 0x87, + 0xF5, 0xB4, 0x6E, 0xA9, 0x88, 0xB2, 0xBC, 0x05, 0x12, 0xFD, 0xA0, 0xFA, 0x92, 0x3D, 0xAD, + 0xD6, 0xC4, 0x5C, 0x53, 0x01, 0xD0, 0x94, 0x83, 0x67, 0x32, 0x65, 0xB5, 0xAB, 0x2E, 0x10, + 0xF4, 0xBA, 0x52, 0x0F, 0x6B, 0xBA, 0xD5, 0x64, 0xA5, 0xC3, 0xD5, 0xE2, 0x7B, 0xDB, 0x08, + 0x0F, 0x7D, 0x20, 0xE1, 0x32, 0x96, 0xA3, 0x18, 0x19, 0x54, 0xC3, 0x9C, 0x64, 0x9C, 0x94, + 0x3E, 0xBE, 0x17, 0xDF, 0x5C, 0x1F, 0x7A, 0xAE, 0x0A, 0x8F, 0xE1, 0x26, 0xC4, 0x77, 0x58, + 0x5A, 0x5D, 0x4D, 0x64, 0x8A, 0x0D, 0x00, 0x8B, 0x6A, 0xF5, 0xE8, 0xCD, 0x31, 0xBE, 0x69, + 0xA9, 0x29, 0x6D, 0x4F, 0x3F, 0xD2, 0x5E, 0xD8, 0x6F, 0x22, 0x1E, 0x4B, 0x93, 0xF6, 0x5F, + 0x59, 0x29, 0x96, 0x75, 0x33, 0x62, 0x4B, 0x92, 0x35, 0x75, 0x0C, 0x30, 0x70, 0x75, 0x50, + 0xB5, 0x85, 0x36, 0xD1, 0x09, 0xA7, 0x13, 0x1C, 0x5A, 0x5B, 0xBE, 0x4A, 0x57, 0x15, 0x56, + 0x7C, 0x12, 0x53, 0x4A, 0xEC, 0x76, 0x60, 0x76, 0x1E, 0xEB, 0xB9, 0xFA, 0xE2, 0x89, 0x1C, + 0x77, 0x45, 0x89, 0xB8, 0x0E, 0x56, 0x6A, 0xD5, 0x57, 0xDD, 0xEF, 0x73, 0x67, 0x19, 0x6B, + 0x72, 0x27, 0xEA, 0x98, 0x70, 0xEF, 0x09, 0xDD, 0xFE, 0xC7, 0x9D, 0x6B, 0x93, 0x19, 0xA6, + 0x87, 0x9B, 0x52, 0x05, 0xD7, 0x6B, 0xF7, 0xAB, 0xA5, 0xAC, 0xF3, 0x3A, 0xFB, 0x59, 0xD1, + 0x7F, 0xC5, 0x4E, 0x68, 0x38, 0x3D, 0x6B, 0xE5, 0xA0, 0x8E, 0x9B, 0x66, 0xDA, 0x53, 0xDC, + 0xDE, 0x00, 0x8B, 0xB2, 0x94, 0xB8, 0x58, 0x2B, 0xD1, 0x32, 0xCD, 0xCC, 0x49, 0x95, 0x9F, + 0xDB, 0xC2, 0x1E, 0x52, 0x72, 0x18, 0x80, 0xC8, 0xAD, 0x03, 0x52, 0xC7, 0x9F, 0x03, 0xA4, + 0x3B, 0xBD, 0x84, 0xC4, 0xCD, 0xFD, 0xC6, 0xC5, 0x29, 0x00, 0x5E, 0x1E, 0x7C, 0xD9, 0xA3, + 0x49, 0xA7, 0x16, 0x8A, 0x35, 0x56, 0x9B, 0xA5, 0xDE, 0xA8, 0x18, 0x96, 0x8D, 0x5A, 0x91, + 0x46, 0x6B, 0xD6, 0xE6, 0x4E, 0x20, 0xBF, 0x62, 0x41, 0x71, 0x98, 0xAF, 0xC4, 0xE8, 0x1C, + 0x28, 0xDD, 0x77, 0xED, 0x40, 0x28, 0x23, 0x23, 0x98, 0xB5, 0x2F, 0xBD, 0xE8, 0x6B, 0xC8, + 0x4F, 0x47, 0x5B, 0x90, 0x16, 0x71, 0x0C, 0xE2, 0xAA, 0xBC, 0x11, 0xA0, 0x6B, 0x4D, 0xBA, + 0xC9, 0x01, 0xEC, 0x16, 0xCF, 0x36, 0x5C, 0xA3, 0xF2, 0xD5, 0x38, 0x13, 0x94, 0x8A, 0x69, + 0x3A, 0x0F, 0x93, 0xE7, 0x9C, 0x46, 0xCA, 0x5D, 0x5A, 0x6D, 0xCA, 0x3D, 0x28, 0xCA, 0x50, + 0xAD, 0x18, 0xBD, 0x13, 0xFC, 0xA5, 0x50, 0x59, 0xDD, 0x9B, 0x18, 0x5F, 0x79, 0xF9, 0xC4, + 0x71, 0x96, 0xA4, 0xE8, 0x1B, 0x21, 0x04, 0xBC, 0x46, 0x0A, 0x05, 0x1E, 0x02, 0xF2, 0xE8, + 0x44, 0x4F, + ]) + .unwrap(); // todo -- there's something I don't understand here that removing this hex:: for a straight array causes peak memory usage to go up. // Find the optimal and make all the tests the same. let sig = &*hex::decode("9061f15cbf2092f744fbcd799eb02414053c1b0f7412124bedc41cf9a3db0166469e874037d7f081e5f8d3d2033a0307d1c49ed01fe64578c4a6fabd80880cdf1911848f184d4bcf536ca795a0fb1aa19ab7ee3ba6b58bd64bbeac9f58650fff1ef5a97ab6916df962072e20e7c6be96090e3a781a504bc4442bd8889a0aa628907a74299f39fa836031f1bd68355bebe7ae93c1e361a9efbed1325d96227070461fcd6f151b8669d9229b977d9ee51fd2260c3e4a2e820416f9e074958dc3b3e2217e6312b7e0b582a048981cf6579f4bc7715b78c808e4c57e3b8aa38b05c04fcedf209f52c1e331ae83dbdff60ba450a17cc397568e54bc3f16ddf30b92747ce460d925b9be20a1d35e2aed97f124af2616a5361df28ba30e522dd08fa00fd28d1ac484d756a89e3a442fefe8332c56cd2a9fde691bdbda43f1cc54cef57bead96120b50c7d4695bdbb1303cc5ddda898e4eeb83083176e40e0232cdd1c3150371df05d6fdad7e1164d90393cf308e99edfeb31fed263e2866ee3b7f3937b399c974d87ba7b489efe3c9b80371d2928446adc31991ab0cefaaa080575b9ec81cfa133a9911c035a8058d0d3f2e34de4a9fb009bb4ccdb16de7b908574a7496725ff857556c1b33917e986c80f1014a9e3083add2fb35f345c5d06159e443329d0da099987b996c3731592b460c2ffd2955f7546f4216100ba43188803ff9b36969685f909fa2539323b8c8ec1c095a5085e554dd450e0e67ab670b6a11ebf6c25520fc13e364060f91f9b7f3d5cb48ff28b8fc83d4293f1f35ad6ff6ae4574ad7a1c6005fc0389a7b21386b0850a05d832fe6a14bb2b1db1f8e20bd09174946cd098b81c8f797e95f2143a949770cf1219bfef039db51a80fc247f65f41554c7173dd805ba82fdf47ab6d4bfd37dfe46fc47904421ae00dc005a22f9c4784b0ea9e665392a412245016d5c6d7673a6a180d228d4255a538e451ffd8b414d40304c0c888992e0ab6de1602109527417bc1c7eb782ae77a8c3cdfc1d13a1e874207898264e38080243109c5969649ac8383417e922ba115331142d0ed35440b15d40bee0cf58af37c0f0524ffac1c71ceed3bb82f76ab108a8ad1a0c8b78d9341148c642369be7bef59d46f49d70c83560607f140848ec9a7607d4a08f8b6e4447f5523f416981888a8de9647ffef79389e4983e5c9387698d0cc2d429322365ce7e7b5fd6d6eb921c813fcf06199fe1ca41e9cfe03b539f321671a2acad0963f876f9db7a1c4371b9f101005217995b5b6a40976246d245da603dba8dac812a5480c3476a99d0ffdf0ef943d72d912543148b2fe78e8b0159324fe9bcd4ced33cd212fe4f3dfd6d4c5e1958beb95ac6b533ace3e78015e3880b52bf45299263a4c0096f8ba5fe3a6298cab675cb7f382e7ef49720eb4cf47376e2d2574122ccf91129c858e948904fecefb91226ed42403ba12dd3258909a87dfcbf65cc3adc3d98d277fdcec7664e2292b7d27afbb5aafb405c20a34b2fe2c0849ee280bb891dfdf59f19b89b0246358db54cf3fdc66eaaaa750c8903f1d42678f3edf0b7530410aa881bc617f94346379854af4532e61f65aae7576c35faf55e155bd6787b4634d54191907e155c239e68480cdfa0c87054bfb62855f409a20d5335fb123e681e64ec847cd985b6062059f436aebac623c038b6c3405ac325191a8d1126a5ef8f38cccbf144a5c324c1e093cf99efbe10ca03d439bcfb8ba5e293b7d318837f7bc42a99964392369da76e79d71d1a2c248a11324a87ae1e3cbeab6fb0d0bcae1ef55e43dfb6f1b4cfb82c7a778fb828a3727ef07685fe38a74b3dd25d015322c2d9f245c08d8c2b43865694233782eb734436c4eddef5406208d6c4572c7371262fe02319cfbbcf2e23bed8aa969d1ae6f5f25ff6b8ebcf0925066f761a39bbff49f0c8dbc3be84f0c442b044ea01b669747e3c8293cfe9ccdf2ef063ae3d28d10720c279a2691616abd23b055cfc6c562125df4ad0fa6631304972ddc3674b1aaa7665bf621320d83eac8d5b371d7d719829f58b23458182558710de31d81ef9a47d8839c79640b2025d1965a418bc90e4115f1423311a8b64fcde0f2d2145ee535b0931b84bc8110445f2ff68d136ed709ddb7ea9ff75f3b4e8b4f836230ca9e81069477f634e07270af60ef96f72557a081d664abcf35548f699484653da645483ff2bf5998617ae8bfa62d56e714f3c0136e5035a3f78e06c2f470df7fd3380d14033f81e2aae6b4d90487dab76b9b3b8761fb56c36f5429da3d4346cb22e641ad8d7d2d80fa240d4e0154e6b3d2f1b3ef6cf174c08d062f575c83a4078174f874364df36a6328beeef69ba7f90e1df9fdcec9a2f15ebf04fa7d6756da2e5a59c9cbbcbc397d6fb28d0fc9a60534dff0752716ed079ad1ab19a224d1c8ae8a53242fd164989ff997489b6520eb3c0e97f4bcc1a9c3cbd44f008c03ef52cf7e626881d246925e0336c0ac668867f853da7820f914115a7c77ac31b66f46fbf97f66fa26416fc4581d459a4f2462d52cf0c79b278955aa73e8fa56e3c320f516bcc54c97e587199c15ab953cc37189b81c70cabb2559e445bcc9d8174ad7574e9acb02f43e0c34ff5e6746ee730ad41ff8eef93c2071c2649063dd92f343c06ef6abaf98f28d98d968071c12cc10a90c22d8b3b3480c76f7a51b7ec594b3435d2e3d779c1a15037697f3a058650472e47eecd5f32eb3243a516f0e703f9888c84690750648d6a9a876bf1f353db6891dc6d317d6e87ac088f42b5f6f20d799ece4fa7aaa928d2ac795e8de83d1e1c7fa2f9a4106693e981c21c63b3221c4fa2649f45f0c6e05dbf24011af16ab2e5fe94a640b485988037ebe1e8ad0b2623d95e9947f0726121d7828614e3b2d77a7a1f9a938bea9a1a7a2627b7d2e358c42ccc6c0b80a15a1c2f6e9aaf0495bdb7bb8d4b0e28a1ab5ab93ca0ff3e3f910c490c13486852534d5e12160835ec5916c5c68349c4e2d8fa956c643277edd3b6c81c88c010421705fd317ff9e3c94df0ed5305f530acbccf8dd0e87140cd38152664a572c168cd72595b7fac243c03f3fb33ef74a28c0e4469f94587c13704e9efe8010b2125aca78c22c33c82366e1a7c4028c2ae2e8d26e1a57e4297fac987f84a0a27f42b4c93a4f4d14569824b0880fb67407ed58f267ac403aa0b1f93784b4b4c67036037e60d58072611b0e90ca316976ef4e0b302cdad1b6dcca92efb8e1f6be2397967508be2c02a25ed0380ba1f7955f857c8fb043297780d136b2b064040c8e55143d715ea997e134ed973c98ef82786f0ccf66c17d863542180c66d54d08e116f2e35d995e214489ad0fad7a55fe9ebc1a777fe34141147c080b98d13463a3bbc6fc82f2fc95f4de7b3591d9c8cd4416917a4338095d5620104b7be13f5a131dd3f7aad5b559d11e8171dfb91e2bb1e47ac3810b1cdc1a1e370c867b7b7b50c4688dce545763157e02f47e1cc661d5bf2fbc336cfae080ab15728b1ab9dd199f2779d451e6178977fb658c17344cffb7aa3af5791a28fc8a089c85187753e5e313c8d1f0fe7755e28be444426a189e8bce2d2f79db31d4c3ca911a83455525355f95d159351cd731a88e55403851236ee2128f279d5be644c042453ae65d9e9f3b40d6c82bdeb002acdee061ecca3f2dceabef9a900e6e063d56ab39cb82dbc77a4677572d7616cd72c0f6d5b9b941dfda1fe7c896b8cc24d65a4322d712a84e94adfc8ed0cc56cc1ae97f775bd3cea5b20b524d9a7a916056e19af095d30171e5e14c7c998f78dc44845edf307363eab7913f680a5e5a1540a6f945507ffa67591f8d1a2920ab3b6e754e35379dd67870c242335e2717903ff3c687e5c33dc953416865d5f23bd752e55492b9d5d888d7b37ef33b0a6774d052b0987c066a2e01767207aa7fbfc393ca62874613dde3794f74fadb5d55b877b877a605918c812610fbcafad72ee245e6dd8721138d6bd3f4eedad853aed1ec437ad02ac937c80dae26fa5f70083bd346779b779387f7b3d2aae57770d8177928833281ccb7a38da24834fd9726fd17eb603cba9041e82bfeed0e33942dde1d48c271f5b39aa7230f41afb89d36f7976eee4f51a036743031c534f64685b94c990a93a5737fe628ee9cda8ed9c08b11d3836f833835c445b317a77ead7599d1a0c08873014510d36bb7ff5fb961277589ea48c32a60c87ec40681be067b17785ec44825bd89faa25249e735a628b6eebcc6cce4e0314c627588118c40b2e0d460d8d5ce358c56458f36914ca203f5a5381c6deb5a76bbc08c40a87437da0d0b571788a05e9f96d9bb770de8a0b1b960ff2a44a964c9b7939853742e83ce8deb79191b2d82454655f227079dd8c5b0216c8470b8e1ac70526301bbfa2bc4adca68a766ccb2a6e0ebf2e99905bf5242590b01703868b3faf841c11c383be145a40fea6375e18a01468e459603b5efdf8a4e9abd179280ae8b5947d78d2f0c4d37715eaa42bc37cf8730e41ffbf9826d46424f2922a96033cefaa8b4bbe4c8b89d43501fd5211d5392ca19a98ba127d9025b5c6e86ba024471940549a2b5d8e14961c9dc19696da1a5bffd01030d5e6100000000000000000000000000000000000000000000000005090f131a1f").unwrap(); @@ -395,8 +1885,8 @@ fn bench_mldsa65_verify() { } fn bench_mldsa65_lowmemory_verify() { - use bouncycastle::mldsa_lowmemory::{MLDSATrait, MLDSA65, MLDSA65_SIG_LEN, MLDSA65PublicKey}; use bouncycastle::hex; + use bouncycastle::mldsa_lowmemory::{MLDSA65, MLDSA65_SIG_LEN, MLDSA65PublicKey, MLDSATrait}; eprintln!("MLDSA65_lowmemory/Verify"); @@ -428,8 +1918,10 @@ fn bench_mldsa65_lowmemory_verify() { } fn bench_mldsa87_verify() { - use bouncycastle::mldsa::{MLDSATrait, MLDSA87, MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87PublicKey}; use bouncycastle::hex; + use bouncycastle::mldsa::{ + MLDSA87, MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87PublicKey, MLDSATrait, + }; eprintln!("MLDSA87/Verify"); @@ -450,7 +1942,182 @@ fn bench_mldsa87_verify() { // eprintln!("sig:\n{}", &*hex::encode(sig)); // let mldsa87_pk = MLDSA87PublicKey::from_bytes(&*hex::decode("9792bcec2f2430686a82fccf3c2f5ff665e771d7ab41b90258cfa7e90ec97124a73b323b9ba21ab64d767c433f5a521effe18f86e46a188952c4467e048b729e7fc4d115e7e48da1896d5fe119b10dcddef62cb307954074b42336e52836de61da941f8d37ea68ac8106fabe19070679af6008537120f70793b8ea9cc0e6e7b7b4c9a5c7421c60f24451ba1e933db1a2ee16c79559f21b3d1b8305850aa42afbb13f1f4d5b9f4835f9d87dfceb162d0ef4a7fdc4cba1743cd1c87bb4967da16cc8764b6569df8ee5bdcbffe9a4e05748e6fdf225af9e4eeb7773b62e8f85f9b56b548945551844fbd89806a4ac369bed2d256100f688a6ad5e0a709826dc4449e91e23c5506e642361ef5a313712f79bc4b3186861ca85a4bab17e7f943d1b8a333aa3ae7ce16b440d6018f9e04daf5725c7f1a93fad1a5a27b67895bd249aa91685de20af32c8b7e268c7f96877d0c85001135a4f0a8f1b8264fa6ebe5a349d8aecad1a16299ccf2fd9c7b85bace2ced3aa1276ba61ee78ed7e5ca5b67cdd458a9354030e6abbbabf56a0a2316fec9dba83b51d42fd3167f1e0f90855d5c66509b210265dc1e54ec44b43ba7cf9aef118b44d80912ce75166a6651e116cebe49229a7062c09931f71abd2293f76f7efc3215ba97800037e58e470bdbbb43c1b0439eaf79c54d93b44aac9efe9fbe151874cfb2a64cbee28cc4c0fe7775e5d870f1c02e5b2e3c5004c995f24c9b779cb753a277d0e71fd425eb6bc2ca56ce129db51f70740f31e63976b50c7312e9797d78c5b1ac24a5fa347cc916e0a83f5c3b675cd30b81e3fa10b93444e07397571cce98b28da51db9056bc728c5b0b1181e2fbd387b4c79ab1a5fefece37167af772ddad14eb4c3982da5a59d0e9eb173ec6315091170027a3ab5ef6aa129cb8585727b9358a28501d713a72f3f1db31714286f9b6408013af06045d75592fc0b7dd47c73ed9c75b11e9d7c69f7cadfc3280a9062c5273c43be1c34f87448864cea7b5c97d6d32f59bd5f25384653bb5c4faa45bea8b89402843e645b6b9269e2bd988ddacb033328ffb060450f7df080053e6969b251e875ecec32cfc592840d69ab69a75e06b379c535d95266b082f4f09c93162b33b0d9f7307a4eaaa52104437fed66f8ee3eabbd45d67b25a8133f496468b52baffdbfad93eef1a9818b5e42ec722788a3d8d3529fc777d2ba570801dfae01ec88302837c1fb9e0355727645ee1046c3f915f6ae82dad4fb6b0356a46518ffc834155c3b4fe6dafa6cc8a5ccf53c73a0849d8d44f7dcf72754e70e1b7dfb447bb4ef49d1a718f6171bbce200950e0ce926106b151a3e871d5ce49731bd6650a9b0ca972da1c5f136d44820ea6383c08f3b384cf2338e789c513f618cc5694a6f0cee104511e1ed7c5f23a1ebfd8a0db8424553240156dbf622831b0c643d1c551b6f3f7a98d29b85c2de05a65fa615eee16495bd90737672115b53e91c5d90028cf3f1a93953a153de53b44084e9ccff6b736693926daefebb2d77aa5ad689b92f31686669df16d1715cc58f7a2cfb72dd1a51e92f825993a74022be7e9eb6054654457094d14928f20215e7b222ac56b51adbec8d8bdb6983979a7e3a21b44b5d1518ca97d0b5195f51ed6a24350c89747e1edea51b448e3e9147054ce927873c90db394d86888e07dff177593d6f79e152302204aeb03be2386af3e24078bd028b1689f5e147c9f452c8ceb02ec59cc9db63a03576ceeafe98239023897da0236630a53c0de7f435a19869792fab36e7b9e635760f09069e6432e700035ac2a02879fff0a1e1bec522047193d94eb5df1efd53eea1144ca78940852f5ec9727904b366ede4f5e2d331fad5fc282ea2c47e923142771c3dd75a87357487def99e5f18e9d9ed623c175d02888c51f82c07a80d54716b3c3c2bdbe2e9f0a9bbaaebeb4d52936876406f5c00e8e4bbd0a5ec05797e6207c5ab6c88f1a688421bd05a114f4d7de2ac241fa0e8bedff47f762ddcbeaa91004f8d31e85095c81054994ad3826e344ba96040810fc0b2ad1de48cfade002c62e5a49a0731ab38344bc1636df16bf607d56855e56d684003c718e4bad9e5a099979fcddeeb1c4a7776cd37a3417cb0e184e29ef9bc0e87475ba663be09e00ab562eb7c0f7165f969a9b42414198ccf1bff2a2c8d689a414ece7662927665689e94db961ebaec5615cbc1a7895c6851ac961432ff1118d4607d32ef9dc732d51333be4b4d0e30ddea784eca8be47e741be9c19631dc470a52ef4dc13a4f3633fd434d787c170977b417df598e1d0dde506bb71d6f0bc17ec70e3b03cdc1965cb36993f633b0472e50d0923ac6c66fdf1d3e6459cc121f0f5f94d09e9dbcf5d690e23233838a0bacb7c638d1b2650a4308cd171b6855126d1da672a6ed85a8d78c286fb56f4ab3d21497528045c63262c8a42af2f9802c53b7bb8be28e78fe0b5ce45fbb7a1af1a3b28a8d94b7890e3c882e39bc98e9f0ad76025bf0dd2f00298e7141a226b3d7cee414f604d1e0ba54d11d5fe58bccea6ad77ad2e8c1caacf32459014b7b91001b1efa8ad172a523fb8e365b577121bf9fd88a2c60c21e821d7b6acb47a5a995e40caced5c223b8fe6de5e18e9d2e5893aefebb7aae7ff1a146260e2f110e939528213a0025a38ec79aabc861b25ebc509a4674c132aaacb7e0146f14efd11cfcaf4caa4f775a716ce325e0a435a4d349d720bcf137450afc45046fc1a1f83a9d329777a7084e4aadae7122ce97005930528eb3c7f7f1129b372887a371155a3ba201a25cbf1dcb64e7cdee092c3141fb5550fe3d0dd82e870e578b2b46500818113b8f6569773c677385b69a42b77dcba7acffd95fd4452e23aaa1d37e1da2151ea658d40a3596b27ac9f8129dc6cf0643772624b59f4f461230df471ca26087c3942d5c6687df6082835935a3f87cb762b0c3b1d0dda4a6533965bef1b7b8292e254c014d090fed857c44c1839c694c0a64e3fad90a11f534722b6ee1574f2e149d55d744de4887024e08511431c062750e16c74ab9f3242f2db3ffb12a8d6107faa229d6f6373b07f36d3932b3bdb04c19dd64eadd7f93c3c564c358a1c81dcf1c9c31e5b06568f97544c17dc15698c5cb38983a9afc42783faa773a52c9d8260690be9e3156aa5bc1509dea3f69587695cd6ff172ba83e6a6d8a7d6bbebbbcda3672731983f89bc5831dc37c3f3c5c56facc697f3cb20bd5dbadbd702e54844ac2f626901fe159db93dfd4773d8fe73562b846c1fc856d1802762840ebc72d7988bde75cbca70d319d32ce0cc0253bb2ad455723ee0c7f4736ce6e6665c5aca32a481c53839bc259167b013d0423395eeb9aaaee3206149a7d550d67fc5fdfe4a8a5c35d2510b664379ab8f72855a2af47abce2a632048eaf89e5cb4a88debc53a595103acce4f1cff18acff07afe1eb5716aa1e40b63134c3a3ae9579fa87f515be093c2d29db6d6b65c93661e00636b592704d093cc6716c2342eb1853d48c85c63ac8a2854462c7b77e7e3bd1eac5bca28ffaa00b5d349f8a547ad875b96a8c2b2910c9301309a3f9138a5693111f55b3c009ca947c39dfc82d98eb1caa4a9cbe885f786fa86e55be062222f8ba90a974073326b31212aece0a34a60").unwrap()).unwrap(); - let pk = MLDSA87PublicKey::from_bytes(&[0x97,0x92,0xbc,0xec,0x2f,0x24,0x30,0x68,0x6a,0x82,0xfc,0xcf,0x3c,0x2f,0x5f,0xf6,0x65,0xe7,0x71,0xd7,0xab,0x41,0xb9,0x02,0x58,0xcf,0xa7,0xe9,0x0e,0xc9,0x71,0x24,0xa7,0x3b,0x32,0x3b,0x9b,0xa2,0x1a,0xb6,0x4d,0x76,0x7c,0x43,0x3f,0x5a,0x52,0x1e,0xff,0xe1,0x8f,0x86,0xe4,0x6a,0x18,0x89,0x52,0xc4,0x46,0x7e,0x04,0x8b,0x72,0x9e,0x7f,0xc4,0xd1,0x15,0xe7,0xe4,0x8d,0xa1,0x89,0x6d,0x5f,0xe1,0x19,0xb1,0x0d,0xcd,0xde,0xf6,0x2c,0xb3,0x07,0x95,0x40,0x74,0xb4,0x23,0x36,0xe5,0x28,0x36,0xde,0x61,0xda,0x94,0x1f,0x8d,0x37,0xea,0x68,0xac,0x81,0x06,0xfa,0xbe,0x19,0x07,0x06,0x79,0xaf,0x60,0x08,0x53,0x71,0x20,0xf7,0x07,0x93,0xb8,0xea,0x9c,0xc0,0xe6,0xe7,0xb7,0xb4,0xc9,0xa5,0xc7,0x42,0x1c,0x60,0xf2,0x44,0x51,0xba,0x1e,0x93,0x3d,0xb1,0xa2,0xee,0x16,0xc7,0x95,0x59,0xf2,0x1b,0x3d,0x1b,0x83,0x05,0x85,0x0a,0xa4,0x2a,0xfb,0xb1,0x3f,0x1f,0x4d,0x5b,0x9f,0x48,0x35,0xf9,0xd8,0x7d,0xfc,0xeb,0x16,0x2d,0x0e,0xf4,0xa7,0xfd,0xc4,0xcb,0xa1,0x74,0x3c,0xd1,0xc8,0x7b,0xb4,0x96,0x7d,0xa1,0x6c,0xc8,0x76,0x4b,0x65,0x69,0xdf,0x8e,0xe5,0xbd,0xcb,0xff,0xe9,0xa4,0xe0,0x57,0x48,0xe6,0xfd,0xf2,0x25,0xaf,0x9e,0x4e,0xeb,0x77,0x73,0xb6,0x2e,0x8f,0x85,0xf9,0xb5,0x6b,0x54,0x89,0x45,0x55,0x18,0x44,0xfb,0xd8,0x98,0x06,0xa4,0xac,0x36,0x9b,0xed,0x2d,0x25,0x61,0x00,0xf6,0x88,0xa6,0xad,0x5e,0x0a,0x70,0x98,0x26,0xdc,0x44,0x49,0xe9,0x1e,0x23,0xc5,0x50,0x6e,0x64,0x23,0x61,0xef,0x5a,0x31,0x37,0x12,0xf7,0x9b,0xc4,0xb3,0x18,0x68,0x61,0xca,0x85,0xa4,0xba,0xb1,0x7e,0x7f,0x94,0x3d,0x1b,0x8a,0x33,0x3a,0xa3,0xae,0x7c,0xe1,0x6b,0x44,0x0d,0x60,0x18,0xf9,0xe0,0x4d,0xaf,0x57,0x25,0xc7,0xf1,0xa9,0x3f,0xad,0x1a,0x5a,0x27,0xb6,0x78,0x95,0xbd,0x24,0x9a,0xa9,0x16,0x85,0xde,0x20,0xaf,0x32,0xc8,0xb7,0xe2,0x68,0xc7,0xf9,0x68,0x77,0xd0,0xc8,0x50,0x01,0x13,0x5a,0x4f,0x0a,0x8f,0x1b,0x82,0x64,0xfa,0x6e,0xbe,0x5a,0x34,0x9d,0x8a,0xec,0xad,0x1a,0x16,0x29,0x9c,0xcf,0x2f,0xd9,0xc7,0xb8,0x5b,0xac,0xe2,0xce,0xd3,0xaa,0x12,0x76,0xba,0x61,0xee,0x78,0xed,0x7e,0x5c,0xa5,0xb6,0x7c,0xdd,0x45,0x8a,0x93,0x54,0x03,0x0e,0x6a,0xbb,0xba,0xbf,0x56,0xa0,0xa2,0x31,0x6f,0xec,0x9d,0xba,0x83,0xb5,0x1d,0x42,0xfd,0x31,0x67,0xf1,0xe0,0xf9,0x08,0x55,0xd5,0xc6,0x65,0x09,0xb2,0x10,0x26,0x5d,0xc1,0xe5,0x4e,0xc4,0x4b,0x43,0xba,0x7c,0xf9,0xae,0xf1,0x18,0xb4,0x4d,0x80,0x91,0x2c,0xe7,0x51,0x66,0xa6,0x65,0x1e,0x11,0x6c,0xeb,0xe4,0x92,0x29,0xa7,0x06,0x2c,0x09,0x93,0x1f,0x71,0xab,0xd2,0x29,0x3f,0x76,0xf7,0xef,0xc3,0x21,0x5b,0xa9,0x78,0x00,0x03,0x7e,0x58,0xe4,0x70,0xbd,0xbb,0xb4,0x3c,0x1b,0x04,0x39,0xea,0xf7,0x9c,0x54,0xd9,0x3b,0x44,0xaa,0xc9,0xef,0xe9,0xfb,0xe1,0x51,0x87,0x4c,0xfb,0x2a,0x64,0xcb,0xee,0x28,0xcc,0x4c,0x0f,0xe7,0x77,0x5e,0x5d,0x87,0x0f,0x1c,0x02,0xe5,0xb2,0xe3,0xc5,0x00,0x4c,0x99,0x5f,0x24,0xc9,0xb7,0x79,0xcb,0x75,0x3a,0x27,0x7d,0x0e,0x71,0xfd,0x42,0x5e,0xb6,0xbc,0x2c,0xa5,0x6c,0xe1,0x29,0xdb,0x51,0xf7,0x07,0x40,0xf3,0x1e,0x63,0x97,0x6b,0x50,0xc7,0x31,0x2e,0x97,0x97,0xd7,0x8c,0x5b,0x1a,0xc2,0x4a,0x5f,0xa3,0x47,0xcc,0x91,0x6e,0x0a,0x83,0xf5,0xc3,0xb6,0x75,0xcd,0x30,0xb8,0x1e,0x3f,0xa1,0x0b,0x93,0x44,0x4e,0x07,0x39,0x75,0x71,0xcc,0xe9,0x8b,0x28,0xda,0x51,0xdb,0x90,0x56,0xbc,0x72,0x8c,0x5b,0x0b,0x11,0x81,0xe2,0xfb,0xd3,0x87,0xb4,0xc7,0x9a,0xb1,0xa5,0xfe,0xfe,0xce,0x37,0x16,0x7a,0xf7,0x72,0xdd,0xad,0x14,0xeb,0x4c,0x39,0x82,0xda,0x5a,0x59,0xd0,0xe9,0xeb,0x17,0x3e,0xc6,0x31,0x50,0x91,0x17,0x00,0x27,0xa3,0xab,0x5e,0xf6,0xaa,0x12,0x9c,0xb8,0x58,0x57,0x27,0xb9,0x35,0x8a,0x28,0x50,0x1d,0x71,0x3a,0x72,0xf3,0xf1,0xdb,0x31,0x71,0x42,0x86,0xf9,0xb6,0x40,0x80,0x13,0xaf,0x06,0x04,0x5d,0x75,0x59,0x2f,0xc0,0xb7,0xdd,0x47,0xc7,0x3e,0xd9,0xc7,0x5b,0x11,0xe9,0xd7,0xc6,0x9f,0x7c,0xad,0xfc,0x32,0x80,0xa9,0x06,0x2c,0x52,0x73,0xc4,0x3b,0xe1,0xc3,0x4f,0x87,0x44,0x88,0x64,0xce,0xa7,0xb5,0xc9,0x7d,0x6d,0x32,0xf5,0x9b,0xd5,0xf2,0x53,0x84,0x65,0x3b,0xb5,0xc4,0xfa,0xa4,0x5b,0xea,0x8b,0x89,0x40,0x28,0x43,0xe6,0x45,0xb6,0xb9,0x26,0x9e,0x2b,0xd9,0x88,0xdd,0xac,0xb0,0x33,0x32,0x8f,0xfb,0x06,0x04,0x50,0xf7,0xdf,0x08,0x00,0x53,0xe6,0x96,0x9b,0x25,0x1e,0x87,0x5e,0xce,0xc3,0x2c,0xfc,0x59,0x28,0x40,0xd6,0x9a,0xb6,0x9a,0x75,0xe0,0x6b,0x37,0x9c,0x53,0x5d,0x95,0x26,0x6b,0x08,0x2f,0x4f,0x09,0xc9,0x31,0x62,0xb3,0x3b,0x0d,0x9f,0x73,0x07,0xa4,0xea,0xaa,0x52,0x10,0x44,0x37,0xfe,0xd6,0x6f,0x8e,0xe3,0xea,0xbb,0xd4,0x5d,0x67,0xb2,0x5a,0x81,0x33,0xf4,0x96,0x46,0x8b,0x52,0xba,0xff,0xdb,0xfa,0xd9,0x3e,0xef,0x1a,0x98,0x18,0xb5,0xe4,0x2e,0xc7,0x22,0x78,0x8a,0x3d,0x8d,0x35,0x29,0xfc,0x77,0x7d,0x2b,0xa5,0x70,0x80,0x1d,0xfa,0xe0,0x1e,0xc8,0x83,0x02,0x83,0x7c,0x1f,0xb9,0xe0,0x35,0x57,0x27,0x64,0x5e,0xe1,0x04,0x6c,0x3f,0x91,0x5f,0x6a,0xe8,0x2d,0xad,0x4f,0xb6,0xb0,0x35,0x6a,0x46,0x51,0x8f,0xfc,0x83,0x41,0x55,0xc3,0xb4,0xfe,0x6d,0xaf,0xa6,0xcc,0x8a,0x5c,0xcf,0x53,0xc7,0x3a,0x08,0x49,0xd8,0xd4,0x4f,0x7d,0xcf,0x72,0x75,0x4e,0x70,0xe1,0xb7,0xdf,0xb4,0x47,0xbb,0x4e,0xf4,0x9d,0x1a,0x71,0x8f,0x61,0x71,0xbb,0xce,0x20,0x09,0x50,0xe0,0xce,0x92,0x61,0x06,0xb1,0x51,0xa3,0xe8,0x71,0xd5,0xce,0x49,0x73,0x1b,0xd6,0x65,0x0a,0x9b,0x0c,0xa9,0x72,0xda,0x1c,0x5f,0x13,0x6d,0x44,0x82,0x0e,0xa6,0x38,0x3c,0x08,0xf3,0xb3,0x84,0xcf,0x23,0x38,0xe7,0x89,0xc5,0x13,0xf6,0x18,0xcc,0x56,0x94,0xa6,0xf0,0xce,0xe1,0x04,0x51,0x1e,0x1e,0xd7,0xc5,0xf2,0x3a,0x1e,0xbf,0xd8,0xa0,0xdb,0x84,0x24,0x55,0x32,0x40,0x15,0x6d,0xbf,0x62,0x28,0x31,0xb0,0xc6,0x43,0xd1,0xc5,0x51,0xb6,0xf3,0xf7,0xa9,0x8d,0x29,0xb8,0x5c,0x2d,0xe0,0x5a,0x65,0xfa,0x61,0x5e,0xee,0x16,0x49,0x5b,0xd9,0x07,0x37,0x67,0x21,0x15,0xb5,0x3e,0x91,0xc5,0xd9,0x00,0x28,0xcf,0x3f,0x1a,0x93,0x95,0x3a,0x15,0x3d,0xe5,0x3b,0x44,0x08,0x4e,0x9c,0xcf,0xf6,0xb7,0x36,0x69,0x39,0x26,0xda,0xef,0xeb,0xb2,0xd7,0x7a,0xa5,0xad,0x68,0x9b,0x92,0xf3,0x16,0x86,0x66,0x9d,0xf1,0x6d,0x17,0x15,0xcc,0x58,0xf7,0xa2,0xcf,0xb7,0x2d,0xd1,0xa5,0x1e,0x92,0xf8,0x25,0x99,0x3a,0x74,0x02,0x2b,0xe7,0xe9,0xeb,0x60,0x54,0x65,0x44,0x57,0x09,0x4d,0x14,0x92,0x8f,0x20,0x21,0x5e,0x7b,0x22,0x2a,0xc5,0x6b,0x51,0xad,0xbe,0xc8,0xd8,0xbd,0xb6,0x98,0x39,0x79,0xa7,0xe3,0xa2,0x1b,0x44,0xb5,0xd1,0x51,0x8c,0xa9,0x7d,0x0b,0x51,0x95,0xf5,0x1e,0xd6,0xa2,0x43,0x50,0xc8,0x97,0x47,0xe1,0xed,0xea,0x51,0xb4,0x48,0xe3,0xe9,0x14,0x70,0x54,0xce,0x92,0x78,0x73,0xc9,0x0d,0xb3,0x94,0xd8,0x68,0x88,0xe0,0x7d,0xff,0x17,0x75,0x93,0xd6,0xf7,0x9e,0x15,0x23,0x02,0x20,0x4a,0xeb,0x03,0xbe,0x23,0x86,0xaf,0x3e,0x24,0x07,0x8b,0xd0,0x28,0xb1,0x68,0x9f,0x5e,0x14,0x7c,0x9f,0x45,0x2c,0x8c,0xeb,0x02,0xec,0x59,0xcc,0x9d,0xb6,0x3a,0x03,0x57,0x6c,0xee,0xaf,0xe9,0x82,0x39,0x02,0x38,0x97,0xda,0x02,0x36,0x63,0x0a,0x53,0xc0,0xde,0x7f,0x43,0x5a,0x19,0x86,0x97,0x92,0xfa,0xb3,0x6e,0x7b,0x9e,0x63,0x57,0x60,0xf0,0x90,0x69,0xe6,0x43,0x2e,0x70,0x00,0x35,0xac,0x2a,0x02,0x87,0x9f,0xff,0x0a,0x1e,0x1b,0xec,0x52,0x20,0x47,0x19,0x3d,0x94,0xeb,0x5d,0xf1,0xef,0xd5,0x3e,0xea,0x11,0x44,0xca,0x78,0x94,0x08,0x52,0xf5,0xec,0x97,0x27,0x90,0x4b,0x36,0x6e,0xde,0x4f,0x5e,0x2d,0x33,0x1f,0xad,0x5f,0xc2,0x82,0xea,0x2c,0x47,0xe9,0x23,0x14,0x27,0x71,0xc3,0xdd,0x75,0xa8,0x73,0x57,0x48,0x7d,0xef,0x99,0xe5,0xf1,0x8e,0x9d,0x9e,0xd6,0x23,0xc1,0x75,0xd0,0x28,0x88,0xc5,0x1f,0x82,0xc0,0x7a,0x80,0xd5,0x47,0x16,0xb3,0xc3,0xc2,0xbd,0xbe,0x2e,0x9f,0x0a,0x9b,0xba,0xae,0xbe,0xb4,0xd5,0x29,0x36,0x87,0x64,0x06,0xf5,0xc0,0x0e,0x8e,0x4b,0xbd,0x0a,0x5e,0xc0,0x57,0x97,0xe6,0x20,0x7c,0x5a,0xb6,0xc8,0x8f,0x1a,0x68,0x84,0x21,0xbd,0x05,0xa1,0x14,0xf4,0xd7,0xde,0x2a,0xc2,0x41,0xfa,0x0e,0x8b,0xed,0xff,0x47,0xf7,0x62,0xdd,0xcb,0xea,0xa9,0x10,0x04,0xf8,0xd3,0x1e,0x85,0x09,0x5c,0x81,0x05,0x49,0x94,0xad,0x38,0x26,0xe3,0x44,0xba,0x96,0x04,0x08,0x10,0xfc,0x0b,0x2a,0xd1,0xde,0x48,0xcf,0xad,0xe0,0x02,0xc6,0x2e,0x5a,0x49,0xa0,0x73,0x1a,0xb3,0x83,0x44,0xbc,0x16,0x36,0xdf,0x16,0xbf,0x60,0x7d,0x56,0x85,0x5e,0x56,0xd6,0x84,0x00,0x3c,0x71,0x8e,0x4b,0xad,0x9e,0x5a,0x09,0x99,0x79,0xfc,0xdd,0xee,0xb1,0xc4,0xa7,0x77,0x6c,0xd3,0x7a,0x34,0x17,0xcb,0x0e,0x18,0x4e,0x29,0xef,0x9b,0xc0,0xe8,0x74,0x75,0xba,0x66,0x3b,0xe0,0x9e,0x00,0xab,0x56,0x2e,0xb7,0xc0,0xf7,0x16,0x5f,0x96,0x9a,0x9b,0x42,0x41,0x41,0x98,0xcc,0xf1,0xbf,0xf2,0xa2,0xc8,0xd6,0x89,0xa4,0x14,0xec,0xe7,0x66,0x29,0x27,0x66,0x56,0x89,0xe9,0x4d,0xb9,0x61,0xeb,0xae,0xc5,0x61,0x5c,0xbc,0x1a,0x78,0x95,0xc6,0x85,0x1a,0xc9,0x61,0x43,0x2f,0xf1,0x11,0x8d,0x46,0x07,0xd3,0x2e,0xf9,0xdc,0x73,0x2d,0x51,0x33,0x3b,0xe4,0xb4,0xd0,0xe3,0x0d,0xde,0xa7,0x84,0xec,0xa8,0xbe,0x47,0xe7,0x41,0xbe,0x9c,0x19,0x63,0x1d,0xc4,0x70,0xa5,0x2e,0xf4,0xdc,0x13,0xa4,0xf3,0x63,0x3f,0xd4,0x34,0xd7,0x87,0xc1,0x70,0x97,0x7b,0x41,0x7d,0xf5,0x98,0xe1,0xd0,0xdd,0xe5,0x06,0xbb,0x71,0xd6,0xf0,0xbc,0x17,0xec,0x70,0xe3,0xb0,0x3c,0xdc,0x19,0x65,0xcb,0x36,0x99,0x3f,0x63,0x3b,0x04,0x72,0xe5,0x0d,0x09,0x23,0xac,0x6c,0x66,0xfd,0xf1,0xd3,0xe6,0x45,0x9c,0xc1,0x21,0xf0,0xf5,0xf9,0x4d,0x09,0xe9,0xdb,0xcf,0x5d,0x69,0x0e,0x23,0x23,0x38,0x38,0xa0,0xba,0xcb,0x7c,0x63,0x8d,0x1b,0x26,0x50,0xa4,0x30,0x8c,0xd1,0x71,0xb6,0x85,0x51,0x26,0xd1,0xda,0x67,0x2a,0x6e,0xd8,0x5a,0x8d,0x78,0xc2,0x86,0xfb,0x56,0xf4,0xab,0x3d,0x21,0x49,0x75,0x28,0x04,0x5c,0x63,0x26,0x2c,0x8a,0x42,0xaf,0x2f,0x98,0x02,0xc5,0x3b,0x7b,0xb8,0xbe,0x28,0xe7,0x8f,0xe0,0xb5,0xce,0x45,0xfb,0xb7,0xa1,0xaf,0x1a,0x3b,0x28,0xa8,0xd9,0x4b,0x78,0x90,0xe3,0xc8,0x82,0xe3,0x9b,0xc9,0x8e,0x9f,0x0a,0xd7,0x60,0x25,0xbf,0x0d,0xd2,0xf0,0x02,0x98,0xe7,0x14,0x1a,0x22,0x6b,0x3d,0x7c,0xee,0x41,0x4f,0x60,0x4d,0x1e,0x0b,0xa5,0x4d,0x11,0xd5,0xfe,0x58,0xbc,0xce,0xa6,0xad,0x77,0xad,0x2e,0x8c,0x1c,0xaa,0xcf,0x32,0x45,0x90,0x14,0xb7,0xb9,0x10,0x01,0xb1,0xef,0xa8,0xad,0x17,0x2a,0x52,0x3f,0xb8,0xe3,0x65,0xb5,0x77,0x12,0x1b,0xf9,0xfd,0x88,0xa2,0xc6,0x0c,0x21,0xe8,0x21,0xd7,0xb6,0xac,0xb4,0x7a,0x5a,0x99,0x5e,0x40,0xca,0xce,0xd5,0xc2,0x23,0xb8,0xfe,0x6d,0xe5,0xe1,0x8e,0x9d,0x2e,0x58,0x93,0xae,0xfe,0xbb,0x7a,0xae,0x7f,0xf1,0xa1,0x46,0x26,0x0e,0x2f,0x11,0x0e,0x93,0x95,0x28,0x21,0x3a,0x00,0x25,0xa3,0x8e,0xc7,0x9a,0xab,0xc8,0x61,0xb2,0x5e,0xbc,0x50,0x9a,0x46,0x74,0xc1,0x32,0xaa,0xac,0xb7,0xe0,0x14,0x6f,0x14,0xef,0xd1,0x1c,0xfc,0xaf,0x4c,0xaa,0x4f,0x77,0x5a,0x71,0x6c,0xe3,0x25,0xe0,0xa4,0x35,0xa4,0xd3,0x49,0xd7,0x20,0xbc,0xf1,0x37,0x45,0x0a,0xfc,0x45,0x04,0x6f,0xc1,0xa1,0xf8,0x3a,0x9d,0x32,0x97,0x77,0xa7,0x08,0x4e,0x4a,0xad,0xae,0x71,0x22,0xce,0x97,0x00,0x59,0x30,0x52,0x8e,0xb3,0xc7,0xf7,0xf1,0x12,0x9b,0x37,0x28,0x87,0xa3,0x71,0x15,0x5a,0x3b,0xa2,0x01,0xa2,0x5c,0xbf,0x1d,0xcb,0x64,0xe7,0xcd,0xee,0x09,0x2c,0x31,0x41,0xfb,0x55,0x50,0xfe,0x3d,0x0d,0xd8,0x2e,0x87,0x0e,0x57,0x8b,0x2b,0x46,0x50,0x08,0x18,0x11,0x3b,0x8f,0x65,0x69,0x77,0x3c,0x67,0x73,0x85,0xb6,0x9a,0x42,0xb7,0x7d,0xcb,0xa7,0xac,0xff,0xd9,0x5f,0xd4,0x45,0x2e,0x23,0xaa,0xa1,0xd3,0x7e,0x1d,0xa2,0x15,0x1e,0xa6,0x58,0xd4,0x0a,0x35,0x96,0xb2,0x7a,0xc9,0xf8,0x12,0x9d,0xc6,0xcf,0x06,0x43,0x77,0x26,0x24,0xb5,0x9f,0x4f,0x46,0x12,0x30,0xdf,0x47,0x1c,0xa2,0x60,0x87,0xc3,0x94,0x2d,0x5c,0x66,0x87,0xdf,0x60,0x82,0x83,0x59,0x35,0xa3,0xf8,0x7c,0xb7,0x62,0xb0,0xc3,0xb1,0xd0,0xdd,0xa4,0xa6,0x53,0x39,0x65,0xbe,0xf1,0xb7,0xb8,0x29,0x2e,0x25,0x4c,0x01,0x4d,0x09,0x0f,0xed,0x85,0x7c,0x44,0xc1,0x83,0x9c,0x69,0x4c,0x0a,0x64,0xe3,0xfa,0xd9,0x0a,0x11,0xf5,0x34,0x72,0x2b,0x6e,0xe1,0x57,0x4f,0x2e,0x14,0x9d,0x55,0xd7,0x44,0xde,0x48,0x87,0x02,0x4e,0x08,0x51,0x14,0x31,0xc0,0x62,0x75,0x0e,0x16,0xc7,0x4a,0xb9,0xf3,0x24,0x2f,0x2d,0xb3,0xff,0xb1,0x2a,0x8d,0x61,0x07,0xfa,0xa2,0x29,0xd6,0xf6,0x37,0x3b,0x07,0xf3,0x6d,0x39,0x32,0xb3,0xbd,0xb0,0x4c,0x19,0xdd,0x64,0xea,0xdd,0x7f,0x93,0xc3,0xc5,0x64,0xc3,0x58,0xa1,0xc8,0x1d,0xcf,0x1c,0x9c,0x31,0xe5,0xb0,0x65,0x68,0xf9,0x75,0x44,0xc1,0x7d,0xc1,0x56,0x98,0xc5,0xcb,0x38,0x98,0x3a,0x9a,0xfc,0x42,0x78,0x3f,0xaa,0x77,0x3a,0x52,0xc9,0xd8,0x26,0x06,0x90,0xbe,0x9e,0x31,0x56,0xaa,0x5b,0xc1,0x50,0x9d,0xea,0x3f,0x69,0x58,0x76,0x95,0xcd,0x6f,0xf1,0x72,0xba,0x83,0xe6,0xa6,0xd8,0xa7,0xd6,0xbb,0xeb,0xbb,0xcd,0xa3,0x67,0x27,0x31,0x98,0x3f,0x89,0xbc,0x58,0x31,0xdc,0x37,0xc3,0xf3,0xc5,0xc5,0x6f,0xac,0xc6,0x97,0xf3,0xcb,0x20,0xbd,0x5d,0xba,0xdb,0xd7,0x02,0xe5,0x48,0x44,0xac,0x2f,0x62,0x69,0x01,0xfe,0x15,0x9d,0xb9,0x3d,0xfd,0x47,0x73,0xd8,0xfe,0x73,0x56,0x2b,0x84,0x6c,0x1f,0xc8,0x56,0xd1,0x80,0x27,0x62,0x84,0x0e,0xbc,0x72,0xd7,0x98,0x8b,0xde,0x75,0xcb,0xca,0x70,0xd3,0x19,0xd3,0x2c,0xe0,0xcc,0x02,0x53,0xbb,0x2a,0xd4,0x55,0x72,0x3e,0xe0,0xc7,0xf4,0x73,0x6c,0xe6,0xe6,0x66,0x5c,0x5a,0xca,0x32,0xa4,0x81,0xc5,0x38,0x39,0xbc,0x25,0x91,0x67,0xb0,0x13,0xd0,0x42,0x33,0x95,0xee,0xb9,0xaa,0xae,0xe3,0x20,0x61,0x49,0xa7,0xd5,0x50,0xd6,0x7f,0xc5,0xfd,0xfe,0x4a,0x8a,0x5c,0x35,0xd2,0x51,0x0b,0x66,0x43,0x79,0xab,0x8f,0x72,0x85,0x5a,0x2a,0xf4,0x7a,0xbc,0xe2,0xa6,0x32,0x04,0x8e,0xaf,0x89,0xe5,0xcb,0x4a,0x88,0xde,0xbc,0x53,0xa5,0x95,0x10,0x3a,0xcc,0xe4,0xf1,0xcf,0xf1,0x8a,0xcf,0xf0,0x7a,0xfe,0x1e,0xb5,0x71,0x6a,0xa1,0xe4,0x0b,0x63,0x13,0x4c,0x3a,0x3a,0xe9,0x57,0x9f,0xa8,0x7f,0x51,0x5b,0xe0,0x93,0xc2,0xd2,0x9d,0xb6,0xd6,0xb6,0x5c,0x93,0x66,0x1e,0x00,0x63,0x6b,0x59,0x27,0x04,0xd0,0x93,0xcc,0x67,0x16,0xc2,0x34,0x2e,0xb1,0x85,0x3d,0x48,0xc8,0x5c,0x63,0xac,0x8a,0x28,0x54,0x46,0x2c,0x7b,0x77,0xe7,0xe3,0xbd,0x1e,0xac,0x5b,0xca,0x28,0xff,0xaa,0x00,0xb5,0xd3,0x49,0xf8,0xa5,0x47,0xad,0x87,0x5b,0x96,0xa8,0xc2,0xb2,0x91,0x0c,0x93,0x01,0x30,0x9a,0x3f,0x91,0x38,0xa5,0x69,0x31,0x11,0xf5,0x5b,0x3c,0x00,0x9c,0xa9,0x47,0xc3,0x9d,0xfc,0x82,0xd9,0x8e,0xb1,0xca,0xa4,0xa9,0xcb,0xe8,0x85,0xf7,0x86,0xfa,0x86,0xe5,0x5b,0xe0,0x62,0x22,0x2f,0x8b,0xa9,0x0a,0x97,0x40,0x73,0x32,0x6b,0x31,0x21,0x2a,0xec,0xe0,0xa3,0x4a,0x60]).unwrap(); + let pk = MLDSA87PublicKey::from_bytes(&[ + 0x97, 0x92, 0xBC, 0xEC, 0x2F, 0x24, 0x30, 0x68, 0x6A, 0x82, 0xFC, 0xCF, 0x3C, 0x2F, 0x5F, + 0xF6, 0x65, 0xE7, 0x71, 0xD7, 0xAB, 0x41, 0xB9, 0x02, 0x58, 0xCF, 0xA7, 0xE9, 0x0E, 0xC9, + 0x71, 0x24, 0xA7, 0x3B, 0x32, 0x3B, 0x9B, 0xA2, 0x1A, 0xB6, 0x4D, 0x76, 0x7C, 0x43, 0x3F, + 0x5A, 0x52, 0x1E, 0xFF, 0xE1, 0x8F, 0x86, 0xE4, 0x6A, 0x18, 0x89, 0x52, 0xC4, 0x46, 0x7E, + 0x04, 0x8B, 0x72, 0x9E, 0x7F, 0xC4, 0xD1, 0x15, 0xE7, 0xE4, 0x8D, 0xA1, 0x89, 0x6D, 0x5F, + 0xE1, 0x19, 0xB1, 0x0D, 0xCD, 0xDE, 0xF6, 0x2C, 0xB3, 0x07, 0x95, 0x40, 0x74, 0xB4, 0x23, + 0x36, 0xE5, 0x28, 0x36, 0xDE, 0x61, 0xDA, 0x94, 0x1F, 0x8D, 0x37, 0xEA, 0x68, 0xAC, 0x81, + 0x06, 0xFA, 0xBE, 0x19, 0x07, 0x06, 0x79, 0xAF, 0x60, 0x08, 0x53, 0x71, 0x20, 0xF7, 0x07, + 0x93, 0xB8, 0xEA, 0x9C, 0xC0, 0xE6, 0xE7, 0xB7, 0xB4, 0xC9, 0xA5, 0xC7, 0x42, 0x1C, 0x60, + 0xF2, 0x44, 0x51, 0xBA, 0x1E, 0x93, 0x3D, 0xB1, 0xA2, 0xEE, 0x16, 0xC7, 0x95, 0x59, 0xF2, + 0x1B, 0x3D, 0x1B, 0x83, 0x05, 0x85, 0x0A, 0xA4, 0x2A, 0xFB, 0xB1, 0x3F, 0x1F, 0x4D, 0x5B, + 0x9F, 0x48, 0x35, 0xF9, 0xD8, 0x7D, 0xFC, 0xEB, 0x16, 0x2D, 0x0E, 0xF4, 0xA7, 0xFD, 0xC4, + 0xCB, 0xA1, 0x74, 0x3C, 0xD1, 0xC8, 0x7B, 0xB4, 0x96, 0x7D, 0xA1, 0x6C, 0xC8, 0x76, 0x4B, + 0x65, 0x69, 0xDF, 0x8E, 0xE5, 0xBD, 0xCB, 0xFF, 0xE9, 0xA4, 0xE0, 0x57, 0x48, 0xE6, 0xFD, + 0xF2, 0x25, 0xAF, 0x9E, 0x4E, 0xEB, 0x77, 0x73, 0xB6, 0x2E, 0x8F, 0x85, 0xF9, 0xB5, 0x6B, + 0x54, 0x89, 0x45, 0x55, 0x18, 0x44, 0xFB, 0xD8, 0x98, 0x06, 0xA4, 0xAC, 0x36, 0x9B, 0xED, + 0x2D, 0x25, 0x61, 0x00, 0xF6, 0x88, 0xA6, 0xAD, 0x5E, 0x0A, 0x70, 0x98, 0x26, 0xDC, 0x44, + 0x49, 0xE9, 0x1E, 0x23, 0xC5, 0x50, 0x6E, 0x64, 0x23, 0x61, 0xEF, 0x5A, 0x31, 0x37, 0x12, + 0xF7, 0x9B, 0xC4, 0xB3, 0x18, 0x68, 0x61, 0xCA, 0x85, 0xA4, 0xBA, 0xB1, 0x7E, 0x7F, 0x94, + 0x3D, 0x1B, 0x8A, 0x33, 0x3A, 0xA3, 0xAE, 0x7C, 0xE1, 0x6B, 0x44, 0x0D, 0x60, 0x18, 0xF9, + 0xE0, 0x4D, 0xAF, 0x57, 0x25, 0xC7, 0xF1, 0xA9, 0x3F, 0xAD, 0x1A, 0x5A, 0x27, 0xB6, 0x78, + 0x95, 0xBD, 0x24, 0x9A, 0xA9, 0x16, 0x85, 0xDE, 0x20, 0xAF, 0x32, 0xC8, 0xB7, 0xE2, 0x68, + 0xC7, 0xF9, 0x68, 0x77, 0xD0, 0xC8, 0x50, 0x01, 0x13, 0x5A, 0x4F, 0x0A, 0x8F, 0x1B, 0x82, + 0x64, 0xFA, 0x6E, 0xBE, 0x5A, 0x34, 0x9D, 0x8A, 0xEC, 0xAD, 0x1A, 0x16, 0x29, 0x9C, 0xCF, + 0x2F, 0xD9, 0xC7, 0xB8, 0x5B, 0xAC, 0xE2, 0xCE, 0xD3, 0xAA, 0x12, 0x76, 0xBA, 0x61, 0xEE, + 0x78, 0xED, 0x7E, 0x5C, 0xA5, 0xB6, 0x7C, 0xDD, 0x45, 0x8A, 0x93, 0x54, 0x03, 0x0E, 0x6A, + 0xBB, 0xBA, 0xBF, 0x56, 0xA0, 0xA2, 0x31, 0x6F, 0xEC, 0x9D, 0xBA, 0x83, 0xB5, 0x1D, 0x42, + 0xFD, 0x31, 0x67, 0xF1, 0xE0, 0xF9, 0x08, 0x55, 0xD5, 0xC6, 0x65, 0x09, 0xB2, 0x10, 0x26, + 0x5D, 0xC1, 0xE5, 0x4E, 0xC4, 0x4B, 0x43, 0xBA, 0x7C, 0xF9, 0xAE, 0xF1, 0x18, 0xB4, 0x4D, + 0x80, 0x91, 0x2C, 0xE7, 0x51, 0x66, 0xA6, 0x65, 0x1E, 0x11, 0x6C, 0xEB, 0xE4, 0x92, 0x29, + 0xA7, 0x06, 0x2C, 0x09, 0x93, 0x1F, 0x71, 0xAB, 0xD2, 0x29, 0x3F, 0x76, 0xF7, 0xEF, 0xC3, + 0x21, 0x5B, 0xA9, 0x78, 0x00, 0x03, 0x7E, 0x58, 0xE4, 0x70, 0xBD, 0xBB, 0xB4, 0x3C, 0x1B, + 0x04, 0x39, 0xEA, 0xF7, 0x9C, 0x54, 0xD9, 0x3B, 0x44, 0xAA, 0xC9, 0xEF, 0xE9, 0xFB, 0xE1, + 0x51, 0x87, 0x4C, 0xFB, 0x2A, 0x64, 0xCB, 0xEE, 0x28, 0xCC, 0x4C, 0x0F, 0xE7, 0x77, 0x5E, + 0x5D, 0x87, 0x0F, 0x1C, 0x02, 0xE5, 0xB2, 0xE3, 0xC5, 0x00, 0x4C, 0x99, 0x5F, 0x24, 0xC9, + 0xB7, 0x79, 0xCB, 0x75, 0x3A, 0x27, 0x7D, 0x0E, 0x71, 0xFD, 0x42, 0x5E, 0xB6, 0xBC, 0x2C, + 0xA5, 0x6C, 0xE1, 0x29, 0xDB, 0x51, 0xF7, 0x07, 0x40, 0xF3, 0x1E, 0x63, 0x97, 0x6B, 0x50, + 0xC7, 0x31, 0x2E, 0x97, 0x97, 0xD7, 0x8C, 0x5B, 0x1A, 0xC2, 0x4A, 0x5F, 0xA3, 0x47, 0xCC, + 0x91, 0x6E, 0x0A, 0x83, 0xF5, 0xC3, 0xB6, 0x75, 0xCD, 0x30, 0xB8, 0x1E, 0x3F, 0xA1, 0x0B, + 0x93, 0x44, 0x4E, 0x07, 0x39, 0x75, 0x71, 0xCC, 0xE9, 0x8B, 0x28, 0xDA, 0x51, 0xDB, 0x90, + 0x56, 0xBC, 0x72, 0x8C, 0x5B, 0x0B, 0x11, 0x81, 0xE2, 0xFB, 0xD3, 0x87, 0xB4, 0xC7, 0x9A, + 0xB1, 0xA5, 0xFE, 0xFE, 0xCE, 0x37, 0x16, 0x7A, 0xF7, 0x72, 0xDD, 0xAD, 0x14, 0xEB, 0x4C, + 0x39, 0x82, 0xDA, 0x5A, 0x59, 0xD0, 0xE9, 0xEB, 0x17, 0x3E, 0xC6, 0x31, 0x50, 0x91, 0x17, + 0x00, 0x27, 0xA3, 0xAB, 0x5E, 0xF6, 0xAA, 0x12, 0x9C, 0xB8, 0x58, 0x57, 0x27, 0xB9, 0x35, + 0x8A, 0x28, 0x50, 0x1D, 0x71, 0x3A, 0x72, 0xF3, 0xF1, 0xDB, 0x31, 0x71, 0x42, 0x86, 0xF9, + 0xB6, 0x40, 0x80, 0x13, 0xAF, 0x06, 0x04, 0x5D, 0x75, 0x59, 0x2F, 0xC0, 0xB7, 0xDD, 0x47, + 0xC7, 0x3E, 0xD9, 0xC7, 0x5B, 0x11, 0xE9, 0xD7, 0xC6, 0x9F, 0x7C, 0xAD, 0xFC, 0x32, 0x80, + 0xA9, 0x06, 0x2C, 0x52, 0x73, 0xC4, 0x3B, 0xE1, 0xC3, 0x4F, 0x87, 0x44, 0x88, 0x64, 0xCE, + 0xA7, 0xB5, 0xC9, 0x7D, 0x6D, 0x32, 0xF5, 0x9B, 0xD5, 0xF2, 0x53, 0x84, 0x65, 0x3B, 0xB5, + 0xC4, 0xFA, 0xA4, 0x5B, 0xEA, 0x8B, 0x89, 0x40, 0x28, 0x43, 0xE6, 0x45, 0xB6, 0xB9, 0x26, + 0x9E, 0x2B, 0xD9, 0x88, 0xDD, 0xAC, 0xB0, 0x33, 0x32, 0x8F, 0xFB, 0x06, 0x04, 0x50, 0xF7, + 0xDF, 0x08, 0x00, 0x53, 0xE6, 0x96, 0x9B, 0x25, 0x1E, 0x87, 0x5E, 0xCE, 0xC3, 0x2C, 0xFC, + 0x59, 0x28, 0x40, 0xD6, 0x9A, 0xB6, 0x9A, 0x75, 0xE0, 0x6B, 0x37, 0x9C, 0x53, 0x5D, 0x95, + 0x26, 0x6B, 0x08, 0x2F, 0x4F, 0x09, 0xC9, 0x31, 0x62, 0xB3, 0x3B, 0x0D, 0x9F, 0x73, 0x07, + 0xA4, 0xEA, 0xAA, 0x52, 0x10, 0x44, 0x37, 0xFE, 0xD6, 0x6F, 0x8E, 0xE3, 0xEA, 0xBB, 0xD4, + 0x5D, 0x67, 0xB2, 0x5A, 0x81, 0x33, 0xF4, 0x96, 0x46, 0x8B, 0x52, 0xBA, 0xFF, 0xDB, 0xFA, + 0xD9, 0x3E, 0xEF, 0x1A, 0x98, 0x18, 0xB5, 0xE4, 0x2E, 0xC7, 0x22, 0x78, 0x8A, 0x3D, 0x8D, + 0x35, 0x29, 0xFC, 0x77, 0x7D, 0x2B, 0xA5, 0x70, 0x80, 0x1D, 0xFA, 0xE0, 0x1E, 0xC8, 0x83, + 0x02, 0x83, 0x7C, 0x1F, 0xB9, 0xE0, 0x35, 0x57, 0x27, 0x64, 0x5E, 0xE1, 0x04, 0x6C, 0x3F, + 0x91, 0x5F, 0x6A, 0xE8, 0x2D, 0xAD, 0x4F, 0xB6, 0xB0, 0x35, 0x6A, 0x46, 0x51, 0x8F, 0xFC, + 0x83, 0x41, 0x55, 0xC3, 0xB4, 0xFE, 0x6D, 0xAF, 0xA6, 0xCC, 0x8A, 0x5C, 0xCF, 0x53, 0xC7, + 0x3A, 0x08, 0x49, 0xD8, 0xD4, 0x4F, 0x7D, 0xCF, 0x72, 0x75, 0x4E, 0x70, 0xE1, 0xB7, 0xDF, + 0xB4, 0x47, 0xBB, 0x4E, 0xF4, 0x9D, 0x1A, 0x71, 0x8F, 0x61, 0x71, 0xBB, 0xCE, 0x20, 0x09, + 0x50, 0xE0, 0xCE, 0x92, 0x61, 0x06, 0xB1, 0x51, 0xA3, 0xE8, 0x71, 0xD5, 0xCE, 0x49, 0x73, + 0x1B, 0xD6, 0x65, 0x0A, 0x9B, 0x0C, 0xA9, 0x72, 0xDA, 0x1C, 0x5F, 0x13, 0x6D, 0x44, 0x82, + 0x0E, 0xA6, 0x38, 0x3C, 0x08, 0xF3, 0xB3, 0x84, 0xCF, 0x23, 0x38, 0xE7, 0x89, 0xC5, 0x13, + 0xF6, 0x18, 0xCC, 0x56, 0x94, 0xA6, 0xF0, 0xCE, 0xE1, 0x04, 0x51, 0x1E, 0x1E, 0xD7, 0xC5, + 0xF2, 0x3A, 0x1E, 0xBF, 0xD8, 0xA0, 0xDB, 0x84, 0x24, 0x55, 0x32, 0x40, 0x15, 0x6D, 0xBF, + 0x62, 0x28, 0x31, 0xB0, 0xC6, 0x43, 0xD1, 0xC5, 0x51, 0xB6, 0xF3, 0xF7, 0xA9, 0x8D, 0x29, + 0xB8, 0x5C, 0x2D, 0xE0, 0x5A, 0x65, 0xFA, 0x61, 0x5E, 0xEE, 0x16, 0x49, 0x5B, 0xD9, 0x07, + 0x37, 0x67, 0x21, 0x15, 0xB5, 0x3E, 0x91, 0xC5, 0xD9, 0x00, 0x28, 0xCF, 0x3F, 0x1A, 0x93, + 0x95, 0x3A, 0x15, 0x3D, 0xE5, 0x3B, 0x44, 0x08, 0x4E, 0x9C, 0xCF, 0xF6, 0xB7, 0x36, 0x69, + 0x39, 0x26, 0xDA, 0xEF, 0xEB, 0xB2, 0xD7, 0x7A, 0xA5, 0xAD, 0x68, 0x9B, 0x92, 0xF3, 0x16, + 0x86, 0x66, 0x9D, 0xF1, 0x6D, 0x17, 0x15, 0xCC, 0x58, 0xF7, 0xA2, 0xCF, 0xB7, 0x2D, 0xD1, + 0xA5, 0x1E, 0x92, 0xF8, 0x25, 0x99, 0x3A, 0x74, 0x02, 0x2B, 0xE7, 0xE9, 0xEB, 0x60, 0x54, + 0x65, 0x44, 0x57, 0x09, 0x4D, 0x14, 0x92, 0x8F, 0x20, 0x21, 0x5E, 0x7B, 0x22, 0x2A, 0xC5, + 0x6B, 0x51, 0xAD, 0xBE, 0xC8, 0xD8, 0xBD, 0xB6, 0x98, 0x39, 0x79, 0xA7, 0xE3, 0xA2, 0x1B, + 0x44, 0xB5, 0xD1, 0x51, 0x8C, 0xA9, 0x7D, 0x0B, 0x51, 0x95, 0xF5, 0x1E, 0xD6, 0xA2, 0x43, + 0x50, 0xC8, 0x97, 0x47, 0xE1, 0xED, 0xEA, 0x51, 0xB4, 0x48, 0xE3, 0xE9, 0x14, 0x70, 0x54, + 0xCE, 0x92, 0x78, 0x73, 0xC9, 0x0D, 0xB3, 0x94, 0xD8, 0x68, 0x88, 0xE0, 0x7D, 0xFF, 0x17, + 0x75, 0x93, 0xD6, 0xF7, 0x9E, 0x15, 0x23, 0x02, 0x20, 0x4A, 0xEB, 0x03, 0xBE, 0x23, 0x86, + 0xAF, 0x3E, 0x24, 0x07, 0x8B, 0xD0, 0x28, 0xB1, 0x68, 0x9F, 0x5E, 0x14, 0x7C, 0x9F, 0x45, + 0x2C, 0x8C, 0xEB, 0x02, 0xEC, 0x59, 0xCC, 0x9D, 0xB6, 0x3A, 0x03, 0x57, 0x6C, 0xEE, 0xAF, + 0xE9, 0x82, 0x39, 0x02, 0x38, 0x97, 0xDA, 0x02, 0x36, 0x63, 0x0A, 0x53, 0xC0, 0xDE, 0x7F, + 0x43, 0x5A, 0x19, 0x86, 0x97, 0x92, 0xFA, 0xB3, 0x6E, 0x7B, 0x9E, 0x63, 0x57, 0x60, 0xF0, + 0x90, 0x69, 0xE6, 0x43, 0x2E, 0x70, 0x00, 0x35, 0xAC, 0x2A, 0x02, 0x87, 0x9F, 0xFF, 0x0A, + 0x1E, 0x1B, 0xEC, 0x52, 0x20, 0x47, 0x19, 0x3D, 0x94, 0xEB, 0x5D, 0xF1, 0xEF, 0xD5, 0x3E, + 0xEA, 0x11, 0x44, 0xCA, 0x78, 0x94, 0x08, 0x52, 0xF5, 0xEC, 0x97, 0x27, 0x90, 0x4B, 0x36, + 0x6E, 0xDE, 0x4F, 0x5E, 0x2D, 0x33, 0x1F, 0xAD, 0x5F, 0xC2, 0x82, 0xEA, 0x2C, 0x47, 0xE9, + 0x23, 0x14, 0x27, 0x71, 0xC3, 0xDD, 0x75, 0xA8, 0x73, 0x57, 0x48, 0x7D, 0xEF, 0x99, 0xE5, + 0xF1, 0x8E, 0x9D, 0x9E, 0xD6, 0x23, 0xC1, 0x75, 0xD0, 0x28, 0x88, 0xC5, 0x1F, 0x82, 0xC0, + 0x7A, 0x80, 0xD5, 0x47, 0x16, 0xB3, 0xC3, 0xC2, 0xBD, 0xBE, 0x2E, 0x9F, 0x0A, 0x9B, 0xBA, + 0xAE, 0xBE, 0xB4, 0xD5, 0x29, 0x36, 0x87, 0x64, 0x06, 0xF5, 0xC0, 0x0E, 0x8E, 0x4B, 0xBD, + 0x0A, 0x5E, 0xC0, 0x57, 0x97, 0xE6, 0x20, 0x7C, 0x5A, 0xB6, 0xC8, 0x8F, 0x1A, 0x68, 0x84, + 0x21, 0xBD, 0x05, 0xA1, 0x14, 0xF4, 0xD7, 0xDE, 0x2A, 0xC2, 0x41, 0xFA, 0x0E, 0x8B, 0xED, + 0xFF, 0x47, 0xF7, 0x62, 0xDD, 0xCB, 0xEA, 0xA9, 0x10, 0x04, 0xF8, 0xD3, 0x1E, 0x85, 0x09, + 0x5C, 0x81, 0x05, 0x49, 0x94, 0xAD, 0x38, 0x26, 0xE3, 0x44, 0xBA, 0x96, 0x04, 0x08, 0x10, + 0xFC, 0x0B, 0x2A, 0xD1, 0xDE, 0x48, 0xCF, 0xAD, 0xE0, 0x02, 0xC6, 0x2E, 0x5A, 0x49, 0xA0, + 0x73, 0x1A, 0xB3, 0x83, 0x44, 0xBC, 0x16, 0x36, 0xDF, 0x16, 0xBF, 0x60, 0x7D, 0x56, 0x85, + 0x5E, 0x56, 0xD6, 0x84, 0x00, 0x3C, 0x71, 0x8E, 0x4B, 0xAD, 0x9E, 0x5A, 0x09, 0x99, 0x79, + 0xFC, 0xDD, 0xEE, 0xB1, 0xC4, 0xA7, 0x77, 0x6C, 0xD3, 0x7A, 0x34, 0x17, 0xCB, 0x0E, 0x18, + 0x4E, 0x29, 0xEF, 0x9B, 0xC0, 0xE8, 0x74, 0x75, 0xBA, 0x66, 0x3B, 0xE0, 0x9E, 0x00, 0xAB, + 0x56, 0x2E, 0xB7, 0xC0, 0xF7, 0x16, 0x5F, 0x96, 0x9A, 0x9B, 0x42, 0x41, 0x41, 0x98, 0xCC, + 0xF1, 0xBF, 0xF2, 0xA2, 0xC8, 0xD6, 0x89, 0xA4, 0x14, 0xEC, 0xE7, 0x66, 0x29, 0x27, 0x66, + 0x56, 0x89, 0xE9, 0x4D, 0xB9, 0x61, 0xEB, 0xAE, 0xC5, 0x61, 0x5C, 0xBC, 0x1A, 0x78, 0x95, + 0xC6, 0x85, 0x1A, 0xC9, 0x61, 0x43, 0x2F, 0xF1, 0x11, 0x8D, 0x46, 0x07, 0xD3, 0x2E, 0xF9, + 0xDC, 0x73, 0x2D, 0x51, 0x33, 0x3B, 0xE4, 0xB4, 0xD0, 0xE3, 0x0D, 0xDE, 0xA7, 0x84, 0xEC, + 0xA8, 0xBE, 0x47, 0xE7, 0x41, 0xBE, 0x9C, 0x19, 0x63, 0x1D, 0xC4, 0x70, 0xA5, 0x2E, 0xF4, + 0xDC, 0x13, 0xA4, 0xF3, 0x63, 0x3F, 0xD4, 0x34, 0xD7, 0x87, 0xC1, 0x70, 0x97, 0x7B, 0x41, + 0x7D, 0xF5, 0x98, 0xE1, 0xD0, 0xDD, 0xE5, 0x06, 0xBB, 0x71, 0xD6, 0xF0, 0xBC, 0x17, 0xEC, + 0x70, 0xE3, 0xB0, 0x3C, 0xDC, 0x19, 0x65, 0xCB, 0x36, 0x99, 0x3F, 0x63, 0x3B, 0x04, 0x72, + 0xE5, 0x0D, 0x09, 0x23, 0xAC, 0x6C, 0x66, 0xFD, 0xF1, 0xD3, 0xE6, 0x45, 0x9C, 0xC1, 0x21, + 0xF0, 0xF5, 0xF9, 0x4D, 0x09, 0xE9, 0xDB, 0xCF, 0x5D, 0x69, 0x0E, 0x23, 0x23, 0x38, 0x38, + 0xA0, 0xBA, 0xCB, 0x7C, 0x63, 0x8D, 0x1B, 0x26, 0x50, 0xA4, 0x30, 0x8C, 0xD1, 0x71, 0xB6, + 0x85, 0x51, 0x26, 0xD1, 0xDA, 0x67, 0x2A, 0x6E, 0xD8, 0x5A, 0x8D, 0x78, 0xC2, 0x86, 0xFB, + 0x56, 0xF4, 0xAB, 0x3D, 0x21, 0x49, 0x75, 0x28, 0x04, 0x5C, 0x63, 0x26, 0x2C, 0x8A, 0x42, + 0xAF, 0x2F, 0x98, 0x02, 0xC5, 0x3B, 0x7B, 0xB8, 0xBE, 0x28, 0xE7, 0x8F, 0xE0, 0xB5, 0xCE, + 0x45, 0xFB, 0xB7, 0xA1, 0xAF, 0x1A, 0x3B, 0x28, 0xA8, 0xD9, 0x4B, 0x78, 0x90, 0xE3, 0xC8, + 0x82, 0xE3, 0x9B, 0xC9, 0x8E, 0x9F, 0x0A, 0xD7, 0x60, 0x25, 0xBF, 0x0D, 0xD2, 0xF0, 0x02, + 0x98, 0xE7, 0x14, 0x1A, 0x22, 0x6B, 0x3D, 0x7C, 0xEE, 0x41, 0x4F, 0x60, 0x4D, 0x1E, 0x0B, + 0xA5, 0x4D, 0x11, 0xD5, 0xFE, 0x58, 0xBC, 0xCE, 0xA6, 0xAD, 0x77, 0xAD, 0x2E, 0x8C, 0x1C, + 0xAA, 0xCF, 0x32, 0x45, 0x90, 0x14, 0xB7, 0xB9, 0x10, 0x01, 0xB1, 0xEF, 0xA8, 0xAD, 0x17, + 0x2A, 0x52, 0x3F, 0xB8, 0xE3, 0x65, 0xB5, 0x77, 0x12, 0x1B, 0xF9, 0xFD, 0x88, 0xA2, 0xC6, + 0x0C, 0x21, 0xE8, 0x21, 0xD7, 0xB6, 0xAC, 0xB4, 0x7A, 0x5A, 0x99, 0x5E, 0x40, 0xCA, 0xCE, + 0xD5, 0xC2, 0x23, 0xB8, 0xFE, 0x6D, 0xE5, 0xE1, 0x8E, 0x9D, 0x2E, 0x58, 0x93, 0xAE, 0xFE, + 0xBB, 0x7A, 0xAE, 0x7F, 0xF1, 0xA1, 0x46, 0x26, 0x0E, 0x2F, 0x11, 0x0E, 0x93, 0x95, 0x28, + 0x21, 0x3A, 0x00, 0x25, 0xA3, 0x8E, 0xC7, 0x9A, 0xAB, 0xC8, 0x61, 0xB2, 0x5E, 0xBC, 0x50, + 0x9A, 0x46, 0x74, 0xC1, 0x32, 0xAA, 0xAC, 0xB7, 0xE0, 0x14, 0x6F, 0x14, 0xEF, 0xD1, 0x1C, + 0xFC, 0xAF, 0x4C, 0xAA, 0x4F, 0x77, 0x5A, 0x71, 0x6C, 0xE3, 0x25, 0xE0, 0xA4, 0x35, 0xA4, + 0xD3, 0x49, 0xD7, 0x20, 0xBC, 0xF1, 0x37, 0x45, 0x0A, 0xFC, 0x45, 0x04, 0x6F, 0xC1, 0xA1, + 0xF8, 0x3A, 0x9D, 0x32, 0x97, 0x77, 0xA7, 0x08, 0x4E, 0x4A, 0xAD, 0xAE, 0x71, 0x22, 0xCE, + 0x97, 0x00, 0x59, 0x30, 0x52, 0x8E, 0xB3, 0xC7, 0xF7, 0xF1, 0x12, 0x9B, 0x37, 0x28, 0x87, + 0xA3, 0x71, 0x15, 0x5A, 0x3B, 0xA2, 0x01, 0xA2, 0x5C, 0xBF, 0x1D, 0xCB, 0x64, 0xE7, 0xCD, + 0xEE, 0x09, 0x2C, 0x31, 0x41, 0xFB, 0x55, 0x50, 0xFE, 0x3D, 0x0D, 0xD8, 0x2E, 0x87, 0x0E, + 0x57, 0x8B, 0x2B, 0x46, 0x50, 0x08, 0x18, 0x11, 0x3B, 0x8F, 0x65, 0x69, 0x77, 0x3C, 0x67, + 0x73, 0x85, 0xB6, 0x9A, 0x42, 0xB7, 0x7D, 0xCB, 0xA7, 0xAC, 0xFF, 0xD9, 0x5F, 0xD4, 0x45, + 0x2E, 0x23, 0xAA, 0xA1, 0xD3, 0x7E, 0x1D, 0xA2, 0x15, 0x1E, 0xA6, 0x58, 0xD4, 0x0A, 0x35, + 0x96, 0xB2, 0x7A, 0xC9, 0xF8, 0x12, 0x9D, 0xC6, 0xCF, 0x06, 0x43, 0x77, 0x26, 0x24, 0xB5, + 0x9F, 0x4F, 0x46, 0x12, 0x30, 0xDF, 0x47, 0x1C, 0xA2, 0x60, 0x87, 0xC3, 0x94, 0x2D, 0x5C, + 0x66, 0x87, 0xDF, 0x60, 0x82, 0x83, 0x59, 0x35, 0xA3, 0xF8, 0x7C, 0xB7, 0x62, 0xB0, 0xC3, + 0xB1, 0xD0, 0xDD, 0xA4, 0xA6, 0x53, 0x39, 0x65, 0xBE, 0xF1, 0xB7, 0xB8, 0x29, 0x2E, 0x25, + 0x4C, 0x01, 0x4D, 0x09, 0x0F, 0xED, 0x85, 0x7C, 0x44, 0xC1, 0x83, 0x9C, 0x69, 0x4C, 0x0A, + 0x64, 0xE3, 0xFA, 0xD9, 0x0A, 0x11, 0xF5, 0x34, 0x72, 0x2B, 0x6E, 0xE1, 0x57, 0x4F, 0x2E, + 0x14, 0x9D, 0x55, 0xD7, 0x44, 0xDE, 0x48, 0x87, 0x02, 0x4E, 0x08, 0x51, 0x14, 0x31, 0xC0, + 0x62, 0x75, 0x0E, 0x16, 0xC7, 0x4A, 0xB9, 0xF3, 0x24, 0x2F, 0x2D, 0xB3, 0xFF, 0xB1, 0x2A, + 0x8D, 0x61, 0x07, 0xFA, 0xA2, 0x29, 0xD6, 0xF6, 0x37, 0x3B, 0x07, 0xF3, 0x6D, 0x39, 0x32, + 0xB3, 0xBD, 0xB0, 0x4C, 0x19, 0xDD, 0x64, 0xEA, 0xDD, 0x7F, 0x93, 0xC3, 0xC5, 0x64, 0xC3, + 0x58, 0xA1, 0xC8, 0x1D, 0xCF, 0x1C, 0x9C, 0x31, 0xE5, 0xB0, 0x65, 0x68, 0xF9, 0x75, 0x44, + 0xC1, 0x7D, 0xC1, 0x56, 0x98, 0xC5, 0xCB, 0x38, 0x98, 0x3A, 0x9A, 0xFC, 0x42, 0x78, 0x3F, + 0xAA, 0x77, 0x3A, 0x52, 0xC9, 0xD8, 0x26, 0x06, 0x90, 0xBE, 0x9E, 0x31, 0x56, 0xAA, 0x5B, + 0xC1, 0x50, 0x9D, 0xEA, 0x3F, 0x69, 0x58, 0x76, 0x95, 0xCD, 0x6F, 0xF1, 0x72, 0xBA, 0x83, + 0xE6, 0xA6, 0xD8, 0xA7, 0xD6, 0xBB, 0xEB, 0xBB, 0xCD, 0xA3, 0x67, 0x27, 0x31, 0x98, 0x3F, + 0x89, 0xBC, 0x58, 0x31, 0xDC, 0x37, 0xC3, 0xF3, 0xC5, 0xC5, 0x6F, 0xAC, 0xC6, 0x97, 0xF3, + 0xCB, 0x20, 0xBD, 0x5D, 0xBA, 0xDB, 0xD7, 0x02, 0xE5, 0x48, 0x44, 0xAC, 0x2F, 0x62, 0x69, + 0x01, 0xFE, 0x15, 0x9D, 0xB9, 0x3D, 0xFD, 0x47, 0x73, 0xD8, 0xFE, 0x73, 0x56, 0x2B, 0x84, + 0x6C, 0x1F, 0xC8, 0x56, 0xD1, 0x80, 0x27, 0x62, 0x84, 0x0E, 0xBC, 0x72, 0xD7, 0x98, 0x8B, + 0xDE, 0x75, 0xCB, 0xCA, 0x70, 0xD3, 0x19, 0xD3, 0x2C, 0xE0, 0xCC, 0x02, 0x53, 0xBB, 0x2A, + 0xD4, 0x55, 0x72, 0x3E, 0xE0, 0xC7, 0xF4, 0x73, 0x6C, 0xE6, 0xE6, 0x66, 0x5C, 0x5A, 0xCA, + 0x32, 0xA4, 0x81, 0xC5, 0x38, 0x39, 0xBC, 0x25, 0x91, 0x67, 0xB0, 0x13, 0xD0, 0x42, 0x33, + 0x95, 0xEE, 0xB9, 0xAA, 0xAE, 0xE3, 0x20, 0x61, 0x49, 0xA7, 0xD5, 0x50, 0xD6, 0x7F, 0xC5, + 0xFD, 0xFE, 0x4A, 0x8A, 0x5C, 0x35, 0xD2, 0x51, 0x0B, 0x66, 0x43, 0x79, 0xAB, 0x8F, 0x72, + 0x85, 0x5A, 0x2A, 0xF4, 0x7A, 0xBC, 0xE2, 0xA6, 0x32, 0x04, 0x8E, 0xAF, 0x89, 0xE5, 0xCB, + 0x4A, 0x88, 0xDE, 0xBC, 0x53, 0xA5, 0x95, 0x10, 0x3A, 0xCC, 0xE4, 0xF1, 0xCF, 0xF1, 0x8A, + 0xCF, 0xF0, 0x7A, 0xFE, 0x1E, 0xB5, 0x71, 0x6A, 0xA1, 0xE4, 0x0B, 0x63, 0x13, 0x4C, 0x3A, + 0x3A, 0xE9, 0x57, 0x9F, 0xA8, 0x7F, 0x51, 0x5B, 0xE0, 0x93, 0xC2, 0xD2, 0x9D, 0xB6, 0xD6, + 0xB6, 0x5C, 0x93, 0x66, 0x1E, 0x00, 0x63, 0x6B, 0x59, 0x27, 0x04, 0xD0, 0x93, 0xCC, 0x67, + 0x16, 0xC2, 0x34, 0x2E, 0xB1, 0x85, 0x3D, 0x48, 0xC8, 0x5C, 0x63, 0xAC, 0x8A, 0x28, 0x54, + 0x46, 0x2C, 0x7B, 0x77, 0xE7, 0xE3, 0xBD, 0x1E, 0xAC, 0x5B, 0xCA, 0x28, 0xFF, 0xAA, 0x00, + 0xB5, 0xD3, 0x49, 0xF8, 0xA5, 0x47, 0xAD, 0x87, 0x5B, 0x96, 0xA8, 0xC2, 0xB2, 0x91, 0x0C, + 0x93, 0x01, 0x30, 0x9A, 0x3F, 0x91, 0x38, 0xA5, 0x69, 0x31, 0x11, 0xF5, 0x5B, 0x3C, 0x00, + 0x9C, 0xA9, 0x47, 0xC3, 0x9D, 0xFC, 0x82, 0xD9, 0x8E, 0xB1, 0xCA, 0xA4, 0xA9, 0xCB, 0xE8, + 0x85, 0xF7, 0x86, 0xFA, 0x86, 0xE5, 0x5B, 0xE0, 0x62, 0x22, 0x2F, 0x8B, 0xA9, 0x0A, 0x97, + 0x40, 0x73, 0x32, 0x6B, 0x31, 0x21, 0x2A, 0xEC, 0xE0, 0xA3, 0x4A, 0x60, + ]) + .unwrap(); let sig = &*hex::decode("781368e64dba542a7eacbd2257335cc943a03241009b797093c615f76a671a7591430441d80bb582304b33b9fce295e0dd57fe169355ddf4453a2aca62d8eb8109ef0d9cf3f5b0a94e04ad81b3e786014243ecde816551aa7fe01c639054256a491756bef59f5034f717ff4f85e70ba7731a49971415b6a7e7d816ab434b9f17a3095ede6fd432be2bfa82724045dda0dfff7a0281e9000939ccba3d8ab3245139c441648c76a6536127e4d1ef0df1531883ab78c8b41323617ad8db03d9908c9e08a9f7321c45051b3c94213347b11c4a84491de7a7be68701e47d7f0e0b33e767bef17694e4d33244ed92ebd74c85ab6c84441cddc14331e6ae8bd23674bda27f09c050d88f7d430feee7f15a72a24d653bb6bec54491b98362ce131d37c7d78a3f9a893db5abdccd6663593b88bc6c97f07f8eafccfd25e8180d918efbcd95bbf3da29f081e3e1932095939198e2a155b2d803a3e84ca4f34569df695c259faf3c0d8f0cd217ebd2dbad542b32fbb54e44aaf0b5dc739fafef2e46db8d68bfc35f44f038cb1f5231a1b5b134ae683e7f3297cc7a95bd191b310f68201450797fe3293cde1672dfeca4b493f53c768ea048a972a4cd84d39ef682957b8f28ba29487b4689b43fec2655823d9bb99ffcf31490366a9860a5d5b8e32a3b8bfeb6f55f88fb80c8c0142086f220e1f6f2862dabda58c3b6f5faa805b39cfac4b6d7ea7acdf1b0690063b0c1ea38c7c4755189966dc631055f153f71b77b114fa5c309316ba512330ea5cdb0bb176001e57461563d17259f35d0c30ef5ac838c0325402bab52c531469526ae3ee6293f7b5769d27e69fa81cd25a31cd095b126e70c57ac3169a5f585a11f1748d9d22f2564911c26a24b2153a78f3a06822f5f1963f237abeb48efd9a9cd478de579c5a0ba84d00e96fbde36d8ce20e7e948547fb6850834ff79d211830f6ee973359781d9d5008fb43a89354782fde4158177f5206ce1d38c889e99e4bb5b4ab34d6a05c42f5d719ea03dbc54adba75a3bb44a3c08c7556462f8c5b7c568a69242cf5be6098eba0a2249c1ca5b2109b6404a962abc1c159c6b48a79fb97e4a3337d99323746221297423f9bd1b12e78489e01e6a10f0fd6bba1cfd6ae1b75dbe69f8b8ee51a4e7f68ba2c407c9c0bad3892b29b0170ab75836fdd49a7ee3c2bb30f2c3d226bcec49140952170b0d160f97b30b7b7b096719538677ebb06922f26925227c8852acc107a8f173b38d96697584bd3dfe169b4073aa58a7bf371d5c4bb0eed30f08212defb3aec902d4546084176bf0f86d93cf36a4689a5e874b32d6b7d3c1e3fbcfd988c35dbc9a8d0a019ad6d7e15ec3ac97125db6abfe00beffe35a81666699a91e15945c62d646690b5b52de8b835ee9be53588fde5d63023b52b2b1f4610c237a829f5901a46042963cce7b85aa040adde02985e14e23c4eadb75221c607d24672e244c66c9c24c3cb7fd90bb23295c9d3d9da516bce3dd462d6660f9f91ef0618a4d4d3d6668c5d1e2e8ed433ebfe0762beb743324e11608f8b14b69ce4c221c1654ba4992a5af2d949c2939f95d1c8fb767af2a843cc7c78f57259d5c0c6ca83fca41ef5ecc4eeeea93e4518c24d3040f2cd90df3e535e989e606fa109e2c453ed7353db1cdb27137f005f9dd8d2aebfb7255a6098b690215e100cfe44ca0f2745fced48322bc9667ab16d2e1c0ff491b96a17b833d4fd44d31c2230ac835796e063ab03000f04f15c70560033763a48552cceaacade9ca5c8055f3745e179068a287183f2bc3ef6327dec5ac7cf7b052ef5a8873e697efde089688f43be464827c2fa83eb531b3674145e95c699c82990e684967dad319d9f64ab16cd9fe9b6c41232ac4ae3795fd8a76aa9b02e970242061c6da45a2af74ad9cb2a79935c92625e242f4bc7fce54d5c10a9e61f875162fa651b66057ba036f062d6d39d0502b93a5640b78c6c2fd20b02ff83676a87a94945d476c349803ea4fe60cdcea65bb2629e2bc09d4472ec63422dee2052f098deaf5531e6c9bed6672a8b699802efe0cded80c8455f585d1ba633d281f1a21adab48e63b44e0c2a4d7608cf98aabf8adc86bcb8f61e8b06cd2385f82e0a3cdd03cab152d5951859c4532f9168e78f17ba2a5772780327dcf4e62b4d26e443762fc488ae4cd4d1156dbd5782595cfd7697a514abc9b160c9ccf08edc86134a755b90e9bb543511e888e3157721a52d1bc5db33029fb335ea2114e21c03368c8d7f4d827960641772a4a32a738df60d19ec77ab09d22f57cc2523b9503b3f5b1cebc5ae15f885f159842db7359a1c89d3d82d3407068f15b6739626eb8c521fc8c5c7491f945d49f14e6989da340bdf49e7f8a792747aa658bc114143ba93f26022d001735b744639bbf22aab2a1851cfc934f9c69d3764fdea3d23db17998e6138cfd7cd9e9a47cb74193bd71aaf28cdd9d1eb595125546a4f4357ebbd1f410e3bf8557892de68509b5b98c5c229e942c910fdd3e54cb6ad54d8dd886cb97ecc06d1e401b8395d0bcb0db9a031dc66c9294f9053c68fc42042b1fa1671fc7d510b70916c0139cfebe3a91244527ce9439860cedb30908197be851cbd1d3b18ca541358449fb34fb5cd569630ed5f67b8795e87828f2ce3becfe457579d82333b0bbab094de391e1f8157bd431e365ca864630932bbebb48f45f8134424e18ab455029b54b19e2f3bfec5e44ad0ea5c03f53d8f925b635838aa7015a7c9e325bdfaff966ea9512dd50f87c8995cea7561c23f4fb06d964ab8f1913a6ca17e4ca60d6bc078e1784f89c673c91d955bcf45f58ca9709579d5e3831df12cfdb7516fd21878cb54243579b9346d2de4be25f508e84b1adc78cb91c03da3c4fd59e4529189838f74f6312820620a5996b791ffcb332f847094613f2148b862034fa89d0d0ff1808d902c5d1af64d5522492d61ecde4c73be89a33782cef1acc1dc327fb2eb9d17642209b85aa8b1dc57cbf067c7aa29da6b7e157d23e171d3ae6f3855834071791402c851ff2dd67109979f7ee5e09e64b4eefdee7112b55ce200bb8c8051e3428c305fa1d576bffbb25a70eb571168fc60dadd928b10cfd07de80a85b8df3edc372d488c21f0d5787611cc6fb73aeeb6f920a109294b49d3870f90de3b360d14df77ef95640bcac7a4dbca901a31db83e83f5c59ce327207ea9b27c3b978d30d53865c1b84764f025e8732d5007554ce5c9cc410b2eefd7e4d990c538557606a6bc47577a43768d30aa3e8598fd6f4fe7ac439f3931c58fd69d90765ac9f456ac7de085e14a0898c4557f5d3baaae07edc607de6900146b97b35aae570153dc107815ef9febdd4fd567d637fee8f8bfb4b3413ea6aead4846ab733a04f1e4bc32a3bbb1c16baf8d0bdb9ccb82fe46479ccfd040b5e64064e539b39c66e4501dc822873ac6119a4a112a1f7cd6df0e5f84356ce853ced34ce69a9e7383534983c51c50269bf8b9586a0e5ba905fd3bce080b00e7f7d48e55f489479b5771e995fb020e58feb74af65c3ee76aa4e69b5ba8bda249a1b2d62c08d418c3635d061846040843991ab475473da85d94981fb84425e7ad951ce0a42be642fe658b7aaae72b147cdc086c24b1571eb2272e2a72b15660d854ebe19ec7d9ab7ea17800d0b6ae727b39217467c662ba08e6f19193951eeff02806a7843eb5c71b2f04dcb605ecedc5128cd67703038c44bf20fe06f3ed8c1368fe38e72944d5c52fca46a45fd48d8fe5da64183d4d62ec01aa3d9d672ec67a01c17f21e02525f0513cce030c664fc8784763086608bc8099c204c255ffed1daf432ceb45fbd135e21e8190c5bfee192171faa77520481e69e87b7f76790bef76cb8d3c88f5c6e32fc59e7bd45351d66696b61d9f40726fb9a98000b68738cf7e34b98b6a4aaa2ac1d7b1407db89783f8077103ea9c9e89247eae078adfb36e21474c3bb1fe0c87687c6233a533a01e1081b93a3521d339f39c075609bace531994988ae314f77fc6034113a138c67eb7e03750cbec8d28bd21afedfefa8f091619ae500b4ca4599d019dc8ca4bf118d70b8676dfc796a4f6d986adba4c8574ed4abaea5465466220e5e53dc8fcb395d1e59d278673cbc4e3f40658df98ac2fd126a94922879e1a3be91c1acc20803c35fa764abbadab07bde85ff4bd9e0fb6f06baf5bf42b8a2cbf6c2f62606becc361552921a12d6c8236fde84db4bddac77e8872478cffc4e148c1c7acfedf6b17d98731c2de36f3cbef1f6f781a940e0874d5b74535bbe066b53064d43b13926570a9e1c4e6da206c8bd252caf2b62e7d223f7ac12939137f330be59374d7295a6c2dff92e07c727510e48d970593e47229fc8bc3bd5b8ea780dacff4d23063df65feda5f8f65b17a333e532acad7916780c74d6a70d38b367f3f6f4e947b85fc15235bbe46b26495d2780098db853a931377cfbedea620f2355ca21e81ce9e0078b0dd6cb70f23ed558682be3b3d594eefe85344e1f275428b316cc088995939298f2a2d15ac9b676ac3e9cb92f2a64dec7732a91fc761aa1b126ea575e3953177da6e1cd78faea824665330a81d9e24572b9860bf0aba4df8bd5d4e3e2c72bbfbb2a985c7ae2f077951fe8401e1d156ecada1e353817b20f41e0b2460a0caaf2b36d6a7f1b35125d797dcc714421027d14171765a646071ed952b6a5294eecf6a3a71c104c843a4a8b3efcc27467b20cb0a94abf5802229ea4d8312783e78791a50b3c0a88fe6497198cd4bf470dac46f34e50019fdee2040cfe99124b312b1122b83e51d878877cec0855f1158c445cfdc2253f4389d5e3a8ba1669abb5976a4617e85f543da9f5e30b10ca7481c8185392782b46fb0a0e5ab408b2945e3c79a1cc49fb7c27254a9b540e7397a5b655bf7e4f83184db32a128aa2e00a624d7dcd6b77efd151f1e5cba8890af9170fa06c555715dc1787e995ad19270973ae95b88dcafdaf62c28843d3f8b9c78dc8e37d911dab3f7ee9d4c7389c654bdcfc05056b360020140e57e31473258a4081e2a708f7caba90c356d0847098fc0762484086aba898a60b023d6a3060402406240748785d51caff52a0ef3dc2a45dadf80ac18502d24422a8cbb10192b88f4e9160206b2ed3e04114f2a339df269e2c36b8613ce37087471701755330cb559575366ee0fa2d3afcda32eede6dd906345daaa04812198e96c42239c242edb90059709f497da5b87705384aef2af22dc2edfef3c00d8c9156d8b3163fe7a7779e04f04911a8b934fb3072eee844484fede5e2ee96d338eefe2da986067ffe0218ada7de1d0e42d823d6b033918278888ba0608ab8f7be997bdf263689a36f5204c802ad836363779b4b0d6ce5083df0b98a2e2c700062a4fa5e57bc73bd45357e01d90c7954bc6904d1ce8166a9168da39a60c5cae8119bb6b9ab074fb2d0aee384fc2c0e4806811d6002b4e2401e7430b50cb0e8075f33d5386aecde256e169d95e2f9c6556c08ba042e68a53ce8aca9cc02818f7382f150dc04de0019b19c7a3ab0d72d6ed013d7a115d74b279f71fd6effef34049877e0b11e0659be938a5de684eaf23513095eb4a1bdf536c3c01a4655c4b4a0673214cef29a481d06a02cc9a5bfc7b8d846c33484cd67b1de98f60b69918f177b64558ca567a6237d35ff01771a42320ce02bb98f3e4ad4ac7db75611bd9961eac662a38b1f785970c99f3dd105ff586f61301c48d66708cbf7d53a733e357b6c256e8b73f0e1305a0bc137989e521100c2ee6259e607fe12198e8bcb988b0854668e40d7cae6adc3ba40ba121b7319d06d988a073d03097b9f5c1c07284b6473ae57bf154811b77baceb0412b8a6983bdd0ccd9e3bf014e520009cb26d5780eabef1bbafa5e25d41098a54c47fce8b68d395291d54284d33aa50b9664d1510b467c8a539361ca9a4448bc01fcb4c4e3ef475e8afb46a494ae13ee9ea8a1266825fba7f32b9712fde252698a68359b50141d90f5c4a06283ddb54ad7e1412ac5ebb12501f7a82b2a7f27b2dbab626c3db4074523b3211d3182ea261397a6f7b187cf2b8a356ded10812f1d305169aedf79b5ff1cf7c2d6e86ee11f28e96aa63b5a03f59fc960ac7d0572e91dda61905c0711a9b26344a2a10aa2041f2b13cb1a9a9a27774b6d0deddc9d81ea1b142ad7b72be47991f2c9261d6708156e38d00b074020766eb0c494392d65b82ca65f7c3352f9bb78325ecb6df596c8ae57826b08ccd6f1d529d2e25925c1ac972425bedeb88a5d0e3138ddc434da3462ebdf6b1239a21f141ec62cbe4bb993ba253b55a76d30fac19c2c1384ef6b9746c07787aa1fe913a1348390bd8c1f386a08c77cf7106c927ce24dffc9d6ee1b32354d95ed2923482531de6b390bf0f5eb80276e90e7ed11131c848bbabec4d317236269a0a3a7cbe0f1272f93949ca6d23ea2a7ee3f697791aab71533423066d400000000000000000000000000000000000000000000000000000000050e181f23292c2f").unwrap(); assert_eq!(sig.len(), MLDSA87_SIG_LEN); @@ -462,8 +2129,8 @@ fn bench_mldsa87_verify() { } fn bench_mldsa87_lowmemory_verify() { - use bouncycastle::mldsa_lowmemory::{MLDSATrait, MLDSA87, MLDSA87_SIG_LEN, MLDSA87PublicKey}; use bouncycastle::hex; + use bouncycastle::mldsa_lowmemory::{MLDSA87, MLDSA87_SIG_LEN, MLDSA87PublicKey, MLDSATrait}; eprintln!("MLDSA87/Verify"); @@ -494,8 +2161,6 @@ fn bench_mldsa87_lowmemory_verify() { } } - - fn main() { // print_struct_sizes() // bench_do_nothing() @@ -510,11 +2175,11 @@ fn main() { // bench_mldsa65_sign() // bench_mldsa65_lowmemory_sign() // bench_mldsa87_sign() - // bench_mldsa87_lowmemory_sign() + bench_mldsa87_lowmemory_sign() // bench_mldsa44_verify() // bench_mldsa44_lowmemory_verify() // bench_mldsa65_verify() // bench_mldsa65_lowmemory_verify() // bench_mldsa87_verify() - bench_mldsa87_lowmemory_verify() -} \ No newline at end of file + // bench_mldsa87_lowmemory_verify() +} diff --git a/src/bench_mldsa_mem_usage.rs b/src/bench_mldsa_mem_usage.rs new file mode 100644 index 0000000..55bdc24 --- /dev/null +++ b/src/bench_mldsa_mem_usage.rs @@ -0,0 +1,469 @@ +//! The purpose of this binary is to perform a single run of the primitive under test so that +//! its peak memory usage can be measured with: +//! +//! > valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_mldsa_mem_usage > /dev/null +//! +//! > ms_print massif.out.835000 +//! +//! or, shoved all into one line: +//! +//! > clear; clear; valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_mldsa_mem_usage > /dev/null; ms_print massif.out.*; rm massif.out.* +//! +//! Make sure you build in release mode! +//! +//! Note: I'm using print!() to force the compiler not to optimize away the actual code. +//! I'm printing the important stuff for benchmarking to stderr so that I can pipe the junk to /dev/null +//! (I'm not doing it the other way because /usr/bin/time prints its useful stuff to stderr as well) +//! +//! Main is at the bottom, controls which this was actually run. + +#![allow(dead_code)] +#![allow(unused_imports)] + +use bouncycastle_core_interface::key_material::{KeyMaterial256, KeyType}; +use bouncycastle_core_interface::traits::{Signature, SignaturePublicKey}; +use bouncycastle_hex as hex; +use bouncycastle_mldsa::MLDSA44PublicKey; + +/// This exists so I can use /usr/bin/time to measure the base memory footprint of the cargo bench harness +fn bench_do_nothing() { + eprintln!("DoNothing"); + + print!("{}", 1 + 1); +} + +fn bench_mldsa44_keygen() { + use bouncycastle_mldsa::{MLDSATrait, MLDSA44}; + + eprintln!("MLDSA44/KeyGen"); + + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + KeyType::Seed, + ).unwrap(); + + let (pk, _sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); + println!("{:x?}", pk.encode()); +} + +fn bench_mldsa44_lowmem_keygen() { + use bouncycastle_mldsa_lowmemory::{MLDSATrait, MLDSA44}; + + eprintln!("MLDSA44_lowmemory/KeyGen"); + + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + KeyType::Seed, + ).unwrap(); + + let (pk, _sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); + println!("{:x?}", pk.encode()); +} + +fn bench_mldsa65_keygen() { + use bouncycastle_mldsa::{MLDSATrait, MLDSA65}; + + eprintln!("MLDSA65/KeyGen"); + + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + KeyType::Seed, + ).unwrap(); + + let (pk, _sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); + println!("{:x?}", pk.encode()); +} + +fn bench_mldsa65_lowmemory_keygen() { + use bouncycastle_mldsa_lowmemory::{MLDSATrait, MLDSA65}; + + eprintln!("MLDSA65_lowmemory/KeyGen"); + + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + KeyType::Seed, + ).unwrap(); + + let (pk, _sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); + println!("{:x?}", pk.encode()); +} + +fn bench_mldsa87_keygen() { + use bouncycastle_mldsa::{MLDSATrait, MLDSA87}; + + eprintln!("MLDSA87/KeyGen"); + + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + KeyType::Seed, + ).unwrap(); + + let (pk, _sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); + println!("{:x?}", pk.encode()); +} + +fn bench_mldsa87_lowmemory_keygen() { + use bouncycastle_mldsa_lowmemory::{MLDSATrait, MLDSA87}; + + eprintln!("MLDSA87_lowmemory/KeyGen"); + + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + KeyType::Seed, + ).unwrap(); + + let (pk, _sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); + println!("{:x?}", pk.encode()); +} + +fn bench_mldsa44_sign() { + use bouncycastle_mldsa::{MLDSATrait, MLDSA44}; + + eprintln!("MLDSA44/Sign"); + + // set up the seeds outside of the timing loop + // Doing different seeds so that the CPU doesn't cache them or do too much branch prediction + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + KeyType::Seed, + ).unwrap(); + + let msg = b"The quick brown fox jumped over the lazy dog"; + + /*** ML-DSA-44 ***/ + // since the goal here is to measure peak memory usage; we're here making an assumption that + // mem usage of .sign will be higher than .keygen + let (_mldsa44_pk, mldsa44_sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); + + let mu = MLDSA44::compute_mu_from_sk(&mldsa44_sk, msg, None).unwrap(); + let sig = MLDSA44::sign_mu_deterministic(&mldsa44_sk, &mu, [0u8; 32]).unwrap(); + print!("{:x?}", sig); +} + +fn bench_mldsa44_lowmemory_sign() { + use bouncycastle_mldsa_lowmemory::{MLDSATrait, MLDSA44}; + + eprintln!("MLDSA44_lowmemory/Sign"); + + // set up the seeds outside of the timing loop + // Doing different seeds so that the CPU doesn't cache them or do too much branch prediction + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + KeyType::Seed, + ).unwrap(); + + let msg = b"The quick brown fox jumped over the lazy dog"; + + /*** ML-DSA-44 ***/ + let (_mldsa44_pk, mldsa44_sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); + + let mu = MLDSA44::compute_mu_from_sk(&mldsa44_sk, msg, None).unwrap(); + let sig = MLDSA44::sign_mu_deterministic(&mldsa44_sk, &mu, [0u8; 32]).unwrap(); + print!("{:x?}", sig); +} + +fn bench_mldsa65_sign() { + use bouncycastle_mldsa::{MLDSATrait, MLDSA65}; + + eprintln!("MLDSA65/Sign"); + + // set up the seeds outside of the timing loop + // Doing different seeds so that the CPU doesn't cache them or do too much branch prediction + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + KeyType::Seed, + ).unwrap(); + + let msg = b"The quick brown fox jumped over the lazy dog"; + + let (_pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); + + let mu = MLDSA65::compute_mu_from_sk(&sk, msg, None).unwrap(); + let sig = MLDSA65::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); + print!("{:x?}", sig); +} + +fn bench_mldsa65_lowmemory_sign() { + use bouncycastle_mldsa_lowmemory::{MLDSATrait, MLDSA65}; + + eprintln!("MLDSA65_lowmemory/Sign"); + + // set up the seeds outside of the timing loop + // Doing different seeds so that the CPU doesn't cache them or do too much branch prediction + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + KeyType::Seed, + ).unwrap(); + + let msg = b"The quick brown fox jumped over the lazy dog"; + + /*** ML-DSA-44 ***/ + let (_mldsa44_pk, mldsa44_sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); + + let mu = MLDSA65::compute_mu_from_sk(&mldsa44_sk, msg, None).unwrap(); + let sig = MLDSA65::sign_mu_deterministic(&mldsa44_sk, &mu, [0u8; 32]).unwrap(); + print!("{:x?}", sig); +} + +fn bench_mldsa87_sign() { + use bouncycastle_mldsa::{MLDSATrait, MLDSA87}; + + eprintln!("MLDSA87/Sign"); + + // set up the seeds outside of the timing loop + // Doing different seeds so that the CPU doesn't cache them or do too much branch prediction + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + KeyType::Seed, + ).unwrap(); + + let msg = b"The quick brown fox jumped over the lazy dog"; + + let (_pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); + + let mu = MLDSA87::compute_mu_from_sk(&sk, msg, None).unwrap(); + let sig = MLDSA87::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); + print!("{:x?}", sig); +} + +fn bench_mldsa87_lowmemory_sign() { + use bouncycastle_mldsa_lowmemory::{MLDSATrait, MLDSA87}; + + eprintln!("MLDSA87_lowmemory/Sign"); + + // set up the seeds outside of the timing loop + // Doing different seeds so that the CPU doesn't cache them or do too much branch prediction + let seed = KeyMaterial256::from_bytes_as_type( + &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + KeyType::Seed, + ).unwrap(); + + let msg = b"The quick brown fox jumped over the lazy dog"; + + /*** ML-DSA-44 ***/ + let (_mldsa44_pk, mldsa44_sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); + + let mu = MLDSA87::compute_mu_from_sk(&mldsa44_sk, msg, None).unwrap(); + let sig = MLDSA87::sign_mu_deterministic(&mldsa44_sk, &mu, [0u8; 32]).unwrap(); + print!("{:x?}", sig); +} + +fn bench_mldsa44_verify() { + use bouncycastle_mldsa::{MLDSATrait, MLDSA44, MLDSA44_SIG_LEN, MLDSA44PublicKey}; + use bouncycastle_hex as hex; + + eprintln!("MLDSA44/Verify"); + + let msg = b"The quick brown fox jumped over the lazy dog"; + + /* One-time setup of the KAT -- commented out so that we're not capturing keygen in the bench */ + // let seed = KeyMaterial256::from_bytes_as_type( + // &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + // KeyType::Seed, + // ).unwrap(); + // + // let (mldsa44_pk, _mldsa44_sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); + + // eprintln!("pk:\n{}", &*hex::encode(&mldsa44_pk.encode())); + // let mu = MLDSA44::compute_mu_from_sk(&mldsa44_sk, msg, None).unwrap(); + // let sig = MLDSA44::sign_mu_deterministic(&mldsa44_sk, &mu, [0u8; 32]).unwrap(); + // eprintln!("sig:\n{}", &*hex::encode(sig)); + + let mldsa44_pk = MLDSA44PublicKey::from_bytes(&*hex::decode("d7b2b47254aae0db45e7930d4a98d2c97d8f1397d1789dafa17024b316e9bec94fc9946d42f19b79a7413bbaa33e7149cb42ed5115693ac041facb988adeb5fe0e1d8631184995b592c397d2294e2e14f90aa414ba3826899ac43f4cccacbc26e9a832b95118d5cb433cbef9660b00138e0817f61e762ca274c36ad554eb22aac1162e4ab01acba1e38c4efd8f80b65b333d0f72e55dfe71ce9c1ebb9889e7c56106c0fd73803a2aecfeafded7aa3cb2ceda54d12bd8cd36a78cf975943b47abd25e880ac452e5742ed1e8d1a82afa86e590c758c15ae4d2840d92bca1a5090f40496597fca7d8b9513f1a1bda6e950aaa98de467507d4a4f5a4f0599216582c3572f62eda8905ab3581670c4a02777a33e0ca7295fd8f4ff6d1a0a3a7683d65f5f5f7fc60da023e826c5f92144c02f7d1ba1075987553ea9367fcd76d990b7fa99cd45afdb8836d43e459f5187df058479709a01ea6835935fa70460990cd3dc1ba401ba94bab1dde41ac67ab3319dcaca06048d4c4eef27ee13a9c17d0538f430f2d642dc2415660de78877d8d8abc72523978c042e4285f4319846c44126242976844c10e556ba215b5a719e59d0c6b2a96d39859071fdcc2cde7524a7bedae54e85b318e854e8fe2b2f3edfac9719128270aafd1e5044c3a4fdafd9ff31f90784b8e8e4596144a0daf586511d3d9962b9ea95af197b4e5fc60f2b1ed15de3a5bef5f89bdc79d91051d9b2816e74fa54531efdc1cbe74d448857f476bcd58f21c0b653b3b76a4e076a6559a302718555cc63f74859aabab925f023861ca8cd0f7badb2871f67d55326d7451135ad45f4a1ba69118fbb2c8a30eec9392ef3f977066c9add5c710cc647b1514d217d958c7017c3e90fd20c04e674b90486e9370a31a001d32f473979e4906749e7e477fa0b74508f8a5f2378312b83c25bd388ca0b0fff7478baf42b71667edaac97c46b129643e586e5b055a0c211946d4f36e675bed5860fa042a315d9826164d6a9237c35a5fbf495490a5bd4df248b95c4aae7784b605673166ac4245b5b4b082a09e9323e62f2078c5b76783446defd736ad3a3702d49b089844900a61833397bc4419b30d7a97a0b387c1911474c4d41b53e32a977acb6f0ea75db65bb39e59e701e76957def6f2d44559c31a77122b5204e3b5c219f1688b14ed0bc0b801b3e6e82dcd43e9c0e9f41744cd9815bd1bc8820d8bb123f04facd1b1b685dd5a2b1b8dbbf3ed933670f095a180b4f192d08b10b8fabbdfcc2b24518e32eea0a5e0c904ca844780083f3b0cd2d0b8b6af67bc355b9494025dc7b0a78fa80e3a2dbfeb51328851d6078198e9493651ae787ec0251f922ba30e9f51df62a6d72784cf3dd205393176dfa324a512bd94970a36dd34a514a86791f0eb36f0145b09ab64651b4a0313b299611a2a1c48891627598768a3114060ba4443486df51522a1ce88b30985c216f8e6ed178dd567b304a0d4cafba882a28342f17a9aa26ae58db630083d2c358fdf566c3f5d62a428567bc9ea8ce95caa0f35474b0bfa8f339a250ab4dfcf2083be8eefbc1055e18fe15370eecb260566d83ff06b211aaec43ca29b54ccd00f8815a2465ef0b46515cc7e41f3124f09efff739309ab58b29a1459a00bce5038e938c9678f72eb0e4ee5fdaae66d9f8573fc97fc42b4959f4bf8b61d78433e86b0335d6e9191c4d8bf487b3905c108cfd6ac24b0ceb7dcb7cf51f84d0ed687b95eaeb1c533c06f0d97023d92a70825837b59ba6cb7d4e56b0a87c203862ae8f315ba5925e8edefa679369a2202766151f16a965f9f81ece76cc070b55869e4db9784cf05c830b3242c8312").unwrap()).unwrap(); + let sig = &*hex::decode("5e93b785c5119c3983a291b18420fdbe4bca53d5a3732922faaacd5a5d32a745c78d105ba10bee1ed8069f19e6c537bda16e89d39004c359d1fd381a0291f1c51f1c38edcdb315c8c69570d8f25f1655ba8ea83aff24b8b6be8de762342e347eab2caa6803ed705952dd6450c5185e9d60ce96e8dca423a02f646cea690164a226e4c3d6a515ce16290f19b2c626da9b450ecf665013c5e226b6c0ac5c07ce90e278f1b0134e385d13e74208a0b3ff052a362579f9207ea01f18a039aa1b97ae3452675b620771f8012ee7a4e55c98bfd2019ed8a3b00acea8e8ab28172faa42ca1fda83c5ffe81a45be736bdedd5fb300ce17078b380f620bdeebad693601372c85eacf79bc98e1b48f2ad7e5dce4279a1295bb2ba60a0c5e3726642d2336c5eb1d37c8623c7558241318d89bc783c4f00098077484623c217560a0c7aaf75dcaccb78ee69c207c27c8bf3965ccf58a80c88efcc7e5deb3615d5045a741c4dac0a021dd060d315d4ec2857eb664d728d0af973bea07e1ca563faa0e19996cea3770316c11a5066665662005ace98f6110e883bae060daa7b6d83379e0878796691708a32b85730de8b92d89f90a3660c949165b14612567662e162232296cbd143517a282e22c46b63606d3c14ed4559a5a1c459bab7f355007ad6f7e3b1e07445dfc96bd9b75080b3d4f68998490a26b5e090be2674071ab925bb650590856c59f8ba7488d2b72f840ac3eafe4dd91f0f51c4364112c1a139e3e942a597b93a1e3f4faded129c14b5978b315e2246a93146a79365f0f597a18340cca86bb15ceed39f175eab1e546535afb966f0a65a8f66f737ab02897eddfe92cf7786894843c2691464776c94bd450a1069138b26df83b2d1dd801143a8fdfdc2514cc5b5831ab53a75c55ef29f40e7c63d2c72abe97e2af14853be49be16f4730a159974970951439e55c1589d0f4a162e3517df9d7abc98d8a307216e7f1cb4627c9175c0eef23337e56d5281b83726fff40a148b0c48e8df3496a2118d80219aef8f40b29fba1f2f78786b67ffb7b7d47d406b765bd136610bedeb95cd7321f58f3b836c9258be35d78b498f3efe1db2b243d734fab159baed8807c3cccf83eb2eaf8a9af01a518d48c60e91a96812ad689c2d83cc4e8e9b3650422bed6f13c24adaad91c95b3e3cf354f0f6bc9ee8941a6b15b6975131d95233d8935de367efc6d86a45dac7d0f1ddd9aebd2c59c027fcda448801e93e733aca51874be9ab927a904f96ddb7a46b2da13261d522b23c950c01d5f5e112b76f851ff234f06f8d5e65b1319abcd79a180ae063d65b28c745878c06dbb69ba73293eab34434bf1a92fba691993bd0ff3edac76a12f80c0ada4b1969c7665589d530a67016a625403c537032904f2e104547cd3ea406260dd357fa06ea012a785826c160e99ffd065b0e3f33c7689d3552ab9e2e09fa7e55bbcef042242bcacad8a3da47bcc54a121f1526c8cd4cc5a892a8131cf4eefaf4248ddd6a11ec427ba378aae89aaf582ce1f4e32690a555e740761d358ad4e92bc38418aa782da916524fb09ab2ca6b3d3113d6f2c2a6a9b9d29d4e7489255252af075cbf9feacedae6f3ec0b070824689dd3c78ac143ed6776d95dd8f13d435a290bdca4c11318e5acce04469644e1374a9451b6204f3b3961b7dd239e306fef5f4f4e51b78b0fb9dcee69c3e790b231f2e65fd1ab1c2a75b07067d5c16dde00983a58ffcdaaaee16d2742e133ed737b48064c8a38eca35ab3fa18f6d62f642b12cfdc7980f2ab7db321fec9dcfe499b4fc1ee7eb297954056617c60a6640b92835d165c3c00a951952614488d5657ba0b5e90ae9e0ef7b3b9ecaebd81b8551b6d70e835b2734761639d42e76ffc5b3272b61c896b45b4bd18f30e58c440643ba159221cc6739a19a65f2911fae47b0d4cac4200a6f043b17a03ad393ecb823ed03c8b6cd68167e6c8234f7432557db272079ee899aede73b6b98d6003f45789a141b60d6db40cd2a5974571a4ad3667b889318ba60285d903a2eac01c21608838c40907de6bbabe042cf2ecdd97f549f95ec698d79222c65ba27c30d332a68d057aecdc9388aa34320e0aa74fdbd4d1b643cace216b6d8ad8f07a99955bfdb743a86b40fc61527baca434ac2a7fbeaa77111dc8098b17e800f59dd77ccb0e67707e60123d334e073a2f5a16ffbcd701389add57c3ceccb88b286ac1e6e3e6485af1a12ea241d14a1b5003d7f3bc9e957d4483c0f9f703b3a187d55e505817615fbc4ae0837616184245cfba61ce3b929e33f52b71cdd7b6a0da55c1f997510b1a9002ca4e0678373a3b1ab2897e6b423f15a440a636cc861491ef41ad0aa627d8e198a5ee7bd7b6cb2c9ce2a8cc015f0d206de4c49e2f87f310954a10d86e294f742ee186f4ae9815f699622792206cafba8f5621738160e6c5d611a8252c6f35085b604ef895164d4ea6ddd310c7d8f0c879fb1f884c5741d096b3d2da0ce1151790dda881d18cb6b19a9fed6f5254b7d52d5d92bbbe24c9d6a65604a0b8ed24ad5c197d683f598743c96b5960e8723732b5bd647e9dbeaa851d0e1cf6d2c070d4442762c28098c5cf5a54b2b5e69a99b10815bf0f477bb71f0d5d3a62ba2b3e29bf84d4b4e574707f5f74af704d277bd6ca38da21e2cdac549e5eae1de7a18ee534c8c2291c908caabf159e90e6549db94ba7a3f3d97dd398a75df5b1a7cdfb25410b7efc4ed00d9995b37b58bf91ed7a3510cffea82f9e1c2a3290406004d09057d63b770fa0e53103199544eba662a2c302cf39008f142d2b16963e95ab10be7c2610168608f353a2f2c41c7056dec1a8c7a6bfa0027f9dedacb7786b67ea2c494d43ba851cf9415c1bcc52f027ec02c65534f608e9d166d51dd431cdf5871f5cdd1579cc06079df075a25062ba7e70d9666c4e7fed34cea0ea0f11ade1eb2a9b397bcaaad1061270ecf497803a5fce7f41e6504fbec71a7de7d066b8261868afc49b9e685f0dcce75e2fcb3ba8cf19057e3941576baf58fb821bd4268f7fae3028601da022e9b468646abdb4fa6098a449b4267d509d9a33f4c3ebcc32dac094d48ed600e765787fb92b1974f74f7bb4c66eb2bbd02895e6a381c1c452eaab1ae4731cf632f61ae2c905921174a3bc9bb4cdc89d630264b614988f3abbea1bd617ffa53d71b7d8a371462b773351a2dccaedd7f59cd728fadee059067bd80c94c8c9a1ffca2dc4f848b829c0561385aa82cc98503d0bb66a6aa4fae0703d12e60e1460efbbcdf2412c13e7c684d1b01102026343a414344585f6e7072748baeb5bbc6d1e2effbfe060e2e3e5160797c9ea6bac7f11024404a52575f6c898c97aab2c3cceaf22f3f535f7b818396a1b1bce6000000000000000000000000000018253642").unwrap(); + assert_eq!(sig.len(), MLDSA44_SIG_LEN); + + if MLDSA44::verify(&mldsa44_pk, msg, None, &sig).is_ok() { + eprintln!("Verification succeeded!"); + } else { + panic!("Verification failed! -- figure that out"); + } +} + +fn bench_mldsa44_lowmemory_verify() { + use bouncycastle_mldsa_lowmemory::{MLDSATrait, MLDSA44, MLDSA44_SIG_LEN, MLDSA44PublicKey}; + use bouncycastle_hex as hex; + + eprintln!("MLDSA44_lowmemory/Verify"); + + let msg = b"The quick brown fox jumped over the lazy dog"; + + /* One-time setup of the KAT -- commented out so that we're not capturing keygen in the bench */ + // let seed = KeyMaterial256::from_bytes_as_type( + // &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + // KeyType::Seed, + // ).unwrap(); + // + // let (mldsa44_pk, _mldsa44_sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); + + // eprintln!("pk:\n{}", &*hex::encode(&mldsa44_pk.encode())); + // let mu = MLDSA44::compute_mu_from_sk(&mldsa44_sk, msg, None).unwrap(); + // let sig = MLDSA44::sign_mu_deterministic(&mldsa44_sk, &mu, [0u8; 32]).unwrap(); + // eprintln!("sig:\n{}", &*hex::encode(sig)); + + let mldsa44_pk = MLDSA44PublicKey::from_bytes(&*hex::decode("d7b2b47254aae0db45e7930d4a98d2c97d8f1397d1789dafa17024b316e9bec94fc9946d42f19b79a7413bbaa33e7149cb42ed5115693ac041facb988adeb5fe0e1d8631184995b592c397d2294e2e14f90aa414ba3826899ac43f4cccacbc26e9a832b95118d5cb433cbef9660b00138e0817f61e762ca274c36ad554eb22aac1162e4ab01acba1e38c4efd8f80b65b333d0f72e55dfe71ce9c1ebb9889e7c56106c0fd73803a2aecfeafded7aa3cb2ceda54d12bd8cd36a78cf975943b47abd25e880ac452e5742ed1e8d1a82afa86e590c758c15ae4d2840d92bca1a5090f40496597fca7d8b9513f1a1bda6e950aaa98de467507d4a4f5a4f0599216582c3572f62eda8905ab3581670c4a02777a33e0ca7295fd8f4ff6d1a0a3a7683d65f5f5f7fc60da023e826c5f92144c02f7d1ba1075987553ea9367fcd76d990b7fa99cd45afdb8836d43e459f5187df058479709a01ea6835935fa70460990cd3dc1ba401ba94bab1dde41ac67ab3319dcaca06048d4c4eef27ee13a9c17d0538f430f2d642dc2415660de78877d8d8abc72523978c042e4285f4319846c44126242976844c10e556ba215b5a719e59d0c6b2a96d39859071fdcc2cde7524a7bedae54e85b318e854e8fe2b2f3edfac9719128270aafd1e5044c3a4fdafd9ff31f90784b8e8e4596144a0daf586511d3d9962b9ea95af197b4e5fc60f2b1ed15de3a5bef5f89bdc79d91051d9b2816e74fa54531efdc1cbe74d448857f476bcd58f21c0b653b3b76a4e076a6559a302718555cc63f74859aabab925f023861ca8cd0f7badb2871f67d55326d7451135ad45f4a1ba69118fbb2c8a30eec9392ef3f977066c9add5c710cc647b1514d217d958c7017c3e90fd20c04e674b90486e9370a31a001d32f473979e4906749e7e477fa0b74508f8a5f2378312b83c25bd388ca0b0fff7478baf42b71667edaac97c46b129643e586e5b055a0c211946d4f36e675bed5860fa042a315d9826164d6a9237c35a5fbf495490a5bd4df248b95c4aae7784b605673166ac4245b5b4b082a09e9323e62f2078c5b76783446defd736ad3a3702d49b089844900a61833397bc4419b30d7a97a0b387c1911474c4d41b53e32a977acb6f0ea75db65bb39e59e701e76957def6f2d44559c31a77122b5204e3b5c219f1688b14ed0bc0b801b3e6e82dcd43e9c0e9f41744cd9815bd1bc8820d8bb123f04facd1b1b685dd5a2b1b8dbbf3ed933670f095a180b4f192d08b10b8fabbdfcc2b24518e32eea0a5e0c904ca844780083f3b0cd2d0b8b6af67bc355b9494025dc7b0a78fa80e3a2dbfeb51328851d6078198e9493651ae787ec0251f922ba30e9f51df62a6d72784cf3dd205393176dfa324a512bd94970a36dd34a514a86791f0eb36f0145b09ab64651b4a0313b299611a2a1c48891627598768a3114060ba4443486df51522a1ce88b30985c216f8e6ed178dd567b304a0d4cafba882a28342f17a9aa26ae58db630083d2c358fdf566c3f5d62a428567bc9ea8ce95caa0f35474b0bfa8f339a250ab4dfcf2083be8eefbc1055e18fe15370eecb260566d83ff06b211aaec43ca29b54ccd00f8815a2465ef0b46515cc7e41f3124f09efff739309ab58b29a1459a00bce5038e938c9678f72eb0e4ee5fdaae66d9f8573fc97fc42b4959f4bf8b61d78433e86b0335d6e9191c4d8bf487b3905c108cfd6ac24b0ceb7dcb7cf51f84d0ed687b95eaeb1c533c06f0d97023d92a70825837b59ba6cb7d4e56b0a87c203862ae8f315ba5925e8edefa679369a2202766151f16a965f9f81ece76cc070b55869e4db9784cf05c830b3242c8312").unwrap()).unwrap(); + let sig = &*hex::decode("5e93b785c5119c3983a291b18420fdbe4bca53d5a3732922faaacd5a5d32a745c78d105ba10bee1ed8069f19e6c537bda16e89d39004c359d1fd381a0291f1c51f1c38edcdb315c8c69570d8f25f1655ba8ea83aff24b8b6be8de762342e347eab2caa6803ed705952dd6450c5185e9d60ce96e8dca423a02f646cea690164a226e4c3d6a515ce16290f19b2c626da9b450ecf665013c5e226b6c0ac5c07ce90e278f1b0134e385d13e74208a0b3ff052a362579f9207ea01f18a039aa1b97ae3452675b620771f8012ee7a4e55c98bfd2019ed8a3b00acea8e8ab28172faa42ca1fda83c5ffe81a45be736bdedd5fb300ce17078b380f620bdeebad693601372c85eacf79bc98e1b48f2ad7e5dce4279a1295bb2ba60a0c5e3726642d2336c5eb1d37c8623c7558241318d89bc783c4f00098077484623c217560a0c7aaf75dcaccb78ee69c207c27c8bf3965ccf58a80c88efcc7e5deb3615d5045a741c4dac0a021dd060d315d4ec2857eb664d728d0af973bea07e1ca563faa0e19996cea3770316c11a5066665662005ace98f6110e883bae060daa7b6d83379e0878796691708a32b85730de8b92d89f90a3660c949165b14612567662e162232296cbd143517a282e22c46b63606d3c14ed4559a5a1c459bab7f355007ad6f7e3b1e07445dfc96bd9b75080b3d4f68998490a26b5e090be2674071ab925bb650590856c59f8ba7488d2b72f840ac3eafe4dd91f0f51c4364112c1a139e3e942a597b93a1e3f4faded129c14b5978b315e2246a93146a79365f0f597a18340cca86bb15ceed39f175eab1e546535afb966f0a65a8f66f737ab02897eddfe92cf7786894843c2691464776c94bd450a1069138b26df83b2d1dd801143a8fdfdc2514cc5b5831ab53a75c55ef29f40e7c63d2c72abe97e2af14853be49be16f4730a159974970951439e55c1589d0f4a162e3517df9d7abc98d8a307216e7f1cb4627c9175c0eef23337e56d5281b83726fff40a148b0c48e8df3496a2118d80219aef8f40b29fba1f2f78786b67ffb7b7d47d406b765bd136610bedeb95cd7321f58f3b836c9258be35d78b498f3efe1db2b243d734fab159baed8807c3cccf83eb2eaf8a9af01a518d48c60e91a96812ad689c2d83cc4e8e9b3650422bed6f13c24adaad91c95b3e3cf354f0f6bc9ee8941a6b15b6975131d95233d8935de367efc6d86a45dac7d0f1ddd9aebd2c59c027fcda448801e93e733aca51874be9ab927a904f96ddb7a46b2da13261d522b23c950c01d5f5e112b76f851ff234f06f8d5e65b1319abcd79a180ae063d65b28c745878c06dbb69ba73293eab34434bf1a92fba691993bd0ff3edac76a12f80c0ada4b1969c7665589d530a67016a625403c537032904f2e104547cd3ea406260dd357fa06ea012a785826c160e99ffd065b0e3f33c7689d3552ab9e2e09fa7e55bbcef042242bcacad8a3da47bcc54a121f1526c8cd4cc5a892a8131cf4eefaf4248ddd6a11ec427ba378aae89aaf582ce1f4e32690a555e740761d358ad4e92bc38418aa782da916524fb09ab2ca6b3d3113d6f2c2a6a9b9d29d4e7489255252af075cbf9feacedae6f3ec0b070824689dd3c78ac143ed6776d95dd8f13d435a290bdca4c11318e5acce04469644e1374a9451b6204f3b3961b7dd239e306fef5f4f4e51b78b0fb9dcee69c3e790b231f2e65fd1ab1c2a75b07067d5c16dde00983a58ffcdaaaee16d2742e133ed737b48064c8a38eca35ab3fa18f6d62f642b12cfdc7980f2ab7db321fec9dcfe499b4fc1ee7eb297954056617c60a6640b92835d165c3c00a951952614488d5657ba0b5e90ae9e0ef7b3b9ecaebd81b8551b6d70e835b2734761639d42e76ffc5b3272b61c896b45b4bd18f30e58c440643ba159221cc6739a19a65f2911fae47b0d4cac4200a6f043b17a03ad393ecb823ed03c8b6cd68167e6c8234f7432557db272079ee899aede73b6b98d6003f45789a141b60d6db40cd2a5974571a4ad3667b889318ba60285d903a2eac01c21608838c40907de6bbabe042cf2ecdd97f549f95ec698d79222c65ba27c30d332a68d057aecdc9388aa34320e0aa74fdbd4d1b643cace216b6d8ad8f07a99955bfdb743a86b40fc61527baca434ac2a7fbeaa77111dc8098b17e800f59dd77ccb0e67707e60123d334e073a2f5a16ffbcd701389add57c3ceccb88b286ac1e6e3e6485af1a12ea241d14a1b5003d7f3bc9e957d4483c0f9f703b3a187d55e505817615fbc4ae0837616184245cfba61ce3b929e33f52b71cdd7b6a0da55c1f997510b1a9002ca4e0678373a3b1ab2897e6b423f15a440a636cc861491ef41ad0aa627d8e198a5ee7bd7b6cb2c9ce2a8cc015f0d206de4c49e2f87f310954a10d86e294f742ee186f4ae9815f699622792206cafba8f5621738160e6c5d611a8252c6f35085b604ef895164d4ea6ddd310c7d8f0c879fb1f884c5741d096b3d2da0ce1151790dda881d18cb6b19a9fed6f5254b7d52d5d92bbbe24c9d6a65604a0b8ed24ad5c197d683f598743c96b5960e8723732b5bd647e9dbeaa851d0e1cf6d2c070d4442762c28098c5cf5a54b2b5e69a99b10815bf0f477bb71f0d5d3a62ba2b3e29bf84d4b4e574707f5f74af704d277bd6ca38da21e2cdac549e5eae1de7a18ee534c8c2291c908caabf159e90e6549db94ba7a3f3d97dd398a75df5b1a7cdfb25410b7efc4ed00d9995b37b58bf91ed7a3510cffea82f9e1c2a3290406004d09057d63b770fa0e53103199544eba662a2c302cf39008f142d2b16963e95ab10be7c2610168608f353a2f2c41c7056dec1a8c7a6bfa0027f9dedacb7786b67ea2c494d43ba851cf9415c1bcc52f027ec02c65534f608e9d166d51dd431cdf5871f5cdd1579cc06079df075a25062ba7e70d9666c4e7fed34cea0ea0f11ade1eb2a9b397bcaaad1061270ecf497803a5fce7f41e6504fbec71a7de7d066b8261868afc49b9e685f0dcce75e2fcb3ba8cf19057e3941576baf58fb821bd4268f7fae3028601da022e9b468646abdb4fa6098a449b4267d509d9a33f4c3ebcc32dac094d48ed600e765787fb92b1974f74f7bb4c66eb2bbd02895e6a381c1c452eaab1ae4731cf632f61ae2c905921174a3bc9bb4cdc89d630264b614988f3abbea1bd617ffa53d71b7d8a371462b773351a2dccaedd7f59cd728fadee059067bd80c94c8c9a1ffca2dc4f848b829c0561385aa82cc98503d0bb66a6aa4fae0703d12e60e1460efbbcdf2412c13e7c684d1b01102026343a414344585f6e7072748baeb5bbc6d1e2effbfe060e2e3e5160797c9ea6bac7f11024404a52575f6c898c97aab2c3cceaf22f3f535f7b818396a1b1bce6000000000000000000000000000018253642").unwrap(); + assert_eq!(sig.len(), MLDSA44_SIG_LEN); + + if MLDSA44::verify(&mldsa44_pk, msg, None, &sig).is_ok() { + eprintln!("Verification succeeded!"); + } else { + panic!("Verification failed! -- figure that out"); + } +} + +fn bench_mldsa65_verify() { + use bouncycastle_mldsa::{MLDSATrait, MLDSA65, MLDSA65_SIG_LEN, MLDSA65PublicKey}; + use bouncycastle_hex as hex; + + eprintln!("MLDSA65/Verify"); + + let msg = b"The quick brown fox jumped over the lazy dog"; + + /* One-time setup of the KAT -- commented out so that we're not capturing keygen in the bench */ + + // let seed = KeyMaterial256::from_bytes_as_type( + // &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + // KeyType::Seed, + // ).unwrap(); + // + // let (mldsa65_pk, mldsa65_sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); + // + // eprintln!("pk:\n{}", &*hex::encode(&mldsa65_pk.encode())); + // let mu = MLDSA65::compute_mu_from_sk(&mldsa65_sk, msg, None).unwrap(); + // let sig = MLDSA65::sign_mu_deterministic(&mldsa65_sk, &mu, [0u8; 32]).unwrap(); + // eprintln!("sig:\n{}", &*hex::encode(sig)); + + let mldsa65_pk = MLDSA65PublicKey::from_bytes(&*hex::decode("48683d91978e31eb3dddb8b0473482d2b88a5f625949fd8f58a561e696bd4c27d05b38dbb2edf01e664efd81be1ea893688ce68aa2d51c5958f8bbc6eb4e89ee67d2c0320954d57212cac7229ff1d6eaf03928bd51511f8d88d847736c7de2730d5978e5410713160978867711bf5539a0bfc4c350c2be572baf0ee2e2fb16ccfea08028d99ac49aebb75937ddce111cdab62fff3cea8ba2233d1e56fbc5c5a1e726de63fadd2af016b119177fa3d971a2d9277173fce55b67745af0b7c21d597dbeb93e6a32f341c49a5a8be9e825088d1f2aa45155d6c8ae15367e4eb003b8fdf7851071949739f9fff09023eaf45104d2a84a45906eed4671a44dc28d27987bb55df69e9e8561f61a80a72699503865fed9b7ee72a8e17a19c408144f4b29afef7031c3a6d8571610b42c9f421245a88f197e16812b031159b65b9687e5b3e934c5225ae98a79ba73d2b399d73510effad19e53b8450f0ba8fce1012fd98d260a74aaaa13fae249a006b1c34f5ba0b882f26378222fb36f2283c243f0ffeb5f1bb414a0a70d55e3d40a56b6cbc88ae1f03b7b2882d98deea28e145c9dedfd8eaf1cef2ed94a8b050f8964f46d1ea0d0c2a43e0dda6182adbf4f6ed175b6742257859bf22f3a417ecf1f9d89317b5e539d587af16b9e1313e04514ffa64ba8b3ff2b8321f8811cb3fb022c8f644e70a4b80a2fbfee604abb7379091ea8e6c5c74dfc0283666b40c0793870028204a136bf5da9568eb798d349038bdb0c11e03445e7847cb5069c75cf28ac601c7799d958210ddbcb226e51afef9f1de47b073873d6d3f97456bede085082e74a298b2cd48f4b3093155f366c8fa601c6af858dfa32c08491b2a29887f90335949a5d6edaa679882a3a95d6bf6d970a221f4b9d3d8cbf384af81aac95e2b3294e04789ac83727a5dc04559f96af41d8a053516feeeebc52746eb6ab2819e09108710d835f011fa63065872ad334d5cdffb2b2310507e92fc993ae317da97f4f309cdaf0f67ed99d90215576083849f953b246d7fedb3fdb67679850a5ad404e64147fb7cf4f6aeddd05afb4b834968d1fe88014960dce5d942236526e12a478d69e5fbe6970310b308c06845018cfc7b2ab430a13a6b1ac7bb02cccbb3d911ac2f11068613fbe029bfdce02cf5cd38950ed72c83944edfbc75615af87f864c051f3c55456c5412863a40c06d1dab562bdff0571b8d3c3917bbd300880bba5e998239b95fa91b7d6416d4f398b3adbcd30983ed3592b4d9ef7d4236fd00f50d98aa53a235ac4172720f77d96172672980cfe8ff7a5a702783edc2ba31b2259015a112fc7f468a9c2f9464039002d30ef678b4cb798bc116216bf7a9a7c18ba03b7b58fd07515d3115049d3614be7a07e744300750df1d2c58753389059eafc3d785ccdd31c07648bedc03a5c3b8ad46d064d59c13d57374729fc4e295362e2a5191204530428bc1522afa28ff5fe1655e304ca5bc8c27ad0e0c6a39dd4df28956c14b38cc93682cefe402bbd5e82d29c464e44eb5d37b48fc568dfe0cc6e8e16baea05e5135590f19294e73e8367b0216dbb815030b9de55913f08039c42351c59e5515dd5af8e089a15e625e8f6dee639386c46497d7a263288774de581a7de9629b41b4424141f978fb8331208efdec3c6e0de39bc57063f3dcd6c470373c08891ea29cbc7cc6d6483b8889083ace86aa7b51b1c2cfe6e2ad18d97ce36fbc56ea42fae97e6a7ac114864478c366df1ebb1e7b11a9098504fd5975bdf1f49dc70002b63c1739a9d263fbad4073f6a9f6c2b8af4b4c332a103a0cffa5deeb2d062ca3c215fd360026be7c5164f4a4424ef74948804d66f46487732c8202c795478647b4ea71d627c086024cca354a41f0877b38f19b3774ad2095c8da53b069e21c76ae2d2007e16719ed40080d334f7da52e9f5a5990439caf083a95b833f02ad10a08c1a6d0f260c007285bd4a2f47703a5aef465287d253b18ac22514316210ff566814b10f87a293d6f199d3c3959990d0c1268b4f50d5f9fcefbbf237bd0c28b80182d6659741f14f10bfbb21bba12ab620aa2396f56c0686b4ea9017990224216b2fe8ad76c4a9148eef9a86a3635a6aa77bc1dcfb6fba59a77dfda9b7530dc0ca8648c8d973738e01bab8f08b4905e84aa4641bd602410cd97520265f2f231f2b35e15eb2fa04d2bd94d5a77abaf1e0e161010a990087f5b46ea988b2bc0512fda0fa923dadd6c45c5301d09483673265b5ab2e10f4ba520f6bbad564a5c3d5e27bdb080f7d20e13296a3181954c39c649c943ebe17df5c1f7aae0a8fe126c477585a5d4d648a0d008b6af5e8cd31be69a9296d4f3fd25ed86f221e4b93f65f5929967533624b9235750c30707550b58536d109a7131c5a5bbe4a5715567c12534aec7660761eebb9fae2891c774589b80e566ad557ddef7367196b7227ea9870ef09ddfec79d6b9319a6879b5205d76bf7aba5acf33afb59d17fc54e68383d6be5a08e9b66da53dcde008bb294b8582bd132cdcc49959fdbc21e52721880c8ad0352c79f03a43bbd84c4cdfdc6c529005e1e7cd9a349a7168a35569ba5dea818968d5a91466bd6e64e20bf62417198afc4e81c28dd77ed4028232398b52fbde86bc84f475b9016710ce2aabc11a06b4dbac901ec16cf365ca3f2d53813948a693a0f93e79c46ca5d5a6dca3d28ca50ad18bd13fca55059dd9b185f79f9c47196a4e81b2104bc460a051e02f2e8444f").unwrap()).unwrap(); + let sig = &*hex::decode("9061f15cbf2092f744fbcd799eb02414053c1b0f7412124bedc41cf9a3db0166469e874037d7f081e5f8d3d2033a0307d1c49ed01fe64578c4a6fabd80880cdf1911848f184d4bcf536ca795a0fb1aa19ab7ee3ba6b58bd64bbeac9f58650fff1ef5a97ab6916df962072e20e7c6be96090e3a781a504bc4442bd8889a0aa628907a74299f39fa836031f1bd68355bebe7ae93c1e361a9efbed1325d96227070461fcd6f151b8669d9229b977d9ee51fd2260c3e4a2e820416f9e074958dc3b3e2217e6312b7e0b582a048981cf6579f4bc7715b78c808e4c57e3b8aa38b05c04fcedf209f52c1e331ae83dbdff60ba450a17cc397568e54bc3f16ddf30b92747ce460d925b9be20a1d35e2aed97f124af2616a5361df28ba30e522dd08fa00fd28d1ac484d756a89e3a442fefe8332c56cd2a9fde691bdbda43f1cc54cef57bead96120b50c7d4695bdbb1303cc5ddda898e4eeb83083176e40e0232cdd1c3150371df05d6fdad7e1164d90393cf308e99edfeb31fed263e2866ee3b7f3937b399c974d87ba7b489efe3c9b80371d2928446adc31991ab0cefaaa080575b9ec81cfa133a9911c035a8058d0d3f2e34de4a9fb009bb4ccdb16de7b908574a7496725ff857556c1b33917e986c80f1014a9e3083add2fb35f345c5d06159e443329d0da099987b996c3731592b460c2ffd2955f7546f4216100ba43188803ff9b36969685f909fa2539323b8c8ec1c095a5085e554dd450e0e67ab670b6a11ebf6c25520fc13e364060f91f9b7f3d5cb48ff28b8fc83d4293f1f35ad6ff6ae4574ad7a1c6005fc0389a7b21386b0850a05d832fe6a14bb2b1db1f8e20bd09174946cd098b81c8f797e95f2143a949770cf1219bfef039db51a80fc247f65f41554c7173dd805ba82fdf47ab6d4bfd37dfe46fc47904421ae00dc005a22f9c4784b0ea9e665392a412245016d5c6d7673a6a180d228d4255a538e451ffd8b414d40304c0c888992e0ab6de1602109527417bc1c7eb782ae77a8c3cdfc1d13a1e874207898264e38080243109c5969649ac8383417e922ba115331142d0ed35440b15d40bee0cf58af37c0f0524ffac1c71ceed3bb82f76ab108a8ad1a0c8b78d9341148c642369be7bef59d46f49d70c83560607f140848ec9a7607d4a08f8b6e4447f5523f416981888a8de9647ffef79389e4983e5c9387698d0cc2d429322365ce7e7b5fd6d6eb921c813fcf06199fe1ca41e9cfe03b539f321671a2acad0963f876f9db7a1c4371b9f101005217995b5b6a40976246d245da603dba8dac812a5480c3476a99d0ffdf0ef943d72d912543148b2fe78e8b0159324fe9bcd4ced33cd212fe4f3dfd6d4c5e1958beb95ac6b533ace3e78015e3880b52bf45299263a4c0096f8ba5fe3a6298cab675cb7f382e7ef49720eb4cf47376e2d2574122ccf91129c858e948904fecefb91226ed42403ba12dd3258909a87dfcbf65cc3adc3d98d277fdcec7664e2292b7d27afbb5aafb405c20a34b2fe2c0849ee280bb891dfdf59f19b89b0246358db54cf3fdc66eaaaa750c8903f1d42678f3edf0b7530410aa881bc617f94346379854af4532e61f65aae7576c35faf55e155bd6787b4634d54191907e155c239e68480cdfa0c87054bfb62855f409a20d5335fb123e681e64ec847cd985b6062059f436aebac623c038b6c3405ac325191a8d1126a5ef8f38cccbf144a5c324c1e093cf99efbe10ca03d439bcfb8ba5e293b7d318837f7bc42a99964392369da76e79d71d1a2c248a11324a87ae1e3cbeab6fb0d0bcae1ef55e43dfb6f1b4cfb82c7a778fb828a3727ef07685fe38a74b3dd25d015322c2d9f245c08d8c2b43865694233782eb734436c4eddef5406208d6c4572c7371262fe02319cfbbcf2e23bed8aa969d1ae6f5f25ff6b8ebcf0925066f761a39bbff49f0c8dbc3be84f0c442b044ea01b669747e3c8293cfe9ccdf2ef063ae3d28d10720c279a2691616abd23b055cfc6c562125df4ad0fa6631304972ddc3674b1aaa7665bf621320d83eac8d5b371d7d719829f58b23458182558710de31d81ef9a47d8839c79640b2025d1965a418bc90e4115f1423311a8b64fcde0f2d2145ee535b0931b84bc8110445f2ff68d136ed709ddb7ea9ff75f3b4e8b4f836230ca9e81069477f634e07270af60ef96f72557a081d664abcf35548f699484653da645483ff2bf5998617ae8bfa62d56e714f3c0136e5035a3f78e06c2f470df7fd3380d14033f81e2aae6b4d90487dab76b9b3b8761fb56c36f5429da3d4346cb22e641ad8d7d2d80fa240d4e0154e6b3d2f1b3ef6cf174c08d062f575c83a4078174f874364df36a6328beeef69ba7f90e1df9fdcec9a2f15ebf04fa7d6756da2e5a59c9cbbcbc397d6fb28d0fc9a60534dff0752716ed079ad1ab19a224d1c8ae8a53242fd164989ff997489b6520eb3c0e97f4bcc1a9c3cbd44f008c03ef52cf7e626881d246925e0336c0ac668867f853da7820f914115a7c77ac31b66f46fbf97f66fa26416fc4581d459a4f2462d52cf0c79b278955aa73e8fa56e3c320f516bcc54c97e587199c15ab953cc37189b81c70cabb2559e445bcc9d8174ad7574e9acb02f43e0c34ff5e6746ee730ad41ff8eef93c2071c2649063dd92f343c06ef6abaf98f28d98d968071c12cc10a90c22d8b3b3480c76f7a51b7ec594b3435d2e3d779c1a15037697f3a058650472e47eecd5f32eb3243a516f0e703f9888c84690750648d6a9a876bf1f353db6891dc6d317d6e87ac088f42b5f6f20d799ece4fa7aaa928d2ac795e8de83d1e1c7fa2f9a4106693e981c21c63b3221c4fa2649f45f0c6e05dbf24011af16ab2e5fe94a640b485988037ebe1e8ad0b2623d95e9947f0726121d7828614e3b2d77a7a1f9a938bea9a1a7a2627b7d2e358c42ccc6c0b80a15a1c2f6e9aaf0495bdb7bb8d4b0e28a1ab5ab93ca0ff3e3f910c490c13486852534d5e12160835ec5916c5c68349c4e2d8fa956c643277edd3b6c81c88c010421705fd317ff9e3c94df0ed5305f530acbccf8dd0e87140cd38152664a572c168cd72595b7fac243c03f3fb33ef74a28c0e4469f94587c13704e9efe8010b2125aca78c22c33c82366e1a7c4028c2ae2e8d26e1a57e4297fac987f84a0a27f42b4c93a4f4d14569824b0880fb67407ed58f267ac403aa0b1f93784b4b4c67036037e60d58072611b0e90ca316976ef4e0b302cdad1b6dcca92efb8e1f6be2397967508be2c02a25ed0380ba1f7955f857c8fb043297780d136b2b064040c8e55143d715ea997e134ed973c98ef82786f0ccf66c17d863542180c66d54d08e116f2e35d995e214489ad0fad7a55fe9ebc1a777fe34141147c080b98d13463a3bbc6fc82f2fc95f4de7b3591d9c8cd4416917a4338095d5620104b7be13f5a131dd3f7aad5b559d11e8171dfb91e2bb1e47ac3810b1cdc1a1e370c867b7b7b50c4688dce545763157e02f47e1cc661d5bf2fbc336cfae080ab15728b1ab9dd199f2779d451e6178977fb658c17344cffb7aa3af5791a28fc8a089c85187753e5e313c8d1f0fe7755e28be444426a189e8bce2d2f79db31d4c3ca911a83455525355f95d159351cd731a88e55403851236ee2128f279d5be644c042453ae65d9e9f3b40d6c82bdeb002acdee061ecca3f2dceabef9a900e6e063d56ab39cb82dbc77a4677572d7616cd72c0f6d5b9b941dfda1fe7c896b8cc24d65a4322d712a84e94adfc8ed0cc56cc1ae97f775bd3cea5b20b524d9a7a916056e19af095d30171e5e14c7c998f78dc44845edf307363eab7913f680a5e5a1540a6f945507ffa67591f8d1a2920ab3b6e754e35379dd67870c242335e2717903ff3c687e5c33dc953416865d5f23bd752e55492b9d5d888d7b37ef33b0a6774d052b0987c066a2e01767207aa7fbfc393ca62874613dde3794f74fadb5d55b877b877a605918c812610fbcafad72ee245e6dd8721138d6bd3f4eedad853aed1ec437ad02ac937c80dae26fa5f70083bd346779b779387f7b3d2aae57770d8177928833281ccb7a38da24834fd9726fd17eb603cba9041e82bfeed0e33942dde1d48c271f5b39aa7230f41afb89d36f7976eee4f51a036743031c534f64685b94c990a93a5737fe628ee9cda8ed9c08b11d3836f833835c445b317a77ead7599d1a0c08873014510d36bb7ff5fb961277589ea48c32a60c87ec40681be067b17785ec44825bd89faa25249e735a628b6eebcc6cce4e0314c627588118c40b2e0d460d8d5ce358c56458f36914ca203f5a5381c6deb5a76bbc08c40a87437da0d0b571788a05e9f96d9bb770de8a0b1b960ff2a44a964c9b7939853742e83ce8deb79191b2d82454655f227079dd8c5b0216c8470b8e1ac70526301bbfa2bc4adca68a766ccb2a6e0ebf2e99905bf5242590b01703868b3faf841c11c383be145a40fea6375e18a01468e459603b5efdf8a4e9abd179280ae8b5947d78d2f0c4d37715eaa42bc37cf8730e41ffbf9826d46424f2922a96033cefaa8b4bbe4c8b89d43501fd5211d5392ca19a98ba127d9025b5c6e86ba024471940549a2b5d8e14961c9dc19696da1a5bffd01030d5e6100000000000000000000000000000000000000000000000005090f131a1f").unwrap(); + assert_eq!(sig.len(), MLDSA65_SIG_LEN); + + if MLDSA65::verify(&mldsa65_pk, msg, None, &sig).is_ok() { + eprintln!("Verification succeeded!"); + } else { + panic!("Verification failed! -- figure that out"); + } +} + +fn bench_mldsa65_lowmemory_verify() { + use bouncycastle_mldsa_lowmemory::{MLDSATrait, MLDSA65, MLDSA65_SIG_LEN, MLDSA65PublicKey}; + use bouncycastle_hex as hex; + + eprintln!("MLDSA65_lowmemory/Verify"); + + let msg = b"The quick brown fox jumped over the lazy dog"; + + /* One-time setup of the KAT -- commented out so that we're not capturing keygen in the bench */ + + // let seed = KeyMaterial256::from_bytes_as_type( + // &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + // KeyType::Seed, + // ).unwrap(); + // + // let (mldsa65_pk, mldsa65_sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); + // + // eprintln!("pk:\n{}", &*hex::encode(&mldsa65_pk.encode())); + // let mu = MLDSA65::compute_mu_from_sk(&mldsa65_sk, msg, None).unwrap(); + // let sig = MLDSA65::sign_mu_deterministic(&mldsa65_sk, &mu, [0u8; 32]).unwrap(); + // eprintln!("sig:\n{}", &*hex::encode(sig)); + + let mldsa65_pk = MLDSA65PublicKey::from_bytes(&*hex::decode("48683d91978e31eb3dddb8b0473482d2b88a5f625949fd8f58a561e696bd4c27d05b38dbb2edf01e664efd81be1ea893688ce68aa2d51c5958f8bbc6eb4e89ee67d2c0320954d57212cac7229ff1d6eaf03928bd51511f8d88d847736c7de2730d5978e5410713160978867711bf5539a0bfc4c350c2be572baf0ee2e2fb16ccfea08028d99ac49aebb75937ddce111cdab62fff3cea8ba2233d1e56fbc5c5a1e726de63fadd2af016b119177fa3d971a2d9277173fce55b67745af0b7c21d597dbeb93e6a32f341c49a5a8be9e825088d1f2aa45155d6c8ae15367e4eb003b8fdf7851071949739f9fff09023eaf45104d2a84a45906eed4671a44dc28d27987bb55df69e9e8561f61a80a72699503865fed9b7ee72a8e17a19c408144f4b29afef7031c3a6d8571610b42c9f421245a88f197e16812b031159b65b9687e5b3e934c5225ae98a79ba73d2b399d73510effad19e53b8450f0ba8fce1012fd98d260a74aaaa13fae249a006b1c34f5ba0b882f26378222fb36f2283c243f0ffeb5f1bb414a0a70d55e3d40a56b6cbc88ae1f03b7b2882d98deea28e145c9dedfd8eaf1cef2ed94a8b050f8964f46d1ea0d0c2a43e0dda6182adbf4f6ed175b6742257859bf22f3a417ecf1f9d89317b5e539d587af16b9e1313e04514ffa64ba8b3ff2b8321f8811cb3fb022c8f644e70a4b80a2fbfee604abb7379091ea8e6c5c74dfc0283666b40c0793870028204a136bf5da9568eb798d349038bdb0c11e03445e7847cb5069c75cf28ac601c7799d958210ddbcb226e51afef9f1de47b073873d6d3f97456bede085082e74a298b2cd48f4b3093155f366c8fa601c6af858dfa32c08491b2a29887f90335949a5d6edaa679882a3a95d6bf6d970a221f4b9d3d8cbf384af81aac95e2b3294e04789ac83727a5dc04559f96af41d8a053516feeeebc52746eb6ab2819e09108710d835f011fa63065872ad334d5cdffb2b2310507e92fc993ae317da97f4f309cdaf0f67ed99d90215576083849f953b246d7fedb3fdb67679850a5ad404e64147fb7cf4f6aeddd05afb4b834968d1fe88014960dce5d942236526e12a478d69e5fbe6970310b308c06845018cfc7b2ab430a13a6b1ac7bb02cccbb3d911ac2f11068613fbe029bfdce02cf5cd38950ed72c83944edfbc75615af87f864c051f3c55456c5412863a40c06d1dab562bdff0571b8d3c3917bbd300880bba5e998239b95fa91b7d6416d4f398b3adbcd30983ed3592b4d9ef7d4236fd00f50d98aa53a235ac4172720f77d96172672980cfe8ff7a5a702783edc2ba31b2259015a112fc7f468a9c2f9464039002d30ef678b4cb798bc116216bf7a9a7c18ba03b7b58fd07515d3115049d3614be7a07e744300750df1d2c58753389059eafc3d785ccdd31c07648bedc03a5c3b8ad46d064d59c13d57374729fc4e295362e2a5191204530428bc1522afa28ff5fe1655e304ca5bc8c27ad0e0c6a39dd4df28956c14b38cc93682cefe402bbd5e82d29c464e44eb5d37b48fc568dfe0cc6e8e16baea05e5135590f19294e73e8367b0216dbb815030b9de55913f08039c42351c59e5515dd5af8e089a15e625e8f6dee639386c46497d7a263288774de581a7de9629b41b4424141f978fb8331208efdec3c6e0de39bc57063f3dcd6c470373c08891ea29cbc7cc6d6483b8889083ace86aa7b51b1c2cfe6e2ad18d97ce36fbc56ea42fae97e6a7ac114864478c366df1ebb1e7b11a9098504fd5975bdf1f49dc70002b63c1739a9d263fbad4073f6a9f6c2b8af4b4c332a103a0cffa5deeb2d062ca3c215fd360026be7c5164f4a4424ef74948804d66f46487732c8202c795478647b4ea71d627c086024cca354a41f0877b38f19b3774ad2095c8da53b069e21c76ae2d2007e16719ed40080d334f7da52e9f5a5990439caf083a95b833f02ad10a08c1a6d0f260c007285bd4a2f47703a5aef465287d253b18ac22514316210ff566814b10f87a293d6f199d3c3959990d0c1268b4f50d5f9fcefbbf237bd0c28b80182d6659741f14f10bfbb21bba12ab620aa2396f56c0686b4ea9017990224216b2fe8ad76c4a9148eef9a86a3635a6aa77bc1dcfb6fba59a77dfda9b7530dc0ca8648c8d973738e01bab8f08b4905e84aa4641bd602410cd97520265f2f231f2b35e15eb2fa04d2bd94d5a77abaf1e0e161010a990087f5b46ea988b2bc0512fda0fa923dadd6c45c5301d09483673265b5ab2e10f4ba520f6bbad564a5c3d5e27bdb080f7d20e13296a3181954c39c649c943ebe17df5c1f7aae0a8fe126c477585a5d4d648a0d008b6af5e8cd31be69a9296d4f3fd25ed86f221e4b93f65f5929967533624b9235750c30707550b58536d109a7131c5a5bbe4a5715567c12534aec7660761eebb9fae2891c774589b80e566ad557ddef7367196b7227ea9870ef09ddfec79d6b9319a6879b5205d76bf7aba5acf33afb59d17fc54e68383d6be5a08e9b66da53dcde008bb294b8582bd132cdcc49959fdbc21e52721880c8ad0352c79f03a43bbd84c4cdfdc6c529005e1e7cd9a349a7168a35569ba5dea818968d5a91466bd6e64e20bf62417198afc4e81c28dd77ed4028232398b52fbde86bc84f475b9016710ce2aabc11a06b4dbac901ec16cf365ca3f2d53813948a693a0f93e79c46ca5d5a6dca3d28ca50ad18bd13fca55059dd9b185f79f9c47196a4e81b2104bc460a051e02f2e8444f").unwrap()).unwrap(); + let sig = &*hex::decode("9061f15cbf2092f744fbcd799eb02414053c1b0f7412124bedc41cf9a3db0166469e874037d7f081e5f8d3d2033a0307d1c49ed01fe64578c4a6fabd80880cdf1911848f184d4bcf536ca795a0fb1aa19ab7ee3ba6b58bd64bbeac9f58650fff1ef5a97ab6916df962072e20e7c6be96090e3a781a504bc4442bd8889a0aa628907a74299f39fa836031f1bd68355bebe7ae93c1e361a9efbed1325d96227070461fcd6f151b8669d9229b977d9ee51fd2260c3e4a2e820416f9e074958dc3b3e2217e6312b7e0b582a048981cf6579f4bc7715b78c808e4c57e3b8aa38b05c04fcedf209f52c1e331ae83dbdff60ba450a17cc397568e54bc3f16ddf30b92747ce460d925b9be20a1d35e2aed97f124af2616a5361df28ba30e522dd08fa00fd28d1ac484d756a89e3a442fefe8332c56cd2a9fde691bdbda43f1cc54cef57bead96120b50c7d4695bdbb1303cc5ddda898e4eeb83083176e40e0232cdd1c3150371df05d6fdad7e1164d90393cf308e99edfeb31fed263e2866ee3b7f3937b399c974d87ba7b489efe3c9b80371d2928446adc31991ab0cefaaa080575b9ec81cfa133a9911c035a8058d0d3f2e34de4a9fb009bb4ccdb16de7b908574a7496725ff857556c1b33917e986c80f1014a9e3083add2fb35f345c5d06159e443329d0da099987b996c3731592b460c2ffd2955f7546f4216100ba43188803ff9b36969685f909fa2539323b8c8ec1c095a5085e554dd450e0e67ab670b6a11ebf6c25520fc13e364060f91f9b7f3d5cb48ff28b8fc83d4293f1f35ad6ff6ae4574ad7a1c6005fc0389a7b21386b0850a05d832fe6a14bb2b1db1f8e20bd09174946cd098b81c8f797e95f2143a949770cf1219bfef039db51a80fc247f65f41554c7173dd805ba82fdf47ab6d4bfd37dfe46fc47904421ae00dc005a22f9c4784b0ea9e665392a412245016d5c6d7673a6a180d228d4255a538e451ffd8b414d40304c0c888992e0ab6de1602109527417bc1c7eb782ae77a8c3cdfc1d13a1e874207898264e38080243109c5969649ac8383417e922ba115331142d0ed35440b15d40bee0cf58af37c0f0524ffac1c71ceed3bb82f76ab108a8ad1a0c8b78d9341148c642369be7bef59d46f49d70c83560607f140848ec9a7607d4a08f8b6e4447f5523f416981888a8de9647ffef79389e4983e5c9387698d0cc2d429322365ce7e7b5fd6d6eb921c813fcf06199fe1ca41e9cfe03b539f321671a2acad0963f876f9db7a1c4371b9f101005217995b5b6a40976246d245da603dba8dac812a5480c3476a99d0ffdf0ef943d72d912543148b2fe78e8b0159324fe9bcd4ced33cd212fe4f3dfd6d4c5e1958beb95ac6b533ace3e78015e3880b52bf45299263a4c0096f8ba5fe3a6298cab675cb7f382e7ef49720eb4cf47376e2d2574122ccf91129c858e948904fecefb91226ed42403ba12dd3258909a87dfcbf65cc3adc3d98d277fdcec7664e2292b7d27afbb5aafb405c20a34b2fe2c0849ee280bb891dfdf59f19b89b0246358db54cf3fdc66eaaaa750c8903f1d42678f3edf0b7530410aa881bc617f94346379854af4532e61f65aae7576c35faf55e155bd6787b4634d54191907e155c239e68480cdfa0c87054bfb62855f409a20d5335fb123e681e64ec847cd985b6062059f436aebac623c038b6c3405ac325191a8d1126a5ef8f38cccbf144a5c324c1e093cf99efbe10ca03d439bcfb8ba5e293b7d318837f7bc42a99964392369da76e79d71d1a2c248a11324a87ae1e3cbeab6fb0d0bcae1ef55e43dfb6f1b4cfb82c7a778fb828a3727ef07685fe38a74b3dd25d015322c2d9f245c08d8c2b43865694233782eb734436c4eddef5406208d6c4572c7371262fe02319cfbbcf2e23bed8aa969d1ae6f5f25ff6b8ebcf0925066f761a39bbff49f0c8dbc3be84f0c442b044ea01b669747e3c8293cfe9ccdf2ef063ae3d28d10720c279a2691616abd23b055cfc6c562125df4ad0fa6631304972ddc3674b1aaa7665bf621320d83eac8d5b371d7d719829f58b23458182558710de31d81ef9a47d8839c79640b2025d1965a418bc90e4115f1423311a8b64fcde0f2d2145ee535b0931b84bc8110445f2ff68d136ed709ddb7ea9ff75f3b4e8b4f836230ca9e81069477f634e07270af60ef96f72557a081d664abcf35548f699484653da645483ff2bf5998617ae8bfa62d56e714f3c0136e5035a3f78e06c2f470df7fd3380d14033f81e2aae6b4d90487dab76b9b3b8761fb56c36f5429da3d4346cb22e641ad8d7d2d80fa240d4e0154e6b3d2f1b3ef6cf174c08d062f575c83a4078174f874364df36a6328beeef69ba7f90e1df9fdcec9a2f15ebf04fa7d6756da2e5a59c9cbbcbc397d6fb28d0fc9a60534dff0752716ed079ad1ab19a224d1c8ae8a53242fd164989ff997489b6520eb3c0e97f4bcc1a9c3cbd44f008c03ef52cf7e626881d246925e0336c0ac668867f853da7820f914115a7c77ac31b66f46fbf97f66fa26416fc4581d459a4f2462d52cf0c79b278955aa73e8fa56e3c320f516bcc54c97e587199c15ab953cc37189b81c70cabb2559e445bcc9d8174ad7574e9acb02f43e0c34ff5e6746ee730ad41ff8eef93c2071c2649063dd92f343c06ef6abaf98f28d98d968071c12cc10a90c22d8b3b3480c76f7a51b7ec594b3435d2e3d779c1a15037697f3a058650472e47eecd5f32eb3243a516f0e703f9888c84690750648d6a9a876bf1f353db6891dc6d317d6e87ac088f42b5f6f20d799ece4fa7aaa928d2ac795e8de83d1e1c7fa2f9a4106693e981c21c63b3221c4fa2649f45f0c6e05dbf24011af16ab2e5fe94a640b485988037ebe1e8ad0b2623d95e9947f0726121d7828614e3b2d77a7a1f9a938bea9a1a7a2627b7d2e358c42ccc6c0b80a15a1c2f6e9aaf0495bdb7bb8d4b0e28a1ab5ab93ca0ff3e3f910c490c13486852534d5e12160835ec5916c5c68349c4e2d8fa956c643277edd3b6c81c88c010421705fd317ff9e3c94df0ed5305f530acbccf8dd0e87140cd38152664a572c168cd72595b7fac243c03f3fb33ef74a28c0e4469f94587c13704e9efe8010b2125aca78c22c33c82366e1a7c4028c2ae2e8d26e1a57e4297fac987f84a0a27f42b4c93a4f4d14569824b0880fb67407ed58f267ac403aa0b1f93784b4b4c67036037e60d58072611b0e90ca316976ef4e0b302cdad1b6dcca92efb8e1f6be2397967508be2c02a25ed0380ba1f7955f857c8fb043297780d136b2b064040c8e55143d715ea997e134ed973c98ef82786f0ccf66c17d863542180c66d54d08e116f2e35d995e214489ad0fad7a55fe9ebc1a777fe34141147c080b98d13463a3bbc6fc82f2fc95f4de7b3591d9c8cd4416917a4338095d5620104b7be13f5a131dd3f7aad5b559d11e8171dfb91e2bb1e47ac3810b1cdc1a1e370c867b7b7b50c4688dce545763157e02f47e1cc661d5bf2fbc336cfae080ab15728b1ab9dd199f2779d451e6178977fb658c17344cffb7aa3af5791a28fc8a089c85187753e5e313c8d1f0fe7755e28be444426a189e8bce2d2f79db31d4c3ca911a83455525355f95d159351cd731a88e55403851236ee2128f279d5be644c042453ae65d9e9f3b40d6c82bdeb002acdee061ecca3f2dceabef9a900e6e063d56ab39cb82dbc77a4677572d7616cd72c0f6d5b9b941dfda1fe7c896b8cc24d65a4322d712a84e94adfc8ed0cc56cc1ae97f775bd3cea5b20b524d9a7a916056e19af095d30171e5e14c7c998f78dc44845edf307363eab7913f680a5e5a1540a6f945507ffa67591f8d1a2920ab3b6e754e35379dd67870c242335e2717903ff3c687e5c33dc953416865d5f23bd752e55492b9d5d888d7b37ef33b0a6774d052b0987c066a2e01767207aa7fbfc393ca62874613dde3794f74fadb5d55b877b877a605918c812610fbcafad72ee245e6dd8721138d6bd3f4eedad853aed1ec437ad02ac937c80dae26fa5f70083bd346779b779387f7b3d2aae57770d8177928833281ccb7a38da24834fd9726fd17eb603cba9041e82bfeed0e33942dde1d48c271f5b39aa7230f41afb89d36f7976eee4f51a036743031c534f64685b94c990a93a5737fe628ee9cda8ed9c08b11d3836f833835c445b317a77ead7599d1a0c08873014510d36bb7ff5fb961277589ea48c32a60c87ec40681be067b17785ec44825bd89faa25249e735a628b6eebcc6cce4e0314c627588118c40b2e0d460d8d5ce358c56458f36914ca203f5a5381c6deb5a76bbc08c40a87437da0d0b571788a05e9f96d9bb770de8a0b1b960ff2a44a964c9b7939853742e83ce8deb79191b2d82454655f227079dd8c5b0216c8470b8e1ac70526301bbfa2bc4adca68a766ccb2a6e0ebf2e99905bf5242590b01703868b3faf841c11c383be145a40fea6375e18a01468e459603b5efdf8a4e9abd179280ae8b5947d78d2f0c4d37715eaa42bc37cf8730e41ffbf9826d46424f2922a96033cefaa8b4bbe4c8b89d43501fd5211d5392ca19a98ba127d9025b5c6e86ba024471940549a2b5d8e14961c9dc19696da1a5bffd01030d5e6100000000000000000000000000000000000000000000000005090f131a1f").unwrap(); + assert_eq!(sig.len(), MLDSA65_SIG_LEN); + + if MLDSA65::verify(&mldsa65_pk, msg, None, &sig).is_ok() { + eprintln!("Verification succeeded!"); + } else { + panic!("Verification failed! -- figure that out"); + } +} + +fn bench_mldsa87_verify() { + use bouncycastle_mldsa::{MLDSATrait, MLDSA87, MLDSA87_SIG_LEN, MLDSA87PublicKey}; + use bouncycastle_hex as hex; + + eprintln!("MLDSA87/Verify"); + + let msg = b"The quick brown fox jumped over the lazy dog"; + + /* One-time setup of the KAT -- commented out so that we're not capturing keygen in the bench */ + + // let seed = KeyMaterial256::from_bytes_as_type( + // &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + // KeyType::Seed, + // ).unwrap(); + // + // let (mldsa65_pk, mldsa65_sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); + // + // eprintln!("pk:\n{}", &*hex::encode(&mldsa65_pk.encode())); + // let mu = MLDSA87::compute_mu_from_sk(&mldsa65_sk, msg, None).unwrap(); + // let sig = MLDSA87::sign_mu_deterministic(&mldsa65_sk, &mu, [0u8; 32]).unwrap(); + // eprintln!("sig:\n{}", &*hex::encode(sig)); + + let mldsa87_pk = MLDSA87PublicKey::from_bytes(&*hex::decode("9792bcec2f2430686a82fccf3c2f5ff665e771d7ab41b90258cfa7e90ec97124a73b323b9ba21ab64d767c433f5a521effe18f86e46a188952c4467e048b729e7fc4d115e7e48da1896d5fe119b10dcddef62cb307954074b42336e52836de61da941f8d37ea68ac8106fabe19070679af6008537120f70793b8ea9cc0e6e7b7b4c9a5c7421c60f24451ba1e933db1a2ee16c79559f21b3d1b8305850aa42afbb13f1f4d5b9f4835f9d87dfceb162d0ef4a7fdc4cba1743cd1c87bb4967da16cc8764b6569df8ee5bdcbffe9a4e05748e6fdf225af9e4eeb7773b62e8f85f9b56b548945551844fbd89806a4ac369bed2d256100f688a6ad5e0a709826dc4449e91e23c5506e642361ef5a313712f79bc4b3186861ca85a4bab17e7f943d1b8a333aa3ae7ce16b440d6018f9e04daf5725c7f1a93fad1a5a27b67895bd249aa91685de20af32c8b7e268c7f96877d0c85001135a4f0a8f1b8264fa6ebe5a349d8aecad1a16299ccf2fd9c7b85bace2ced3aa1276ba61ee78ed7e5ca5b67cdd458a9354030e6abbbabf56a0a2316fec9dba83b51d42fd3167f1e0f90855d5c66509b210265dc1e54ec44b43ba7cf9aef118b44d80912ce75166a6651e116cebe49229a7062c09931f71abd2293f76f7efc3215ba97800037e58e470bdbbb43c1b0439eaf79c54d93b44aac9efe9fbe151874cfb2a64cbee28cc4c0fe7775e5d870f1c02e5b2e3c5004c995f24c9b779cb753a277d0e71fd425eb6bc2ca56ce129db51f70740f31e63976b50c7312e9797d78c5b1ac24a5fa347cc916e0a83f5c3b675cd30b81e3fa10b93444e07397571cce98b28da51db9056bc728c5b0b1181e2fbd387b4c79ab1a5fefece37167af772ddad14eb4c3982da5a59d0e9eb173ec6315091170027a3ab5ef6aa129cb8585727b9358a28501d713a72f3f1db31714286f9b6408013af06045d75592fc0b7dd47c73ed9c75b11e9d7c69f7cadfc3280a9062c5273c43be1c34f87448864cea7b5c97d6d32f59bd5f25384653bb5c4faa45bea8b89402843e645b6b9269e2bd988ddacb033328ffb060450f7df080053e6969b251e875ecec32cfc592840d69ab69a75e06b379c535d95266b082f4f09c93162b33b0d9f7307a4eaaa52104437fed66f8ee3eabbd45d67b25a8133f496468b52baffdbfad93eef1a9818b5e42ec722788a3d8d3529fc777d2ba570801dfae01ec88302837c1fb9e0355727645ee1046c3f915f6ae82dad4fb6b0356a46518ffc834155c3b4fe6dafa6cc8a5ccf53c73a0849d8d44f7dcf72754e70e1b7dfb447bb4ef49d1a718f6171bbce200950e0ce926106b151a3e871d5ce49731bd6650a9b0ca972da1c5f136d44820ea6383c08f3b384cf2338e789c513f618cc5694a6f0cee104511e1ed7c5f23a1ebfd8a0db8424553240156dbf622831b0c643d1c551b6f3f7a98d29b85c2de05a65fa615eee16495bd90737672115b53e91c5d90028cf3f1a93953a153de53b44084e9ccff6b736693926daefebb2d77aa5ad689b92f31686669df16d1715cc58f7a2cfb72dd1a51e92f825993a74022be7e9eb6054654457094d14928f20215e7b222ac56b51adbec8d8bdb6983979a7e3a21b44b5d1518ca97d0b5195f51ed6a24350c89747e1edea51b448e3e9147054ce927873c90db394d86888e07dff177593d6f79e152302204aeb03be2386af3e24078bd028b1689f5e147c9f452c8ceb02ec59cc9db63a03576ceeafe98239023897da0236630a53c0de7f435a19869792fab36e7b9e635760f09069e6432e700035ac2a02879fff0a1e1bec522047193d94eb5df1efd53eea1144ca78940852f5ec9727904b366ede4f5e2d331fad5fc282ea2c47e923142771c3dd75a87357487def99e5f18e9d9ed623c175d02888c51f82c07a80d54716b3c3c2bdbe2e9f0a9bbaaebeb4d52936876406f5c00e8e4bbd0a5ec05797e6207c5ab6c88f1a688421bd05a114f4d7de2ac241fa0e8bedff47f762ddcbeaa91004f8d31e85095c81054994ad3826e344ba96040810fc0b2ad1de48cfade002c62e5a49a0731ab38344bc1636df16bf607d56855e56d684003c718e4bad9e5a099979fcddeeb1c4a7776cd37a3417cb0e184e29ef9bc0e87475ba663be09e00ab562eb7c0f7165f969a9b42414198ccf1bff2a2c8d689a414ece7662927665689e94db961ebaec5615cbc1a7895c6851ac961432ff1118d4607d32ef9dc732d51333be4b4d0e30ddea784eca8be47e741be9c19631dc470a52ef4dc13a4f3633fd434d787c170977b417df598e1d0dde506bb71d6f0bc17ec70e3b03cdc1965cb36993f633b0472e50d0923ac6c66fdf1d3e6459cc121f0f5f94d09e9dbcf5d690e23233838a0bacb7c638d1b2650a4308cd171b6855126d1da672a6ed85a8d78c286fb56f4ab3d21497528045c63262c8a42af2f9802c53b7bb8be28e78fe0b5ce45fbb7a1af1a3b28a8d94b7890e3c882e39bc98e9f0ad76025bf0dd2f00298e7141a226b3d7cee414f604d1e0ba54d11d5fe58bccea6ad77ad2e8c1caacf32459014b7b91001b1efa8ad172a523fb8e365b577121bf9fd88a2c60c21e821d7b6acb47a5a995e40caced5c223b8fe6de5e18e9d2e5893aefebb7aae7ff1a146260e2f110e939528213a0025a38ec79aabc861b25ebc509a4674c132aaacb7e0146f14efd11cfcaf4caa4f775a716ce325e0a435a4d349d720bcf137450afc45046fc1a1f83a9d329777a7084e4aadae7122ce97005930528eb3c7f7f1129b372887a371155a3ba201a25cbf1dcb64e7cdee092c3141fb5550fe3d0dd82e870e578b2b46500818113b8f6569773c677385b69a42b77dcba7acffd95fd4452e23aaa1d37e1da2151ea658d40a3596b27ac9f8129dc6cf0643772624b59f4f461230df471ca26087c3942d5c6687df6082835935a3f87cb762b0c3b1d0dda4a6533965bef1b7b8292e254c014d090fed857c44c1839c694c0a64e3fad90a11f534722b6ee1574f2e149d55d744de4887024e08511431c062750e16c74ab9f3242f2db3ffb12a8d6107faa229d6f6373b07f36d3932b3bdb04c19dd64eadd7f93c3c564c358a1c81dcf1c9c31e5b06568f97544c17dc15698c5cb38983a9afc42783faa773a52c9d8260690be9e3156aa5bc1509dea3f69587695cd6ff172ba83e6a6d8a7d6bbebbbcda3672731983f89bc5831dc37c3f3c5c56facc697f3cb20bd5dbadbd702e54844ac2f626901fe159db93dfd4773d8fe73562b846c1fc856d1802762840ebc72d7988bde75cbca70d319d32ce0cc0253bb2ad455723ee0c7f4736ce6e6665c5aca32a481c53839bc259167b013d0423395eeb9aaaee3206149a7d550d67fc5fdfe4a8a5c35d2510b664379ab8f72855a2af47abce2a632048eaf89e5cb4a88debc53a595103acce4f1cff18acff07afe1eb5716aa1e40b63134c3a3ae9579fa87f515be093c2d29db6d6b65c93661e00636b592704d093cc6716c2342eb1853d48c85c63ac8a2854462c7b77e7e3bd1eac5bca28ffaa00b5d349f8a547ad875b96a8c2b2910c9301309a3f9138a5693111f55b3c009ca947c39dfc82d98eb1caa4a9cbe885f786fa86e55be062222f8ba90a974073326b31212aece0a34a60").unwrap()).unwrap(); + let sig = &*hex::decode("781368e64dba542a7eacbd2257335cc943a03241009b797093c615f76a671a7591430441d80bb582304b33b9fce295e0dd57fe169355ddf4453a2aca62d8eb8109ef0d9cf3f5b0a94e04ad81b3e786014243ecde816551aa7fe01c639054256a491756bef59f5034f717ff4f85e70ba7731a49971415b6a7e7d816ab434b9f17a3095ede6fd432be2bfa82724045dda0dfff7a0281e9000939ccba3d8ab3245139c441648c76a6536127e4d1ef0df1531883ab78c8b41323617ad8db03d9908c9e08a9f7321c45051b3c94213347b11c4a84491de7a7be68701e47d7f0e0b33e767bef17694e4d33244ed92ebd74c85ab6c84441cddc14331e6ae8bd23674bda27f09c050d88f7d430feee7f15a72a24d653bb6bec54491b98362ce131d37c7d78a3f9a893db5abdccd6663593b88bc6c97f07f8eafccfd25e8180d918efbcd95bbf3da29f081e3e1932095939198e2a155b2d803a3e84ca4f34569df695c259faf3c0d8f0cd217ebd2dbad542b32fbb54e44aaf0b5dc739fafef2e46db8d68bfc35f44f038cb1f5231a1b5b134ae683e7f3297cc7a95bd191b310f68201450797fe3293cde1672dfeca4b493f53c768ea048a972a4cd84d39ef682957b8f28ba29487b4689b43fec2655823d9bb99ffcf31490366a9860a5d5b8e32a3b8bfeb6f55f88fb80c8c0142086f220e1f6f2862dabda58c3b6f5faa805b39cfac4b6d7ea7acdf1b0690063b0c1ea38c7c4755189966dc631055f153f71b77b114fa5c309316ba512330ea5cdb0bb176001e57461563d17259f35d0c30ef5ac838c0325402bab52c531469526ae3ee6293f7b5769d27e69fa81cd25a31cd095b126e70c57ac3169a5f585a11f1748d9d22f2564911c26a24b2153a78f3a06822f5f1963f237abeb48efd9a9cd478de579c5a0ba84d00e96fbde36d8ce20e7e948547fb6850834ff79d211830f6ee973359781d9d5008fb43a89354782fde4158177f5206ce1d38c889e99e4bb5b4ab34d6a05c42f5d719ea03dbc54adba75a3bb44a3c08c7556462f8c5b7c568a69242cf5be6098eba0a2249c1ca5b2109b6404a962abc1c159c6b48a79fb97e4a3337d99323746221297423f9bd1b12e78489e01e6a10f0fd6bba1cfd6ae1b75dbe69f8b8ee51a4e7f68ba2c407c9c0bad3892b29b0170ab75836fdd49a7ee3c2bb30f2c3d226bcec49140952170b0d160f97b30b7b7b096719538677ebb06922f26925227c8852acc107a8f173b38d96697584bd3dfe169b4073aa58a7bf371d5c4bb0eed30f08212defb3aec902d4546084176bf0f86d93cf36a4689a5e874b32d6b7d3c1e3fbcfd988c35dbc9a8d0a019ad6d7e15ec3ac97125db6abfe00beffe35a81666699a91e15945c62d646690b5b52de8b835ee9be53588fde5d63023b52b2b1f4610c237a829f5901a46042963cce7b85aa040adde02985e14e23c4eadb75221c607d24672e244c66c9c24c3cb7fd90bb23295c9d3d9da516bce3dd462d6660f9f91ef0618a4d4d3d6668c5d1e2e8ed433ebfe0762beb743324e11608f8b14b69ce4c221c1654ba4992a5af2d949c2939f95d1c8fb767af2a843cc7c78f57259d5c0c6ca83fca41ef5ecc4eeeea93e4518c24d3040f2cd90df3e535e989e606fa109e2c453ed7353db1cdb27137f005f9dd8d2aebfb7255a6098b690215e100cfe44ca0f2745fced48322bc9667ab16d2e1c0ff491b96a17b833d4fd44d31c2230ac835796e063ab03000f04f15c70560033763a48552cceaacade9ca5c8055f3745e179068a287183f2bc3ef6327dec5ac7cf7b052ef5a8873e697efde089688f43be464827c2fa83eb531b3674145e95c699c82990e684967dad319d9f64ab16cd9fe9b6c41232ac4ae3795fd8a76aa9b02e970242061c6da45a2af74ad9cb2a79935c92625e242f4bc7fce54d5c10a9e61f875162fa651b66057ba036f062d6d39d0502b93a5640b78c6c2fd20b02ff83676a87a94945d476c349803ea4fe60cdcea65bb2629e2bc09d4472ec63422dee2052f098deaf5531e6c9bed6672a8b699802efe0cded80c8455f585d1ba633d281f1a21adab48e63b44e0c2a4d7608cf98aabf8adc86bcb8f61e8b06cd2385f82e0a3cdd03cab152d5951859c4532f9168e78f17ba2a5772780327dcf4e62b4d26e443762fc488ae4cd4d1156dbd5782595cfd7697a514abc9b160c9ccf08edc86134a755b90e9bb543511e888e3157721a52d1bc5db33029fb335ea2114e21c03368c8d7f4d827960641772a4a32a738df60d19ec77ab09d22f57cc2523b9503b3f5b1cebc5ae15f885f159842db7359a1c89d3d82d3407068f15b6739626eb8c521fc8c5c7491f945d49f14e6989da340bdf49e7f8a792747aa658bc114143ba93f26022d001735b744639bbf22aab2a1851cfc934f9c69d3764fdea3d23db17998e6138cfd7cd9e9a47cb74193bd71aaf28cdd9d1eb595125546a4f4357ebbd1f410e3bf8557892de68509b5b98c5c229e942c910fdd3e54cb6ad54d8dd886cb97ecc06d1e401b8395d0bcb0db9a031dc66c9294f9053c68fc42042b1fa1671fc7d510b70916c0139cfebe3a91244527ce9439860cedb30908197be851cbd1d3b18ca541358449fb34fb5cd569630ed5f67b8795e87828f2ce3becfe457579d82333b0bbab094de391e1f8157bd431e365ca864630932bbebb48f45f8134424e18ab455029b54b19e2f3bfec5e44ad0ea5c03f53d8f925b635838aa7015a7c9e325bdfaff966ea9512dd50f87c8995cea7561c23f4fb06d964ab8f1913a6ca17e4ca60d6bc078e1784f89c673c91d955bcf45f58ca9709579d5e3831df12cfdb7516fd21878cb54243579b9346d2de4be25f508e84b1adc78cb91c03da3c4fd59e4529189838f74f6312820620a5996b791ffcb332f847094613f2148b862034fa89d0d0ff1808d902c5d1af64d5522492d61ecde4c73be89a33782cef1acc1dc327fb2eb9d17642209b85aa8b1dc57cbf067c7aa29da6b7e157d23e171d3ae6f3855834071791402c851ff2dd67109979f7ee5e09e64b4eefdee7112b55ce200bb8c8051e3428c305fa1d576bffbb25a70eb571168fc60dadd928b10cfd07de80a85b8df3edc372d488c21f0d5787611cc6fb73aeeb6f920a109294b49d3870f90de3b360d14df77ef95640bcac7a4dbca901a31db83e83f5c59ce327207ea9b27c3b978d30d53865c1b84764f025e8732d5007554ce5c9cc410b2eefd7e4d990c538557606a6bc47577a43768d30aa3e8598fd6f4fe7ac439f3931c58fd69d90765ac9f456ac7de085e14a0898c4557f5d3baaae07edc607de6900146b97b35aae570153dc107815ef9febdd4fd567d637fee8f8bfb4b3413ea6aead4846ab733a04f1e4bc32a3bbb1c16baf8d0bdb9ccb82fe46479ccfd040b5e64064e539b39c66e4501dc822873ac6119a4a112a1f7cd6df0e5f84356ce853ced34ce69a9e7383534983c51c50269bf8b9586a0e5ba905fd3bce080b00e7f7d48e55f489479b5771e995fb020e58feb74af65c3ee76aa4e69b5ba8bda249a1b2d62c08d418c3635d061846040843991ab475473da85d94981fb84425e7ad951ce0a42be642fe658b7aaae72b147cdc086c24b1571eb2272e2a72b15660d854ebe19ec7d9ab7ea17800d0b6ae727b39217467c662ba08e6f19193951eeff02806a7843eb5c71b2f04dcb605ecedc5128cd67703038c44bf20fe06f3ed8c1368fe38e72944d5c52fca46a45fd48d8fe5da64183d4d62ec01aa3d9d672ec67a01c17f21e02525f0513cce030c664fc8784763086608bc8099c204c255ffed1daf432ceb45fbd135e21e8190c5bfee192171faa77520481e69e87b7f76790bef76cb8d3c88f5c6e32fc59e7bd45351d66696b61d9f40726fb9a98000b68738cf7e34b98b6a4aaa2ac1d7b1407db89783f8077103ea9c9e89247eae078adfb36e21474c3bb1fe0c87687c6233a533a01e1081b93a3521d339f39c075609bace531994988ae314f77fc6034113a138c67eb7e03750cbec8d28bd21afedfefa8f091619ae500b4ca4599d019dc8ca4bf118d70b8676dfc796a4f6d986adba4c8574ed4abaea5465466220e5e53dc8fcb395d1e59d278673cbc4e3f40658df98ac2fd126a94922879e1a3be91c1acc20803c35fa764abbadab07bde85ff4bd9e0fb6f06baf5bf42b8a2cbf6c2f62606becc361552921a12d6c8236fde84db4bddac77e8872478cffc4e148c1c7acfedf6b17d98731c2de36f3cbef1f6f781a940e0874d5b74535bbe066b53064d43b13926570a9e1c4e6da206c8bd252caf2b62e7d223f7ac12939137f330be59374d7295a6c2dff92e07c727510e48d970593e47229fc8bc3bd5b8ea780dacff4d23063df65feda5f8f65b17a333e532acad7916780c74d6a70d38b367f3f6f4e947b85fc15235bbe46b26495d2780098db853a931377cfbedea620f2355ca21e81ce9e0078b0dd6cb70f23ed558682be3b3d594eefe85344e1f275428b316cc088995939298f2a2d15ac9b676ac3e9cb92f2a64dec7732a91fc761aa1b126ea575e3953177da6e1cd78faea824665330a81d9e24572b9860bf0aba4df8bd5d4e3e2c72bbfbb2a985c7ae2f077951fe8401e1d156ecada1e353817b20f41e0b2460a0caaf2b36d6a7f1b35125d797dcc714421027d14171765a646071ed952b6a5294eecf6a3a71c104c843a4a8b3efcc27467b20cb0a94abf5802229ea4d8312783e78791a50b3c0a88fe6497198cd4bf470dac46f34e50019fdee2040cfe99124b312b1122b83e51d878877cec0855f1158c445cfdc2253f4389d5e3a8ba1669abb5976a4617e85f543da9f5e30b10ca7481c8185392782b46fb0a0e5ab408b2945e3c79a1cc49fb7c27254a9b540e7397a5b655bf7e4f83184db32a128aa2e00a624d7dcd6b77efd151f1e5cba8890af9170fa06c555715dc1787e995ad19270973ae95b88dcafdaf62c28843d3f8b9c78dc8e37d911dab3f7ee9d4c7389c654bdcfc05056b360020140e57e31473258a4081e2a708f7caba90c356d0847098fc0762484086aba898a60b023d6a3060402406240748785d51caff52a0ef3dc2a45dadf80ac18502d24422a8cbb10192b88f4e9160206b2ed3e04114f2a339df269e2c36b8613ce37087471701755330cb559575366ee0fa2d3afcda32eede6dd906345daaa04812198e96c42239c242edb90059709f497da5b87705384aef2af22dc2edfef3c00d8c9156d8b3163fe7a7779e04f04911a8b934fb3072eee844484fede5e2ee96d338eefe2da986067ffe0218ada7de1d0e42d823d6b033918278888ba0608ab8f7be997bdf263689a36f5204c802ad836363779b4b0d6ce5083df0b98a2e2c700062a4fa5e57bc73bd45357e01d90c7954bc6904d1ce8166a9168da39a60c5cae8119bb6b9ab074fb2d0aee384fc2c0e4806811d6002b4e2401e7430b50cb0e8075f33d5386aecde256e169d95e2f9c6556c08ba042e68a53ce8aca9cc02818f7382f150dc04de0019b19c7a3ab0d72d6ed013d7a115d74b279f71fd6effef34049877e0b11e0659be938a5de684eaf23513095eb4a1bdf536c3c01a4655c4b4a0673214cef29a481d06a02cc9a5bfc7b8d846c33484cd67b1de98f60b69918f177b64558ca567a6237d35ff01771a42320ce02bb98f3e4ad4ac7db75611bd9961eac662a38b1f785970c99f3dd105ff586f61301c48d66708cbf7d53a733e357b6c256e8b73f0e1305a0bc137989e521100c2ee6259e607fe12198e8bcb988b0854668e40d7cae6adc3ba40ba121b7319d06d988a073d03097b9f5c1c07284b6473ae57bf154811b77baceb0412b8a6983bdd0ccd9e3bf014e520009cb26d5780eabef1bbafa5e25d41098a54c47fce8b68d395291d54284d33aa50b9664d1510b467c8a539361ca9a4448bc01fcb4c4e3ef475e8afb46a494ae13ee9ea8a1266825fba7f32b9712fde252698a68359b50141d90f5c4a06283ddb54ad7e1412ac5ebb12501f7a82b2a7f27b2dbab626c3db4074523b3211d3182ea261397a6f7b187cf2b8a356ded10812f1d305169aedf79b5ff1cf7c2d6e86ee11f28e96aa63b5a03f59fc960ac7d0572e91dda61905c0711a9b26344a2a10aa2041f2b13cb1a9a9a27774b6d0deddc9d81ea1b142ad7b72be47991f2c9261d6708156e38d00b074020766eb0c494392d65b82ca65f7c3352f9bb78325ecb6df596c8ae57826b08ccd6f1d529d2e25925c1ac972425bedeb88a5d0e3138ddc434da3462ebdf6b1239a21f141ec62cbe4bb993ba253b55a76d30fac19c2c1384ef6b9746c07787aa1fe913a1348390bd8c1f386a08c77cf7106c927ce24dffc9d6ee1b32354d95ed2923482531de6b390bf0f5eb80276e90e7ed11131c848bbabec4d317236269a0a3a7cbe0f1272f93949ca6d23ea2a7ee3f697791aab71533423066d400000000000000000000000000000000000000000000000000000000050e181f23292c2f").unwrap(); + assert_eq!(sig.len(), MLDSA87_SIG_LEN); + + if MLDSA87::verify(&mldsa87_pk, msg, None, &sig).is_ok() { + eprintln!("Verification succeeded!"); + } else { + panic!("Verification failed! -- figure that out"); + } +} + +fn bench_mldsa87_lowmemory_verify() { + use bouncycastle_mldsa_lowmemory::{MLDSATrait, MLDSA87, MLDSA87_SIG_LEN, MLDSA87PublicKey}; + use bouncycastle_hex as hex; + + eprintln!("MLDSA87/Verify"); + + let msg = b"The quick brown fox jumped over the lazy dog"; + + /* One-time setup of the KAT -- commented out so that we're not capturing keygen in the bench */ + + // let seed = KeyMaterial256::from_bytes_as_type( + // &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), + // KeyType::Seed, + // ).unwrap(); + // + // let (mldsa65_pk, mldsa65_sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); + // + // eprintln!("pk:\n{}", &*hex::encode(&mldsa65_pk.encode())); + // let mu = MLDSA87::compute_mu_from_sk(&mldsa65_sk, msg, None).unwrap(); + // let sig = MLDSA87::sign_mu_deterministic(&mldsa65_sk, &mu, [0u8; 32]).unwrap(); + // eprintln!("sig:\n{}", &*hex::encode(sig)); + + let mldsa87_pk = MLDSA87PublicKey::from_bytes(&*hex::decode("9792bcec2f2430686a82fccf3c2f5ff665e771d7ab41b90258cfa7e90ec97124a73b323b9ba21ab64d767c433f5a521effe18f86e46a188952c4467e048b729e7fc4d115e7e48da1896d5fe119b10dcddef62cb307954074b42336e52836de61da941f8d37ea68ac8106fabe19070679af6008537120f70793b8ea9cc0e6e7b7b4c9a5c7421c60f24451ba1e933db1a2ee16c79559f21b3d1b8305850aa42afbb13f1f4d5b9f4835f9d87dfceb162d0ef4a7fdc4cba1743cd1c87bb4967da16cc8764b6569df8ee5bdcbffe9a4e05748e6fdf225af9e4eeb7773b62e8f85f9b56b548945551844fbd89806a4ac369bed2d256100f688a6ad5e0a709826dc4449e91e23c5506e642361ef5a313712f79bc4b3186861ca85a4bab17e7f943d1b8a333aa3ae7ce16b440d6018f9e04daf5725c7f1a93fad1a5a27b67895bd249aa91685de20af32c8b7e268c7f96877d0c85001135a4f0a8f1b8264fa6ebe5a349d8aecad1a16299ccf2fd9c7b85bace2ced3aa1276ba61ee78ed7e5ca5b67cdd458a9354030e6abbbabf56a0a2316fec9dba83b51d42fd3167f1e0f90855d5c66509b210265dc1e54ec44b43ba7cf9aef118b44d80912ce75166a6651e116cebe49229a7062c09931f71abd2293f76f7efc3215ba97800037e58e470bdbbb43c1b0439eaf79c54d93b44aac9efe9fbe151874cfb2a64cbee28cc4c0fe7775e5d870f1c02e5b2e3c5004c995f24c9b779cb753a277d0e71fd425eb6bc2ca56ce129db51f70740f31e63976b50c7312e9797d78c5b1ac24a5fa347cc916e0a83f5c3b675cd30b81e3fa10b93444e07397571cce98b28da51db9056bc728c5b0b1181e2fbd387b4c79ab1a5fefece37167af772ddad14eb4c3982da5a59d0e9eb173ec6315091170027a3ab5ef6aa129cb8585727b9358a28501d713a72f3f1db31714286f9b6408013af06045d75592fc0b7dd47c73ed9c75b11e9d7c69f7cadfc3280a9062c5273c43be1c34f87448864cea7b5c97d6d32f59bd5f25384653bb5c4faa45bea8b89402843e645b6b9269e2bd988ddacb033328ffb060450f7df080053e6969b251e875ecec32cfc592840d69ab69a75e06b379c535d95266b082f4f09c93162b33b0d9f7307a4eaaa52104437fed66f8ee3eabbd45d67b25a8133f496468b52baffdbfad93eef1a9818b5e42ec722788a3d8d3529fc777d2ba570801dfae01ec88302837c1fb9e0355727645ee1046c3f915f6ae82dad4fb6b0356a46518ffc834155c3b4fe6dafa6cc8a5ccf53c73a0849d8d44f7dcf72754e70e1b7dfb447bb4ef49d1a718f6171bbce200950e0ce926106b151a3e871d5ce49731bd6650a9b0ca972da1c5f136d44820ea6383c08f3b384cf2338e789c513f618cc5694a6f0cee104511e1ed7c5f23a1ebfd8a0db8424553240156dbf622831b0c643d1c551b6f3f7a98d29b85c2de05a65fa615eee16495bd90737672115b53e91c5d90028cf3f1a93953a153de53b44084e9ccff6b736693926daefebb2d77aa5ad689b92f31686669df16d1715cc58f7a2cfb72dd1a51e92f825993a74022be7e9eb6054654457094d14928f20215e7b222ac56b51adbec8d8bdb6983979a7e3a21b44b5d1518ca97d0b5195f51ed6a24350c89747e1edea51b448e3e9147054ce927873c90db394d86888e07dff177593d6f79e152302204aeb03be2386af3e24078bd028b1689f5e147c9f452c8ceb02ec59cc9db63a03576ceeafe98239023897da0236630a53c0de7f435a19869792fab36e7b9e635760f09069e6432e700035ac2a02879fff0a1e1bec522047193d94eb5df1efd53eea1144ca78940852f5ec9727904b366ede4f5e2d331fad5fc282ea2c47e923142771c3dd75a87357487def99e5f18e9d9ed623c175d02888c51f82c07a80d54716b3c3c2bdbe2e9f0a9bbaaebeb4d52936876406f5c00e8e4bbd0a5ec05797e6207c5ab6c88f1a688421bd05a114f4d7de2ac241fa0e8bedff47f762ddcbeaa91004f8d31e85095c81054994ad3826e344ba96040810fc0b2ad1de48cfade002c62e5a49a0731ab38344bc1636df16bf607d56855e56d684003c718e4bad9e5a099979fcddeeb1c4a7776cd37a3417cb0e184e29ef9bc0e87475ba663be09e00ab562eb7c0f7165f969a9b42414198ccf1bff2a2c8d689a414ece7662927665689e94db961ebaec5615cbc1a7895c6851ac961432ff1118d4607d32ef9dc732d51333be4b4d0e30ddea784eca8be47e741be9c19631dc470a52ef4dc13a4f3633fd434d787c170977b417df598e1d0dde506bb71d6f0bc17ec70e3b03cdc1965cb36993f633b0472e50d0923ac6c66fdf1d3e6459cc121f0f5f94d09e9dbcf5d690e23233838a0bacb7c638d1b2650a4308cd171b6855126d1da672a6ed85a8d78c286fb56f4ab3d21497528045c63262c8a42af2f9802c53b7bb8be28e78fe0b5ce45fbb7a1af1a3b28a8d94b7890e3c882e39bc98e9f0ad76025bf0dd2f00298e7141a226b3d7cee414f604d1e0ba54d11d5fe58bccea6ad77ad2e8c1caacf32459014b7b91001b1efa8ad172a523fb8e365b577121bf9fd88a2c60c21e821d7b6acb47a5a995e40caced5c223b8fe6de5e18e9d2e5893aefebb7aae7ff1a146260e2f110e939528213a0025a38ec79aabc861b25ebc509a4674c132aaacb7e0146f14efd11cfcaf4caa4f775a716ce325e0a435a4d349d720bcf137450afc45046fc1a1f83a9d329777a7084e4aadae7122ce97005930528eb3c7f7f1129b372887a371155a3ba201a25cbf1dcb64e7cdee092c3141fb5550fe3d0dd82e870e578b2b46500818113b8f6569773c677385b69a42b77dcba7acffd95fd4452e23aaa1d37e1da2151ea658d40a3596b27ac9f8129dc6cf0643772624b59f4f461230df471ca26087c3942d5c6687df6082835935a3f87cb762b0c3b1d0dda4a6533965bef1b7b8292e254c014d090fed857c44c1839c694c0a64e3fad90a11f534722b6ee1574f2e149d55d744de4887024e08511431c062750e16c74ab9f3242f2db3ffb12a8d6107faa229d6f6373b07f36d3932b3bdb04c19dd64eadd7f93c3c564c358a1c81dcf1c9c31e5b06568f97544c17dc15698c5cb38983a9afc42783faa773a52c9d8260690be9e3156aa5bc1509dea3f69587695cd6ff172ba83e6a6d8a7d6bbebbbcda3672731983f89bc5831dc37c3f3c5c56facc697f3cb20bd5dbadbd702e54844ac2f626901fe159db93dfd4773d8fe73562b846c1fc856d1802762840ebc72d7988bde75cbca70d319d32ce0cc0253bb2ad455723ee0c7f4736ce6e6665c5aca32a481c53839bc259167b013d0423395eeb9aaaee3206149a7d550d67fc5fdfe4a8a5c35d2510b664379ab8f72855a2af47abce2a632048eaf89e5cb4a88debc53a595103acce4f1cff18acff07afe1eb5716aa1e40b63134c3a3ae9579fa87f515be093c2d29db6d6b65c93661e00636b592704d093cc6716c2342eb1853d48c85c63ac8a2854462c7b77e7e3bd1eac5bca28ffaa00b5d349f8a547ad875b96a8c2b2910c9301309a3f9138a5693111f55b3c009ca947c39dfc82d98eb1caa4a9cbe885f786fa86e55be062222f8ba90a974073326b31212aece0a34a60").unwrap()).unwrap(); + let sig = &*hex::decode("781368e64dba542a7eacbd2257335cc943a03241009b797093c615f76a671a7591430441d80bb582304b33b9fce295e0dd57fe169355ddf4453a2aca62d8eb8109ef0d9cf3f5b0a94e04ad81b3e786014243ecde816551aa7fe01c639054256a491756bef59f5034f717ff4f85e70ba7731a49971415b6a7e7d816ab434b9f17a3095ede6fd432be2bfa82724045dda0dfff7a0281e9000939ccba3d8ab3245139c441648c76a6536127e4d1ef0df1531883ab78c8b41323617ad8db03d9908c9e08a9f7321c45051b3c94213347b11c4a84491de7a7be68701e47d7f0e0b33e767bef17694e4d33244ed92ebd74c85ab6c84441cddc14331e6ae8bd23674bda27f09c050d88f7d430feee7f15a72a24d653bb6bec54491b98362ce131d37c7d78a3f9a893db5abdccd6663593b88bc6c97f07f8eafccfd25e8180d918efbcd95bbf3da29f081e3e1932095939198e2a155b2d803a3e84ca4f34569df695c259faf3c0d8f0cd217ebd2dbad542b32fbb54e44aaf0b5dc739fafef2e46db8d68bfc35f44f038cb1f5231a1b5b134ae683e7f3297cc7a95bd191b310f68201450797fe3293cde1672dfeca4b493f53c768ea048a972a4cd84d39ef682957b8f28ba29487b4689b43fec2655823d9bb99ffcf31490366a9860a5d5b8e32a3b8bfeb6f55f88fb80c8c0142086f220e1f6f2862dabda58c3b6f5faa805b39cfac4b6d7ea7acdf1b0690063b0c1ea38c7c4755189966dc631055f153f71b77b114fa5c309316ba512330ea5cdb0bb176001e57461563d17259f35d0c30ef5ac838c0325402bab52c531469526ae3ee6293f7b5769d27e69fa81cd25a31cd095b126e70c57ac3169a5f585a11f1748d9d22f2564911c26a24b2153a78f3a06822f5f1963f237abeb48efd9a9cd478de579c5a0ba84d00e96fbde36d8ce20e7e948547fb6850834ff79d211830f6ee973359781d9d5008fb43a89354782fde4158177f5206ce1d38c889e99e4bb5b4ab34d6a05c42f5d719ea03dbc54adba75a3bb44a3c08c7556462f8c5b7c568a69242cf5be6098eba0a2249c1ca5b2109b6404a962abc1c159c6b48a79fb97e4a3337d99323746221297423f9bd1b12e78489e01e6a10f0fd6bba1cfd6ae1b75dbe69f8b8ee51a4e7f68ba2c407c9c0bad3892b29b0170ab75836fdd49a7ee3c2bb30f2c3d226bcec49140952170b0d160f97b30b7b7b096719538677ebb06922f26925227c8852acc107a8f173b38d96697584bd3dfe169b4073aa58a7bf371d5c4bb0eed30f08212defb3aec902d4546084176bf0f86d93cf36a4689a5e874b32d6b7d3c1e3fbcfd988c35dbc9a8d0a019ad6d7e15ec3ac97125db6abfe00beffe35a81666699a91e15945c62d646690b5b52de8b835ee9be53588fde5d63023b52b2b1f4610c237a829f5901a46042963cce7b85aa040adde02985e14e23c4eadb75221c607d24672e244c66c9c24c3cb7fd90bb23295c9d3d9da516bce3dd462d6660f9f91ef0618a4d4d3d6668c5d1e2e8ed433ebfe0762beb743324e11608f8b14b69ce4c221c1654ba4992a5af2d949c2939f95d1c8fb767af2a843cc7c78f57259d5c0c6ca83fca41ef5ecc4eeeea93e4518c24d3040f2cd90df3e535e989e606fa109e2c453ed7353db1cdb27137f005f9dd8d2aebfb7255a6098b690215e100cfe44ca0f2745fced48322bc9667ab16d2e1c0ff491b96a17b833d4fd44d31c2230ac835796e063ab03000f04f15c70560033763a48552cceaacade9ca5c8055f3745e179068a287183f2bc3ef6327dec5ac7cf7b052ef5a8873e697efde089688f43be464827c2fa83eb531b3674145e95c699c82990e684967dad319d9f64ab16cd9fe9b6c41232ac4ae3795fd8a76aa9b02e970242061c6da45a2af74ad9cb2a79935c92625e242f4bc7fce54d5c10a9e61f875162fa651b66057ba036f062d6d39d0502b93a5640b78c6c2fd20b02ff83676a87a94945d476c349803ea4fe60cdcea65bb2629e2bc09d4472ec63422dee2052f098deaf5531e6c9bed6672a8b699802efe0cded80c8455f585d1ba633d281f1a21adab48e63b44e0c2a4d7608cf98aabf8adc86bcb8f61e8b06cd2385f82e0a3cdd03cab152d5951859c4532f9168e78f17ba2a5772780327dcf4e62b4d26e443762fc488ae4cd4d1156dbd5782595cfd7697a514abc9b160c9ccf08edc86134a755b90e9bb543511e888e3157721a52d1bc5db33029fb335ea2114e21c03368c8d7f4d827960641772a4a32a738df60d19ec77ab09d22f57cc2523b9503b3f5b1cebc5ae15f885f159842db7359a1c89d3d82d3407068f15b6739626eb8c521fc8c5c7491f945d49f14e6989da340bdf49e7f8a792747aa658bc114143ba93f26022d001735b744639bbf22aab2a1851cfc934f9c69d3764fdea3d23db17998e6138cfd7cd9e9a47cb74193bd71aaf28cdd9d1eb595125546a4f4357ebbd1f410e3bf8557892de68509b5b98c5c229e942c910fdd3e54cb6ad54d8dd886cb97ecc06d1e401b8395d0bcb0db9a031dc66c9294f9053c68fc42042b1fa1671fc7d510b70916c0139cfebe3a91244527ce9439860cedb30908197be851cbd1d3b18ca541358449fb34fb5cd569630ed5f67b8795e87828f2ce3becfe457579d82333b0bbab094de391e1f8157bd431e365ca864630932bbebb48f45f8134424e18ab455029b54b19e2f3bfec5e44ad0ea5c03f53d8f925b635838aa7015a7c9e325bdfaff966ea9512dd50f87c8995cea7561c23f4fb06d964ab8f1913a6ca17e4ca60d6bc078e1784f89c673c91d955bcf45f58ca9709579d5e3831df12cfdb7516fd21878cb54243579b9346d2de4be25f508e84b1adc78cb91c03da3c4fd59e4529189838f74f6312820620a5996b791ffcb332f847094613f2148b862034fa89d0d0ff1808d902c5d1af64d5522492d61ecde4c73be89a33782cef1acc1dc327fb2eb9d17642209b85aa8b1dc57cbf067c7aa29da6b7e157d23e171d3ae6f3855834071791402c851ff2dd67109979f7ee5e09e64b4eefdee7112b55ce200bb8c8051e3428c305fa1d576bffbb25a70eb571168fc60dadd928b10cfd07de80a85b8df3edc372d488c21f0d5787611cc6fb73aeeb6f920a109294b49d3870f90de3b360d14df77ef95640bcac7a4dbca901a31db83e83f5c59ce327207ea9b27c3b978d30d53865c1b84764f025e8732d5007554ce5c9cc410b2eefd7e4d990c538557606a6bc47577a43768d30aa3e8598fd6f4fe7ac439f3931c58fd69d90765ac9f456ac7de085e14a0898c4557f5d3baaae07edc607de6900146b97b35aae570153dc107815ef9febdd4fd567d637fee8f8bfb4b3413ea6aead4846ab733a04f1e4bc32a3bbb1c16baf8d0bdb9ccb82fe46479ccfd040b5e64064e539b39c66e4501dc822873ac6119a4a112a1f7cd6df0e5f84356ce853ced34ce69a9e7383534983c51c50269bf8b9586a0e5ba905fd3bce080b00e7f7d48e55f489479b5771e995fb020e58feb74af65c3ee76aa4e69b5ba8bda249a1b2d62c08d418c3635d061846040843991ab475473da85d94981fb84425e7ad951ce0a42be642fe658b7aaae72b147cdc086c24b1571eb2272e2a72b15660d854ebe19ec7d9ab7ea17800d0b6ae727b39217467c662ba08e6f19193951eeff02806a7843eb5c71b2f04dcb605ecedc5128cd67703038c44bf20fe06f3ed8c1368fe38e72944d5c52fca46a45fd48d8fe5da64183d4d62ec01aa3d9d672ec67a01c17f21e02525f0513cce030c664fc8784763086608bc8099c204c255ffed1daf432ceb45fbd135e21e8190c5bfee192171faa77520481e69e87b7f76790bef76cb8d3c88f5c6e32fc59e7bd45351d66696b61d9f40726fb9a98000b68738cf7e34b98b6a4aaa2ac1d7b1407db89783f8077103ea9c9e89247eae078adfb36e21474c3bb1fe0c87687c6233a533a01e1081b93a3521d339f39c075609bace531994988ae314f77fc6034113a138c67eb7e03750cbec8d28bd21afedfefa8f091619ae500b4ca4599d019dc8ca4bf118d70b8676dfc796a4f6d986adba4c8574ed4abaea5465466220e5e53dc8fcb395d1e59d278673cbc4e3f40658df98ac2fd126a94922879e1a3be91c1acc20803c35fa764abbadab07bde85ff4bd9e0fb6f06baf5bf42b8a2cbf6c2f62606becc361552921a12d6c8236fde84db4bddac77e8872478cffc4e148c1c7acfedf6b17d98731c2de36f3cbef1f6f781a940e0874d5b74535bbe066b53064d43b13926570a9e1c4e6da206c8bd252caf2b62e7d223f7ac12939137f330be59374d7295a6c2dff92e07c727510e48d970593e47229fc8bc3bd5b8ea780dacff4d23063df65feda5f8f65b17a333e532acad7916780c74d6a70d38b367f3f6f4e947b85fc15235bbe46b26495d2780098db853a931377cfbedea620f2355ca21e81ce9e0078b0dd6cb70f23ed558682be3b3d594eefe85344e1f275428b316cc088995939298f2a2d15ac9b676ac3e9cb92f2a64dec7732a91fc761aa1b126ea575e3953177da6e1cd78faea824665330a81d9e24572b9860bf0aba4df8bd5d4e3e2c72bbfbb2a985c7ae2f077951fe8401e1d156ecada1e353817b20f41e0b2460a0caaf2b36d6a7f1b35125d797dcc714421027d14171765a646071ed952b6a5294eecf6a3a71c104c843a4a8b3efcc27467b20cb0a94abf5802229ea4d8312783e78791a50b3c0a88fe6497198cd4bf470dac46f34e50019fdee2040cfe99124b312b1122b83e51d878877cec0855f1158c445cfdc2253f4389d5e3a8ba1669abb5976a4617e85f543da9f5e30b10ca7481c8185392782b46fb0a0e5ab408b2945e3c79a1cc49fb7c27254a9b540e7397a5b655bf7e4f83184db32a128aa2e00a624d7dcd6b77efd151f1e5cba8890af9170fa06c555715dc1787e995ad19270973ae95b88dcafdaf62c28843d3f8b9c78dc8e37d911dab3f7ee9d4c7389c654bdcfc05056b360020140e57e31473258a4081e2a708f7caba90c356d0847098fc0762484086aba898a60b023d6a3060402406240748785d51caff52a0ef3dc2a45dadf80ac18502d24422a8cbb10192b88f4e9160206b2ed3e04114f2a339df269e2c36b8613ce37087471701755330cb559575366ee0fa2d3afcda32eede6dd906345daaa04812198e96c42239c242edb90059709f497da5b87705384aef2af22dc2edfef3c00d8c9156d8b3163fe7a7779e04f04911a8b934fb3072eee844484fede5e2ee96d338eefe2da986067ffe0218ada7de1d0e42d823d6b033918278888ba0608ab8f7be997bdf263689a36f5204c802ad836363779b4b0d6ce5083df0b98a2e2c700062a4fa5e57bc73bd45357e01d90c7954bc6904d1ce8166a9168da39a60c5cae8119bb6b9ab074fb2d0aee384fc2c0e4806811d6002b4e2401e7430b50cb0e8075f33d5386aecde256e169d95e2f9c6556c08ba042e68a53ce8aca9cc02818f7382f150dc04de0019b19c7a3ab0d72d6ed013d7a115d74b279f71fd6effef34049877e0b11e0659be938a5de684eaf23513095eb4a1bdf536c3c01a4655c4b4a0673214cef29a481d06a02cc9a5bfc7b8d846c33484cd67b1de98f60b69918f177b64558ca567a6237d35ff01771a42320ce02bb98f3e4ad4ac7db75611bd9961eac662a38b1f785970c99f3dd105ff586f61301c48d66708cbf7d53a733e357b6c256e8b73f0e1305a0bc137989e521100c2ee6259e607fe12198e8bcb988b0854668e40d7cae6adc3ba40ba121b7319d06d988a073d03097b9f5c1c07284b6473ae57bf154811b77baceb0412b8a6983bdd0ccd9e3bf014e520009cb26d5780eabef1bbafa5e25d41098a54c47fce8b68d395291d54284d33aa50b9664d1510b467c8a539361ca9a4448bc01fcb4c4e3ef475e8afb46a494ae13ee9ea8a1266825fba7f32b9712fde252698a68359b50141d90f5c4a06283ddb54ad7e1412ac5ebb12501f7a82b2a7f27b2dbab626c3db4074523b3211d3182ea261397a6f7b187cf2b8a356ded10812f1d305169aedf79b5ff1cf7c2d6e86ee11f28e96aa63b5a03f59fc960ac7d0572e91dda61905c0711a9b26344a2a10aa2041f2b13cb1a9a9a27774b6d0deddc9d81ea1b142ad7b72be47991f2c9261d6708156e38d00b074020766eb0c494392d65b82ca65f7c3352f9bb78325ecb6df596c8ae57826b08ccd6f1d529d2e25925c1ac972425bedeb88a5d0e3138ddc434da3462ebdf6b1239a21f141ec62cbe4bb993ba253b55a76d30fac19c2c1384ef6b9746c07787aa1fe913a1348390bd8c1f386a08c77cf7106c927ce24dffc9d6ee1b32354d95ed2923482531de6b390bf0f5eb80276e90e7ed11131c848bbabec4d317236269a0a3a7cbe0f1272f93949ca6d23ea2a7ee3f697791aab71533423066d400000000000000000000000000000000000000000000000000000000050e181f23292c2f").unwrap(); + assert_eq!(sig.len(), MLDSA87_SIG_LEN); + + if MLDSA87::verify(&mldsa87_pk, msg, None, &sig).is_ok() { + eprintln!("Verification succeeded!"); + } else { + panic!("Verification failed! -- figure that out"); + } +} + + + +fn main() { + // bench_do_nothing(); + // bench_mldsa44_keygen(); + // bench_mldsa44_lowmem_keygen(); + // bench_mldsa65_keygen(); + // bench_mldsa65_lowmemory_keygen() + // bench_mldsa87_keygen(); + // bench_mldsa87_lowmemory_keygen() + // bench_mldsa44_sign(); + // bench_mldsa44_lowmemory_sign(); + // bench_mldsa65_sign(); + // bench_mldsa65_lowmemory_sign(); + // bench_mldsa87_sign(); + // bench_mldsa87_lowmemory_sign(); + // bench_mldsa44_verify(); + // bench_mldsa44_lowmemory_verify(); + // bench_mldsa65_verify(); + // bench_mldsa65_lowmemory_verify(); + // bench_mldsa87_verify(); + bench_mldsa87_lowmemory_verify(); +} \ No newline at end of file From ef214a65b0c8e77a6daf9ddf4b3f32dfe1c76efd Mon Sep 17 00:00:00 2001 From: Quant-TheodoreFelix <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Tue, 9 Jun 2026 14:42:35 +0900 Subject: [PATCH 05/35] =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EB=B2=84=ED=8D=BC?= =?UTF-8?q?=20zeroize=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 좜λ ₯ 슬라이슀λ₯Ό λ°›λŠ” _out 계열 ν•¨μˆ˜ μ§„μž…λΆ€μ—μ„œ out fill(0) μ„ -μ΄ˆκΈ°ν™” - μ˜€λ²„μ‚¬μ΄μ¦ˆ 버퍼 λ’€μͺ½μ΄λ‚˜ μ‘°κΈ° λ°˜ν™˜ μ‹œ λ‚¨λŠ” 이전 데이터 λ…ΈμΆœ λ°©μ§€ - digest 좜λ ₯ 버퍼 λ’€μͺ½κΉŒμ§€ 0이 λ˜λ„λ‘ sha3 ν…ŒμŠ€νŠΈ 1건 κ°±μ‹  - ν–‰ λ‹¨μœ„ λΆ€λΆ„ 기둝 ν•¨μˆ˜λŠ” λ‹€λ₯Έ 행을 μ§€μš°λ―€λ‘œ μ œμ™Έ --- crypto/base64/src/lib.rs | 2 ++ crypto/factory/src/hash_factory.rs | 4 ++++ crypto/factory/src/mac_factory.rs | 4 ++++ crypto/factory/src/rng_factory.rs | 2 ++ crypto/factory/src/xof_factory.rs | 6 ++++++ crypto/hex/src/lib.rs | 4 ++++ crypto/hmac/src/lib.rs | 6 ++++++ crypto/mldsa/src/aux_functions.rs | 2 ++ crypto/mldsa/src/hash_mldsa.rs | 12 ++++++++++++ crypto/mldsa/src/mldsa.rs | 14 ++++++++++++++ crypto/mldsa/src/mldsa_keys.rs | 10 ++++++++++ crypto/mldsa_lowmemory/src/aux_functions.rs | 2 ++ crypto/mldsa_lowmemory/src/hash_mldsa.rs | 8 ++++++++ crypto/mldsa_lowmemory/src/mldsa.rs | 8 ++++++++ crypto/mldsa_lowmemory/src/mldsa_keys.rs | 2 ++ crypto/mlkem/src/matrix.rs | 2 ++ crypto/mlkem/src/mlkem_keys.rs | 10 +++++++++- crypto/mlkem/src/polynomial.rs | 2 ++ crypto/mlkem_lowmemory/src/mlkem_keys.rs | 4 ++++ crypto/mlkem_lowmemory/src/polynomial.rs | 2 ++ crypto/rng/src/hash_drbg80090a.rs | 8 ++++++++ crypto/sha2/src/sha256.rs | 4 ++++ crypto/sha2/src/sha512.rs | 4 ++++ crypto/sha3/src/keccak.rs | 2 ++ crypto/sha3/src/sha3.rs | 8 ++++++++ crypto/sha3/src/shake.rs | 10 +++++++++- crypto/sha3/tests/sha3_tests.rs | 7 +++++-- crypto/utils/src/ct.rs | 2 ++ 28 files changed, 147 insertions(+), 4 deletions(-) diff --git a/crypto/base64/src/lib.rs b/crypto/base64/src/lib.rs index 8b7cfeb..90330d0 100644 --- a/crypto/base64/src/lib.rs +++ b/crypto/base64/src/lib.rs @@ -193,6 +193,8 @@ impl Base64Encoder { assert!(inref.len() >= 3); assert!(out.len() >= 4); + out.fill(0); + out[0] = Self::ct_bin_to_b64(inref[0] >> 2); out[1] = Self::ct_bin_to_b64(((inref[0] & 0x03) << 4) | inref[1] >> 4); out[2] = Self::ct_bin_to_b64(((inref[1] & 0x0F) << 2) | inref[2] >> 6); diff --git a/crypto/factory/src/hash_factory.rs b/crypto/factory/src/hash_factory.rs index d65586f..9d12117 100644 --- a/crypto/factory/src/hash_factory.rs +++ b/crypto/factory/src/hash_factory.rs @@ -129,6 +129,8 @@ impl Hash for HashFactory { } fn hash_out(self, data: &[u8], output: &mut [u8]) -> usize { + output.fill(0); + match self { Self::SHA224(h) => h.hash_out(data, output), Self::SHA256(h) => h.hash_out(data, output), @@ -168,6 +170,8 @@ impl Hash for HashFactory { } fn do_final_out(self, output: &mut [u8]) -> usize { + output.fill(0); + match self { Self::SHA224(h) => h.do_final_out(output), Self::SHA256(h) => h.do_final_out(output), diff --git a/crypto/factory/src/mac_factory.rs b/crypto/factory/src/mac_factory.rs index 64ae090..141c59f 100644 --- a/crypto/factory/src/mac_factory.rs +++ b/crypto/factory/src/mac_factory.rs @@ -175,6 +175,8 @@ impl MAC for MACFactory { } fn mac_out(self, data: &[u8], out: &mut [u8]) -> Result { + out.fill(0); + match self { Self::HMAC_SHA224(h) => h.mac_out(data, out), Self::HMAC_SHA256(h) => h.mac_out(data, out), @@ -227,6 +229,8 @@ impl MAC for MACFactory { } fn do_final_out(self, mut out: &mut [u8]) -> Result { + out.fill(0); + match self { Self::HMAC_SHA224(h) => h.do_final_out(&mut out), Self::HMAC_SHA256(h) => h.do_final_out(&mut out), diff --git a/crypto/factory/src/rng_factory.rs b/crypto/factory/src/rng_factory.rs index 89aa3e7..f1792d1 100644 --- a/crypto/factory/src/rng_factory.rs +++ b/crypto/factory/src/rng_factory.rs @@ -108,6 +108,8 @@ impl RNG for RNGFactory { } fn next_bytes_out(&mut self, out: &mut [u8]) -> Result { + out.fill(0); + match self { Self::HashDRBG_SHA256(rng) => {rng.next_bytes_out(out) }, Self::HashDRBG_SHA512(rng) => { rng.next_bytes_out(out) }, diff --git a/crypto/factory/src/xof_factory.rs b/crypto/factory/src/xof_factory.rs index 2809a39..e35e86e 100644 --- a/crypto/factory/src/xof_factory.rs +++ b/crypto/factory/src/xof_factory.rs @@ -86,6 +86,8 @@ impl XOF for XOFFactory { } fn hash_xof_out(self, data: &[u8], output: &mut [u8]) -> usize { + output.fill(0); + match self { Self::SHAKE128(h) => h.hash_xof_out(data, output), Self::SHAKE256(h) => h.hash_xof_out(data, output), @@ -118,6 +120,8 @@ impl XOF for XOFFactory { } fn squeeze_out(&mut self, output: &mut [u8]) -> usize { + output.fill(0); + match self { Self::SHAKE128(h) => h.squeeze_out(output), Self::SHAKE256(h) => h.squeeze_out(output), @@ -136,6 +140,8 @@ impl XOF for XOFFactory { num_bits: usize, output: &mut u8, ) -> Result<(), HashError> { + *output = 0; + match self { Self::SHAKE128(h) => h.squeeze_partial_byte_final_out(num_bits, output), Self::SHAKE256(h) => h.squeeze_partial_byte_final_out(num_bits, output), diff --git a/crypto/hex/src/lib.rs b/crypto/hex/src/lib.rs index 2e71988..6fcf787 100644 --- a/crypto/hex/src/lib.rs +++ b/crypto/hex/src/lib.rs @@ -47,6 +47,8 @@ pub fn encode_out>(input: T, out: &mut [u8]) -> Result> 4); out[2 * i + 1] = ct_word_to_hex(inref[i] & 0x0F); @@ -90,6 +92,8 @@ pub fn decode_out>(input: T, out: &mut [u8]) -> Result HMAC { )); } + out.fill(0); + // Per RFC 2104 Section 2, save our inner digest to calculate our // outer digest. Note that we can't (necessarily) reuse out as a // scratch pad here: if we're truncating the output but not @@ -378,6 +380,8 @@ impl MAC for HMAC { } fn mac_out(mut self, data: &[u8], mut out: &mut [u8]) -> Result { + out.fill(0); + self.do_update(data); self.do_final_out(&mut out) } @@ -398,6 +402,8 @@ impl MAC for HMAC { } fn do_final_out(self, mut out: &mut [u8]) -> Result { + out.fill(0); + self.do_final_internal_out(&mut out) } diff --git a/crypto/mldsa/src/aux_functions.rs b/crypto/mldsa/src/aux_functions.rs index 8303063..3164838 100644 --- a/crypto/mldsa/src/aux_functions.rs +++ b/crypto/mldsa/src/aux_functions.rs @@ -397,6 +397,8 @@ pub(crate) fn sig_encode< h: &Vector, output: &mut [u8; SIG_LEN], ) -> usize { + output.fill(0); + let mut pos = 0; output[..LAMBDA_over_4].copy_from_slice(c_tilde); diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 780bd76..52fbdc8 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -478,6 +478,8 @@ impl< ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + let mut ph_m = [0u8; PH_LEN]; _ = HASH::default().hash_out(msg, &mut ph_m); Self::sign_ph_with_expanded_key_out(sk, &ph_m, ctx, output) @@ -500,6 +502,8 @@ impl< ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN]; HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; Self::sign_ph_deterministic_out(&sk.sk, Some(&sk.A_hat), ctx, ph, rnd, output) @@ -556,6 +560,8 @@ impl< return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); } + output.fill(0); + // Algorithm 7 // 6: πœ‡ ← H(BytesToBits(π‘‘π‘Ÿ)||𝑀', 64) let mu = { @@ -860,6 +866,8 @@ impl< ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + let mut ph_m = [0u8; PH_LEN]; _ = HASH::default().hash_out(msg, &mut ph_m); Self::sign_ph_out(sk, &ph_m, ctx, output) @@ -898,6 +906,8 @@ impl< )); } + output.fill(0); + if self.sk.is_some() { if self.signer_rnd.is_none() { Self::sign_ph_out(&self.sk.unwrap(), &ph, Some(&self.ctx[..self.ctx_len]), output) @@ -1045,6 +1055,8 @@ impl< ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN]; HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; Self::sign_ph_deterministic_out(sk, None, ctx, ph, rnd, output) diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index d207f01..dd8eb0f 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -845,6 +845,8 @@ impl< rnd: [u8; 32], output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + // 1: (𝜌, 𝐾, π‘‘π‘Ÿ, 𝐬1, 𝐬2, 𝐭0) ← skDecode(π‘ π‘˜) // 2: 𝐬1Μ‚_hat ← NTT(𝐬1) // 3: 𝐬2Μ‚_hat ← NTT(𝐬2) @@ -1134,6 +1136,8 @@ impl< ctx: Option<&[u8]>, out: &mut [u8; SIG_LEN], ) -> Result { + out.fill(0); + let mu = MuBuilder::compute_mu(&sk.tr(), msg, ctx)?; Self::sign_mu_out(&sk.sk, Some(&sk.A_hat), &mu, out) } @@ -1154,6 +1158,8 @@ impl< mu: &[u8; 64], output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN]; HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; @@ -1175,6 +1181,8 @@ impl< mu: &[u8; 64], out: &mut [u8; SIG_LEN], ) -> Result { + out.fill(0); + Self::sign_mu_out(&sk.sk, A_hat, mu, out) } @@ -1196,6 +1204,8 @@ impl< rnd: [u8; 32], output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + match A_hat { Some(A_hat) => Self::sign_internal(sk, A_hat, mu, rnd, output), None => Self::sign_internal(sk, &sk.A_hat(), mu, rnd, output), @@ -1930,6 +1940,8 @@ impl< ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + let mu = MuBuilder::compute_mu(&sk.tr(), msg, ctx)?; let bytes_written = Self::sign_mu_out(sk, None, &mu, output)?; @@ -1966,6 +1978,8 @@ impl< )); } + output.fill(0); + if self.sk.is_some() { if self.signer_rnd.is_none() { Self::sign_mu_out(&self.sk.unwrap(), None, &mu, output) diff --git a/crypto/mldsa/src/mldsa_keys.rs b/crypto/mldsa/src/mldsa_keys.rs index 96a3a5d..c1643ba 100644 --- a/crypto/mldsa/src/mldsa_keys.rs +++ b/crypto/mldsa/src/mldsa_keys.rs @@ -203,6 +203,8 @@ impl SignaturePublicKey usize { + out.fill(0); + self.pk_encode_out(out) } @@ -279,6 +281,8 @@ impl< } fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize { + out.fill(0); + self.pk.encode_out(out) } @@ -431,6 +435,8 @@ impl usize { + out.fill(0); + // counter of progress along the output buffer let mut off: usize = 0; @@ -720,6 +726,8 @@ impl usize { + out.fill(0); + self.sk_encode_out(out) } @@ -976,6 +984,8 @@ impl< } fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { + out.fill(0); + self.sk.encode_out(out) } diff --git a/crypto/mldsa_lowmemory/src/aux_functions.rs b/crypto/mldsa_lowmemory/src/aux_functions.rs index ce0f4ca..bcc1e52 100644 --- a/crypto/mldsa_lowmemory/src/aux_functions.rs +++ b/crypto/mldsa_lowmemory/src/aux_functions.rs @@ -167,6 +167,8 @@ pub(crate) fn bitpack_gamma1( z: &Polynomial, out: &mut [u8; POLY_Z_PACKED_LEN], ) { + out.fill(0); + let mut t: [u32; 4] = [0; 4]; match GAMMA1 { MLDSA44_GAMMA1 => { diff --git a/crypto/mldsa_lowmemory/src/hash_mldsa.rs b/crypto/mldsa_lowmemory/src/hash_mldsa.rs index 33b9176..99140fa 100644 --- a/crypto/mldsa_lowmemory/src/hash_mldsa.rs +++ b/crypto/mldsa_lowmemory/src/hash_mldsa.rs @@ -590,6 +590,8 @@ impl< return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); } + output.fill(0); + // Algorithm 7 // 6: πœ‡ ← H(BytesToBits(π‘‘π‘Ÿ)||𝑀', 64) let mut h = H::new(); @@ -809,6 +811,8 @@ impl< ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + let mut ph_m = [0u8; PH_LEN]; _ = HASH::default().hash_out(msg, &mut ph_m); Self::sign_ph_out(sk, &ph_m, ctx, output) @@ -847,6 +851,8 @@ impl< )); } + output.fill(0); + if self.sk.is_some() { if self.signer_rnd.is_none() { Self::sign_ph_out( @@ -1024,6 +1030,8 @@ impl< ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN]; HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; Self::sign_ph_deterministic_out(sk, ctx, ph, rnd, output) diff --git a/crypto/mldsa_lowmemory/src/mldsa.rs b/crypto/mldsa_lowmemory/src/mldsa.rs index a6efa48..dc6ae2e 100644 --- a/crypto/mldsa_lowmemory/src/mldsa.rs +++ b/crypto/mldsa_lowmemory/src/mldsa.rs @@ -949,6 +949,8 @@ impl< mu: &[u8; 64], output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN]; HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; @@ -1181,6 +1183,8 @@ impl< rnd: [u8; 32], output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + SK::from_keymaterial(&seed)?; Self::sign_mu_deterministic_out(&SK::from_keymaterial(&seed)?, mu, rnd, output) } @@ -1586,6 +1590,8 @@ impl< ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN], ) -> Result { + output.fill(0); + let mu = MuBuilder::compute_mu(&sk.tr(), msg, ctx)?; let bytes_written = Self::sign_mu_out(sk, &mu, output)?; @@ -1622,6 +1628,8 @@ impl< )); } + output.fill(0); + if self.sk.is_some() { if self.signer_rnd.is_none() { Self::sign_mu_out(&self.sk.unwrap(), &mu, output) diff --git a/crypto/mldsa_lowmemory/src/mldsa_keys.rs b/crypto/mldsa_lowmemory/src/mldsa_keys.rs index 81985bd..b805d89 100644 --- a/crypto/mldsa_lowmemory/src/mldsa_keys.rs +++ b/crypto/mldsa_lowmemory/src/mldsa_keys.rs @@ -183,6 +183,8 @@ impl SignatureP fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize { debug_assert_eq!(out.len(), PK_LEN); + out.fill(0); + out[..32].copy_from_slice(&self.rho); out[32..].copy_from_slice(&self.t1_packed); diff --git a/crypto/mlkem/src/matrix.rs b/crypto/mlkem/src/matrix.rs index 381e7be..563d19c 100644 --- a/crypto/mlkem/src/matrix.rs +++ b/crypto/mlkem/src/matrix.rs @@ -171,6 +171,8 @@ impl Vector // let mut s = self.clone(); // s.conditional_sub_q(); + out.fill(0); + let mut idx = 0; match du { 10 => { // MLKEM512 and MLKEM 768 diff --git a/crypto/mlkem/src/mlkem_keys.rs b/crypto/mlkem/src/mlkem_keys.rs index df04429..a93934d 100644 --- a/crypto/mlkem/src/mlkem_keys.rs +++ b/crypto/mlkem/src/mlkem_keys.rs @@ -180,6 +180,8 @@ impl KEMPublicKey for MLKEMPublicK debug_assert_eq!(PK_LEN, 12*k*32 + 32); debug_assert_eq!(POLY_BYTES, 12*32); + out.fill(0); + let (pk_chunks, last_chunk) = out.as_chunks_mut::(); // that should divide evenly the remainder of the array, leaving space for rho at the end @@ -276,6 +278,8 @@ impl, const PK_LEN: u } fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize { + out.fill(0); + self.ek.encode_out(out) } @@ -390,7 +394,7 @@ impl< /// 3: dk ← (dkPKE β€– ek β€– H(ek) β€– 𝑧) fn sk_encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { out.fill(0); - + debug_assert_eq!(SK_LEN, /* dk_pke*/12*k*32 + /*ek*/PK_LEN + /*H(ek)*/32 + /*z*/32); let mut pos = 0usize; @@ -584,6 +588,8 @@ impl< } fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { + out.fill(0); + self.sk_encode_out(out) } @@ -751,6 +757,8 @@ impl< } fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { + out.fill(0); + self.dk.encode_out(out) } diff --git a/crypto/mlkem/src/polynomial.rs b/crypto/mlkem/src/polynomial.rs index 13bc411..1a9bb2e 100644 --- a/crypto/mlkem/src/polynomial.rs +++ b/crypto/mlkem/src/polynomial.rs @@ -127,6 +127,8 @@ impl Polynomial { // each of the N i16's will take dv bits debug_assert_eq!(out.len(), N * (dv as usize) / 8); + out.fill(0); + let mut t = [0u8; 8]; let mut idx = 0; diff --git a/crypto/mlkem_lowmemory/src/mlkem_keys.rs b/crypto/mlkem_lowmemory/src/mlkem_keys.rs index b897d17..b9e58f3 100644 --- a/crypto/mlkem_lowmemory/src/mlkem_keys.rs +++ b/crypto/mlkem_lowmemory/src/mlkem_keys.rs @@ -174,6 +174,8 @@ impl KEMPublicKe fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize { debug_assert_eq!(self.t_hat_packed.len(), T_PACKED_LEN); + out.fill(0); + out[..T_PACKED_LEN].copy_from_slice(&self.t_hat_packed); debug_assert_eq!(out[T_PACKED_LEN..].len(), 32); out[T_PACKED_LEN..].copy_from_slice(&self.rho); @@ -549,6 +551,8 @@ impl< fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize { debug_assert_eq!(SK_LEN, 64); + out.fill(0); + out[..32].copy_from_slice(&self.seed_d); out[32..].copy_from_slice(&self.z); diff --git a/crypto/mlkem_lowmemory/src/polynomial.rs b/crypto/mlkem_lowmemory/src/polynomial.rs index bec4ee4..a2b1603 100644 --- a/crypto/mlkem_lowmemory/src/polynomial.rs +++ b/crypto/mlkem_lowmemory/src/polynomial.rs @@ -150,6 +150,8 @@ impl Polynomial { // each of the N i16's will take dv bits debug_assert_eq!(out.len(), N * (dv as usize) / 8); + out.fill(0); + let mut t = [0u8; 8]; let mut idx = 0; diff --git a/crypto/rng/src/hash_drbg80090a.rs b/crypto/rng/src/hash_drbg80090a.rs index 4614692..31486ea 100644 --- a/crypto/rng/src/hash_drbg80090a.rs +++ b/crypto/rng/src/hash_drbg80090a.rs @@ -376,6 +376,8 @@ impl Sp80090ADrbg for HashDRBG80090A { return Err(RNGError::ReseedRequired); } + out.fill(0); + // 2. If (additional_input β‰  Null), then do // 2.1 w = Hash (0x02 || V || additional_input). // 2.2 V = (V + w) mod 2^seedlen. @@ -490,6 +492,8 @@ impl RNG for HashDRBG80090A { } fn next_bytes_out(&mut self, out: &mut [u8]) -> Result { + out.fill(0); + self.generate_out("next_bytes_out".as_bytes(), out) } @@ -521,6 +525,8 @@ fn hash_df( panic!("hash_df can't produce that much output!") } + out.fill(0); + // out is "temp" in SP 800-90Ar1 let no_of_bits_to_return: u32 = (out.len() * 8) as u32; let len = u32::div_ceil(out.len() as u32, H::OUTPUT_LEN as u32); @@ -614,6 +620,8 @@ fn hashgen(v: &[u8], out: &mut [u8]) { // 6. Return (returned_bits). // 1. m = ceil(requested_no_of_bits / outlen) + out.fill(0); + let m = u32::div_ceil(out.len() as u32, H::OUTPUT_LEN as u32); // requested_no_of_bits = out.len() diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index 10d45e1..7073c59 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -197,6 +197,8 @@ impl Hash for SHA256Internal { } fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + output.fill(0); + self.do_update(data); self.do_final_out(output) } @@ -241,6 +243,8 @@ impl Hash for SHA256Internal { } fn do_final_out(mut self, output: &mut [u8]) -> usize { + output.fill(0); + let n = *min(&output.len(), &PARAMS::OUTPUT_LEN); let bit_len: u64 = self.byte_count << 3; diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 66d5265..c404fc6 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -209,6 +209,8 @@ impl Hash for Sha512Internal { } fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + output.fill(0); + self.do_update(data); self.do_final_out(output) } @@ -252,6 +254,8 @@ impl Hash for Sha512Internal { } fn do_final_out(mut self, output: &mut [u8]) -> usize { + output.fill(0); + let n = *min(&output.len(), &PARAMS::OUTPUT_LEN); let bit_len_hi: u64 = self.byte_count >> 61; diff --git a/crypto/sha3/src/keccak.rs b/crypto/sha3/src/keccak.rs index 55ee617..5e6f6a8 100644 --- a/crypto/sha3/src/keccak.rs +++ b/crypto/sha3/src/keccak.rs @@ -271,6 +271,8 @@ impl KeccakDigest { /// Panics if the output buffer is too small. /// Returns the number of bytes written. pub(super) fn squeeze(&mut self, out: &mut [u8]) -> usize { + out.fill(0); + if !self.squeezing { self.pad_and_switch_to_squeezing_phase(); } diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index a55ac26..ed5656c 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -29,6 +29,8 @@ impl SHA3 { /// Swallows errors and simply returns an empty Vec if the hashes fails for whatever reason. fn hash_internal(mut self, data: &[u8], output: &mut [u8]) -> usize { + output.fill(0); + self.do_update(data); self.do_final_out(output) } @@ -121,6 +123,8 @@ impl Hash for SHA3 { } fn hash_out(self, data: &[u8], mut output: &mut [u8]) -> usize { + output.fill(0); + self.hash_internal(data, &mut output) } @@ -140,6 +144,8 @@ impl Hash for SHA3 { // todo -- why doesn't this take a &mut [u8; HASH_LEN] ? // That's probably more user-friendly than this auto-truncating that I have here. fn do_final_out(mut self, output: &mut [u8]) -> usize { + output.fill(0); + self.keccak.absorb_bits(0x02, 2).expect("do_final_out: keccak.absorb_bits failed."); // this shouldn't fail because by construction you can only enter this function once, and this is the only way to absorb partial bits. let bytes_written = if output.len() <= self.output_len() { @@ -172,6 +178,8 @@ impl Hash for SHA3 { num_partial_bits: usize, output: &mut [u8], ) -> Result { + output.fill(0); + // Mutants note: yep, this is just bit-setting into empty space, so it doesn't matter whether it's OR or XOR. let mut final_input: u16 = ((partial_byte as u16) & ((1 << num_partial_bits) - 1)) | (0x02 << num_partial_bits); diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index f7cd88e..edf953d 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -54,6 +54,8 @@ impl SHAKE { } fn hash_internal_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + output.fill(0); + self.absorb(data); self.squeeze_out(output) } @@ -200,6 +202,8 @@ impl XOF for SHAKE { } fn hash_xof_out(self, data: &[u8], output: &mut [u8]) -> usize { + output.fill(0); + self.hash_internal_out(data, output) } @@ -239,6 +243,8 @@ impl XOF for SHAKE { } fn squeeze_out(&mut self, output: &mut [u8]) -> usize { + output.fill(0); + if !self.keccak.squeezing { self.keccak.absorb_bits(0x0F, 4).expect("Absorb_bits failed"); }; @@ -262,6 +268,8 @@ impl XOF for SHAKE { return Err(HashError::InvalidLength("must be in the range [0,7]")); } + *output = 0; + let mut buf = [0u8; 1]; self.keccak.squeeze(&mut buf); *output = buf[0] >> 8 - num_bits; @@ -271,4 +279,4 @@ impl XOF for SHAKE { fn max_security_strength(&self) -> SecurityStrength { SecurityStrength::from_bits(PARAMS::SIZE as usize) } -} \ No newline at end of file +} diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index 787e9fb..b2d5311 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -66,10 +66,13 @@ mod sha3_tests { assert_eq!(&out, b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); assert_eq!(bytes_written, 64); - // check that if you feed it an output slice that's bigger than it needs, that it doesn't touch the extra bytes. + // Q. T. Felix NOTE: With the application of zeroize, the entire output buffer is pre-initialized to 0, + // so the bytes after the digest length are now also 0. + // Previously, the contract was to leave the trailing bytes untouched, + // but this has been changed to fill them with zeros to prevent exposure of stale data. let mut out = DUMMY_SEED_512.clone(); SHA3_256::new().hash_out(DUMMY_SEED_512, &mut out); - assert_eq!(&out[32..], &DUMMY_SEED_512[32..]); + assert!(out[32..].iter().all(|&b| b == 0)); } #[test] diff --git a/crypto/utils/src/ct.rs b/crypto/utils/src/ct.rs index 96b1950..10b23e7 100644 --- a/crypto/utils/src/ct.rs +++ b/crypto/utils/src/ct.rs @@ -292,6 +292,8 @@ pub fn conditional_copy_bytes( out: &mut [u8; LEN], take_a: bool, ) { + out.fill(0); + // we want the behaviour of // if take_a { 0xFF } else { 0x00 } // but without using any branches that could leak timing signals From c642ef633108ce1983bb357f5e338bd34c545345 Mon Sep 17 00:00:00 2001 From: Quant-TheodoreFelix <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Wed, 10 Jun 2026 13:36:21 +0900 Subject: [PATCH 06/35] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=20-=20=EB=B9=84=EA=B3=B5=EA=B0=9C=20mlkem=20compress=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20fill(0)=20=EC=A0=9C=EA=B1=B0=20(=EC=84=B1?= =?UTF-8?q?=EB=8A=A5)=20-=20ct=20conditional=5Fcopy=5Fbytes=20fill(0)=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20(constant-time=20=EA=B0=80=EC=A0=95=20?= =?UTF-8?q?=EC=9C=84=EB=B0=98=20=EA=B0=80=EB=8A=A5)=20-=20core=20traits?= =?UTF-8?q?=EC=9D=98=20=5Fout=20=ED=95=A8=EC=88=98=20docstring=EC=97=90=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EB=B2=84=ED=8D=BC=20zeroize=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C=20-=20sha3=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=EC=97=90=EC=84=9C=20=EC=9D=B4=EB=A6=84=EA=B3=BC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=9D=B4=EB=A0=A5=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?-=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20=EB=85=B8=ED=8A=B8=EC=9D=98?= =?UTF-8?q?=20=EC=99=84=EB=A3=8C=EB=90=9C=20TODO=20=ED=95=AD=EB=AA=A9?= =?UTF-8?q?=EC=9D=84=20changelog=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- alpha_0.1.2_release_notes.md | 4 ++-- crypto/core/src/traits.rs | 20 ++++++++++++++++++++ crypto/mlkem/src/matrix.rs | 2 -- crypto/mlkem/src/polynomial.rs | 2 -- crypto/mlkem_lowmemory/src/polynomial.rs | 2 -- crypto/sha3/tests/sha3_tests.rs | 5 +---- crypto/utils/src/ct.rs | 2 -- 7 files changed, 23 insertions(+), 14 deletions(-) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index 06dcefa..d6609db 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -12,8 +12,6 @@ * Check out Megan's email May 13 about KeyMaterial: "I was wondering if there might be scope for a closure based approach that could guarantee encapsulation of the state change from safe to hazardous back to safe again." -* Anywhere that you have an `_out(.. out: &mut [u8])`, start by zeroizing it with .fill(0); .. a good task for Claude? - And should be documented in the style guide? * Go back to previous algs and apply memory optimization tricks like internal functions. And add a docs section "Memory Usage" that measures with valgrind. * Ensure that all crates have `#![forbid(missing_docs)]` @@ -54,5 +52,7 @@ * ML-DSA * Low-Memory ML-DSA -- runs in about 1/10th of the usual memory (~ 30 kb of stack) with only minor 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 diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 27dd844..3713fcb 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -30,6 +30,7 @@ pub trait Hash : Default { /// A static one-shot API that hashes the provided data into the provided output slice. /// `data` can be of any length, including zero bytes. + /// The entire output buffer is zeroized before the hash output is written. /// The return value is the number of bytes written. fn hash_out(self, data: &[u8], output: &mut [u8]) -> usize; @@ -50,6 +51,8 @@ pub trait Hash : Default { /// If the provided buffer is smaller than the hash's output length, the output will be truncated. /// If the provided buffor is larger than the hash's output length, the output will be placed in /// the first [Hash::output_len] bytes. + /// The entire output buffer is zeroized before the hash output is written, so any bytes past + /// [Hash::output_len] will be 0. /// /// The return value is the number of bytes written. fn do_final_out(self, output: &mut [u8]) -> usize; @@ -65,6 +68,7 @@ pub trait Hash : Default { /// The same as [Hash::do_final_out], but allows for supplying a partial byte as the last input. /// Assumes that the input is in the least significant bits (big endian). /// will be placed in the first [Hash::output_len] bytes. + /// The entire output buffer is zeroized before the hash output is written. /// The return value is the number of bytes written. fn do_final_partial_bits_out( self, @@ -208,6 +212,7 @@ pub trait KEMPublicKey : PartialEq + Eq + Clone + Debug + D /// 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; @@ -218,6 +223,7 @@ pub trait KEMPrivateKey : PartialEq + Eq + Clone + Secret + /// 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; @@ -293,6 +299,8 @@ pub trait MAC: Sized { /// Depending on the underlying MAC implementation, NIST may require that the library enforce /// a minimum length on the mac output value. See documentation for the underlying implementation /// to see conditions under which it throws [MACError::InvalidLength]. + /// + /// The entire output buffer is zeroized before the MAC value is written. fn mac_out(self, data: &[u8],out: &mut [u8]) -> Result; /// One-shot API that verifies a MAC for the provided data. @@ -318,6 +326,8 @@ pub trait MAC: Sized { /// Depending on the underlying MAC implementation, NIST may require that the library enforce /// a minimum length on the mac output value. See documentation for the underlying implementation /// to see conditions under which it throws [MACError::InvalidLength]. + /// + /// The entire output buffer is zeroized before the MAC value is written. fn do_final_out(self, out: &mut [u8]) -> Result; /// Internally, this will re-compute the MAC value and then compare it to the provided mac value @@ -392,6 +402,7 @@ pub trait RNG : Default { fn next_bytes(&mut self, len: usize) -> Result, RNGError>; /// Returns the number of bytes written. + /// The entire output buffer is zeroized before the random bytes are written. fn next_bytes_out(&mut self, out: &mut [u8]) -> Result; fn fill_keymaterial_out(&mut self, out: &mut impl KeyMaterialTrait) -> Result; @@ -443,6 +454,7 @@ pub trait PHSignature< /// might throw an error, ignore the provided ctx value, or append the ctx to the msg in a non-standard way. fn sign_ph(sk: &SK, ph: &[u8; PH_LEN], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError>; /// 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_ph_out(sk: &SK, ph: &[u8; PH_LEN], ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN]) -> Result; /// 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). @@ -501,6 +513,7 @@ pub trait Signature< fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError>; /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. + /// The entire output buffer is zeroized before the signature is written. fn sign_out(sk: &SK, msg: &[u8], ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN]) -> Result; /* streaming signing API */ @@ -516,6 +529,7 @@ pub trait Signature< fn sign_final(self) -> Result<[u8; SIG_LEN], SignatureError>; /// 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; /// On success, returns Ok(()) @@ -543,6 +557,7 @@ pub trait SignaturePublicKey : PartialEq + Eq + Clone + Deb /// 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; @@ -553,6 +568,7 @@ pub trait SignaturePrivateKey : PartialEq + Eq + Clone + Se /// 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; @@ -583,6 +599,7 @@ pub trait XOF : Default { /// A static one-shot API that digests the input data and produces `result_len` bytes of output. /// Fills the provided output slice. + /// The entire output buffer is zeroized before the output is written. fn hash_xof_out(self, data: &[u8], output: &mut [u8]) -> usize; fn absorb(&mut self, data: &[u8]); @@ -599,6 +616,7 @@ pub trait XOF : Default { /// Can be called multiple times. /// Fills the provided output slice. + /// The entire output buffer is zeroized before the output is written. fn squeeze_out(&mut self, output: &mut [u8]) -> usize; /// Squeezes a partial byte from the XOF. @@ -606,6 +624,8 @@ pub trait XOF : Default { /// This is a final call and consumes self. fn squeeze_partial_byte_final(self, num_bits: usize) -> Result; + /// The same as [XOF::squeeze_partial_byte_final], but writes into the provided output byte. + /// The output byte is zeroized before the result is written. fn squeeze_partial_byte_final_out( self, num_bits: usize, diff --git a/crypto/mlkem/src/matrix.rs b/crypto/mlkem/src/matrix.rs index 563d19c..381e7be 100644 --- a/crypto/mlkem/src/matrix.rs +++ b/crypto/mlkem/src/matrix.rs @@ -171,8 +171,6 @@ impl Vector // let mut s = self.clone(); // s.conditional_sub_q(); - out.fill(0); - let mut idx = 0; match du { 10 => { // MLKEM512 and MLKEM 768 diff --git a/crypto/mlkem/src/polynomial.rs b/crypto/mlkem/src/polynomial.rs index 1a9bb2e..13bc411 100644 --- a/crypto/mlkem/src/polynomial.rs +++ b/crypto/mlkem/src/polynomial.rs @@ -127,8 +127,6 @@ impl Polynomial { // each of the N i16's will take dv bits debug_assert_eq!(out.len(), N * (dv as usize) / 8); - out.fill(0); - let mut t = [0u8; 8]; let mut idx = 0; diff --git a/crypto/mlkem_lowmemory/src/polynomial.rs b/crypto/mlkem_lowmemory/src/polynomial.rs index a2b1603..bec4ee4 100644 --- a/crypto/mlkem_lowmemory/src/polynomial.rs +++ b/crypto/mlkem_lowmemory/src/polynomial.rs @@ -150,8 +150,6 @@ impl Polynomial { // each of the N i16's will take dv bits debug_assert_eq!(out.len(), N * (dv as usize) / 8); - out.fill(0); - let mut t = [0u8; 8]; let mut idx = 0; diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index b2d5311..68fd94b 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -66,10 +66,7 @@ mod sha3_tests { assert_eq!(&out, b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); assert_eq!(bytes_written, 64); - // Q. T. Felix NOTE: With the application of zeroize, the entire output buffer is pre-initialized to 0, - // so the bytes after the digest length are now also 0. - // Previously, the contract was to leave the trailing bytes untouched, - // but this has been changed to fill them with zeros to prevent exposure of stale data. + // check that the bytes of an oversized output buffer past the digest length get zeroized. let mut out = DUMMY_SEED_512.clone(); SHA3_256::new().hash_out(DUMMY_SEED_512, &mut out); assert!(out[32..].iter().all(|&b| b == 0)); diff --git a/crypto/utils/src/ct.rs b/crypto/utils/src/ct.rs index 10b23e7..96b1950 100644 --- a/crypto/utils/src/ct.rs +++ b/crypto/utils/src/ct.rs @@ -292,8 +292,6 @@ pub fn conditional_copy_bytes( out: &mut [u8; LEN], take_a: bool, ) { - out.fill(0); - // we want the behaviour of // if take_a { 0xFF } else { 0x00 } // but without using any branches that could leak timing signals From f828c023ecb721078c8992927338ecfc7345720e Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Thu, 11 Jun 2026 14:54:59 -0500 Subject: [PATCH 07/35] tweaked todo list --- alpha_0.1.2_release_notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index d6609db..afb4af1 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -47,6 +47,8 @@ Box is for? * Deal with as many of the inline TODOs as possible * Close all open github issues and document them in this file. +* After everything is merged, circle back to crucible, and make sure that the harness still works (and maybe remove the + nightly build toolchain) # 0.1.2 Features / Changelog From e7eafba3205452d7f5259b117fc7bb0c14758010 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Thu, 11 Jun 2026 15:02:22 -0500 Subject: [PATCH 08/35] dir rename to use hyphen instead of underscore --- Cargo.toml | 22 +++++++++---------- .../Cargo.toml | 0 .../benches/mldsa_benches.rs | 0 .../benches/note_on_mem_usage_benches.md | 0 .../src/aux_functions.rs | 0 .../src/hash_mldsa.rs | 0 .../src/lib.rs | 0 .../src/low_memory_helpers.rs | 0 .../src/mldsa.rs | 0 .../src/mldsa_keys.rs | 0 .../src/polynomial.rs | 0 .../tests/bc_test_data.rs | 0 .../tests/hash_mldsa_tests.rs | 0 .../tests/mldsa_key_tests.rs | 0 .../tests/mldsa_tests.rs | 0 .../tests/wycheproof.rs | 0 .../Cargo.toml | 0 .../benches/mlkem_benches.rs | 0 .../src/aux_functions.rs | 0 .../src/lib.rs | 0 .../src/low_memory_helpers.rs | 0 .../src/mlkem.rs | 0 .../src/mlkem_keys.rs | 0 .../src/polynomial.rs | 0 .../tests/bc_test_data.rs | 0 .../tests/mlkem_key_tests.rs | 0 .../tests/mlkem_tests.rs | 0 .../tests/wycheproof.rs | 0 28 files changed, 11 insertions(+), 11 deletions(-) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/Cargo.toml (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/benches/mldsa_benches.rs (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/benches/note_on_mem_usage_benches.md (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/src/aux_functions.rs (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/src/hash_mldsa.rs (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/src/lib.rs (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/src/low_memory_helpers.rs (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/src/mldsa.rs (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/src/mldsa_keys.rs (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/src/polynomial.rs (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/tests/bc_test_data.rs (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/tests/hash_mldsa_tests.rs (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/tests/mldsa_key_tests.rs (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/tests/mldsa_tests.rs (100%) rename crypto/{mldsa_lowmemory => mldsa-lowmemory}/tests/wycheproof.rs (100%) rename crypto/{mlkem_lowmemory => mlkem-lowmemory}/Cargo.toml (100%) rename crypto/{mlkem_lowmemory => mlkem-lowmemory}/benches/mlkem_benches.rs (100%) rename crypto/{mlkem_lowmemory => mlkem-lowmemory}/src/aux_functions.rs (100%) rename crypto/{mlkem_lowmemory => mlkem-lowmemory}/src/lib.rs (100%) rename crypto/{mlkem_lowmemory => mlkem-lowmemory}/src/low_memory_helpers.rs (100%) rename crypto/{mlkem_lowmemory => mlkem-lowmemory}/src/mlkem.rs (100%) rename crypto/{mlkem_lowmemory => mlkem-lowmemory}/src/mlkem_keys.rs (100%) rename crypto/{mlkem_lowmemory => mlkem-lowmemory}/src/polynomial.rs (100%) rename crypto/{mlkem_lowmemory => mlkem-lowmemory}/tests/bc_test_data.rs (100%) rename crypto/{mlkem_lowmemory => mlkem-lowmemory}/tests/mlkem_key_tests.rs (100%) rename crypto/{mlkem_lowmemory => mlkem-lowmemory}/tests/mlkem_tests.rs (100%) rename crypto/{mlkem_lowmemory => mlkem-lowmemory}/tests/wycheproof.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index ecf3f90..4301f8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [ "cli", "crypto/*", "mem_usage_benches" ] +members = ["cli", "crypto/*", "mem_usage_benches"] [workspace.package] edition = "2024" @@ -8,21 +8,21 @@ edition = "2024" # *** Internal Dependencies *** bouncycastle = { path = "./", version = "0.1.1" } -bouncycastle-base64 = { path = "./crypto/base64", version = "0.1.1"} +bouncycastle-base64 = { path = "./crypto/base64", version = "0.1.1" } bouncycastle-core = { path = "crypto/core", version = "0.1.1" } -bouncycastle-core-test-framework = { path = "./crypto/core-test-framework", version = "0.1.1"} -bouncycastle-factory = { path = "./crypto/factory", version = "0.1.1"} +bouncycastle-core-test-framework = { path = "./crypto/core-test-framework", version = "0.1.1" } +bouncycastle-factory = { path = "./crypto/factory", version = "0.1.1" } bouncycastle-hex = { path = "./crypto/hex", version = "0.1.1" } -bouncycastle-hkdf = { path = "./crypto/hkdf", version = "0.1.1"} -bouncycastle-hmac = { path = "./crypto/hmac", version = "0.1.1"} +bouncycastle-hkdf = { path = "./crypto/hkdf", version = "0.1.1" } +bouncycastle-hmac = { path = "./crypto/hmac", version = "0.1.1" } bouncycastle-mlkem = { path = "./crypto/mlkem", version = "0.1.2" } -bouncycastle-mlkem-lowmemory = { path = "./crypto/mlkem_lowmemory", version = "0.1.2" } +bouncycastle-mlkem-lowmemory = { path = "./crypto/mlkem-lowmemory", version = "0.1.2" } bouncycastle-mldsa = { path = "./crypto/mldsa", version = "0.1.2" } -bouncycastle-mldsa-lowmemory = { path = "./crypto/mldsa_lowmemory", version = "0.1.2" } +bouncycastle-mldsa-lowmemory = { path = "./crypto/mldsa-lowmemory", version = "0.1.2" } bouncycastle-rng = { path = "./crypto/rng", version = "0.1.1" } -bouncycastle-sha2 = { path = "./crypto/sha2", version = "0.1.1"} -bouncycastle-sha3 = { path = "./crypto/sha3", version = "0.1.1"} -bouncycastle-utils = { path = "./crypto/utils", version = "0.1.1"} +bouncycastle-sha2 = { path = "./crypto/sha2", version = "0.1.1" } +bouncycastle-sha3 = { path = "./crypto/sha3", version = "0.1.1" } +bouncycastle-utils = { path = "./crypto/utils", version = "0.1.1" } # *** External Dependencies *** diff --git a/crypto/mldsa_lowmemory/Cargo.toml b/crypto/mldsa-lowmemory/Cargo.toml similarity index 100% rename from crypto/mldsa_lowmemory/Cargo.toml rename to crypto/mldsa-lowmemory/Cargo.toml diff --git a/crypto/mldsa_lowmemory/benches/mldsa_benches.rs b/crypto/mldsa-lowmemory/benches/mldsa_benches.rs similarity index 100% rename from crypto/mldsa_lowmemory/benches/mldsa_benches.rs rename to crypto/mldsa-lowmemory/benches/mldsa_benches.rs diff --git a/crypto/mldsa_lowmemory/benches/note_on_mem_usage_benches.md b/crypto/mldsa-lowmemory/benches/note_on_mem_usage_benches.md similarity index 100% rename from crypto/mldsa_lowmemory/benches/note_on_mem_usage_benches.md rename to crypto/mldsa-lowmemory/benches/note_on_mem_usage_benches.md diff --git a/crypto/mldsa_lowmemory/src/aux_functions.rs b/crypto/mldsa-lowmemory/src/aux_functions.rs similarity index 100% rename from crypto/mldsa_lowmemory/src/aux_functions.rs rename to crypto/mldsa-lowmemory/src/aux_functions.rs diff --git a/crypto/mldsa_lowmemory/src/hash_mldsa.rs b/crypto/mldsa-lowmemory/src/hash_mldsa.rs similarity index 100% rename from crypto/mldsa_lowmemory/src/hash_mldsa.rs rename to crypto/mldsa-lowmemory/src/hash_mldsa.rs diff --git a/crypto/mldsa_lowmemory/src/lib.rs b/crypto/mldsa-lowmemory/src/lib.rs similarity index 100% rename from crypto/mldsa_lowmemory/src/lib.rs rename to crypto/mldsa-lowmemory/src/lib.rs diff --git a/crypto/mldsa_lowmemory/src/low_memory_helpers.rs b/crypto/mldsa-lowmemory/src/low_memory_helpers.rs similarity index 100% rename from crypto/mldsa_lowmemory/src/low_memory_helpers.rs rename to crypto/mldsa-lowmemory/src/low_memory_helpers.rs diff --git a/crypto/mldsa_lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs similarity index 100% rename from crypto/mldsa_lowmemory/src/mldsa.rs rename to crypto/mldsa-lowmemory/src/mldsa.rs diff --git a/crypto/mldsa_lowmemory/src/mldsa_keys.rs b/crypto/mldsa-lowmemory/src/mldsa_keys.rs similarity index 100% rename from crypto/mldsa_lowmemory/src/mldsa_keys.rs rename to crypto/mldsa-lowmemory/src/mldsa_keys.rs diff --git a/crypto/mldsa_lowmemory/src/polynomial.rs b/crypto/mldsa-lowmemory/src/polynomial.rs similarity index 100% rename from crypto/mldsa_lowmemory/src/polynomial.rs rename to crypto/mldsa-lowmemory/src/polynomial.rs diff --git a/crypto/mldsa_lowmemory/tests/bc_test_data.rs b/crypto/mldsa-lowmemory/tests/bc_test_data.rs similarity index 100% rename from crypto/mldsa_lowmemory/tests/bc_test_data.rs rename to crypto/mldsa-lowmemory/tests/bc_test_data.rs diff --git a/crypto/mldsa_lowmemory/tests/hash_mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs similarity index 100% rename from crypto/mldsa_lowmemory/tests/hash_mldsa_tests.rs rename to crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs diff --git a/crypto/mldsa_lowmemory/tests/mldsa_key_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_key_tests.rs similarity index 100% rename from crypto/mldsa_lowmemory/tests/mldsa_key_tests.rs rename to crypto/mldsa-lowmemory/tests/mldsa_key_tests.rs diff --git a/crypto/mldsa_lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs similarity index 100% rename from crypto/mldsa_lowmemory/tests/mldsa_tests.rs rename to crypto/mldsa-lowmemory/tests/mldsa_tests.rs diff --git a/crypto/mldsa_lowmemory/tests/wycheproof.rs b/crypto/mldsa-lowmemory/tests/wycheproof.rs similarity index 100% rename from crypto/mldsa_lowmemory/tests/wycheproof.rs rename to crypto/mldsa-lowmemory/tests/wycheproof.rs diff --git a/crypto/mlkem_lowmemory/Cargo.toml b/crypto/mlkem-lowmemory/Cargo.toml similarity index 100% rename from crypto/mlkem_lowmemory/Cargo.toml rename to crypto/mlkem-lowmemory/Cargo.toml diff --git a/crypto/mlkem_lowmemory/benches/mlkem_benches.rs b/crypto/mlkem-lowmemory/benches/mlkem_benches.rs similarity index 100% rename from crypto/mlkem_lowmemory/benches/mlkem_benches.rs rename to crypto/mlkem-lowmemory/benches/mlkem_benches.rs diff --git a/crypto/mlkem_lowmemory/src/aux_functions.rs b/crypto/mlkem-lowmemory/src/aux_functions.rs similarity index 100% rename from crypto/mlkem_lowmemory/src/aux_functions.rs rename to crypto/mlkem-lowmemory/src/aux_functions.rs diff --git a/crypto/mlkem_lowmemory/src/lib.rs b/crypto/mlkem-lowmemory/src/lib.rs similarity index 100% rename from crypto/mlkem_lowmemory/src/lib.rs rename to crypto/mlkem-lowmemory/src/lib.rs diff --git a/crypto/mlkem_lowmemory/src/low_memory_helpers.rs b/crypto/mlkem-lowmemory/src/low_memory_helpers.rs similarity index 100% rename from crypto/mlkem_lowmemory/src/low_memory_helpers.rs rename to crypto/mlkem-lowmemory/src/low_memory_helpers.rs diff --git a/crypto/mlkem_lowmemory/src/mlkem.rs b/crypto/mlkem-lowmemory/src/mlkem.rs similarity index 100% rename from crypto/mlkem_lowmemory/src/mlkem.rs rename to crypto/mlkem-lowmemory/src/mlkem.rs diff --git a/crypto/mlkem_lowmemory/src/mlkem_keys.rs b/crypto/mlkem-lowmemory/src/mlkem_keys.rs similarity index 100% rename from crypto/mlkem_lowmemory/src/mlkem_keys.rs rename to crypto/mlkem-lowmemory/src/mlkem_keys.rs diff --git a/crypto/mlkem_lowmemory/src/polynomial.rs b/crypto/mlkem-lowmemory/src/polynomial.rs similarity index 100% rename from crypto/mlkem_lowmemory/src/polynomial.rs rename to crypto/mlkem-lowmemory/src/polynomial.rs diff --git a/crypto/mlkem_lowmemory/tests/bc_test_data.rs b/crypto/mlkem-lowmemory/tests/bc_test_data.rs similarity index 100% rename from crypto/mlkem_lowmemory/tests/bc_test_data.rs rename to crypto/mlkem-lowmemory/tests/bc_test_data.rs diff --git a/crypto/mlkem_lowmemory/tests/mlkem_key_tests.rs b/crypto/mlkem-lowmemory/tests/mlkem_key_tests.rs similarity index 100% rename from crypto/mlkem_lowmemory/tests/mlkem_key_tests.rs rename to crypto/mlkem-lowmemory/tests/mlkem_key_tests.rs diff --git a/crypto/mlkem_lowmemory/tests/mlkem_tests.rs b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs similarity index 100% rename from crypto/mlkem_lowmemory/tests/mlkem_tests.rs rename to crypto/mlkem-lowmemory/tests/mlkem_tests.rs diff --git a/crypto/mlkem_lowmemory/tests/wycheproof.rs b/crypto/mlkem-lowmemory/tests/wycheproof.rs similarity index 100% rename from crypto/mlkem_lowmemory/tests/wycheproof.rs rename to crypto/mlkem-lowmemory/tests/wycheproof.rs From d82041b25c9c05690086baa3169de6efb80708e4 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 22 Jun 2026 09:29:08 -0500 Subject: [PATCH 09/35] Various small refactoring tasks: * removed nightly feature from mldsa * Split the Signature trait into Signer and SignatureVerifier * Split the KEM trait into KEMEncapsulator and KEMDecapsulator * Touched up some documentation --- alpha_0.1.2_release_notes.md | 37 +-- cli/src/mldsa_cmd.rs | 4 +- cli/src/mlkem_cmd.rs | 2 +- crypto/core-test-framework/src/kem.rs | 56 ++-- crypto/core-test-framework/src/signature.rs | 146 +++++----- crypto/core/src/key_material.rs | 94 +++---- crypto/core/src/lib.rs | 1 - crypto/core/src/traits.rs | 174 ++++++++---- .../mldsa-lowmemory/benches/mldsa_benches.rs | 2 +- crypto/mldsa-lowmemory/src/hash_mldsa.rs | 260 +++++++++++++++--- crypto/mldsa-lowmemory/src/lib.rs | 11 +- crypto/mldsa-lowmemory/src/mldsa.rs | 113 ++++++-- crypto/mldsa-lowmemory/tests/bc_test_data.rs | 2 +- .../mldsa-lowmemory/tests/hash_mldsa_tests.rs | 22 +- .../mldsa-lowmemory/tests/mldsa_key_tests.rs | 16 +- crypto/mldsa-lowmemory/tests/mldsa_tests.rs | 8 +- crypto/mldsa-lowmemory/tests/wycheproof.rs | 2 +- crypto/mldsa/benches/mldsa_benches.rs | 2 +- crypto/mldsa/src/hash_mldsa.rs | 255 ++++++++++++----- crypto/mldsa/src/lib.rs | 20 +- crypto/mldsa/src/mldsa.rs | 98 +++++-- crypto/mldsa/tests/bc_test_data.rs | 2 +- crypto/mldsa/tests/hash_mldsa_tests.rs | 22 +- crypto/mldsa/tests/mldsa_key_tests.rs | 20 +- crypto/mldsa/tests/mldsa_tests.rs | 8 +- crypto/mldsa/tests/wycheproof.rs | 2 +- .../mlkem-lowmemory/benches/mlkem_benches.rs | 2 +- crypto/mlkem-lowmemory/src/lib.rs | 7 +- crypto/mlkem-lowmemory/src/mlkem.rs | 63 ++++- .../mlkem-lowmemory/tests/mlkem_key_tests.rs | 18 +- crypto/mlkem-lowmemory/tests/mlkem_tests.rs | 10 +- crypto/mlkem-lowmemory/tests/wycheproof.rs | 2 +- crypto/mlkem/benches/mlkem_benches.rs | 2 +- crypto/mlkem/src/aux_functions.rs | 16 +- crypto/mlkem/src/lib.rs | 11 +- crypto/mlkem/src/mlkem.rs | 52 +++- crypto/mlkem/src/polynomial.rs | 18 +- crypto/mlkem/tests/bc_test_data.rs | 4 +- crypto/mlkem/tests/mlkem_key_tests.rs | 21 +- crypto/mlkem/tests/mlkem_tests.rs | 10 +- crypto/mlkem/tests/wycheproof.rs | 2 +- crypto/rng/src/hash_drbg80090a.rs | 2 + crypto/rng/src/lib.rs | 2 - mem_usage_benches/bench_mldsa_mem_usage.rs | 2 +- mem_usage_benches/bench_mlkem_mem_usage.rs | 2 +- 45 files changed, 1085 insertions(+), 540 deletions(-) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index afb4af1..cebd420 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -6,12 +6,8 @@ * Check the crate release checklist and run claude against the style guide (maybe Francis could cross-check me) * Run Crucible testing * Add factories for ML-DSA and ML-KEM (if we are keeping factories, see below) -* Split the Signature trait into a Signer and a Verifier so that, for example, we can implement the verifier for MTC in - a different struct from the signer; or so that you can get FIPS compliance on old algorithms that are currently only - FIPS-allowed for verification of existing signatures but not for creation of new ones. * Check out Megan's email May 13 about KeyMaterial: "I was wondering if there might be scope for a closure based - approach that could - guarantee encapsulation of the state change from safe to hazardous back to safe again." + approach that could guarantee encapsulation of the state change from safe to hazardous back to safe again." * Go back to previous algs and apply memory optimization tricks like internal functions. And add a docs section "Memory Usage" that measures with valgrind. * Ensure that all crates have `#![forbid(missing_docs)]` @@ -21,30 +17,13 @@ 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 - problem of the caller to properly seed the RNG? * Factories ... Are they worth it? Michael Richardson says Very Yes. If we are keeping them, then we need a serious re-engineering of them because I really dislike that currently they make it hard for the underlying primitive to have static one-shot APIs. -* Add back the Memoable trait from nursery (maybe under a different name) that lets you serialize out the - intermediate state, especially important for SHA2, SHA3, and HMAC because TLS needs to be able to fork a state, - finalize() a copy and then keep feeding the other copy. -* Do some science about perf impacts of acting on a local hard-copy vs acting in-place on some specific bit of - memory -* Change the tone of the documentation (both the crate docs and the inline comments) to be less individual ("I" - statements) and be more factual ("it is", or "the project", or "the bc-rust library" as appropriate). -* Relax the requirement on XOF that once you start squeezing, you can't absorb anymore. This will likely need to be an - exposed "bell & whistle" because it is an obvious way to do something like the TLS handshake transcript where you need - to periodically spit out hashes and then continue absorbing more input. We'll need to study the SHA3 / SHAKE FIPS - documents because it might be that this is forbidden as part of the definition of SHAKE, but is allowed if you use the - KECCAK primitive raw. We need to make a decision on how to handle this, and provide some sample code in crate docs. -* Need a rust expert: I use a bunch of #![feature(_)]'s that are only available in nightly. ... what should I do - about that? -* Research task: no_std means that everything is on the stack, which can cause you to blow your stack limit. Research - how an application that itself is not no_std can put our large structs (like key objects) on the heap. Is this what - Box is for? * Deal with as many of the inline TODOs as possible * Close all open github issues and document them in this file. * After everything is merged, circle back to crucible, and make sure that the harness still works (and maybe remove the @@ -52,9 +31,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..cfb99bd 100644 --- a/cli/src/mldsa_cmd.rs +++ b/cli/src/mldsa_cmd.rs @@ -2,7 +2,9 @@ //! 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::{ + SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, +}; 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/key_material.rs b/crypto/core/src/key_material.rs index 48f903e..6097413 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -57,13 +57,40 @@ pub trait KeyMaterialTrait { /// Loads the provided data into a new KeyMaterial of the specified type. /// This is discouraged unless the caller knows the provenance of the data, such as loading it /// from a cryptographic private key file. - /// It will detect if you give it all-zero source data and set the key type to [KeyType::Zeroized] instead. + /// + /// This behaves differently on all-zero input key depending on whether [KeyMaterialTrait::allow_hazardous_operations] is set: + /// if not set, then it will succeed, setting the key type to [KeyType::Zeroized] and also return a [KeyMaterialError::ActingOnZeroizedKey] + /// to indicate that you may want to perform error-handling, which could be manually setting the key type + /// if you intend to allow zero keys, or do some other error-handling, like figure out why your RNG is broken. + /// Note that even if a [KeyMaterialError::ActingOnZeroizedKey] is returned, the object is still populated and usable. + /// For example, you could catch it like this: + /// ``` + /// use bouncycastle_core::key_material::{KeyMaterial256, KeyType, KeyMaterialTrait}; + /// use bouncycastle_core::key_material::KeyMaterial; + /// use bouncycastle_core::errors::KeyMaterialError; + /// + /// let key_bytes = [0u8; 16]; + /// let mut key = KeyMaterial256::new(); + /// let res = key.set_bytes_as_type(&key_bytes, KeyType::BytesLowEntropy); + /// match res { + /// Err(KeyMaterialError::ActingOnZeroizedKey) => { + /// // Either figure out why your passed an all-zero key, + /// // or set the key type manually, if that's what you intended. + /// key.allow_hazardous_operations(); + /// key.set_key_type(KeyType::BytesLowEntropy).unwrap(); // probably you should do something more elegant than .unwrap in your code ;) + /// key.drop_hazardous_operations(); + /// }, + /// Err(_) => { /* figure out what else went wrong */ }, + /// Ok(_) => { /* good */ }, + /// } + /// ``` + /// On the other hand, if [KeyMaterialTrait::allow_hazardous_operations] is set then it will just do what you asked without complaining. + /// /// Since this zeroizes and resets the key material, this is considered a dangerous conversion. /// - /// The only hazardous operation here that requires setting [KeyMaterialTrait::allow_hazardous_operations] is giving it - /// an all-zero key, which is checked as a courtesy to catch mistakes of feeding an initialized buffer - /// instead of an actual key. See the note on [KeyMaterial::set_bytes_as_type] for suggestions - /// for handling this. + /// Will set the [SecurityStrength] automatically according to the following rules: + /// * If [KeyType] is [KeyType::Zeroized] or [KeyType::BytesLowEntropy] then it will be [SecurityStrength::None]. + /// * Otherwise it will set it based on the length of the provided source bytes. fn set_bytes_as_type( &mut self, source: &[u8], @@ -122,18 +149,18 @@ pub trait KeyMaterialTrait { fn set_security_strength(&mut self, strength: SecurityStrength) -> Result<(), KeyMaterialError>; - /// Sets this instance to be able to perform potentially hazardous conversions such as + /// Sets this instance to be able to perform potentially hazardous operations such as /// casting a KeyMaterial of type RawUnknownEntropy or RawLowEntropy into RawFullEntropy or SymmetricCipherKey, /// or manually setting the key bytes via [KeyMaterialTrait::mut_ref_to_bytes], which then requires you to be responsible /// for setting the key_len and key_type afterwards. /// - /// The purpose of the hazardous_conversions guard is not to prevent the user from accessing their data, + /// The purpose of the hazardous operations guard is not to prevent the user from accessing their data, /// but rather to make the developer think carefully about the operation they are about to perform, /// and to give static analysis tools an obvious marker that a given KeyMaterial variable warrants /// further inspection. fn allow_hazardous_operations(&mut self); - /// Resets this instance to not be able to perform potentially hazardous conversions. + /// Resets this instance to not be able to perform potentially hazardous operations. fn drop_hazardous_operations(&mut self); /// Sets the key_type of this KeyMaterial object. @@ -286,43 +313,6 @@ impl KeyMaterial { } impl KeyMaterialTrait for KeyMaterial { - /// Loads the provided data into a new KeyMaterial of the specified type. - /// This is discouraged unless the caller knows the provenance of the data, such as loading it - /// from a cryptographic private key file. - /// - /// This behaves differently on all-zero input key depending on whether [KeyMaterialTrait::allow_hazardous_operations] is set: - /// if not set, then it will succeed, setting the key type to [KeyType::Zeroized] and also return a [KeyMaterialError::ActingOnZeroizedKey] - /// to indicate that you may want to perform error-handling, which could be manually setting the key type - /// if you intend to allow zero keys, or do some other error-handling, like figure out why your RNG is broken. - /// Note that even if a [KeyMaterialError::ActingOnZeroizedKey] is returned, the object is still populated and usable. - /// For example, you could catch it like this: - /// ``` - /// use bouncycastle_core::key_material::{KeyMaterial256, KeyType, KeyMaterialTrait}; - /// use bouncycastle_core::key_material::KeyMaterial; - /// use bouncycastle_core::errors::KeyMaterialError; - /// - /// let key_bytes = [0u8; 16]; - /// let mut key = KeyMaterial256::new(); - /// let res = key.set_bytes_as_type(&key_bytes, KeyType::BytesLowEntropy); - /// match res { - /// Err(KeyMaterialError::ActingOnZeroizedKey) => { - /// // Either figure out why your passed an all-zero key, - /// // or set the key type manually, if that's what you intended. - /// key.allow_hazardous_operations(); - /// key.set_key_type(KeyType::BytesLowEntropy).unwrap(); // probably you should do something more elegant than .unwrap in your code ;) - /// key.drop_hazardous_operations(); - /// }, - /// Err(_) => { /* figure out what else went wrong */ }, - /// Ok(_) => { /* good */ }, - /// } - /// ``` - /// On the other hand, if [KeyMaterialTrait::allow_hazardous_operations] is set then it will just do what you asked without complaining. - /// - /// Since this zeroizes and resets the key material, this is considered a dangerous conversion. - /// - /// Will set the [SecurityStrength] automatically according to the following rules: - /// * If [KeyType] is [KeyType::Zeroized] or [KeyType::BytesLowEntropy] then it will be [SecurityStrength::None]. - /// * Otherwise it will set it based on the length of the provided source bytes. fn set_bytes_as_type( &mut self, source: &[u8], @@ -453,26 +443,12 @@ impl KeyMaterialTrait for KeyMaterial { self.drop_hazardous_operations(); Ok(()) } - /// Sets this instance to be able to perform potentially hazardous operations such as - /// casting a KeyMaterial of type RawUnknownEntropy or RawLowEntropy into RawFullEntropy or SymmetricCipherKey. - /// - /// The purpose of the hazardous operations guard is not to prevent the user from accessing their data, - /// but rather to make the developer think carefully about the operation they are about to perform, - /// and to give static analysis tools an obvious marker that a given KeyMaterial variable warrants - /// further inspection. fn allow_hazardous_operations(&mut self) { self.allow_hazardous_operations = true; } - /// Resets this instance to not be able to perform potentially hazardous operations. fn drop_hazardous_operations(&mut self) { self.allow_hazardous_operations = false; } - /// Sets the key_type of this KeyMaterial object. - /// Does not perform any operations on the actual key material, other than changing the key_type field. - /// If allow_hazardous_operations is true, this method will allow conversion to any KeyType, otherwise - /// checking is performed to ensure that the conversion is "safe". - /// This drops the allow_hazardous_operations flag, so if you need to do multiple hazardous operations - /// on the same instance, then you'll need to call .allow_hazardous_operations() each time. fn convert_key_type(&mut self, new_key_type: KeyType) -> Result<(), KeyMaterialError> { if self.allow_hazardous_operations { // just do it 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..6927d0d 100644 --- a/crypto/mldsa-lowmemory/benches/mldsa_benches.rs +++ b/crypto/mldsa-lowmemory/benches/mldsa_benches.rs @@ -1,5 +1,5 @@ use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -use bouncycastle_core::traits::Signature; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ MLDSA44, MLDSA44_SIG_LEN, MLDSA65, MLDSA65_SIG_LEN, MLDSA87, MLDSA87_SIG_LEN, MLDSATrait, diff --git a/crypto/mldsa-lowmemory/src/hash_mldsa.rs b/crypto/mldsa-lowmemory/src/hash_mldsa.rs index ec0978f..1c9f9e8 100644 --- a/crypto/mldsa-lowmemory/src/hash_mldsa.rs +++ b/crypto/mldsa-lowmemory/src/hash_mldsa.rs @@ -3,11 +3,11 @@ //! mode of [MLDSA]; possibly because you have to digest the message before you know which public key //! will sign it. //! -//! HashML-DSA is a full signature algorithm implementing the [Signature] trait: +//! HashML-DSA is a full signature algorithm implementing the [Signer] and [SignatureVerifier] traits: //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::Signature; +//! use bouncycastle_core::traits::{Signer, SignatureVerifier}; //! use bouncycastle_mldsa_lowmemory::{MLDSATrait, HashMLDSA65_with_SHA512, HashMLDSA44_with_SHA512}; //! //! let msg = b"The quick brown fox jumped over the lazy dog"; @@ -24,11 +24,13 @@ //! } //! ``` //! -//! But you also have access to the pre-hashed function available from [PHSignature]: +//! But you also have access to the pre-hashed function available from [PHSigner] and [PHSignatureVerifier]: //! //! ```rust //! use bouncycastle_core::errors::SignatureError; -//! use bouncycastle_core::traits::{Signature, PHSignature, Hash}; +//! use bouncycastle_core::traits::{ +//! Hash, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, +//! }; //! use bouncycastle_sha2::SHA512; //! use bouncycastle_mldsa_lowmemory::{MLDSATrait, HashMLDSA65_with_SHA512, HashMLDSA44_with_SHA512}; //! @@ -44,14 +46,14 @@ //! let sig = HashMLDSA65_with_SHA512::sign_ph(&sk, &ph, None).unwrap(); //! // This is the signature value that you can save to a file or whatever you need. //! -//! // This verifies either through the usual one-shot API of the [Signature] trait +//! // This verifies either through the usual one-shot API of the [SignatureVerifier] trait //! match HashMLDSA65_with_SHA512::verify(&pk, msg, None, &sig) { //! Ok(()) => println!("Signature is valid!"), //! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), //! Err(e) => panic!("Something else went wrong: {:?}", e), //! } //! -//! // Or though the verify_ph of the [PHSignature] trait +//! // Or though the verify_ph of the [PHSignatureVerifier] trait //! match HashMLDSA65_with_SHA512::verify_ph(&pk, &ph, None, &sig) { //! Ok(()) => println!("Signature is valid!"), //! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), @@ -61,7 +63,7 @@ //! //! Note that the [HashMLDSA] object is just a light wrapper around [MLDSA], and, for example, they share key types, //! so if you need the fancy keygen functions, just use them from [MLDSA]. -//! But a simple [HashMLDSA::keygen] is provided in order to have conformance to the [Signature] trait. +//! But a simple [HashMLDSA::keygen] is provided. use crate::mldsa::{H, MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSATrait}; use crate::mldsa::{ @@ -94,7 +96,8 @@ use crate::{ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ - Algorithm, Hash, PHSignature, RNG, SecurityStrength, Signature, XOF, + Algorithm, Hash, PHSignatureVerifier, PHSigner, RNG, SecurityStrength, SignatureVerifier, + Signer, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha2::{SHA256, SHA512}; @@ -511,6 +514,42 @@ impl< GAMMA1_MASK_LEN, > { + /// Generate a keypair, sourcing randomness from bouncycastle's default os-backed RNG. + /// + /// Key generation is intentionally not part of the [Signer] / [SignatureVerifier] traits; + /// it is provided as an inherent associated function directly on the algorithm struct. + /// Keys are interchangeable between MLDSA and HashMLDSA. + /// Error condition: basically only on RNG failures. + pub fn keygen() -> Result<(PK, SK), SignatureError> { + MLDSA::< + PK_LEN, + SK_LEN, + FULL_SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + >::keygen() + } + /// Imports a secret key from a seed. pub fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError> { MLDSA::< @@ -644,9 +683,9 @@ impl< Ok(bytes_written) } - /// To be used for deterministic signing in conjunction with the [Signature::sign_init], - /// [Signature::sign_update], and [Signature::sign_final] flow. - /// Can be set anywhere after [Signature::sign_init] and before [Signature::sign_final] + /// To be used for deterministic signing in conjunction with the [Signer::sign_init], + /// [Signer::sign_update], and [Signer::sign_final] flow. + /// Can be set anywhere after [Signer::sign_init] and before [Signer::sign_final] pub fn set_signer_rnd(&mut self, rnd: [u8; 32]) { self.signer_rnd = Some(rnd); } @@ -736,7 +775,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> Signature +> Signer for HashMLDSA< HASH, PH_LEN, @@ -768,37 +807,6 @@ impl< GAMMA1_MASK_LEN, > { - /// Keygen, and keys in general, are interchangeable between MLDSA and HashMLDSA. - fn keygen() -> Result<(PK, SK), SignatureError> { - MLDSA::< - PK_LEN, - SK_LEN, - FULL_SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::keygen() - } - /// Algorithm 4 HashML-DSA.Sign(π‘ π‘˜, 𝑀 , 𝑐𝑑π‘₯, PH) /// Generate a β€œpre-hash” ML-DSA signature. fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { @@ -884,7 +892,89 @@ impl< unreachable!() } } +} +impl< + HASH: Hash + Default, + PK: MLDSAPublicKeyTrait + + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait< + k, + l, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > + MLDSAPrivateKeyInternalTrait< + LAMBDA, + GAMMA2, + k, + l, + ETA, + S1_PACKED_LEN, + S2_PACKED_LEN, + PK_LEN, + SK_LEN, + >, + const PH_LEN: usize, + const oid: &'static [u8], + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const SIG_LEN: usize, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const S1_PACKED_LEN: usize, + const S2_PACKED_LEN: usize, + const T1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, + const GAMMA1_MASK_LEN: usize, +> SignatureVerifier + for HashMLDSA< + HASH, + PH_LEN, + oid, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mut ph_m = [0u8; PH_LEN]; _ = HASH::default().hash_out(msg, &mut ph_m); @@ -969,7 +1059,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> PHSignature +> PHSigner for HashMLDSA< HASH, PH_LEN, @@ -1028,7 +1118,89 @@ impl< HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; Self::sign_ph_deterministic_out(sk, ctx, ph, rnd, output) } +} +impl< + HASH: Hash + Default, + const PH_LEN: usize, + const oid: &'static [u8], + const PK_LEN: usize, + const SK_LEN: usize, + const FULL_SK_LEN: usize, + const SIG_LEN: usize, + PK: MLDSAPublicKeyTrait + + MLDSAPublicKeyInternalTrait, + SK: MLDSAPrivateKeyTrait< + k, + l, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + > + MLDSAPrivateKeyInternalTrait< + LAMBDA, + GAMMA2, + k, + l, + ETA, + S1_PACKED_LEN, + S2_PACKED_LEN, + PK_LEN, + SK_LEN, + >, + const TAU: i32, + const LAMBDA: i32, + const GAMMA1: i32, + const GAMMA2: i32, + const k: usize, + const l: usize, + const ETA: usize, + const BETA: i32, + const OMEGA: i32, + const C_TILDE: usize, + const POLY_Z_PACKED_LEN: usize, + const POLY_W1_PACKED_LEN: usize, + const S1_PACKED_LEN: usize, + const S2_PACKED_LEN: usize, + const T1_PACKED_LEN: usize, + const LAMBDA_over_4: usize, + const GAMMA1_MINUS_BETA: i32, + const GAMMA2_MINUS_BETA: i32, + const GAMMA1_MASK_LEN: usize, +> PHSignatureVerifier + for HashMLDSA< + HASH, + PH_LEN, + oid, + PK_LEN, + SK_LEN, + FULL_SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + S1_PACKED_LEN, + S2_PACKED_LEN, + T1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + > +{ fn verify_ph( pk: &PK, ph: &[u8; PH_LEN], diff --git a/crypto/mldsa-lowmemory/src/lib.rs b/crypto/mldsa-lowmemory/src/lib.rs index 4463d77..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..8b7884f 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_key_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_key_tests.rs @@ -5,9 +5,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, - }; + use bouncycastle_core::traits::{SecurityStrength, SignaturePrivateKey, SignaturePublicKey}; use bouncycastle_core_test_framework::signature::TestFrameworkSignatureKeys; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::mldsa::{MLDSA_SEED_LEN, MLDSA44_FULL_SK_LEN}; @@ -24,9 +22,15 @@ mod mldsa_key_tests { fn core_framework_tests() { let tf = TestFrameworkSignatureKeys::new(); - tf.test_keys::(); - tf.test_keys::(); - tf.test_keys::(); + tf.test_keys::( + MLDSA44::keygen, + ); + tf.test_keys::( + MLDSA65::keygen, + ); + tf.test_keys::( + MLDSA87::keygen, + ); } #[test] diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index b3eb8c3..e45ed76 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -5,7 +5,7 @@ mod mldsa_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - RNG, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, + RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::signature::*; @@ -22,9 +22,9 @@ mod mldsa_tests { fn test_framework_signature() { let tf = TestFrameworkSignature::new(false, true); - tf.test_signature::(false); - tf.test_signature::(false); - tf.test_signature::(false); + tf.test_signature::(MLDSA44::keygen, false); + tf.test_signature::(MLDSA65::keygen, false); + tf.test_signature::(MLDSA87::keygen, false); } /// This runs the full bitflipping tests and takes several minutes. diff --git a/crypto/mldsa-lowmemory/tests/wycheproof.rs b/crypto/mldsa-lowmemory/tests/wycheproof.rs index ca9df24..080efdf 100644 --- a/crypto/mldsa-lowmemory/tests/wycheproof.rs +++ b/crypto/mldsa-lowmemory/tests/wycheproof.rs @@ -23,7 +23,7 @@ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{SecurityStrength, Signature, SignaturePublicKey}; +use bouncycastle_core::traits::{SecurityStrength, SignaturePublicKey, SignatureVerifier}; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ MLDSA44, MLDSA44PublicKey, MLDSA65, MLDSA65PublicKey, MLDSA87, MLDSA87PublicKey, diff --git a/crypto/mldsa/benches/mldsa_benches.rs b/crypto/mldsa/benches/mldsa_benches.rs index aa8f055..87be0fe 100644 --- a/crypto/mldsa/benches/mldsa_benches.rs +++ b/crypto/mldsa/benches/mldsa_benches.rs @@ -1,5 +1,5 @@ use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -use bouncycastle_core::traits::Signature; +use bouncycastle_core::traits::{SignatureVerifier, Signer}; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ MLDSA44, MLDSA44_SIG_LEN, MLDSA44PrivateKeyExpanded, MLDSA44PublicKeyExpanded, MLDSA65, diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 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..81e2493 100644 --- a/crypto/mldsa/tests/mldsa_key_tests.rs +++ b/crypto/mldsa/tests/mldsa_key_tests.rs @@ -2,9 +2,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, - }; + use bouncycastle_core::traits::{SecurityStrength, SignaturePrivateKey, SignaturePublicKey}; use bouncycastle_core_test_framework::signature::TestFrameworkSignatureKeys; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ @@ -15,17 +13,23 @@ 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..b370ec2 100644 --- a/crypto/mlkem-lowmemory/tests/mlkem_key_tests.rs +++ b/crypto/mlkem-lowmemory/tests/mlkem_key_tests.rs @@ -1,14 +1,14 @@ #[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::{MLKEM512, MLKEM768, MLKEM1024}; 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::{ MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey, MLKEM1024PrivateKey, MLKEM1024PublicKey, @@ -20,9 +20,13 @@ 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..ef40434 100644 --- a/crypto/mlkem/tests/bc_test_data.rs +++ b/crypto/mlkem/tests/bc_test_data.rs @@ -5,7 +5,9 @@ #[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..e6bdca1 100644 --- a/crypto/mlkem/tests/mlkem_key_tests.rs +++ b/crypto/mlkem/tests/mlkem_key_tests.rs @@ -2,15 +2,14 @@ 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::{MLKEM512, MLKEM768, MLKEM1024}; 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, - MLKEMPrivateKeyTrait, MLKEMPublicKeyTrait, MLKEMTrait, + 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}; use bouncycastle_mlkem::{ MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey, MLKEM1024PrivateKey, MLKEM1024PublicKey, @@ -22,9 +21,13 @@ 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..f8fe792 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::{SignaturePrivateKey, SignaturePublicKey, SignatureVerifier}; 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..cce2897 100644 --- a/mem_usage_benches/bench_mlkem_mem_usage.rs +++ b/mem_usage_benches/bench_mlkem_mem_usage.rs @@ -26,7 +26,7 @@ #![allow(unused_imports)] use bouncycastle::core::key_material::{KeyMaterial512, KeyType}; -use bouncycastle::core::traits::{KEM, KEMPublicKey}; +use bouncycastle::core::traits::{KEMDecapsulator, KEMEncapsulator, KEMPublicKey}; use bouncycastle::mlkem::mlkem::MLKEMTrait; use bouncycastle::mlkem::{ MLKEM512_CT_LEN, MLKEM512_PK_LEN, MLKEM768_CT_LEN, MLKEM768_PK_LEN, MLKEM1024_CT_LEN, From e8e66779840150c64196c875256d317686d5195a Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 22 Jun 2026 09:41:13 -0500 Subject: [PATCH 10/35] improvements to KeyMaterial. Closes #38 --- crypto/core/tests/key_material_tests.rs | 93 ++++++++++++++++++++----- 1 file changed, 75 insertions(+), 18 deletions(-) diff --git a/crypto/core/tests/key_material_tests.rs b/crypto/core/tests/key_material_tests.rs index 5e773fd..95d2b6b 100644 --- a/crypto/core/tests/key_material_tests.rs +++ b/crypto/core/tests/key_material_tests.rs @@ -65,7 +65,7 @@ mod test_key_material { key.allow_hazardous_operations(); key.set_bytes_as_type(&key_bytes, KeyType::BytesLowEntropy).unwrap(); assert_eq!(key.key_type(), KeyType::BytesLowEntropy); - + key.drop_hazardous_operations(); // nothing else requires setting hazardous operations. } @@ -94,6 +94,7 @@ mod test_key_material { key.set_key_len(32).unwrap(); assert_eq!(key.ref_to_bytes(), &[2u8; 32]); assert_eq!(key.key_len(), 32); + key.drop_hazardous_operations(); } #[test] @@ -173,15 +174,28 @@ mod test_key_material { #[test] fn zeroize() { let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); + let capacity = key.capacity(); + + // Sanity check: the backing buffer actually holds non-zero key material before it is wiped. + // Without this, the post-zeroize assertion below could pass vacuously. + key.allow_hazardous_operations(); + assert!(key.mut_ref_to_bytes().unwrap().iter().any(|&b| b != 0)); + key.drop_hazardous_operations(); + key.zeroize(); let key_len = key.key_len(); assert_eq!(key_len, 0); assert_eq!(key.key_type(), KeyType::Zeroized); + // zeroize() must wipe the entire backing buffer. + // Full capacity must be inspected to confirm the previously-set bytes were + // actually overwritten with zeros. + // Note: key_len is now 0, so ref_to_bytes() returns an empty slice. key.allow_hazardous_operations(); - let mut buf = vec![0u8; key_len]; - buf.copy_from_slice(key.ref_to_bytes()); - assert!(buf.iter().all(|&b| b == 0)); + let full_buf = key.mut_ref_to_bytes().unwrap(); + assert_eq!(full_buf.len(), capacity); + assert!(full_buf.iter().all(|&b| b == 0)); + key.drop_hazardous_operations(); } #[test] @@ -263,8 +277,8 @@ mod test_key_material { key.convert_key_type(KeyType::BytesFullEntropy).unwrap(); assert_eq!(key.key_type(), KeyType::BytesFullEntropy); assert!(key.is_full_entropy()); - key.drop_hazardous_operations(); + match key.convert_key_type(KeyType::SymmetricCipherKey) { Ok(()) => { /* good */ } _ => panic!("Expected Ok(())"), @@ -277,6 +291,7 @@ mod test_key_material { let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); key.allow_hazardous_operations(); key.convert_key_type(KeyType::BytesFullEntropy).unwrap(); + key.drop_hazardous_operations(); match key.convert_key_type(KeyType::Seed) { Ok(()) => { /* good */ } _ => panic!("Expected Ok(())"), @@ -406,18 +421,22 @@ mod test_key_material { key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); key.allow_hazardous_operations(); key.convert_key_type(KeyType::BytesFullEntropy).unwrap(); + key.drop_hazardous_operations(); key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); key.allow_hazardous_operations(); key.convert_key_type(KeyType::MACKey).unwrap(); + key.drop_hazardous_operations(); key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); key.allow_hazardous_operations(); key.convert_key_type(KeyType::SymmetricCipherKey).unwrap(); + key.drop_hazardous_operations(); key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); key.allow_hazardous_operations(); key.convert_key_type(KeyType::Seed).unwrap(); + key.drop_hazardous_operations(); } #[test] @@ -493,6 +512,7 @@ mod test_key_material { // should work if you allow hazardous conversions. key.allow_hazardous_operations(); key.convert_key_type(KeyType::SymmetricCipherKey).unwrap(); + key.drop_hazardous_operations(); } #[test] @@ -570,6 +590,7 @@ mod test_key_material { // now it should work key.set_security_strength(SecurityStrength::_128bit).unwrap(); assert_eq!(key.security_strength(), SecurityStrength::_128bit); + key.drop_hazardous_operations(); // BytesLowEntropy keys cannot have a security strength other than None. // success @@ -583,12 +604,14 @@ mod test_key_material { Err(KeyMaterialError::SecurityStrength(_)) => { /* good */ } _ => panic!("Expected KeyMaterialError::SecurityStrength"), } + key.drop_hazardous_operations(); // Zeroized keys cannot have a security strength other than None. // success let mut key = KeyMaterial256::new(); key.allow_hazardous_operations(); key.set_key_len(32).unwrap(); // still zeroized + key.drop_hazardous_operations(); assert_eq!(key.key_type(), KeyType::Zeroized); // setting to ::None should work .. even without setting .allow_hazardous_operations() key.set_security_strength(SecurityStrength::None).unwrap(); @@ -598,6 +621,7 @@ mod test_key_material { Err(KeyMaterialError::SecurityStrength(_)) => { /* good */ } _ => panic!("Expected KeyMaterialError::SecurityStrength"), } + key.drop_hazardous_operations(); } #[test] @@ -678,22 +702,55 @@ mod test_key_material { #[test] fn eq() { - // On instances of the same exact type (size). - let key1 = KeyMaterial256::from_bytes( - b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F", - ) - .unwrap(); - let key2 = KeyMaterial256::from_bytes( - b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F", - ) - .unwrap(); + // For context: + // DUMMY_KEY: &[u8; 64] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\ + // \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\ + // \x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F\ + // \x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\x3E\x3F"; + + // Same bytes, full capacity. Should be equal. + let key1 = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); + let key2 = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); assert_eq!(key1, key2); - let key3 = KeyMaterial256::from_bytes( - b"\x0F\x0E\x0D\x0C\x0B\x0A\x10\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00", - ) - .unwrap(); + // Same length, different content. Should NOT be equal. + let key3 = KeyMaterial256::from_bytes(&[0xFFu8; 32]).unwrap(); assert_ne!(key1, key3); + + // Different length, overlapping prefix. Should NOT be equal. + let key_short = KeyMaterial256::from_bytes(&DUMMY_KEY[..16]).unwrap(); + assert_ne!(key1, key_short); + + // PartialEq ignores key_type: same bytes, different KeyType. Should be equal. + let key_low = + KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::BytesLowEntropy).unwrap(); + let key_mac = + KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::MACKey).unwrap(); + assert_eq!(key_low, key_mac); + + // PartialEq ignores security_strength: same bytes, different strength. Should be equal. + let key_strong = + KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::BytesFullEntropy) + .unwrap(); + let mut key_weak = + KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::BytesFullEntropy) + .unwrap(); + key_weak.set_security_strength(SecurityStrength::_128bit).unwrap(); + assert_ne!(key_strong.security_strength(), key_weak.security_strength()); // strengths differ + assert_eq!(key_strong, key_weak); // but keys are still equal + + // Partially-filled buffers with identical content. Should be equal. + let key_half1 = KeyMaterial256::from_bytes(&DUMMY_KEY[..16]).unwrap(); + let key_half2 = KeyMaterial256::from_bytes(&DUMMY_KEY[..16]).unwrap(); + assert_eq!(key_half1, key_half2); + + // Verify with a second size (KeyMaterial512) to cover the generic impl. + let key512_a = KeyMaterial512::from_bytes(&DUMMY_KEY[..64]).unwrap(); + let key512_b = KeyMaterial512::from_bytes(&DUMMY_KEY[..64]).unwrap(); + assert_eq!(key512_a, key512_b); + + let key512_c = KeyMaterial512::from_bytes(&[0xFFu8; 64]).unwrap(); + assert_ne!(key512_a, key512_c); } #[test] From a2ecff951c4efdf471eaf9d0cef0e619f03c4da1 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 24 Jun 2026 12:40:20 -0500 Subject: [PATCH 11/35] Fixed a couple of minor docs bugs --- crypto/mldsa/src/hash_mldsa.rs | 2 +- crypto/mlkem/src/polynomial.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index b7e3988..4b69c41 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -24,7 +24,7 @@ //! } //! ``` //! -//! But you also have access to the pre-hashed functions available from [PHSigner] and [PHVerifier]: +//! But you also have access to the pre-hashed functions available from [PHSigner] and [PHSignatureVerifier]: //! //! ```rust //! use bouncycastle_core::errors::SignatureError; diff --git a/crypto/mlkem/src/polynomial.rs b/crypto/mlkem/src/polynomial.rs index fba4b39..60ccaac 100644 --- a/crypto/mlkem/src/polynomial.rs +++ b/crypto/mlkem/src/polynomial.rs @@ -299,7 +299,7 @@ impl Polynomial { /// Multiplication of two polynomials in NTT domain /// /// Borrowed from: -/// https://github.com/pq-crystals/kyber/blob/main/ref/poly.c#L290 +/// /// 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(); From 1dc4f6765aaf3369638f4251a17ce6a8d4179231 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 24 Jun 2026 12:44:02 -0500 Subject: [PATCH 12/35] rename mut_ref_to_bytes to ref_to_bytes_mut for idiomatic Rust #41 --- INTRODUCTION.md | 4 ++-- crypto/core/src/key_material.rs | 8 ++++---- crypto/core/tests/key_material_tests.rs | 14 +++++++------- crypto/hkdf/src/lib.rs | 6 +++--- crypto/rng/src/hash_drbg80090a.rs | 6 +++--- crypto/sha3/src/sha3.rs | 2 +- crypto/sha3/src/shake.rs | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/INTRODUCTION.md b/INTRODUCTION.md index a6cc415..13d816b 100644 --- a/INTRODUCTION.md +++ b/INTRODUCTION.md @@ -123,7 +123,7 @@ While the `KeyMaterial` is fundamentally just a buffer of bytes, it tracks many The `KeyMaterial` object is used consistently across the library and any functions that manipulate a key material object will properly update the metadata to track any changes made to the key's entropy or security strength. For example, a `KeyMaterial512{ key_type: MACKey, security_strength: _256bit}` will have its security strength downgraded to 128 bit if you pass it through a SHA256-based KDF, indicating that it is no longer sufficient to generate a full-strength AES256 or ML-DSA-87. -Of course, there will always be things developers need to do that the library did not provide a utility function for, for example, you may actually need an all-zero MACKey in order to implement certain standardized MAC algorithms. To the end, the library will allow you to, for example, force a key type to any full-entropy key type and security strength, or even get a direct immutable or mutable reference to the underlying buffer via `.ref_to_bytes()` and `mut_ref_to_bytes()`, but only with use of the `allow_hazardous_operations` flag: +Of course, there will always be things developers need to do that the library did not provide a utility function for, for example, you may actually need an all-zero MACKey in order to implement certain standardized MAC algorithms. To the end, the library will allow you to, for example, force a key type to any full-entropy key type and security strength, or even get a direct immutable or mutable reference to the underlying buffer via `.ref_to_bytes()` and `ref_to_bytes_mut()`, but only with use of the `allow_hazardous_operations` flag: ```rust key.allow_hazardous_operations(); @@ -202,4 +202,4 @@ As this is an alpha release, we're eagerly looking for feedback from the communi You can reach us at Sincerely, -Mike Ounsworth (lead maintainer of BC-Rust), on behalf of the Legion of the Bouncy Castle and the entire Bouncy Castle community \ No newline at end of file +Mike Ounsworth (lead maintainer of BC-Rust), on behalf of the Legion of the Bouncy Castle and the entire Bouncy Castle community diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index 6097413..61f0413 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -112,7 +112,7 @@ pub trait KeyMaterialTrait { /// This requires [KeyMaterialTrait::allow_hazardous_operations] to be set. /// When writing directly to the buffer, you are responsible for setting the key_len and key_type afterwards, /// and you should [KeyMaterialTrait::drop_hazardous_operations]. - fn mut_ref_to_bytes(&mut self) -> Result<&mut [u8], KeyMaterialError>; + fn ref_to_bytes_mut(&mut self) -> Result<&mut [u8], KeyMaterialError>; /// The size of the internal buffer; ie the largest key that this instance can hold. /// Equivalent to the constant param this object was created with. @@ -151,7 +151,7 @@ pub trait KeyMaterialTrait { /// Sets this instance to be able to perform potentially hazardous operations such as /// casting a KeyMaterial of type RawUnknownEntropy or RawLowEntropy into RawFullEntropy or SymmetricCipherKey, - /// or manually setting the key bytes via [KeyMaterialTrait::mut_ref_to_bytes], which then requires you to be responsible + /// or manually setting the key bytes via [KeyMaterialTrait::ref_to_bytes_mut], which then requires you to be responsible /// for setting the key_len and key_type afterwards. /// /// The purpose of the hazardous operations guard is not to prevent the user from accessing their data, @@ -254,7 +254,7 @@ impl KeyMaterial { let mut key = Self::new(); key.allow_hazardous_operations(); - rng.next_bytes_out(&mut key.mut_ref_to_bytes().unwrap()) + rng.next_bytes_out(&mut key.ref_to_bytes_mut().unwrap()) .map_err(|_| KeyMaterialError::GenericError("RNG failed."))?; key.key_len = KEY_LEN; @@ -354,7 +354,7 @@ impl KeyMaterialTrait for KeyMaterial { &self.buf[..self.key_len] } - fn mut_ref_to_bytes(&mut self) -> Result<&mut [u8], KeyMaterialError> { + fn ref_to_bytes_mut(&mut self) -> Result<&mut [u8], KeyMaterialError> { if !self.allow_hazardous_operations { return Err(KeyMaterialError::HazardousOperationNotPermitted); } diff --git a/crypto/core/tests/key_material_tests.rs b/crypto/core/tests/key_material_tests.rs index 95d2b6b..9c65827 100644 --- a/crypto/core/tests/key_material_tests.rs +++ b/crypto/core/tests/key_material_tests.rs @@ -75,7 +75,7 @@ mod test_key_material { assert_eq!(key.capacity(), 32); assert_eq!(key.ref_to_bytes(), &[1u8; 16]); // note: this is also testing that even though the internal buffer is larger than 16 bytes, it slices it down to length. - match key.mut_ref_to_bytes() { + match key.ref_to_bytes_mut() { Ok(_) => { panic!("getting a mut ref should require setting hazardous operations.") } @@ -85,12 +85,12 @@ mod test_key_material { } } key.allow_hazardous_operations(); - assert_eq!(key.mut_ref_to_bytes().unwrap().len(), 32); - assert_eq!(key.mut_ref_to_bytes().unwrap()[..16], [1u8; 16]); - assert_eq!(key.mut_ref_to_bytes().unwrap()[16..], [0u8; 16]); + assert_eq!(key.ref_to_bytes_mut().unwrap().len(), 32); + assert_eq!(key.ref_to_bytes_mut().unwrap()[..16], [1u8; 16]); + assert_eq!(key.ref_to_bytes_mut().unwrap()[16..], [0u8; 16]); // and I can set them - key.mut_ref_to_bytes().unwrap().copy_from_slice(&[2u8; 32]); + key.ref_to_bytes_mut().unwrap().copy_from_slice(&[2u8; 32]); key.set_key_len(32).unwrap(); assert_eq!(key.ref_to_bytes(), &[2u8; 32]); assert_eq!(key.key_len(), 32); @@ -179,7 +179,7 @@ mod test_key_material { // Sanity check: the backing buffer actually holds non-zero key material before it is wiped. // Without this, the post-zeroize assertion below could pass vacuously. key.allow_hazardous_operations(); - assert!(key.mut_ref_to_bytes().unwrap().iter().any(|&b| b != 0)); + assert!(key.ref_to_bytes_mut().unwrap().iter().any(|&b| b != 0)); key.drop_hazardous_operations(); key.zeroize(); @@ -192,7 +192,7 @@ mod test_key_material { // actually overwritten with zeros. // Note: key_len is now 0, so ref_to_bytes() returns an empty slice. key.allow_hazardous_operations(); - let full_buf = key.mut_ref_to_bytes().unwrap(); + let full_buf = key.ref_to_bytes_mut().unwrap(); assert_eq!(full_buf.len(), capacity); assert!(full_buf.iter().all(|&b| b == 0)); key.drop_hazardous_operations(); diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 76b47f0..a6ecc51 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -397,7 +397,7 @@ impl HKDF { let mut bytes_written: usize = 0; okm.allow_hazardous_operations(); - let out: &mut [u8] = okm.mut_ref_to_bytes()?; + let out: &mut [u8] = okm.ref_to_bytes_mut()?; // Could potentially speed this up by unrolling T(0) and T(1) // We're gonna have to kludge the prk key type to MACKey to make HMAC happy, but we'll set it back to the original value afterwards. @@ -568,9 +568,9 @@ impl HKDF { let output_key_type = self.entropy.get_output_key_type(); // need to do this above self.hmac.do_final_out, which will consume self. - okm.allow_hazardous_operations(); // doing it here to get mut_ref_to_bytes + okm.allow_hazardous_operations(); // doing it here to get ref_to_bytes_mut let bytes_written = - self.hmac.unwrap().do_final_out(&mut okm.mut_ref_to_bytes().unwrap())?; + self.hmac.unwrap().do_final_out(&mut okm.ref_to_bytes_mut().unwrap())?; okm.set_key_len(bytes_written)?; okm.set_key_type(output_key_type)?; if output_key_type <= KeyType::BytesLowEntropy { diff --git a/crypto/rng/src/hash_drbg80090a.rs b/crypto/rng/src/hash_drbg80090a.rs index 75b4177..ffac1db 100644 --- a/crypto/rng/src/hash_drbg80090a.rs +++ b/crypto/rng/src/hash_drbg80090a.rs @@ -145,12 +145,12 @@ impl HashDRBG80090A { seed.set_key_type(KeyType::Seed).unwrap(); match H::HASH { SupportedHash::SHA256 => { - getrandom::fill(&mut seed.mut_ref_to_bytes().unwrap()[..32]).unwrap(); + getrandom::fill(&mut seed.ref_to_bytes_mut().unwrap()[..32]).unwrap(); seed.set_key_len(32).unwrap(); seed.set_security_strength(SecurityStrength::_128bit).unwrap(); } SupportedHash::SHA512 => { - getrandom::fill(&mut seed.mut_ref_to_bytes().unwrap()).unwrap(); + getrandom::fill(&mut seed.ref_to_bytes_mut().unwrap()).unwrap(); seed.set_key_len(64).unwrap(); seed.set_security_strength(SecurityStrength::_256bit).unwrap(); } @@ -463,7 +463,7 @@ impl Sp80090ADrbg for HashDRBG80090A { out: &mut impl KeyMaterialTrait, ) -> Result { out.allow_hazardous_operations(); - let bytes_written = self.generate_out(additional_input, out.mut_ref_to_bytes().unwrap())?; + let bytes_written = self.generate_out(additional_input, out.ref_to_bytes_mut().unwrap())?; out.set_key_len(bytes_written)?; out.set_key_type(KeyType::BytesFullEntropy)?; diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index cb22998..21edef3 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -82,7 +82,7 @@ impl SHA3 { let mut key_type = self.kdf_key_type.clone(); let output_security_strength = self.kdf_security_strength.clone(); output_key.allow_hazardous_operations(); - let bytes_written = self.do_final_out(output_key.mut_ref_to_bytes()?); + let bytes_written = self.do_final_out(output_key.ref_to_bytes_mut()?); output_key.set_key_len(bytes_written)?; // since we've done some computation, the result will not actually be zeroized, even if all input key material was zeroized. diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index 59f3bac..3e64e6c 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -113,7 +113,7 @@ impl SHAKE { output_key.allow_hazardous_operations(); let bytes_written = self.squeeze_out( output_key - .mut_ref_to_bytes() + .ref_to_bytes_mut() .expect("We just set .allow_hazardous_operations(), so this should be fine."), ); output_key.set_key_len(bytes_written)?; From ecced8df0c96458752adf646cf62d78bde3137b3 Mon Sep 17 00:00:00 2001 From: Nikola Pajkovsky Date: Fri, 26 Jun 2026 10:46:31 +0200 Subject: [PATCH 13/35] Remove DEFAULT_HASH_NAME constants and eliminate unwrap() in HashFactory defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the three pub DEFAULT_*_HASH_NAME constants with direct enum variant construction in Default, default_128_bit(), and default_256_bit(). The constants were only used internally to drive Self::new().unwrap() calls β€” removing them makes the defaults infallible at compile time and reduces the public surface area of the factory. Signed-off-by: Nikola Pajkovsky --- crypto/factory/src/hash_factory.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crypto/factory/src/hash_factory.rs b/crypto/factory/src/hash_factory.rs index 9d12117..052f4b5 100644 --- a/crypto/factory/src/hash_factory.rs +++ b/crypto/factory/src/hash_factory.rs @@ -35,11 +35,6 @@ use bouncycastle_sha2::{SHA224_NAME, SHA256_NAME, SHA384_NAME, SHA512_NAME}; use bouncycastle_sha3 as sha3; use bouncycastle_sha3::{SHA3_224_NAME, SHA3_256_NAME, SHA3_384_NAME, SHA3_512_NAME}; -/*** Defaults ***/ -pub const DEFAULT_HASH_NAME: &str = SHA3_256_NAME; -pub const DEFAULT_128BIT_HASH_NAME: &str = SHA3_256_NAME; -pub const DEFAULT_256BIT_HASH_NAME: &str = SHA3_512_NAME; - /// All members must impl Hash. /// Note: no SHAKE because SHAKE is not NIST approved as a hash function. See FIPS 202 section A.2. pub enum HashFactory { @@ -55,16 +50,16 @@ pub enum HashFactory { impl Default for HashFactory { fn default() -> HashFactory { - Self::new(DEFAULT_HASH_NAME).unwrap() + Self::SHA3_256(sha3::SHA3_256::new()) } } impl AlgorithmFactory for HashFactory { fn default_128_bit() -> HashFactory { - Self::new(DEFAULT_128BIT_HASH_NAME).unwrap() + Self::SHA3_256(sha3::SHA3_256::new()) } fn default_256_bit() -> HashFactory { - Self::new(DEFAULT_256BIT_HASH_NAME).unwrap() + Self::SHA3_512(sha3::SHA3_512::new()) } fn new(alg_name: &str) -> Result { From 909161760c1014a5b6fe7a8b274de4dc037843be Mon Sep 17 00:00:00 2001 From: Nikola Pajkovsky Date: Fri, 26 Jun 2026 10:50:24 +0200 Subject: [PATCH 14/35] Remove DEFAULT_KDF_NAME constants and eliminate unwrap() in KDFFactory defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same pattern as the HashFactory cleanup: replace the three pub DEFAULT_*_KDF_NAME constants with direct enum variant construction in Default, default_128_bit(), and default_256_bit(). The constants were only used internally to drive unwrap() calls on Self::new() β€” removing them makes the defaults infallible at compile time and shrinks the public API surface. Signed-off-by: Nikola Pajkovsky --- crypto/factory/src/kdf_factory.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crypto/factory/src/kdf_factory.rs b/crypto/factory/src/kdf_factory.rs index f3deffb..50aa274 100644 --- a/crypto/factory/src/kdf_factory.rs +++ b/crypto/factory/src/kdf_factory.rs @@ -58,11 +58,6 @@ use bouncycastle_sha3::{ SHA3_224_NAME, SHA3_256_NAME, SHA3_384_NAME, SHA3_512_NAME, SHAKE128_NAME, SHAKE256_NAME, }; -/*** Defaults ***/ -pub const DEFAULT_KDF_NAME: &str = HKDF_SHA512_NAME; -pub const DEFAULT_128BIT_KDF_NAME: &str = HKDF_SHA256_NAME; -pub const DEFAULT_256BIT_KDF_NAME: &str = HKDF_SHA512_NAME; - // All members must impl KDF. pub enum KDFFactory { #[allow(non_camel_case_types)] @@ -79,17 +74,17 @@ pub enum KDFFactory { impl Default for KDFFactory { fn default() -> Self { - KDFFactory::new(DEFAULT_KDF_NAME).unwrap() + Self::HKDF_SHA512(hkdf::HKDF_SHA512::new()) } } impl AlgorithmFactory for KDFFactory { fn default_128_bit() -> Self { - KDFFactory::new(DEFAULT_128BIT_KDF_NAME).unwrap() + Self::HKDF_SHA256(hkdf::HKDF_SHA256::new()) } fn default_256_bit() -> Self { - KDFFactory::new(DEFAULT_256BIT_KDF_NAME).unwrap() + Self::HKDF_SHA512(hkdf::HKDF_SHA512::new()) } fn new(alg_name: &str) -> Result { From bda139f586e1620ec0396c6f4659ece724071bff Mon Sep 17 00:00:00 2001 From: Nikola Pajkovsky Date: Fri, 26 Jun 2026 10:55:38 +0200 Subject: [PATCH 15/35] Remove DEFAULT_KDF_NAME constants and eliminate unwrap() in KDFFactory defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace pub DEFAULT_KDF_NAME, DEFAULT_128BIT_KDF_NAME, and DEFAULT_256BIT_KDF_NAME with direct enum variant construction in Default, default_128_bit(), and default_256_bit(). The constants existed solely to feed Self::new().unwrap() β€” constructing the variants directly makes the defaults infallible at compile time and removes three unnecessary items from the public API. Signed-off-by: Nikola Pajkovsky --- crypto/factory/src/rng_factory.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crypto/factory/src/rng_factory.rs b/crypto/factory/src/rng_factory.rs index 9f1b8e0..1431c90 100644 --- a/crypto/factory/src/rng_factory.rs +++ b/crypto/factory/src/rng_factory.rs @@ -50,11 +50,6 @@ use bouncycastle_core::traits::{RNG, SecurityStrength}; use bouncycastle_rng as rng; use bouncycastle_rng::{HASH_DRBG_SHA256_NAME, HASH_DRBG_SHA512_NAME}; -/*** Defaults ***/ -pub const DEFAULT_DRBG_NAME: &str = HASH_DRBG_SHA512_NAME; -pub const DEFAULT_128BIT_DRBG_NAME: &str = HASH_DRBG_SHA256_NAME; -pub const DEFAULT_256BIT_DRBG_NAME: &str = HASH_DRBG_SHA512_NAME; - /// All members must impl RNG. pub enum RNGFactory { #[allow(non_camel_case_types)] @@ -65,16 +60,16 @@ pub enum RNGFactory { impl Default for RNGFactory { fn default() -> Self { - Self::new(DEFAULT_DRBG_NAME).unwrap() + Self::HashDRBG_SHA512(rng::HashDRBG_SHA512::new_from_os()) } } impl AlgorithmFactory for RNGFactory { fn default_128_bit() -> Self { - Self::new(DEFAULT_128BIT_DRBG_NAME).unwrap() + Self::HashDRBG_SHA256(rng::HashDRBG_SHA256::new_from_os()) } fn default_256_bit() -> Self { - Self::new(DEFAULT_256BIT_DRBG_NAME).unwrap() + Self::HashDRBG_SHA512(rng::HashDRBG_SHA512::new_from_os()) } fn new(alg_name: &str) -> Result { From 09b9f5319f5da94345976936905adc72a960cd8c Mon Sep 17 00:00:00 2001 From: Nikola Pajkovsky Date: Fri, 26 Jun 2026 11:54:50 +0200 Subject: [PATCH 16/35] Eliminate unwrap() and assert!() in ML-DSA signature verification Replace manual length checks followed by unwrap() with try_into().map_err() to coerce the signature slice to a fixed-size array in one step. Replace assert!() + unwrap() on the streaming verifier's optional public key with ok_or(), returning a proper SignatureError::GenericError instead of panicking. Use .then_some().ok_or() to convert the boolean verify result into a Result without an if/else branch. Signed-off-by: Nikola Pajkovsky --- crypto/mldsa/src/mldsa.rs | 54 ++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index ca41c81..680c9ae 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -1536,15 +1536,12 @@ impl< sig: &[u8], ) -> Result<(), SignatureError> { let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)?; - - if sig.len() != SIG_LEN { - return Err(SignatureError::LengthError("Signature value is not the correct length.")); - } - if Self::verify_mu_internal(&pk.pk, &pk.A_hat(), &mu, &sig[..SIG_LEN].try_into().unwrap()) { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } + let sig: &[u8; SIG_LEN] = sig.try_into().map_err(|_| { + SignatureError::LengthError("Signature value is not the correct length.") + })?; + Self::verify_mu_internal(&pk.pk, &pk.A_hat(), &mu, sig) + .then_some(()) + .ok_or(SignatureError::SignatureVerificationFailed) } /// Algorithm 8 ML-DSA.Verify_internal(π‘π‘˜, 𝑀′, 𝜎) @@ -2062,15 +2059,12 @@ impl< { fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mu = MuBuilder::compute_mu(&pk.compute_tr(), msg, ctx)?; - - if sig.len() != SIG_LEN { - return Err(SignatureError::LengthError("Signature value is not the correct length.")); - } - if Self::verify_mu_internal(pk, &pk.A_hat(), &mu, &sig.try_into().unwrap()) { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } + let sig: &[u8; SIG_LEN] = sig.try_into().map_err(|_| { + SignatureError::LengthError("Signature value is not the correct length.") + })?; + Self::verify_mu_internal(pk, &pk.A_hat(), &mu, sig) + .then_some(()) + .ok_or(SignatureError::SignatureVerificationFailed) } fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result { @@ -2091,20 +2085,16 @@ impl< fn verify_final(self, sig: &[u8]) -> Result<(), SignatureError> { let mu = self.mu_builder.do_final(); - assert!( - self.pk.is_some(), - "Somehow you managed to construct a streaming verifier without a public key, impressive!" - ); - let pk: &PK = &self.pk.unwrap(); - - if sig.len() != SIG_LEN { - return Err(SignatureError::LengthError("Signature value is not the correct length.")); - } - if Self::verify_mu_internal(pk, &pk.A_hat(), &mu, &sig[..SIG_LEN].try_into().unwrap()) { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } + let pk: &PK = self + .pk + .as_ref() + .ok_or(SignatureError::GenericError("No public key set on streaming verifier."))?; + let sig: &[u8; SIG_LEN] = sig.try_into().map_err(|_| { + SignatureError::LengthError("Signature value is not the correct length.") + })?; + Self::verify_mu_internal(pk, &pk.A_hat(), &mu, sig) + .then_some(()) + .ok_or(SignatureError::SignatureVerificationFailed) } } From ceb471f4126f4aaca6155f1247266a3943dc287f Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Fri, 26 Jun 2026 10:49:22 -0500 Subject: [PATCH 17/35] Added a closure-based do_hazardous_operations(). Closes #39 --- alpha_0.1.2_release_notes.md | 4 + cli/src/helpers.rs | 13 +- cli/src/hkdf_cmd.rs | 8 +- cli/src/mac_cmd.rs | 7 +- crypto/core-test-framework/src/kdf.rs | 6 +- crypto/core-test-framework/src/mac.rs | 54 ++- crypto/core/src/key_material.rs | 458 ++++++++++++------ crypto/core/src/traits.rs | 8 +- crypto/core/tests/key_material_tests.rs | 377 ++++++++------ crypto/hkdf/src/lib.rs | 130 ++--- crypto/hkdf/tests/hkdf_tests.rs | 38 +- crypto/hmac/tests/hmac_tests.rs | 35 +- crypto/mldsa-lowmemory/src/mldsa_keys.rs | 9 +- crypto/mldsa-lowmemory/tests/bc_test_data.rs | 33 +- .../mldsa-lowmemory/tests/mldsa_key_tests.rs | 22 +- crypto/mldsa-lowmemory/tests/mldsa_tests.rs | 21 +- crypto/mldsa-lowmemory/tests/wycheproof.rs | 92 ++-- crypto/mldsa/tests/bc_test_data.rs | 40 +- crypto/mldsa/tests/mldsa_key_tests.rs | 25 +- crypto/mldsa/tests/mldsa_tests.rs | 22 +- crypto/mldsa/tests/wycheproof.rs | 25 +- crypto/mlkem-lowmemory/src/mlkem.rs | 16 +- crypto/mlkem-lowmemory/src/mlkem_keys.rs | 26 +- crypto/mlkem-lowmemory/tests/bc_test_data.rs | 13 +- crypto/mlkem-lowmemory/tests/mlkem_tests.rs | 8 +- crypto/mlkem-lowmemory/tests/wycheproof.rs | 25 +- crypto/mlkem/src/mlkem.rs | 16 +- crypto/mlkem/src/mlkem_keys.rs | 17 +- crypto/mlkem/tests/bc_test_data.rs | 10 +- crypto/mlkem/tests/mlkem_tests.rs | 8 +- crypto/mlkem/tests/wycheproof.rs | 73 +-- crypto/rng/src/hash_drbg80090a.rs | 63 ++- crypto/rng/tests/hash_drbg80090a_tests.rs | 3 +- crypto/sha3/src/sha3.rs | 37 +- crypto/sha3/src/shake.rs | 38 +- crypto/sha3/tests/sha3_tests.rs | 11 +- 36 files changed, 1105 insertions(+), 686 deletions(-) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index cebd420..c59991b 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -28,6 +28,8 @@ * Close all open github issues and document them in this file. * After everything is merged, circle back to crucible, and make sure that the harness still works (and maybe remove the nightly build toolchain) +* Search for all the uses of .unwrap() in non-test code and replace each with either a comment or an expect with a + meaningful error string. # 0.1.2 Features / Changelog @@ -38,6 +40,8 @@ * 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. +* Reworked the way KeyMaterial hazardous operations work; instead of a stateful .allow_hazardous_operations() / + .drop_hazardous_operations(), it now uses a closure-based do_hazardous_operations(). Github issue #39. * Github issues resolved: * #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/helpers.rs b/cli/src/helpers.rs index 62a7741..43993a3 100644 --- a/cli/src/helpers.rs +++ b/cli/src/helpers.rs @@ -1,4 +1,6 @@ -use bouncycastle::core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle::core::key_material::{ + KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, +}; use bouncycastle::core::traits::SecurityStrength; use bouncycastle::hex; use std::fs::File; @@ -106,10 +108,11 @@ pub(crate) fn parse_seed(bytes: &[u8]) -> Result::from_bytes(&salt_bytes).unwrap(); // force it just so the CLI behaves properly even with all-zero or zero-length keys - salt_key.allow_hazardous_operations(); - salt_key.convert_key_type(KeyType::MACKey).unwrap(); + do_hazardous_operations(&mut salt_key, |salt_key| salt_key.set_key_type(KeyType::MACKey)) + .unwrap(); ikm_bytes = if ikm.is_some() { hex::decode(ikm.as_ref().unwrap()).unwrap() diff --git a/cli/src/mac_cmd.rs b/cli/src/mac_cmd.rs index 343cb0b..bb7aafc 100644 --- a/cli/src/mac_cmd.rs +++ b/cli/src/mac_cmd.rs @@ -2,7 +2,9 @@ use std::io::{Read, Write}; use std::process::exit; use std::{fs, io}; -use bouncycastle::core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; +use bouncycastle::core::key_material::{ + KeyMaterial512, KeyMaterialTrait, KeyType, do_hazardous_operations, +}; use bouncycastle::core::traits::MAC; use bouncycastle::hex; use bouncycastle::hmac::{HMAC_SHA256, HMAC_SHA512}; @@ -34,8 +36,7 @@ pub(crate) fn mac_cmd( exit(-1); } let mut key = KeyMaterial512::from_bytes(&key_bytes).unwrap(); - key.allow_hazardous_operations(); - key.convert_key_type(KeyType::MACKey).unwrap(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::MACKey)).unwrap(); // instantiate the MAC object and call do_mac() match hmac_variant { diff --git a/crypto/core-test-framework/src/kdf.rs b/crypto/core-test-framework/src/kdf.rs index 3f60b49..21d1b4a 100644 --- a/crypto/core-test-framework/src/kdf.rs +++ b/crypto/core-test-framework/src/kdf.rs @@ -30,7 +30,7 @@ impl TestFrameworkKDF { // account for the fact that XOF style KDFs will will the provided buffer. assert!(bytes_written >= expected_output.key_len()); assert_eq!(output.key_len(), bytes_written); - output.truncate(expected_output.key_len()).unwrap(); + output.set_key_len(expected_output.key_len()).unwrap(); // truncates should be infallible assert_eq!(output.key_len(), expected_output.key_len()); assert_eq!(output.ref_to_bytes(), expected_output.ref_to_bytes()); @@ -99,7 +99,7 @@ impl TestFrameworkKDF { let output = kdf.derive_key_from_multiple(keys, additional_input).unwrap(); // This is sortof a hack since the rust language won't easily allow me to make the KeyMaterials the same length if output.key_len() < expected_output.key_len() { - expected_output.truncate(output.key_len()).unwrap(); + expected_output.set_key_len(output.key_len()).unwrap(); // truncates should be infallible } assert_eq!(output.key_len(), expected_output.key_len()); assert_eq!(output.ref_to_bytes(), expected_output.ref_to_bytes()); @@ -112,7 +112,7 @@ impl TestFrameworkKDF { // account for the fact that XOF style KDFs will will the provided buffer. assert!(bytes_written >= expected_output.key_len()); assert_eq!(output.key_len(), bytes_written); - output.truncate(expected_output.key_len()).unwrap(); + output.set_key_len(expected_output.key_len()).unwrap(); // truncates should be infallible assert_eq!(output.key_len(), expected_output.key_len()); assert_eq!(output.ref_to_bytes(), expected_output.ref_to_bytes()); diff --git a/crypto/core-test-framework/src/mac.rs b/crypto/core-test-framework/src/mac.rs index 87c8db5..a87d7f6 100644 --- a/crypto/core-test-framework/src/mac.rs +++ b/crypto/core-test-framework/src/mac.rs @@ -1,6 +1,8 @@ use crate::DUMMY_SEED_512; use bouncycastle_core::errors::{KeyMaterialError, MACError}; -use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; +use bouncycastle_core::key_material::{ + KeyMaterial512, KeyMaterialTrait, KeyType, do_hazardous_operations, +}; use bouncycastle_core::traits::MAC; use bouncycastle_core::traits::SecurityStrength; @@ -88,30 +90,32 @@ impl TestFrameworkMAC { let mut low_security_key = KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::MACKey).unwrap(); - low_security_key.allow_hazardous_operations(); - match M::new_allow_weak_key(key).unwrap().max_security_strength() { - SecurityStrength::None => { - low_security_key.truncate(13).unwrap(); - low_security_key.set_security_strength(SecurityStrength::None).unwrap(); - } - SecurityStrength::_112bit => { - low_security_key.truncate(28).unwrap(); - low_security_key.set_security_strength(SecurityStrength::None).unwrap(); - } - SecurityStrength::_128bit => { - low_security_key.truncate(32).unwrap(); - low_security_key.set_security_strength(SecurityStrength::_112bit).unwrap(); - } - SecurityStrength::_192bit => { - low_security_key.truncate(48).unwrap(); - low_security_key.set_security_strength(SecurityStrength::_128bit).unwrap(); - } - SecurityStrength::_256bit => { - low_security_key.truncate(64).unwrap(); - low_security_key.set_security_strength(SecurityStrength::_192bit).unwrap(); - } - }; - low_security_key.drop_hazardous_operations(); + do_hazardous_operations(&mut low_security_key, |low_security_key| { + match M::new_allow_weak_key(key).unwrap().max_security_strength() { + SecurityStrength::None => { + low_security_key.set_key_len(13).unwrap(); // truncates should be infallible + low_security_key.set_security_strength(SecurityStrength::None).unwrap(); + } + SecurityStrength::_112bit => { + low_security_key.set_key_len(28).unwrap(); // truncate should be infallible + low_security_key.set_security_strength(SecurityStrength::None).unwrap(); + } + SecurityStrength::_128bit => { + low_security_key.set_key_len(32).unwrap(); // truncate should be infallible + low_security_key.set_security_strength(SecurityStrength::_112bit).unwrap(); + } + SecurityStrength::_192bit => { + low_security_key.set_key_len(48).unwrap(); // truncate should be infallible + low_security_key.set_security_strength(SecurityStrength::_128bit).unwrap(); + } + SecurityStrength::_256bit => { + low_security_key.set_key_len(64).unwrap(); // truncate should be infallible + low_security_key.set_security_strength(SecurityStrength::_192bit).unwrap(); + } + }; + Ok(()) + }) + .unwrap(); // init assert!( diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index 61f0413..8535c84 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -2,10 +2,6 @@ //! The main purpose is to hold metadata about the contained key material such as the key type and //! entropy content to prevent accidental misuse security bugs, such as deriving cryptographic keys //! from uninitialized data. -//! -//! This object allows several types of manual-overrides, which typically require setting the [KeyMaterial::allow_hazardous_operations] flag. -//! For example, the raw bytes data can be extracted, or the key forced to a certain type, -//! but well-designed use of the bc-rust.test library should not need to ever set the [KeyMaterial::allow_hazardous_operations] flag. //! The core idea of this wrapper is to keep track of the usage of the key material, including //! the amount of entropy that it is presumed to contain in order to prevent users from accidentally //! using it inappropriately in a way that could lead to security weaknesses. @@ -21,21 +17,38 @@ //! * Keyed KDFs that are given a key of RawFullEntropy or KeyedHashKey a KeyMaterial data of type RawLowEntropy or RawUnknownEntropy will promote it into RawFullEntropy. //! * Symmetric ciphers or asymmetric ciphers such as X25519 or ML-KEM that accept private key seeds will expect KeyMaterial of type AsymmetricPrivateKeySeed. //! -//! However, there is a [KeyMaterial::convert_key_type] for cases where the user has more context knowledge than the library. +//! However, there is a [KeyMaterialTrait::set_key_type] for cases where the user has more context knowledge than the library. //! Some conversions, such as converting a key of type RawLowEntropy into a SymmetricCipherKey, will fail unless -//! the user has explicitly allowed them via calling allow_hazardous_operations() prior to the conversion. +//! run inside of a [do_hazardous_operations] closure, see below. //! -//! Examples of hazardous conversions that require allow_hazardous_operations() to be called first: -//! -//! * Converting a KeyMaterial of type RawLowEntropy or RawUnknownEntropy into RawFullEntropy or any other full-entropy key type. -//! * Converting any algorithm-specific key type into a different algorithm-specific key type, which is considered hazardous since key reuse between different cryptographic algorithms is generally discouraged and can sometimes lead to key leakage. +//! # Security //! //! Additional security features: //! * Zeroizes on destruction. //! * Implementing Display and Debug to print metadata but not key material to prevent accidental logging. //! +//! # Hazardous Operations +//! +//! This object allows several types of manual-overrides, many of which are considered +//! "hazardous operations" since by definition they are allowing you to bypass checks meant to detect +//! conditions that could lead to security vulnerabilities. +//! Consider, for example, that you are reading a symmetric key from somewhere outside the library, +//! maybe from disk or from another process, but maybe you handed in the wrong variable and instead +//! handed in an uninitialized (all-zero) buffer. +//! Since this is a common bug that has catestrophic security implications, the library will normally +//! check for all-zero KeyMoterial objects and throw an error. +//! But there will be cases in which you really do need to use an all-zero key, so you can create +//! one if you do it in hazardous operations mode. +//! +//! Examples of hazardous conversions that are required to be run inside of a do_hazardous_operations() closure: +//! +//! * Converting a KeyMaterial of type RawLowEntropy or RawUnknownEntropy into RawFullEntropy or any other full-entropy key type. +//! * Converting any algorithm-specific key type into a different algorithm-specific key type, which is considered hazardous since key reuse between different cryptographic algorithms is generally discouraged and can sometimes lead to key leakage. +//! //! As with all wrappers of this nature, the intent is to protect the user from making silly mistakes, not to prevent expert users from doing what they need to do. //! It as always possible, for example, to extract the bytes from a KeyMaterial object, manipulate them, and then re-wrap them in a new KeyMaterial object. +//! +//! See [do_hazardous_operations] for documentation and sample code. use crate::errors::KeyMaterialError; use crate::traits::{RNG, Secret, SecurityStrength}; @@ -53,19 +66,20 @@ pub type KeyMaterial512 = KeyMaterial<64>; /// A helper class used across the bc-rust.test library to hold bytes-like key material. /// See [KeyMaterial] for for details, such as constructors. -pub trait KeyMaterialTrait { +#[allow(private_bounds)] +pub trait KeyMaterialTrait: KeyMaterialInternalTrait { /// Loads the provided data into a new KeyMaterial of the specified type. /// This is discouraged unless the caller knows the provenance of the data, such as loading it /// from a cryptographic private key file. /// - /// This behaves differently on all-zero input key depending on whether [KeyMaterialTrait::allow_hazardous_operations] is set: + /// This behaves differently on all-zero input key depending on whether it is run within a [do_hazardous_operations] closure: /// if not set, then it will succeed, setting the key type to [KeyType::Zeroized] and also return a [KeyMaterialError::ActingOnZeroizedKey] /// to indicate that you may want to perform error-handling, which could be manually setting the key type /// if you intend to allow zero keys, or do some other error-handling, like figure out why your RNG is broken. /// Note that even if a [KeyMaterialError::ActingOnZeroizedKey] is returned, the object is still populated and usable. /// For example, you could catch it like this: /// ``` - /// use bouncycastle_core::key_material::{KeyMaterial256, KeyType, KeyMaterialTrait}; + /// use bouncycastle_core::key_material::{KeyMaterial256, KeyType, KeyMaterialTrait, do_hazardous_operations}; /// use bouncycastle_core::key_material::KeyMaterial; /// use bouncycastle_core::errors::KeyMaterialError; /// @@ -76,15 +90,15 @@ pub trait KeyMaterialTrait { /// Err(KeyMaterialError::ActingOnZeroizedKey) => { /// // Either figure out why your passed an all-zero key, /// // or set the key type manually, if that's what you intended. - /// key.allow_hazardous_operations(); - /// key.set_key_type(KeyType::BytesLowEntropy).unwrap(); // probably you should do something more elegant than .unwrap in your code ;) - /// key.drop_hazardous_operations(); + /// do_hazardous_operations(&mut key, |key| { + /// key.set_key_type(KeyType::BytesLowEntropy) + /// }).unwrap(); // probably you should do something more elegant than .unwrap in your code ;) /// }, /// Err(_) => { /* figure out what else went wrong */ }, /// Ok(_) => { /* good */ }, /// } /// ``` - /// On the other hand, if [KeyMaterialTrait::allow_hazardous_operations] is set then it will just do what you asked without complaining. + /// On the other hand, if run inside a [do_hazardous_operations] closure then it will just do what you asked without complaining. /// /// Since this zeroizes and resets the key material, this is considered a dangerous conversion. /// @@ -100,18 +114,19 @@ pub trait KeyMaterialTrait { /// Get a reference to the underlying key material bytes. /// /// By reading the key bytes out of the [KeyMaterialTrait] object, you lose the protections that it offers, - /// however, this does not require [KeyMaterialTrait::allow_hazardous_operations] in the name of API ergonomics: - /// setting [KeyMaterialTrait::allow_hazardous_operations] requires a mutable reference and reading the bytes + /// however, this does not require [do_hazardous_operations] in the name of API ergonomics: + /// setting [do_hazardous_operations] requires a mutable reference and reading the bytes /// is not an operation that should require mutability. - /// TODO -- consider whether this should consume the object fn ref_to_bytes(&self) -> &[u8]; /// Get a mutable reference to the underlying key material bytes so that you can read or write /// to the underlying bytes without needing to create a temporary buffer, especially useful in /// cases where the required size of that buffer may be tricky to figure out at compile-time. - /// This requires [KeyMaterialTrait::allow_hazardous_operations] to be set. - /// When writing directly to the buffer, you are responsible for setting the key_len and key_type afterwards, - /// and you should [KeyMaterialTrait::drop_hazardous_operations]. + /// + /// # 🚨 Hazardous Operation🚨 + /// This function needs to be run within a [do_hazardous_operations] closure. + /// + /// When writing directly to the buffer, you are responsible for setting the key_len and key_type afterward. fn ref_to_bytes_mut(&mut self) -> Result<&mut [u8], KeyMaterialError>; /// The size of the internal buffer; ie the largest key that this instance can hold. @@ -121,12 +136,35 @@ pub trait KeyMaterialTrait { /// Length of the key material in bytes. fn key_len(&self) -> usize; - /// Requires [KeyMaterialTrait::allow_hazardous_operations]. + /// Sets the internal key length without changing the capacity of the KeyMaterial. + /// Primarily intended for truncation if you are provided with a key that is larger than you need, + /// or to extend the length of an undersized KeyMaterial. + /// + /// If truncating, it will automatically downgrade the SecurityStrength accordingly. + /// + /// # 🚨 Hazardous Operation🚨 + /// Using this function to extend the length of a key is always hazardous and needs to be run + /// within a [do_hazardous_operations] closure since this can result + /// in a key containing a large number of zeroes, or containing key material from a previous key + /// held in the same buffer. When extending the length, you take responsibility for the security + /// implications. + /// + /// Truncation (that is, reducing the length) is always safe and does not require a + /// [do_hazardous_operations] closure. fn set_key_len(&mut self, key_len: usize) -> Result<(), KeyMaterialError>; fn key_type(&self) -> KeyType; - /// Requires [KeyMaterialTrait::allow_hazardous_operations]. + /// Sets (or safely converts) the [KeyType] of this KeyMaterial object. + /// Does not perform any operations on the actual key material, other than changing the key_type field. + /// + /// # 🚨 Hazardous Operation🚨 + /// Inside a [do_hazardous_operations] closure this will set the key to any [KeyType]. + /// Outside such a closure, only "safe" conversions are permitted: a [KeyType::BytesFullEntropy] + /// key may be converted to any type, and any type may be converted to itself (a no-op). + /// A hazardous conversion attempted outside a [do_hazardous_operations] closure returns + /// [KeyMaterialError::HazardousOperationNotPermitted], and converting a [KeyType::Zeroized] key + /// returns [KeyMaterialError::ActingOnZeroizedKey]. fn set_key_type(&mut self, key_type: KeyType) -> Result<(), KeyMaterialError>; /// Security Strength, as used here, aligns with NIST SP 800-90A guidance for random number generation, @@ -142,47 +180,32 @@ pub trait KeyMaterialTrait { /// tracked independantly from key length and entropy level / key type. fn security_strength(&self) -> SecurityStrength; - /// Requires [KeyMaterialTrait::allow_hazardous_operations] to raise the security strength, but not to lower it. - /// Throws [KeyMaterialError::HazardousOperationNotPermitted] on a request to raise the security level without - /// [KeyMaterialTrait::allow_hazardous_operations] set. - /// Throws [KeyMaterialError::InvalidLength] on a request to set the security level higher than the current key length. + /// Set the [SecurityStrength] of the KeyMaterial. + /// + /// # 🚨 Hazardous Operation🚨 + /// This function needs to be run within a [do_hazardous_operations] closure to raise the security + /// strength, but not to lower it. + /// + /// Outside of a [do_hazardous_operations] closure it will throw a + /// [KeyMaterialError::HazardousOperationNotPermitted] on a request to raise the security level, and + /// throw a [KeyMaterialError::InvalidLength] on a request to set the security level higher than the current key length. Inside a [do_hazardous_operations] it will do what you asked without complaining. fn set_security_strength(&mut self, strength: SecurityStrength) -> Result<(), KeyMaterialError>; - /// Sets this instance to be able to perform potentially hazardous operations such as - /// casting a KeyMaterial of type RawUnknownEntropy or RawLowEntropy into RawFullEntropy or SymmetricCipherKey, - /// or manually setting the key bytes via [KeyMaterialTrait::ref_to_bytes_mut], which then requires you to be responsible - /// for setting the key_len and key_type afterwards. - /// - /// The purpose of the hazardous operations guard is not to prevent the user from accessing their data, - /// but rather to make the developer think carefully about the operation they are about to perform, - /// and to give static analysis tools an obvious marker that a given KeyMaterial variable warrants - /// further inspection. - fn allow_hazardous_operations(&mut self); - - /// Resets this instance to not be able to perform potentially hazardous operations. - fn drop_hazardous_operations(&mut self); - - /// Sets the key_type of this KeyMaterial object. - /// Does not perform any operations on the actual key material, other than changing the key_type field. - /// If allow_hazardous_operations is true, this method will allow conversion to any KeyType, otherwise - /// checking is performed to ensure that the conversion is "safe". - /// This drops the allow_hazardous_operations flag, so if you need to do multiple hazardous conversions - /// on the same instance, then you'll need to call .allow_hazardous_operations() each time. - fn convert_key_type(&mut self, new_key_type: KeyType) -> Result<(), KeyMaterialError>; - + /// Whether or not the KeyMaterial is one of the full entropy key types. fn is_full_entropy(&self) -> bool; + /// Securely resets the contents to all zeroes. + /// Note that KeyMaterial will automatically zeroize itself when dropped, so it is not necessary + /// to call this method simply because the object is going out of scope, but it provided + /// in case you want to zeroize it early, or before re-using the same instance of KeyMaterial to + /// hold a different key, potentially of a different length. fn zeroize(&mut self); - /// Is simply an alias to [KeyMaterialTrait::set_key_len], however, this does not require [KeyMaterialTrait::allow_hazardous_operations] - /// since truncation is a safe operation. - /// If truncating below the current security strength, the security strength will be lowered accordingly. - fn truncate(&mut self, new_len: usize) -> Result<(), KeyMaterialError>; - /// Adds the other KeyMaterial into this one, assuming there is space. - /// Does not require [KeyMaterialTrait::allow_hazardous_operations]. + /// /// Throws [KeyMaterialError::InvalidLength] if this object does not have enough space to add the other one. + /// /// The resulting [KeyType] and security strength will be the lesser of the two keys. /// In other words, concatenating two 128-bit full entropy keys generated at a 128-bit DRBG security level /// will result in a 256-bit full entropy key still at the 128-bit DRBG security level. @@ -252,15 +275,16 @@ impl KeyMaterial { /// Create a new instance of KeyMaterial containing random bytes from the provided random number generator. pub fn from_rng(rng: &mut impl RNG) -> Result { let mut key = Self::new(); - key.allow_hazardous_operations(); - rng.next_bytes_out(&mut key.ref_to_bytes_mut().unwrap()) - .map_err(|_| KeyMaterialError::GenericError("RNG failed."))?; + do_hazardous_operations(&mut key, |key| { + rng.next_bytes_out(&mut key.ref_to_bytes_mut().unwrap()) + .map_err(|_| KeyMaterialError::GenericError("RNG failed."))?; + Ok(()) + })?; key.key_len = KEY_LEN; key.key_type = KeyType::BytesFullEntropy; key.security_strength = rng.security_strength(); - key.drop_hazardous_operations(); Ok(key) } @@ -319,7 +343,6 @@ impl KeyMaterialTrait for KeyMaterial { key_type: KeyType, ) -> Result<(), KeyMaterialError> { let allowed_hazardous_operations = self.allow_hazardous_operations; - self.allow_hazardous_operations(); if source.len() > KEY_LEN { return Err(KeyMaterialError::InputDataLongerThanKeyCapacity); @@ -335,12 +358,14 @@ impl KeyMaterialTrait for KeyMaterial { self.key_len = source.len(); self.key_type = new_key_type; - if new_key_type <= KeyType::BytesLowEntropy { - self.set_security_strength(SecurityStrength::None)?; - } else { - self.set_security_strength(SecurityStrength::from_bits(source.len() * 8))?; - } - self.drop_hazardous_operations(); + do_hazardous_operations(self, |s| { + if new_key_type <= KeyType::BytesLowEntropy { + s.set_security_strength(SecurityStrength::None)?; + } else { + s.set_security_strength(SecurityStrength::from_bits(source.len() * 8))?; + } + Ok(()) + })?; // return if new_key_type == KeyType::Zeroized { @@ -370,23 +395,79 @@ impl KeyMaterialTrait for KeyMaterial { } fn set_key_len(&mut self, key_len: usize) -> Result<(), KeyMaterialError> { - if !self.allow_hazardous_operations { - return Err(KeyMaterialError::HazardousOperationNotPermitted); - } if key_len > KEY_LEN { return Err(KeyMaterialError::InvalidLength); } - self.key_len = key_len; - Ok(()) + + // are we extending the key length, or truncating? + if key_len <= self.key_len { + // truncation is always allowed (not hazardous) + + self.security_strength = + min(&self.security_strength, &SecurityStrength::from_bits(key_len * 8)).clone(); + + if key_len == 0 { + self.key_type = KeyType::Zeroized; + } + + self.key_len = key_len; + + Ok(()) + } else { + if !self.allow_hazardous_operations { + return Err(KeyMaterialError::HazardousOperationNotPermitted); + } + self.key_len = key_len; + Ok(()) + } } fn key_type(&self) -> KeyType { self.key_type.clone() } fn set_key_type(&mut self, key_type: KeyType) -> Result<(), KeyMaterialError> { - if !self.allow_hazardous_operations { - return Err(KeyMaterialError::HazardousOperationNotPermitted); + if self.allow_hazardous_operations { + // just do it + self.key_type = key_type; + return Ok(()); } - self.key_type = key_type.clone(); + + match self.key_type { + KeyType::Zeroized => { + return Err(KeyMaterialError::ActingOnZeroizedKey); + } + KeyType::BytesFullEntropy => { + // raw full entropy can be safely converted to anything. + self.key_type = key_type; + } + KeyType::BytesLowEntropy => match key_type { + KeyType::BytesLowEntropy => { /* No change */ } + _ => { + return Err(KeyMaterialError::HazardousOperationNotPermitted); + } + }, + KeyType::MACKey => match key_type { + KeyType::MACKey => { /* No change */ } + // Else: Once a KeyMaterial is typed, it should stay that way. + _ => { + return Err(KeyMaterialError::HazardousOperationNotPermitted); + } + }, + KeyType::SymmetricCipherKey => match key_type { + KeyType::SymmetricCipherKey => { /* No change */ } + // Else: Once a KeyMaterial is typed, it should stay that way. + _ => { + return Err(KeyMaterialError::HazardousOperationNotPermitted); + } + }, + KeyType::Seed => match key_type { + KeyType::Seed => { /* No change */ } + // Else: Once a KeyMaterial is typed, it should stay that way. + _ => { + return Err(KeyMaterialError::HazardousOperationNotPermitted); + } + }, + } + Ok(()) } fn security_strength(&self) -> SecurityStrength { @@ -440,69 +521,6 @@ impl KeyMaterialTrait for KeyMaterial { } self.security_strength = strength; - self.drop_hazardous_operations(); - Ok(()) - } - fn allow_hazardous_operations(&mut self) { - self.allow_hazardous_operations = true; - } - fn drop_hazardous_operations(&mut self) { - self.allow_hazardous_operations = false; - } - fn convert_key_type(&mut self, new_key_type: KeyType) -> Result<(), KeyMaterialError> { - if self.allow_hazardous_operations { - // just do it - self.key_type = new_key_type; - return Ok(()); - } - - match self.key_type { - KeyType::Zeroized => { - return Err(KeyMaterialError::ActingOnZeroizedKey); - } - KeyType::BytesFullEntropy => { - // raw full entropy can be safely converted to anything. - self.key_type = new_key_type; - } - KeyType::BytesLowEntropy => { - match new_key_type { - KeyType::BytesLowEntropy => { /* No change */ } - _ => { - return Err(KeyMaterialError::HazardousOperationNotPermitted); - } - } - } - KeyType::MACKey => { - match new_key_type { - KeyType::MACKey => { /* No change */ } - // Else: Once a KeyMaterial is typed, it should stay that way. - _ => { - return Err(KeyMaterialError::HazardousOperationNotPermitted); - } - } - } - KeyType::SymmetricCipherKey => { - match new_key_type { - KeyType::SymmetricCipherKey => { /* No change */ } - // Else: Once a KeyMaterial is typed, it should stay that way. - _ => { - return Err(KeyMaterialError::HazardousOperationNotPermitted); - } - } - } - KeyType::Seed => { - match new_key_type { - KeyType::Seed => { /* No change */ } - // Else: Once a KeyMaterial is typed, it should stay that way. - _ => { - return Err(KeyMaterialError::HazardousOperationNotPermitted); - } - } - } - } - - // each call to allow_hazardous_operations() is only good for one conversion. - self.drop_hazardous_operations(); Ok(()) } fn is_full_entropy(&self) -> bool { @@ -521,22 +539,6 @@ impl KeyMaterialTrait for KeyMaterial { self.key_type = KeyType::Zeroized; } - fn truncate(&mut self, new_len: usize) -> Result<(), KeyMaterialError> { - if new_len > self.key_len { - return Err(KeyMaterialError::InvalidLength); - } - - self.security_strength = - min(&self.security_strength, &SecurityStrength::from_bits(new_len * 8)).clone(); - - if new_len == 0 { - self.key_type = KeyType::Zeroized; - } - - self.key_len = new_len; - Ok(()) - } - fn concatenate(&mut self, other: &dyn KeyMaterialTrait) -> Result { let new_key_len = self.key_len() + other.key_len(); if self.key_len() + other.key_len() > KEY_LEN { @@ -630,3 +632,149 @@ impl Drop for KeyMaterial { self.zeroize() } } + +/* Hazardous Operations Runner */ + +/// Internal-use trait holding the low-level hazardous-operations guard toggle. +/// +/// These methods are deliberately split out of [KeyMaterialTrait] into a private trait so that +/// they are not accessible from outside this module. +/// +/// This is a supertrait of [KeyMaterialTrait], so anything that implements [KeyMaterialTrait] +/// also implements this. [KeyMaterialTrait] therefore stays dyn-compatible (both methods here are +/// object-safe), which matters because `Box` is used widely as a return type. +trait KeyMaterialInternalTrait { + /// Whether this instance is currently allowed to perform potentially hazardous operations. + fn allows_hazardous_operations(&self) -> bool; + /// Sets this instance to be able to perform potentially hazardous operations such as + /// casting a KeyMaterial of type RawUnknownEntropy or RawLowEntropy into RawFullEntropy or SymmetricCipherKey, + /// or manually setting the key bytes via [KeyMaterialTrait::mut_ref_to_bytes], which then requires you to be responsible + /// for setting the key_len and key_type afterwards. + /// + /// The purpose of the hazardous operations guard is not to prevent the user from accessing their data, + /// but rather to make the developer think carefully about the operation they are about to perform, + /// and to give static analysis tools an obvious marker that a given KeyMaterial variable warrants + /// further inspection. + /// + /// Prefer the scoped [KeyMaterial::do_hazardous_operations] wrapper, which calls this and + /// [KeyMaterialInternalTrait::drop_hazardous_operations] for you so the guard can't be left set. + fn allow_hazardous_operations(&mut self); + + /// Resets this instance to not be able to perform potentially hazardous operations. + fn drop_hazardous_operations(&mut self); +} + +impl KeyMaterialInternalTrait for KeyMaterial { + fn allows_hazardous_operations(&self) -> bool { + self.allow_hazardous_operations + } + fn allow_hazardous_operations(&mut self) { + self.allow_hazardous_operations = true; + } + fn drop_hazardous_operations(&mut self) { + self.allow_hazardous_operations = false; + } +} + +/// Runs the provided closure within which hazardous operations are allowed. +/// All hazardous operations will return a [KeyMaterialError::HazardousOperationNotPermitted] +/// if used outside of this closure. +/// +/// Example usage: +/// +/// ```rust +/// use bouncycastle_core::key_material::{KeyType, KeyMaterial256, KeyMaterialTrait, do_hazardous_operations}; +/// use bouncycastle_core::traits::SecurityStrength; +/// +/// // Let's create an all-zero key +/// let mut key = KeyMaterial256::default(); +/// +/// // Let's set a key of all zeroes, which the library would normally force to be +/// // [KeyType::Zeroized], but we want to force it to [KeyType::Seed], which is considered a +/// // hazardous operation. +/// do_hazardous_operations(&mut key, |key| { +/// key.set_bytes_as_type(&[8u8; 32], KeyType::Seed) +/// // note that the closure is required to return Result<(), KeyMaterialError>, +/// // so we can chain [KeyMaterial::set_bytes_as_type], otherwise we would need +/// // to end with Ok(()). +/// }).unwrap(); +/// +/// assert_eq!(key.key_len(), 32); +/// assert_eq!(key.key_type(), KeyType::Seed); +/// ``` +/// +/// ```rust +/// use bouncycastle_core::key_material::{KeyType, KeyMaterial256, KeyMaterialTrait, do_hazardous_operations}; +/// use bouncycastle_core::traits::SecurityStrength; +/// +/// // Let's create an all-zero key +/// let mut key = KeyMaterial256::default(); +/// assert_eq!(key.key_type(), KeyType::Zeroized); +/// assert_eq!(key.security_strength(), SecurityStrength::None); +/// +/// // Now we want to tell the library that this all-zero key +/// // is to be used as a 32-byte [KeyType::Seed] at the 256-bit security strength, +/// // which the library will not allow you to do outside of the hazerdous operations closure. +/// do_hazardous_operations(&mut key, |key| { +/// key.set_key_len(32)?; +/// key.set_key_type(KeyType::Seed)?; +/// key.set_security_strength(SecurityStrength::_256bit)?; +/// Ok(()) +/// }).unwrap(); +/// +/// assert_eq!(key.key_type(), KeyType::Seed); +/// assert_eq!(key.security_strength(), SecurityStrength::_256bit); +/// ``` +/// +/// Another common usage of hazardous operations is to get a direct mutable reference to the +/// underlying KeyMaterial byte buffer; for example if you want to copy in key bytes from somewhere else. +/// +/// ```rust +/// use bouncycastle_core::key_material::{KeyType, KeyMaterial512, KeyMaterialTrait, do_hazardous_operations}; +/// use bouncycastle_core::traits::SecurityStrength; +/// +/// // In this example, we initialize a KeyMateriol512 (64 bytes) with only 32 bytes of input. +/// let mut key = KeyMaterial512::from_bytes_as_type( +/// &[1u8; 32], +/// KeyType::BytesFullEntropy +/// ).unwrap(); +/// assert_eq!(key.key_len(), 32); +/// +/// // Now we want to expand the length to 64 bytes and copy in an additional 32 bytes of key data, +/// // using [KeyMaterial::mut_ref_to_bytes]. +/// let additional_bytes = [2u8; 32]; +/// do_hazardous_operations(&mut key, |key| { +/// key.set_key_len(64)?; +/// key.ref_to_bytes_mut()?[32..].copy_from_slice(&additional_bytes); +/// Ok(()) +/// }).unwrap(); +/// +/// assert_eq!(key.key_len(), 64); +/// // Reading the key bytes via [KeyMateriol::ref_to_bytes] is not a hazardous operation. +/// assert_eq!(key.ref_to_bytes()[..32], [1u8; 32]); +/// assert_eq!(key.ref_to_bytes()[32..], [2u8; 32]); +/// ``` +/// +// Dev note: This is a free function rather than a method on [KeyMaterialTrait] because it is +// generic over the closure type, which would make the trait non-dyn-compatible; the trait is used +// as `&dyn KeyMaterialTrait` elsewhere (e.g. [KeyMaterialTrait::concatenate], [KeyMaterialTrait::equals]). +// The toggle itself lives on the module-private [KeyMaterialInternalTrait], so external crates cannot +// flip the guard by hand and must go through this scoped wrapper (hence `#[allow(private_bounds)]`). +#[allow(private_bounds)] +pub fn do_hazardous_operations(key: &mut KEY, f: F) -> Result<(), KeyMaterialError> +where + KEY: KeyMaterialTrait + ?Sized, + F: FnOnce(&mut KEY) -> Result<(), KeyMaterialError>, +{ + let allows = key.allows_hazardous_operations(); + + key.allow_hazardous_operations(); + let ret = f(key); + + // to allow nested closures, if this key instance allowed + // before entering, then leave it. + if !allows { + key.drop_hazardous_operations(); + } + ret +} diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 5247bbd..595fc06 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -129,8 +129,8 @@ pub trait KDF: Default { /// /// Output length: this function will behave differently depending on the underlying hash primitive; /// some, such as SHA2 or SHA3 will produce a fixed-length output, while others, such as SHAKE or HKDF, - /// will fill the provided KeyMaterial to capacity and require you to truncate it afterwards - /// using [KeyMaterialTrait::truncate]. + /// will fill the provided KeyMaterial to capacity and require you to truncate it afterward + /// using [KeyMaterialTrait::set_key_len]. fn derive_key_out( self, key: &impl KeyMaterialTrait, @@ -169,8 +169,8 @@ pub trait KDF: Default { /// /// Output length: this function will behave differently depending on the underlying hash primitive; /// some, such as SHA2 or SHA3 will produce a fixed-length output, while others, such as SHAKE or HKDF, - /// will fill the provided KeyMaterial to capacity and require you to truncate it afterwards - /// by using [KeyMaterialTrait::truncate]. + /// will fill the provided KeyMaterial to capacity and require you to truncate it afterward + /// by using [KeyMaterialTrait::set_key_len]. fn derive_key_from_multiple_out( self, keys: &[&impl KeyMaterialTrait], diff --git a/crypto/core/tests/key_material_tests.rs b/crypto/core/tests/key_material_tests.rs index 9c65827..08ce397 100644 --- a/crypto/core/tests/key_material_tests.rs +++ b/crypto/core/tests/key_material_tests.rs @@ -3,7 +3,7 @@ mod test_key_material { use bouncycastle_core::errors::KeyMaterialError; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial0, KeyMaterial128, KeyMaterial256, KeyMaterial512, - KeyMaterialTrait, KeyType, + KeyMaterialTrait, KeyType, do_hazardous_operations, }; use bouncycastle_core::traits::SecurityStrength; @@ -47,10 +47,12 @@ mod test_key_material { assert_eq!(key.key_type(), KeyType::Zeroized); assert_eq!(key.key_len(), 16); - // ... but we can force it. - key.allow_hazardous_operations(); - key.set_key_type(KeyType::BytesLowEntropy).unwrap(); - key.drop_hazardous_operations(); + // but it'll allow it within tho do_hazardous closure. + do_hazardous_operations(&mut key, |key| { + key.set_key_type(KeyType::BytesLowEntropy)?; + Ok(()) + }) + .unwrap(); } Err(_) => { panic!("should have thrown a KeyMaterialError::ActingOnZeroizedKey error.") @@ -59,18 +61,22 @@ mod test_key_material { assert_eq!(key.key_type(), KeyType::BytesLowEntropy); assert_eq!(key.security_strength(), SecurityStrength::None); - // but it'll allow it if you allow hazardous operations first. + // but it'll allow it within tho do_hazardous closure. let key_bytes = [0u8; 16]; let mut key = KeyMaterial256::new(); - key.allow_hazardous_operations(); - key.set_bytes_as_type(&key_bytes, KeyType::BytesLowEntropy).unwrap(); + do_hazardous_operations(&mut key, |key| { + key.set_bytes_as_type(&key_bytes, KeyType::BytesLowEntropy)?; + Ok(()) + }) + .unwrap(); assert_eq!(key.key_type(), KeyType::BytesLowEntropy); - key.drop_hazardous_operations(); + // nothing else requires setting hazardous operations. } #[test] fn test_refs() { + // by hand β€” this test exercises guard state-change semantics, so review carefully. let mut key = KeyMaterial256::from_bytes(&[1u8; 16]).unwrap(); assert_eq!(key.capacity(), 32); assert_eq!(key.ref_to_bytes(), &[1u8; 16]); // note: this is also testing that even though the internal buffer is larger than 16 bytes, it slices it down to length. @@ -84,17 +90,25 @@ mod test_key_material { panic!("getting a mut ref should require setting hazardous operations.") } } - key.allow_hazardous_operations(); - assert_eq!(key.ref_to_bytes_mut().unwrap().len(), 32); - assert_eq!(key.ref_to_bytes_mut().unwrap()[..16], [1u8; 16]); - assert_eq!(key.ref_to_bytes_mut().unwrap()[16..], [0u8; 16]); + + // check that we can read from the mut_ref_to_bytes + // (which is an odd way to use it, but legal) + do_hazardous_operations(&mut key, |key| { + assert_eq!(key.ref_to_bytes_mut()?.len(), 32); + assert_eq!(key.ref_to_bytes_mut()?[..16], [1u8; 16]); + assert_eq!(key.ref_to_bytes_mut()?[16..], [0u8; 16]); + Ok(()) + }) + .unwrap(); // and I can set them - key.ref_to_bytes_mut().unwrap().copy_from_slice(&[2u8; 32]); - key.set_key_len(32).unwrap(); + do_hazardous_operations(&mut key, |key| { + key.ref_to_bytes_mut().unwrap().copy_from_slice(&[2u8; 32]); + key.set_key_len(32) + }) + .unwrap(); assert_eq!(key.ref_to_bytes(), &[2u8; 32]); assert_eq!(key.key_len(), 32); - key.drop_hazardous_operations(); } #[test] @@ -178,9 +192,11 @@ mod test_key_material { // Sanity check: the backing buffer actually holds non-zero key material before it is wiped. // Without this, the post-zeroize assertion below could pass vacuously. - key.allow_hazardous_operations(); - assert!(key.ref_to_bytes_mut().unwrap().iter().any(|&b| b != 0)); - key.drop_hazardous_operations(); + do_hazardous_operations(&mut key, |key| { + assert!(key.ref_to_bytes_mut().unwrap().iter().any(|&b| b != 0)); + Ok(()) + }) + .unwrap(); key.zeroize(); let key_len = key.key_len(); @@ -191,37 +207,33 @@ mod test_key_material { // Full capacity must be inspected to confirm the previously-set bytes were // actually overwritten with zeros. // Note: key_len is now 0, so ref_to_bytes() returns an empty slice. - key.allow_hazardous_operations(); - let full_buf = key.ref_to_bytes_mut().unwrap(); - assert_eq!(full_buf.len(), capacity); - assert!(full_buf.iter().all(|&b| b == 0)); - key.drop_hazardous_operations(); + do_hazardous_operations(&mut key, |key| { + let full_buf = key.ref_to_bytes_mut().unwrap(); + assert_eq!(full_buf.len(), capacity); + assert!(full_buf.iter().all(|&b| b == 0)); + Ok(()) + }) + .unwrap(); } #[test] - fn test_truncate() { + fn test_truncation() { let mut key = KeyMaterial512::from_bytes(&DUMMY_KEY[..64]).unwrap(); assert_eq!(key.key_len(), 64); // This should be no change - match key.truncate(64) { + match key.set_key_len(64) { Ok(()) => { /* good */ } _ => panic!("Expected Ok(())"), } assert_eq!(key.key_len(), 64); - key.truncate(32).unwrap(); + key.set_key_len(32).unwrap(); assert_eq!(key.key_len(), 32); - match key.truncate(64) { - Err(KeyMaterialError::InvalidLength) => { /* good */ } - _ => panic!("Expected InvalidLength"), - } - - key.truncate(16).unwrap(); + key.set_key_len(16).unwrap(); assert_eq!(key.key_len(), 16); - key.allow_hazardous_operations(); let key_len = key.key_len(); let mut buf = vec![0u8; key_len]; buf.copy_from_slice(key.ref_to_bytes()); @@ -231,7 +243,7 @@ mod test_key_material { // Test truncating a key to zero length let mut key_zero = KeyMaterial512::from_bytes(&DUMMY_KEY[..64]).unwrap(); - key_zero.truncate(0).unwrap(); + key_zero.set_key_len(0).unwrap(); assert_eq!(key_zero.key_len(), 0); assert_eq!(key_zero.key_type(), KeyType::Zeroized); @@ -239,19 +251,18 @@ mod test_key_material { let mut key = KeyMaterial512::from_bytes_as_type(&[1u8; 64], KeyType::BytesFullEntropy).unwrap(); assert_eq!(key.security_strength(), SecurityStrength::_256bit); - key.truncate(16).unwrap(); + key.set_key_len(16).unwrap(); assert_eq!(key.security_strength(), SecurityStrength::_128bit); - key.truncate(14).unwrap(); + key.set_key_len(14).unwrap(); assert_eq!(key.security_strength(), SecurityStrength::_112bit); - key.truncate(11).unwrap(); + key.set_key_len(11).unwrap(); assert_eq!(key.security_strength(), SecurityStrength::None); // truncate should not raise the security level let mut key = KeyMaterial512::from_bytes_as_type(&[1u8; 64], KeyType::BytesFullEntropy).unwrap(); key.set_security_strength(SecurityStrength::_112bit).unwrap(); - key.drop_hazardous_operations(); - key.truncate(64).unwrap(); + key.set_key_len(64).unwrap(); assert_eq!(key.security_strength(), SecurityStrength::_112bit); } @@ -268,31 +279,32 @@ mod test_key_material { } // This should fail. - match key.convert_key_type(KeyType::BytesFullEntropy) { + match key.set_key_type(KeyType::BytesFullEntropy) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } - key.allow_hazardous_operations(); - key.convert_key_type(KeyType::BytesFullEntropy).unwrap(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::BytesFullEntropy)) + .unwrap(); assert_eq!(key.key_type(), KeyType::BytesFullEntropy); assert!(key.is_full_entropy()); - key.drop_hazardous_operations(); - match key.convert_key_type(KeyType::SymmetricCipherKey) { + // Now we can convert BytesFullEntropy -> SymmetricCipherKey outside of a hazop block + match key.set_key_type(KeyType::SymmetricCipherKey) { Ok(()) => { /* good */ } _ => panic!("Expected Ok(())"), } - match key.convert_key_type(KeyType::BytesFullEntropy) { + match key.set_key_type(KeyType::BytesFullEntropy) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - key.allow_hazardous_operations(); - key.convert_key_type(KeyType::BytesFullEntropy).unwrap(); - key.drop_hazardous_operations(); - match key.convert_key_type(KeyType::Seed) { + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::BytesFullEntropy)) + .unwrap(); + + // Now we can convert BytesFullEntropy -> Seed outside of a hazop block + match key.set_key_type(KeyType::Seed) { Ok(()) => { /* good */ } _ => panic!("Expected Ok(())"), } @@ -300,34 +312,27 @@ mod test_key_material { // each KeyType can convert to itself let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - key.allow_hazardous_operations(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::BytesLowEntropy)) + .unwrap(); key.set_key_type(KeyType::BytesLowEntropy).unwrap(); - key.drop_hazardous_operations(); - key.convert_key_type(KeyType::BytesLowEntropy).unwrap(); let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - key.allow_hazardous_operations(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::BytesFullEntropy)) + .unwrap(); key.set_key_type(KeyType::BytesFullEntropy).unwrap(); - key.drop_hazardous_operations(); - key.convert_key_type(KeyType::BytesFullEntropy).unwrap(); let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - key.allow_hazardous_operations(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::MACKey)).unwrap(); key.set_key_type(KeyType::MACKey).unwrap(); - key.drop_hazardous_operations(); - key.convert_key_type(KeyType::MACKey).unwrap(); let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - key.allow_hazardous_operations(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::SymmetricCipherKey)) + .unwrap(); key.set_key_type(KeyType::SymmetricCipherKey).unwrap(); - key.drop_hazardous_operations(); - key.convert_key_type(KeyType::SymmetricCipherKey).unwrap(); let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - key.allow_hazardous_operations(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::Seed)).unwrap(); key.set_key_type(KeyType::Seed).unwrap(); - key.drop_hazardous_operations(); - key.convert_key_type(KeyType::Seed).unwrap(); } #[test] @@ -336,23 +341,23 @@ mod test_key_material { assert_eq!(zeroized_key.key_type(), KeyType::Zeroized); /* All conversions should fail. */ - match zeroized_key.convert_key_type(KeyType::BytesLowEntropy) { + match zeroized_key.set_key_type(KeyType::BytesLowEntropy) { Err(KeyMaterialError::ActingOnZeroizedKey) => { /* good */ } _ => panic!("Expected ActingOnZeroizedKey"), } - match zeroized_key.convert_key_type(KeyType::BytesFullEntropy) { + match zeroized_key.set_key_type(KeyType::BytesFullEntropy) { Err(KeyMaterialError::ActingOnZeroizedKey) => { /* good */ } _ => panic!("Expected ActingOnZeroizedKey"), } - match zeroized_key.convert_key_type(KeyType::MACKey) { + match zeroized_key.set_key_type(KeyType::MACKey) { Err(KeyMaterialError::ActingOnZeroizedKey) => { /* good */ } _ => panic!("Expected ActingOnZeroizedKey"), } - match zeroized_key.convert_key_type(KeyType::Seed) { + match zeroized_key.set_key_type(KeyType::Seed) { Err(KeyMaterialError::ActingOnZeroizedKey) => { /* good */ } _ => panic!("Expected ActingOnZeroizedKey"), } - match zeroized_key.convert_key_type(KeyType::SymmetricCipherKey) { + match zeroized_key.set_key_type(KeyType::SymmetricCipherKey) { Err(KeyMaterialError::ActingOnZeroizedKey) => { /* good */ } _ => panic!("Expected ActingOnZeroizedKey"), } @@ -382,11 +387,12 @@ mod test_key_material { // but it should still have set the key bytes; it's just giving you a friendly warning assert_eq!(zero_key.key_type(), KeyType::Zeroized); - // ... but will allow it if you set .allow_hazardous_operations() first. + // ... but will allow it inside a hazop closure let mut zero_key = KeyMaterial256::new(); - zero_key.allow_hazardous_operations(); - zero_key.set_bytes_as_type(&[0u8; 19], KeyType::MACKey).unwrap(); - zero_key.drop_hazardous_operations(); + do_hazardous_operations(&mut zero_key, |key| { + key.set_bytes_as_type(&[0u8; 19], KeyType::MACKey) + }) + .unwrap(); assert_eq!(zero_key.key_type(), KeyType::MACKey); } @@ -400,43 +406,37 @@ mod test_key_material { // ... none /* All the hazardous conversions should fail. */ - match key.convert_key_type(KeyType::BytesFullEntropy) { + match key.set_key_type(KeyType::BytesFullEntropy) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } - match key.convert_key_type(KeyType::MACKey) { + match key.set_key_type(KeyType::MACKey) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } - match key.convert_key_type(KeyType::SymmetricCipherKey) { + match key.set_key_type(KeyType::SymmetricCipherKey) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } - match key.convert_key_type(KeyType::Seed) { + match key.set_key_type(KeyType::Seed) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } /* Should work if you allow hazardous conversions. */ key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - key.allow_hazardous_operations(); - key.convert_key_type(KeyType::BytesFullEntropy).unwrap(); - key.drop_hazardous_operations(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::BytesFullEntropy)) + .unwrap(); key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - key.allow_hazardous_operations(); - key.convert_key_type(KeyType::MACKey).unwrap(); - key.drop_hazardous_operations(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::MACKey)).unwrap(); key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - key.allow_hazardous_operations(); - key.convert_key_type(KeyType::SymmetricCipherKey).unwrap(); - key.drop_hazardous_operations(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::SymmetricCipherKey)) + .unwrap(); key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - key.allow_hazardous_operations(); - key.convert_key_type(KeyType::Seed).unwrap(); - key.drop_hazardous_operations(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::Seed)).unwrap(); } #[test] @@ -488,31 +488,28 @@ mod test_key_material { /// Not exhaustive, cargo mutants will probably not be satisfied. fn test_hazardous_conversions_cast_types() { let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - key.allow_hazardous_operations(); - key.convert_key_type(KeyType::MACKey).unwrap(); - key.drop_hazardous_operations(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::MACKey)).unwrap(); // converting to self should work (idempotency) - key.convert_key_type(KeyType::MACKey).unwrap(); + key.set_key_type(KeyType::MACKey).unwrap(); /* All the hazardous conversions should fail. */ - match key.convert_key_type(KeyType::BytesFullEntropy) { + match key.set_key_type(KeyType::BytesFullEntropy) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } - match key.convert_key_type(KeyType::SymmetricCipherKey) { + match key.set_key_type(KeyType::SymmetricCipherKey) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } - match key.convert_key_type(KeyType::Seed) { + match key.set_key_type(KeyType::Seed) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } // should work if you allow hazardous conversions. - key.allow_hazardous_operations(); - key.convert_key_type(KeyType::SymmetricCipherKey).unwrap(); - key.drop_hazardous_operations(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::SymmetricCipherKey)) + .unwrap(); } #[test] @@ -572,56 +569,83 @@ mod test_key_material { assert_eq!(key.security_strength(), SecurityStrength::None); // test set_security_strength() - // Can't increase the security level without setting .allow_hazardous_operations() first. + // Can't increase the security level outside of a hazop block first. let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); assert_eq!(key.key_type(), KeyType::BytesLowEntropy); match key.set_security_strength(SecurityStrength::_128bit) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected KeyMaterialError::HazardousOperationNotPermitted"), } - key.allow_hazardous_operations(); - match key.set_security_strength(SecurityStrength::_128bit) { - Err(KeyMaterialError::SecurityStrength(_)) => { /* good */ } - _ => panic!("Expected KeyMaterialError::SecurityStrength"), - } - - // So let's set it to a full-entropy type - key.set_key_type(KeyType::BytesFullEntropy).unwrap(); - // now it should work - key.set_security_strength(SecurityStrength::_128bit).unwrap(); + do_hazardous_operations(&mut key, |key| { + match key.set_security_strength(SecurityStrength::_128bit) { + Err(KeyMaterialError::SecurityStrength(_)) => { + /* good */ + Ok(()) + } + _ => panic!("Expected KeyMaterialError::SecurityStrength"), + } + }) + .unwrap(); + + // Even in a hazops block, you can't set a BytesLowEntropy to any Security Strength other than None + do_hazardous_operations(&mut key, |key| { + match key.set_security_strength(SecurityStrength::_128bit) { + Err(KeyMaterialError::SecurityStrength(_)) => { + /* good */ + Ok(()) + } + _ => panic!("Expected KeyMaterialError::SecurityStrength"), + } + }) + .unwrap(); + // But it'll work if you set it to a full entropy type + do_hazardous_operations(&mut key, |key| { + key.set_key_type(KeyType::BytesFullEntropy).unwrap(); + key.set_security_strength(SecurityStrength::_128bit) + }) + .unwrap(); + assert_eq!(key.key_type(), KeyType::BytesFullEntropy); assert_eq!(key.security_strength(), SecurityStrength::_128bit); - key.drop_hazardous_operations(); // BytesLowEntropy keys cannot have a security strength other than None. // success let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); assert_eq!(key.key_type(), KeyType::BytesLowEntropy); - // setting to ::None should work .. even without setting .allow_hazardous_operations() + // setting to ::None should work .. even outside of a hazop block key.set_security_strength(SecurityStrength::None).unwrap(); // but to ::_128bit should fail - key.allow_hazardous_operations(); - match key.set_security_strength(SecurityStrength::_128bit) { - Err(KeyMaterialError::SecurityStrength(_)) => { /* good */ } - _ => panic!("Expected KeyMaterialError::SecurityStrength"), - } - key.drop_hazardous_operations(); + do_hazardous_operations(&mut key, |key| { + match key.set_security_strength(SecurityStrength::_128bit) { + Err(KeyMaterialError::SecurityStrength(_)) => { + /* good */ + Ok(()) + } + _ => panic!("Expected KeyMaterialError::SecurityStrength"), + } + }) + .unwrap(); // Zeroized keys cannot have a security strength other than None. // success let mut key = KeyMaterial256::new(); - key.allow_hazardous_operations(); - key.set_key_len(32).unwrap(); // still zeroized - key.drop_hazardous_operations(); + do_hazardous_operations(&mut key, |key| { + key.set_key_len(32) // still zeroized + }) + .unwrap(); assert_eq!(key.key_type(), KeyType::Zeroized); - // setting to ::None should work .. even without setting .allow_hazardous_operations() + // setting to ::None should work, even outside of a hazop block key.set_security_strength(SecurityStrength::None).unwrap(); - // but to ::_128bit should fail - key.allow_hazardous_operations(); - match key.set_security_strength(SecurityStrength::_128bit) { - Err(KeyMaterialError::SecurityStrength(_)) => { /* good */ } - _ => panic!("Expected KeyMaterialError::SecurityStrength"), - } - key.drop_hazardous_operations(); + // but to ::_128bit should fail, even in a hazop block + do_hazardous_operations(&mut key, |key| { + match key.set_security_strength(SecurityStrength::_128bit) { + Err(KeyMaterialError::SecurityStrength(_)) => { + /* good */ + Ok(()) + } + _ => panic!("Expected KeyMaterialError::SecurityStrength"), + } + }) + .unwrap(); } #[test] @@ -638,9 +662,11 @@ mod test_key_material { assert_eq!(key1.ref_to_bytes()[16..], [2u8; 16]); let mut zeroized_key = KeyMaterial256::default(); - zeroized_key.allow_hazardous_operations(); - zeroized_key.set_key_len(8).unwrap(); - zeroized_key.drop_hazardous_operations(); + do_hazardous_operations(&mut zeroized_key, |zeroized_key| { + zeroized_key.set_key_len(8).unwrap(); + Ok(()) + }) + .unwrap(); assert_eq!(zeroized_key.key_type(), KeyType::Zeroized); assert_eq!(zeroized_key.key_len(), 8); zeroized_key.concatenate(&key2).unwrap(); @@ -652,9 +678,11 @@ mod test_key_material { // This should be symmetric, so test it in the other direction too. let mut zeroized_key = KeyMaterial256::default(); - zeroized_key.allow_hazardous_operations(); - zeroized_key.set_key_len(8).unwrap(); - zeroized_key.drop_hazardous_operations(); + do_hazardous_operations(&mut zeroized_key, |zeroized_key| { + zeroized_key.set_key_len(8).unwrap(); + Ok(()) + }) + .unwrap(); assert_eq!(zeroized_key.key_type(), KeyType::Zeroized); assert_eq!(zeroized_key.key_len(), 8); let mut key2 = KeyMaterial256::from_bytes(&[1u8; 16]).unwrap(); @@ -772,8 +800,11 @@ mod test_key_material { // success case -- zero keys -- different type and different KeyType let key1 = KeyMaterial0::new(); let mut key2 = KeyMaterial256::new(); - key2.allow_hazardous_operations(); - key2.convert_key_type(KeyType::SymmetricCipherKey).unwrap(); + do_hazardous_operations(&mut key2, |key2| { + key2.set_key_type(KeyType::SymmetricCipherKey).unwrap(); + Ok(()) + }) + .unwrap(); assert!(key1.equals(&key2)); assert!(key2.equals(&key1)); @@ -786,8 +817,11 @@ mod test_key_material { // success case -- non-zero keys -- different type and different KeyType let key1 = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); let mut key2 = KeyMaterial512::from_bytes(&DUMMY_KEY[..32]).unwrap(); - key2.allow_hazardous_operations(); - key2.convert_key_type(KeyType::SymmetricCipherKey).unwrap(); + do_hazardous_operations(&mut key2, |key2| { + key2.set_key_type(KeyType::SymmetricCipherKey).unwrap(); + Ok(()) + }) + .unwrap(); assert!(key1.equals(&key2)); assert!(key2.equals(&key1)); @@ -841,4 +875,67 @@ mod test_key_material { } } } + + #[test] + /// Pins the error behaviour of [do_hazardous_operations]: + /// 1. an `Err` returned from the closure propagates out verbatim (not swallowed or remapped), + /// 2. a real `KeyMaterialError` raised by a guarded op inside the closure propagates via `?`, + /// 3. the "flattening" idiom the KDF/RNG crates rely on β€” collapsing a *foreign* error type + /// into a `KeyMaterialError` with `map_err` inside the closure β€” surfaces as that + /// `KeyMaterialError`, and + /// 4. the guard is still cleared on the error path (an erroring closure does not leave the + /// instance stuck in hazardous mode). + fn test_hazardous_ops_error_handling() { + let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); + + // 1. An explicit Err returned from the closure propagates out unchanged. + let result = + do_hazardous_operations(&mut key, |_key| Err(KeyMaterialError::GenericError("boom"))); + match result { + Err(KeyMaterialError::GenericError("boom")) => { /* good */ } + _ => panic!("the closure's error should propagate out verbatim"), + } + + // 2. A real KeyMaterialError raised by a guarded op inside the closure propagates via `?`. + // Raising to _256bit requires >= 32 bytes, but this key is only 16, so it fails. + let mut short = + KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..16], KeyType::BytesFullEntropy) + .unwrap(); + let result = do_hazardous_operations(&mut short, |k| { + k.set_security_strength(SecurityStrength::_256bit)?; + Ok(()) + }); + match result { + Err(KeyMaterialError::SecurityStrength(_)) => { /* good */ } + _ => panic!("the SecurityStrength error should propagate from inside the closure"), + } + + // 3. The "flattening" idiom: a foreign error type is collapsed into a KeyMaterialError via + // map_err inside the closure (exactly how hkdf/rng flatten MACError/RNGError), so it + // surfaces as that KeyMaterialError. Outside the closure, the caller would then convert + // the KeyMaterialError back to its own error type via `?` / `From`. + #[derive(Debug)] + struct ForeignError; + fn foreign_op() -> Result<(), ForeignError> { + Err(ForeignError) + } + + let result = do_hazardous_operations(&mut key, |_k| { + foreign_op().map_err(|_| KeyMaterialError::GenericError("flattened"))?; + Ok(()) + }); + match result { + Err(KeyMaterialError::GenericError("flattened")) => { /* good */ } + _ => panic!("the foreign error should be flattened into a KeyMaterialError"), + } + + // 4. The guard is cleared even when the closure returns Err: a guarded op afterwards, outside + // any closure, must still be rejected. `key` is BytesLowEntropy and was never mutated by + // the erroring closures above, so converting it to Seed is a hazardous op that requires + // the guard -- which proves the guard was restored to "off". + match key.set_key_type(KeyType::Seed) { + Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } + _ => panic!("the guard should have been cleared after the erroring closures"), + } + } } diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index a6ecc51..27c5607 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -147,7 +147,8 @@ #![forbid(unsafe_code)] -use bouncycastle_core::errors::{KDFError, MACError}; +use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError}; +use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial0, KeyMaterial512, KeyMaterialTrait, KeyType, }; @@ -156,7 +157,6 @@ use bouncycastle_hmac::HMAC; use bouncycastle_sha2::{SHA256, SHA512}; use bouncycastle_utils::{max, min}; use std::marker::PhantomData; - // Imports needed only for docs #[allow(unused_imports)] use bouncycastle_core::traits::XOF; @@ -396,8 +396,6 @@ impl HKDF { let N = L.div_ceil(hash_len) as u8; let mut bytes_written: usize = 0; - okm.allow_hazardous_operations(); - let out: &mut [u8] = okm.ref_to_bytes_mut()?; // Could potentially speed this up by unrolling T(0) and T(1) // We're gonna have to kludge the prk key type to MACKey to make HMAC happy, but we'll set it back to the original value afterwards. @@ -408,19 +406,27 @@ impl HKDF { let mut T = [0u8; HMAC_BLOCK_LEN]; let mut t_len: usize = 0; let mut i = 1u8; - while i < N { - // todo: might need this to be new_allow_weak_key() - let mut hmac = HMAC::::new(&prk_as_mac_key)?; - hmac.do_update(&T[..t_len]); - hmac.do_update(info); - hmac.do_update(&[i]); - - t_len = hmac.do_final_out(&mut T)?; - debug_assert_eq!(t_len, hash_len); // this will be true for every iteration after T(0) / T(1) - out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]); - bytes_written += t_len; - i += 1; - } + + key_material::do_hazardous_operations(okm, |okm| { + let out = okm.ref_to_bytes_mut()?; + while i < N { + // todo: might need this to be new_allow_weak_key() + let mut hmac = HMAC::::new(&prk_as_mac_key) + .map_err(|_| KeyMaterialError::GenericError("HMAC initialization failed"))?; + hmac.do_update(&T[..t_len]); + hmac.do_update(info); + hmac.do_update(&[i]); + + t_len = hmac + .do_final_out(&mut T) + .map_err(|_| KeyMaterialError::GenericError("HMAC finalization failed"))?; + debug_assert_eq!(t_len, hash_len); // this will be true for every iteration after T(0) / T(1) + out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]); + bytes_written += t_len; + i += 1; + } + Ok(()) + })?; // On the last iteration, we don't take all of the output. let remaining = L - bytes_written; @@ -432,26 +438,32 @@ impl HKDF { t_len = hmac.do_final_out(&mut T[..remaining])?; debug_assert_eq!(t_len, remaining); // this will be true for every iteration after T(0) / T(1) - out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]); + + key_material::do_hazardous_operations(okm, |okm| { + let out = okm.ref_to_bytes_mut()?; + out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]); + Ok(()) + })?; bytes_written += t_len; // set the KeyType of the output // since we've done some computation, the result will not actually be zeroized, even if all input key material was zeroized. - if prk.key_type() == KeyType::Zeroized { - okm.set_key_type(KeyType::BytesLowEntropy)?; - } else { - okm.set_key_type(prk.key_type().clone())?; - } - okm.set_key_len(bytes_written)?; - if okm.key_type() <= KeyType::BytesLowEntropy { - okm.set_security_strength(SecurityStrength::None)?; - } else { - okm.set_security_strength( - min(&SecurityStrength::from_bytes(okm.key_len()), &entropy.security_strength) - .clone(), - )?; - } - okm.drop_hazardous_operations(); + key_material::do_hazardous_operations(okm, |okm| { + if prk.key_type() == KeyType::Zeroized { + okm.set_key_type(KeyType::BytesLowEntropy)?; + } else { + okm.set_key_type(prk.key_type().clone())?; + } + okm.set_key_len(bytes_written)?; + if okm.key_type() <= KeyType::BytesLowEntropy { + okm.set_security_strength(SecurityStrength::None) + } else { + okm.set_security_strength( + min(&SecurityStrength::from_bytes(okm.key_len()), &entropy.security_strength) + .clone(), + ) + } + })?; Ok(bytes_written) } @@ -568,20 +580,27 @@ impl HKDF { let output_key_type = self.entropy.get_output_key_type(); // need to do this above self.hmac.do_final_out, which will consume self. - okm.allow_hazardous_operations(); // doing it here to get ref_to_bytes_mut - let bytes_written = - self.hmac.unwrap().do_final_out(&mut okm.ref_to_bytes_mut().unwrap())?; - okm.set_key_len(bytes_written)?; - okm.set_key_type(output_key_type)?; - if output_key_type <= KeyType::BytesLowEntropy { - okm.set_security_strength(SecurityStrength::None)?; - } else { - okm.set_security_strength( - min(&SecurityStrength::from_bytes(okm.key_len()), &self.entropy.security_strength) + let mut bytes_written = 0; + key_material::do_hazardous_operations(okm, |okm| { + bytes_written = self + .hmac + .unwrap() + .do_final_out(&mut okm.ref_to_bytes_mut()?) + .map_err(|_| KeyMaterialError::GenericError("HMAC do_final_out failed"))?; + okm.set_key_len(bytes_written)?; + okm.set_key_type(output_key_type)?; + if output_key_type <= KeyType::BytesLowEntropy { + okm.set_security_strength(SecurityStrength::None) + } else { + okm.set_security_strength( + min( + &SecurityStrength::from_bytes(okm.key_len()), + &self.entropy.security_strength, + ) .clone(), - )?; - } - okm.drop_hazardous_operations(); + ) + } + })?; Ok(bytes_written) } } @@ -605,7 +624,7 @@ impl KDF for HKDF { ) -> Result, KDFError> { let mut output_key = KeyMaterial512::new(); _ = self.derive_key_out(key, additional_input, &mut output_key)?; - output_key.truncate(H::OUTPUT_LEN)?; + output_key.set_key_len(H::OUTPUT_LEN)?; Ok(Box::new(output_key)) } @@ -643,7 +662,7 @@ impl KDF for HKDF { ) -> Result, KDFError> { let mut output_key = KeyMaterial512::new(); _ = self.derive_key_from_multiple_out(keys, additional_input, &mut output_key)?; - output_key.truncate(*min(&output_key.key_len(), &H::OUTPUT_LEN))?; + output_key.set_key_len(*min(&output_key.key_len(), &H::OUTPUT_LEN))?; Ok(Box::new(output_key)) } @@ -676,13 +695,16 @@ impl KDF for HKDF { let bytes_written = HKDF::::expand_out(&prk, additional_input, output_key.capacity(), output_key)?; - output_key.allow_hazardous_operations(); - output_key.set_key_type(entropy.get_output_key_type())?; - output_key.set_security_strength( - min(&SecurityStrength::from_bytes(output_key.key_len()), &entropy.security_strength) + key_material::do_hazardous_operations(output_key, |output_key| { + output_key.set_key_type(entropy.get_output_key_type())?; + output_key.set_security_strength( + min( + &SecurityStrength::from_bytes(output_key.key_len()), + &entropy.security_strength, + ) .clone(), - )?; - output_key.drop_hazardous_operations(); + ) + })?; Ok(bytes_written) } diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index 1e13d37..4d598fa 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod hkdf_tests { use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError}; + use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial0, KeyMaterial128, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, @@ -528,20 +529,18 @@ mod hkdf_tests { /*** First with the one-shot APIs ::extract() and ::expand_out(). ***/ let mut ikm_key = KeyMaterial::<100>::new(); - ikm_key.allow_hazardous_operations(); - let r = ikm_key.set_bytes_as_type(&hex::decode(ikm).unwrap(), KeyType::BytesFullEntropy); // just for testing, ignore the error about zeroized keys - if r.is_err() { - ikm_key.allow_hazardous_operations(); - ikm_key.set_key_type(KeyType::MACKey).unwrap(); - } + key_material::do_hazardous_operations(&mut ikm_key, |ikm_key| { + // just for testing, ignore the error about zeroized keys + ikm_key.set_bytes_as_type(&hex::decode(ikm).unwrap(), KeyType::BytesFullEntropy) + }) + .unwrap(); let mut salt_key = KeyMaterial::<100>::new(); - salt_key.allow_hazardous_operations(); - let r = salt_key.set_bytes_as_type(&hex::decode(salt).unwrap(), KeyType::MACKey); // just for testing, ignore the error about zeroized keys - if r.is_err() { - salt_key.allow_hazardous_operations(); - ikm_key.set_key_type(KeyType::MACKey).unwrap(); - } + key_material::do_hazardous_operations(&mut salt_key, |salt_key| { + // just for testing, ignore the error about zeroized keys + salt_key.set_bytes_as_type(&hex::decode(salt).unwrap(), KeyType::MACKey) + }) + .unwrap(); let info = hex::decode(info).unwrap(); let mut prk_key = HKDF_SHA256::extract(&salt_key, &ikm_key).unwrap(); @@ -550,12 +549,14 @@ mod hkdf_tests { // Some of the RFC5896 test vectors have input keys that are too short to meet the entropy seeding rules. // So, just for testing, we'll bump this up to full entropy, regardless of what entropy HKDF::extract() // thinks it should be based on the inputs. - prk_key.allow_hazardous_operations(); - prk_key.set_key_type(KeyType::MACKey).unwrap(); + key_material::do_hazardous_operations(&mut prk_key, |prk_key| { + prk_key.set_key_type(KeyType::MACKey) + }) + .unwrap(); let mut okm_key = KeyMaterial::<100>::new(); _ = HKDF_SHA256::expand_out(&prk_key, &info, L, &mut okm_key).unwrap(); - okm_key.truncate(L).unwrap(); + okm_key.set_key_len(L).unwrap(); assert_eq!(okm_key.ref_to_bytes().len(), L); assert_eq!(okm_key.ref_to_bytes(), hex::decode(okm).unwrap()); @@ -683,8 +684,9 @@ mod hkdf_tests { // SP800-56Cr2 tcId 1 let mut salt = KeyMaterial::<128>::new(); // have to do it this way for it to accept a zeroized key - salt.allow_hazardous_operations(); - salt.set_bytes_as_type(&hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), KeyType::MACKey).unwrap(); + key_material::do_hazardous_operations(&mut salt, |salt|{ + salt.set_bytes_as_type(&hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), KeyType::MACKey) + }).unwrap(); // ikm = z||t // let ikm = KeyMaterial_internal::<128>::from_bytes_as_type(&hex::decode("589410408990A227518017C37997BE2F770AF54063E7393B2AA5463196136D18F365733139EB74CDD6B7268F41D33DB6").unwrap(), KeyType::MACKey).unwrap(); @@ -719,7 +721,7 @@ mod hkdf_tests { let output_key = hkdf.derive_key(&ikm, &additional_input).unwrap(); // kdf.derive_key is a one-step that doesn't expand, so need to truncate the expected key to match. let mut expected_key_truncated = expected_key.clone(); - expected_key_truncated.truncate(output_key.key_len()).unwrap(); + expected_key_truncated.set_key_len(output_key.key_len()).unwrap(); assert_eq!(output_key.ref_to_bytes(), expected_key_truncated.ref_to_bytes()); testframework diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index a3d8e8c..862bfa1 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod hmac_tests { use bouncycastle_core::errors::{KeyMaterialError, MACError}; + use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; @@ -16,8 +17,10 @@ mod hmac_tests { fn simple_tests() { // Simple test with zero-length key let mut zero_length_key = KeyMaterial256::default(); - zero_length_key.allow_hazardous_operations(); - zero_length_key.convert_key_type(KeyType::MACKey).unwrap(); + key_material::do_hazardous_operations(&mut zero_length_key, |zero_length_key| { + zero_length_key.set_key_type(KeyType::MACKey) + }) + .unwrap(); assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); @@ -152,8 +155,8 @@ mod hmac_tests { HMAC_SHA256::new_allow_weak_key(&zero_key).unwrap(); // non-zero len key of all-zero bytes - zero_key.allow_hazardous_operations(); - zero_key.set_key_len(32).unwrap(); + key_material::do_hazardous_operations(&mut zero_key, |zero_key| zero_key.set_key_len(32)) + .unwrap(); HMAC_SHA256::new_allow_weak_key(&zero_key).unwrap(); // but we don't allow zero-len keys that are not Zeroized or MACKey @@ -270,8 +273,10 @@ mod hmac_tests { fn hmac_sha224() { let test_framework = TestFrameworkMAC::new(); let mut zero_length_key = KeyMaterial256::default(); - zero_length_key.allow_hazardous_operations(); - zero_length_key.convert_key_type(KeyType::MACKey).unwrap(); + key_material::do_hazardous_operations(&mut zero_length_key, |zero_length_key| { + zero_length_key.set_key_type(KeyType::MACKey) + }) + .unwrap(); assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); @@ -346,8 +351,10 @@ mod hmac_tests { // test with zero-length key let test_framework = TestFrameworkMAC::new(); let mut zero_length_key = KeyMaterial256::default(); - zero_length_key.allow_hazardous_operations(); - zero_length_key.convert_key_type(KeyType::MACKey).unwrap(); + key_material::do_hazardous_operations(&mut zero_length_key, |zero_length_key| { + zero_length_key.set_key_type(KeyType::MACKey) + }) + .unwrap(); assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); @@ -423,8 +430,10 @@ mod hmac_tests { // test with zero-length key let test_framework = TestFrameworkMAC::new(); let mut zero_length_key = KeyMaterial256::default(); - zero_length_key.allow_hazardous_operations(); - zero_length_key.convert_key_type(KeyType::MACKey).unwrap(); + key_material::do_hazardous_operations(&mut zero_length_key, |zero_length_key| { + zero_length_key.set_key_type(KeyType::MACKey) + }) + .unwrap(); assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); @@ -498,8 +507,10 @@ mod hmac_tests { // test with zero-length key let test_framework = TestFrameworkMAC::new(); let mut zero_length_key = KeyMaterial256::default(); - zero_length_key.allow_hazardous_operations(); - zero_length_key.convert_key_type(KeyType::MACKey).unwrap(); + key_material::do_hazardous_operations(&mut zero_length_key, |zero_length_key| { + zero_length_key.set_key_type(KeyType::MACKey) + }) + .unwrap(); assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); diff --git a/crypto/mldsa-lowmemory/src/mldsa_keys.rs b/crypto/mldsa-lowmemory/src/mldsa_keys.rs index b805d89..d4b1c88 100644 --- a/crypto/mldsa-lowmemory/src/mldsa_keys.rs +++ b/crypto/mldsa-lowmemory/src/mldsa_keys.rs @@ -21,6 +21,7 @@ use crate::mldsa::{ }; use crate::{ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME}; use bouncycastle_core::errors::SignatureError; +use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ Secret, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, XOF, @@ -610,10 +611,10 @@ impl< return Err(SignatureError::DecodingError("Invalid seed length")); } let mut keymat = KeyMaterial::<32>::from_bytes(bytes)?; - keymat.allow_hazardous_operations(); - keymat.set_key_type(KeyType::Seed)?; - keymat.set_security_strength(SecurityStrength::_256bit)?; - keymat.drop_hazardous_operations(); + key_material::do_hazardous_operations(&mut keymat, |keymat| { + keymat.set_key_type(KeyType::Seed)?; + keymat.set_security_strength(SecurityStrength::_256bit) + })?; Self::new(&keymat) } diff --git a/crypto/mldsa-lowmemory/tests/bc_test_data.rs b/crypto/mldsa-lowmemory/tests/bc_test_data.rs index fdc5c56..94c4b67 100644 --- a/crypto/mldsa-lowmemory/tests/bc_test_data.rs +++ b/crypto/mldsa-lowmemory/tests/bc_test_data.rs @@ -16,6 +16,7 @@ mod bc_test_data { #[allow(dead_code)] use crate::BustedMuBuilder; use bouncycastle_core::errors::SignatureError; + use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ Hash, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, @@ -167,10 +168,10 @@ mod bc_test_data { ) .unwrap(); // for the purposes of the test cases, accept an all-zero seed - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - seed.set_security_strength(SecurityStrength::_256bit).unwrap(); - seed.drop_hazardous_operations(); + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit) + }); match self.parameter_set.as_str() { "ML-DSA-44" => { @@ -700,10 +701,10 @@ mod bc_test_data { ) .unwrap(); // for the purposes of the test cases, accept an all-zero seed - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - seed.set_security_strength(SecurityStrength::_256bit).unwrap(); - seed.drop_hazardous_operations(); + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit) + }); let (pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); let pk_sized: [u8; MLDSA44_PK_LEN] = @@ -776,10 +777,10 @@ mod bc_test_data { ) .unwrap(); // for the purposes of the test cases, accept an all-zero seed - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - seed.set_security_strength(SecurityStrength::_256bit).unwrap(); - seed.drop_hazardous_operations(); + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit) + }); let (pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); let pk_sized: [u8; MLDSA65_PK_LEN] = @@ -846,10 +847,10 @@ mod bc_test_data { ) .unwrap(); // for the purposes of the test cases, accept an all-zero seed - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - seed.set_security_strength(SecurityStrength::_256bit).unwrap(); - seed.drop_hazardous_operations(); + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit) + }); let (pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); let pk_sized: [u8; MLDSA87_PK_LEN] = diff --git a/crypto/mldsa-lowmemory/tests/mldsa_key_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_key_tests.rs index 8b7884f..f80978c 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_key_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_key_tests.rs @@ -4,6 +4,7 @@ mod mldsa_key_tests { #![allow(unused_imports)] use bouncycastle_core::errors::SignatureError; + use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{SecurityStrength, SignaturePrivateKey, SignaturePublicKey}; use bouncycastle_core_test_framework::signature::TestFrameworkSignatureKeys; @@ -96,19 +97,22 @@ mod mldsa_key_tests { // it'll reject a keyen with a seed too weak, and preserve the seed otherwise let mut seed128 = seed.clone(); - seed128.allow_hazardous_operations(); - seed128.set_security_strength(SecurityStrength::_128bit).unwrap(); - seed128.drop_hazardous_operations(); + key_material::do_hazardous_operations(&mut seed128, |seed| { + seed.set_security_strength(SecurityStrength::_128bit) + }) + .unwrap(); let mut seed192 = seed.clone(); - seed192.allow_hazardous_operations(); - seed192.set_security_strength(SecurityStrength::_192bit).unwrap(); - seed192.drop_hazardous_operations(); + key_material::do_hazardous_operations(&mut seed192, |seed| { + seed.set_security_strength(SecurityStrength::_192bit) + }) + .unwrap(); let mut seed256 = seed.clone(); - seed256.allow_hazardous_operations(); - seed256.set_security_strength(SecurityStrength::_256bit).unwrap(); - seed256.drop_hazardous_operations(); + key_material::do_hazardous_operations(&mut seed256, |seed| { + seed.set_security_strength(SecurityStrength::_256bit) + }) + .unwrap(); // MLDSA44 let (_pk, sk) = MLDSA44::keygen_from_seed(&seed128).unwrap(); diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index e45ed76..593b6f9 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -3,6 +3,7 @@ mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; use bouncycastle_core::errors::SignatureError; + use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, @@ -188,8 +189,10 @@ mod mldsa_tests { assert_eq!(derived_pk.encode(), expected_pk_bytes.as_slice()); // success case KeyType: BytesFullEntropy - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::BytesFullEntropy).unwrap(); + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::BytesFullEntropy) + }) + .unwrap(); _ = MLDSA44::keygen_from_seed(&seed).unwrap(); // Failure case: key type != Seed || BytesFullEntropy @@ -446,9 +449,10 @@ mod mldsa_tests { KeyType::Seed, ) .unwrap(); - low_security_seed.allow_hazardous_operations(); - low_security_seed.set_security_strength(SecurityStrength::_192bit).unwrap(); - low_security_seed.drop_hazardous_operations(); + key_material::do_hazardous_operations(&mut low_security_seed, |seed| { + seed.set_security_strength(SecurityStrength::_192bit) + }) + .unwrap(); // a 128bit secure seed should be rejected by MLDSA87 MLDSA65::sign_mu_deterministic_from_seed(&low_security_seed, &mu, rnd).unwrap(); @@ -459,9 +463,10 @@ mod mldsa_tests { KeyType::Seed, ) .unwrap(); - low_security_seed.allow_hazardous_operations(); - low_security_seed.set_security_strength(SecurityStrength::_128bit).unwrap(); - low_security_seed.drop_hazardous_operations(); + key_material::do_hazardous_operations(&mut low_security_seed, |seed| { + seed.set_security_strength(SecurityStrength::_128bit) + }) + .unwrap(); // a 128bit secure seed should be rejected by MLDSA87 match MLDSA87::sign_mu_deterministic_from_seed(&low_security_seed, &mu, rnd) { Err(SignatureError::KeyGenError(_)) => { /* good */ } diff --git a/crypto/mldsa-lowmemory/tests/wycheproof.rs b/crypto/mldsa-lowmemory/tests/wycheproof.rs index 080efdf..3dcdedc 100644 --- a/crypto/mldsa-lowmemory/tests/wycheproof.rs +++ b/crypto/mldsa-lowmemory/tests/wycheproof.rs @@ -22,6 +22,7 @@ #![allow(dead_code)] use bouncycastle_core::errors::SignatureError; +use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{SecurityStrength, SignaturePublicKey, SignatureVerifier}; use bouncycastle_hex as hex; @@ -268,10 +269,24 @@ impl MLDSASignSeedTestCase { } }; // allow an all-zero seed for testing - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match seed.set_security_strength(SecurityStrength::_256bit) { - Ok(_) => (), + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed)?; + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => Ok(()), + Err(e) => { + if self.result == "invalid" { + /* good */ + Ok(()) + } else { + panic!("{:?}", e) + } + } + } + }) + .unwrap(); + + let (pk, sk) = match MLDSA44::keygen_from_seed(&seed) { + Ok((pk, sk)) => (pk, sk), Err(e) => { if self.result == "invalid" { /* good */ @@ -280,13 +295,6 @@ impl MLDSASignSeedTestCase { panic!("{:?}", e) } } - } - - let (pk, sk) = match MLDSA44::keygen_from_seed(&seed) { - Ok((pk, sk)) => (pk, sk), - Err(e) => { - panic!("{:?}", e) - } }; let loaded_pk = @@ -358,24 +366,31 @@ impl MLDSASignSeedTestCase { } }; // allow an all-zero seed for testing - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match seed.set_security_strength(SecurityStrength::_256bit) { - Ok(_) => (), - Err(e) => { - if self.result == "invalid" { - /* good */ - return; - } else { - panic!("{:?}", e) + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => Ok(()), + Err(e) => { + if self.result == "invalid" { + /* good */ + Ok(()) + } else { + panic!("{:?}", e) + } } } - } + }) + .unwrap(); let (pk, sk) = match MLDSA65::keygen_from_seed(&seed) { Ok((pk, sk)) => (pk, sk), Err(e) => { - panic!("{:?}", e) + if self.result == "invalid" { + /* good, test passed */ + return; + } else { + panic!("{:?}", e) + } } }; @@ -448,24 +463,31 @@ impl MLDSASignSeedTestCase { } }; // allow an all-zero seed for testing - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match seed.set_security_strength(SecurityStrength::_256bit) { - Ok(_) => (), - Err(e) => { - if self.result == "invalid" { - /* good */ - return; - } else { - panic!("{:?}", e) + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => Ok(()), + Err(e) => { + if self.result == "invalid" { + /* good */ + Ok(()) + } else { + panic!("{:?}", e) + } } } - } + }) + .unwrap(); let (pk, sk) = match MLDSA87::keygen_from_seed(&seed) { Ok((pk, sk)) => (pk, sk), Err(e) => { - panic!("{:?}", e) + if self.result == "invalid" { + /* good, test passed */ + return; + } else { + panic!("{:?}", e) + } } }; diff --git a/crypto/mldsa/tests/bc_test_data.rs b/crypto/mldsa/tests/bc_test_data.rs index a732489..3891229 100644 --- a/crypto/mldsa/tests/bc_test_data.rs +++ b/crypto/mldsa/tests/bc_test_data.rs @@ -12,7 +12,9 @@ use bouncycastle_sha3::SHAKE256; mod bc_test_data { use crate::BustedMuBuilder; use bouncycastle_core::errors::SignatureError; - use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; + use bouncycastle_core::key_material::{ + KeyMaterial256, KeyMaterialTrait, KeyType, do_hazardous_operations, + }; use bouncycastle_core::traits::{ Hash, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, }; @@ -162,10 +164,11 @@ mod bc_test_data { ) .unwrap(); // for the purposes of the test cases, accept an all-zero seed - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - seed.set_security_strength(SecurityStrength::_256bit).unwrap(); - seed.drop_hazardous_operations(); + do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed)?; + seed.set_security_strength(SecurityStrength::_256bit) + }) + .unwrap(); match self.parameter_set.as_str() { "ML-DSA-44" => { @@ -706,10 +709,11 @@ mod bc_test_data { ) .unwrap(); // for the purposes of the test cases, accept an all-zero seed - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - seed.set_security_strength(SecurityStrength::_256bit).unwrap(); - seed.drop_hazardous_operations(); + do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed)?; + seed.set_security_strength(SecurityStrength::_256bit) + }) + .unwrap(); let (pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); let pk_sized: [u8; MLDSA44_PK_LEN] = @@ -784,10 +788,11 @@ mod bc_test_data { ) .unwrap(); // for the purposes of the test cases, accept an all-zero seed - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - seed.set_security_strength(SecurityStrength::_256bit).unwrap(); - seed.drop_hazardous_operations(); + do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed)?; + seed.set_security_strength(SecurityStrength::_256bit) + }) + .unwrap(); let (pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); let pk_sized: [u8; MLDSA65_PK_LEN] = @@ -856,10 +861,11 @@ mod bc_test_data { ) .unwrap(); // for the purposes of the test cases, accept an all-zero seed - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - seed.set_security_strength(SecurityStrength::_256bit).unwrap(); - seed.drop_hazardous_operations(); + do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed)?; + seed.set_security_strength(SecurityStrength::_256bit) + }) + .unwrap(); let (pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); let pk_sized: [u8; MLDSA87_PK_LEN] = diff --git a/crypto/mldsa/tests/mldsa_key_tests.rs b/crypto/mldsa/tests/mldsa_key_tests.rs index 81e2493..0fc5fe1 100644 --- a/crypto/mldsa/tests/mldsa_key_tests.rs +++ b/crypto/mldsa/tests/mldsa_key_tests.rs @@ -1,7 +1,9 @@ #[cfg(test)] mod mldsa_key_tests { use bouncycastle_core::errors::SignatureError; - use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; + use bouncycastle_core::key_material::{ + KeyMaterial256, KeyMaterialTrait, KeyType, do_hazardous_operations, + }; use bouncycastle_core::traits::{SecurityStrength, SignaturePrivateKey, SignaturePublicKey}; use bouncycastle_core_test_framework::signature::TestFrameworkSignatureKeys; use bouncycastle_hex as hex; @@ -140,19 +142,22 @@ mod mldsa_key_tests { // it'll reject a keyen with a seed too weak, and preserve the seed otherwise let mut seed128 = seed.clone(); - seed128.allow_hazardous_operations(); - seed128.set_security_strength(SecurityStrength::_128bit).unwrap(); - seed128.drop_hazardous_operations(); + do_hazardous_operations(&mut seed128, |seed128| { + seed128.set_security_strength(SecurityStrength::_128bit) + }) + .unwrap(); let mut seed192 = seed.clone(); - seed192.allow_hazardous_operations(); - seed192.set_security_strength(SecurityStrength::_192bit).unwrap(); - seed192.drop_hazardous_operations(); + do_hazardous_operations(&mut seed192, |seed192| { + seed192.set_security_strength(SecurityStrength::_192bit) + }) + .unwrap(); let mut seed256 = seed.clone(); - seed256.allow_hazardous_operations(); - seed256.set_security_strength(SecurityStrength::_256bit).unwrap(); - seed256.drop_hazardous_operations(); + do_hazardous_operations(&mut seed256, |seed256| { + seed256.set_security_strength(SecurityStrength::_256bit) + }) + .unwrap(); // MLDSA44 let (_pk, sk) = MLDSA44::keygen_from_seed(&seed128).unwrap(); diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index b87a0a8..242800a 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -3,7 +3,9 @@ mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; use bouncycastle_core::errors::SignatureError; - use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; + use bouncycastle_core::key_material::{ + KeyMaterial256, KeyMaterialTrait, KeyType, do_hazardous_operations, + }; use bouncycastle_core::traits::{ RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, }; @@ -223,8 +225,8 @@ mod mldsa_tests { assert_eq!(derived_pk.encode(), expected_pk_bytes.as_slice()); // success case KeyType: BytesFullEntropy - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::BytesFullEntropy).unwrap(); + do_hazardous_operations(&mut seed, |seed| seed.set_key_type(KeyType::BytesFullEntropy)) + .unwrap(); _ = MLDSA44::keygen_from_seed(&seed).unwrap(); // Failure case: key type != Seed || BytesFullEntropy @@ -586,9 +588,10 @@ mod mldsa_tests { KeyType::Seed, ) .unwrap(); - low_security_seed.allow_hazardous_operations(); - low_security_seed.set_security_strength(SecurityStrength::_192bit).unwrap(); - low_security_seed.drop_hazardous_operations(); + do_hazardous_operations(&mut low_security_seed, |low_security_seed| { + low_security_seed.set_security_strength(SecurityStrength::_192bit) + }) + .unwrap(); // a 128bit secure seed should be rejected by MLDSA87 MLDSA65::sign_mu_deterministic_from_seed(&low_security_seed, &mu, rnd).unwrap(); @@ -599,9 +602,10 @@ mod mldsa_tests { KeyType::Seed, ) .unwrap(); - low_security_seed.allow_hazardous_operations(); - low_security_seed.set_security_strength(SecurityStrength::_128bit).unwrap(); - low_security_seed.drop_hazardous_operations(); + do_hazardous_operations(&mut low_security_seed, |low_security_seed| { + low_security_seed.set_security_strength(SecurityStrength::_128bit) + }) + .unwrap(); // a 128bit secure seed should be rejected by MLDSA87 match MLDSA87::sign_mu_deterministic_from_seed(&low_security_seed, &mu, rnd) { Err(SignatureError::KeyGenError(_)) => { /* good */ } diff --git a/crypto/mldsa/tests/wycheproof.rs b/crypto/mldsa/tests/wycheproof.rs index abe6842..523be36 100644 --- a/crypto/mldsa/tests/wycheproof.rs +++ b/crypto/mldsa/tests/wycheproof.rs @@ -18,7 +18,9 @@ #![allow(dead_code)] use bouncycastle_core::errors::SignatureError; -use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; +use bouncycastle_core::key_material::{ + KeyMaterial256, KeyMaterialTrait, KeyType, do_hazardous_operations, +}; use bouncycastle_core::traits::{ SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, }; @@ -590,9 +592,10 @@ impl MLDSASignSeedTestCase { } }; // allow an all-zero seed for testing - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match seed.set_security_strength(SecurityStrength::_256bit) { + do_hazardous_operations(&mut seed, |seed| seed.set_key_type(KeyType::Seed)).unwrap(); + match do_hazardous_operations(&mut seed, |seed| { + seed.set_security_strength(SecurityStrength::_256bit) + }) { Ok(_) => (), Err(e) => { if self.result == "invalid" { @@ -681,9 +684,10 @@ impl MLDSASignSeedTestCase { } }; // allow an all-zero seed for testing - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match seed.set_security_strength(SecurityStrength::_256bit) { + do_hazardous_operations(&mut seed, |seed| seed.set_key_type(KeyType::Seed)).unwrap(); + match do_hazardous_operations(&mut seed, |seed| { + seed.set_security_strength(SecurityStrength::_256bit) + }) { Ok(_) => (), Err(e) => { if self.result == "invalid" { @@ -772,9 +776,10 @@ impl MLDSASignSeedTestCase { } }; // allow an all-zero seed for testing - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match seed.set_security_strength(SecurityStrength::_256bit) { + do_hazardous_operations(&mut seed, |seed| seed.set_key_type(KeyType::Seed)).unwrap(); + match do_hazardous_operations(&mut seed, |seed| { + seed.set_security_strength(SecurityStrength::_256bit) + }) { Ok(_) => (), Err(e) => { if self.result == "invalid" { diff --git a/crypto/mlkem-lowmemory/src/mlkem.rs b/crypto/mlkem-lowmemory/src/mlkem.rs index 98eca08..5e63c0b 100644 --- a/crypto/mlkem-lowmemory/src/mlkem.rs +++ b/crypto/mlkem-lowmemory/src/mlkem.rs @@ -13,7 +13,9 @@ use crate::mlkem_keys::{MLKEMPrivateKeyInternalTrait, MLKEMPrivateKeyTrait}; 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::key_material::{ + KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, +}; use bouncycastle_core::traits::{ Algorithm, Hash, KEMDecapsulator, KEMEncapsulator, RNG, SecurityStrength, XOF, }; @@ -693,9 +695,9 @@ impl< let mut ss_keymaterial = KeyMaterial::::from_bytes_as_type(&ss_bytes, KeyType::BytesFullEntropy)?; - ss_keymaterial.allow_hazardous_operations(); - ss_keymaterial.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize))?; - ss_keymaterial.drop_hazardous_operations(); + do_hazardous_operations(&mut ss_keymaterial, |ss_keymaterial| { + ss_keymaterial.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize)) + })?; Ok((ss_keymaterial, ct)) } @@ -748,9 +750,9 @@ impl< let mut ss_keymaterial = KeyMaterial::::from_bytes_as_type(&ss_bytes, KeyType::BytesFullEntropy)?; - ss_keymaterial.allow_hazardous_operations(); - ss_keymaterial.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize))?; - ss_keymaterial.drop_hazardous_operations(); + do_hazardous_operations(&mut ss_keymaterial, |ss_keymaterial| { + ss_keymaterial.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize)) + })?; Ok(ss_keymaterial) } diff --git a/crypto/mlkem-lowmemory/src/mlkem_keys.rs b/crypto/mlkem-lowmemory/src/mlkem_keys.rs index b9e58f3..4c310ab 100644 --- a/crypto/mlkem-lowmemory/src/mlkem_keys.rs +++ b/crypto/mlkem-lowmemory/src/mlkem_keys.rs @@ -18,7 +18,9 @@ use crate::mlkem::{ use crate::polynomial::Polynomial; use crate::{ML_KEM_512_NAME, ML_KEM_768_NAME, ML_KEM_1024_NAME}; use bouncycastle_core::errors::KEMError; -use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::key_material::{ + KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, +}; use bouncycastle_core::traits::{Hash, KEMPrivateKey, KEMPublicKey, Secret, SecurityStrength}; use bouncycastle_sha3::SHA3_256; use core::fmt; @@ -385,15 +387,15 @@ impl< tmp[..32].copy_from_slice(&self.seed_d); tmp[32..].copy_from_slice(&self.z); let mut seed = KeyMaterial::<64>::from_bytes_as_type(&tmp, KeyType::Seed).unwrap(); - seed.allow_hazardous_operations(); - seed.set_security_strength(match k { - 2 => SecurityStrength::_128bit, - 3 => SecurityStrength::_192bit, - 4 => SecurityStrength::_256bit, - _ => unreachable!("Invalid mlkem param set"), + do_hazardous_operations(&mut seed, |seed| { + seed.set_security_strength(match k { + 2 => SecurityStrength::_128bit, + 3 => SecurityStrength::_192bit, + 4 => SecurityStrength::_256bit, + _ => unreachable!("Invalid mlkem param set"), + }) }) .unwrap(); - seed.drop_hazardous_operations(); Some(seed) } @@ -564,10 +566,10 @@ impl< return Err(KEMError::DecodingError("Invalid seed length")); } let mut keymat = KeyMaterial::<64>::from_bytes(bytes)?; - keymat.allow_hazardous_operations(); - keymat.set_key_type(KeyType::Seed)?; - keymat.set_security_strength(SecurityStrength::_256bit)?; - keymat.drop_hazardous_operations(); + do_hazardous_operations(&mut keymat, |keymat| { + keymat.set_key_type(KeyType::Seed)?; + keymat.set_security_strength(SecurityStrength::_256bit) + })?; Self::new(&keymat) } diff --git a/crypto/mlkem-lowmemory/tests/bc_test_data.rs b/crypto/mlkem-lowmemory/tests/bc_test_data.rs index 8379b2e..beb259c 100644 --- a/crypto/mlkem-lowmemory/tests/bc_test_data.rs +++ b/crypto/mlkem-lowmemory/tests/bc_test_data.rs @@ -6,7 +6,9 @@ #[cfg(test)] mod bc_test_data { - use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; + use bouncycastle_core::key_material::{ + KeyMaterial512, KeyMaterialTrait, KeyType, do_hazardous_operations, + }; use bouncycastle_core::traits::{KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; use bouncycastle_mlkem_lowmemory::mlkem::{ @@ -157,10 +159,11 @@ mod bc_test_data { let mut seed = KeyMaterial512::from_bytes_as_type(&seed_bytes, KeyType::Seed).unwrap(); // for the purposes of the test cases, accept an all-zero seed - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - seed.set_security_strength(SecurityStrength::_256bit).unwrap(); - seed.drop_hazardous_operations(); + do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed)?; + seed.set_security_strength(SecurityStrength::_256bit) + }) + .unwrap(); match self.parameter_set.as_str() { "ML-KEM-512" => { diff --git a/crypto/mlkem-lowmemory/tests/mlkem_tests.rs b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs index 71cc449..2922f13 100644 --- a/crypto/mlkem-lowmemory/tests/mlkem_tests.rs +++ b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs @@ -2,7 +2,9 @@ #[cfg(test)] mod mlkem_tests { use bouncycastle_core::errors::KEMError; - use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; + use bouncycastle_core::key_material::{ + KeyMaterial512, KeyMaterialTrait, KeyType, do_hazardous_operations, + }; use bouncycastle_core::traits::{ KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength, XOF, }; @@ -336,8 +338,8 @@ mod mlkem_tests { assert_eq!(derived_pk.encode(), expected_pk_bytes.as_slice()); // success case KeyType: BytesFullEntropy - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::BytesFullEntropy).unwrap(); + do_hazardous_operations(&mut seed, |seed| seed.set_key_type(KeyType::BytesFullEntropy)) + .unwrap(); _ = MLKEM512::keygen_from_seed(&seed).unwrap(); // Failure case: key type != Seed || BytesFullEntropy diff --git a/crypto/mlkem-lowmemory/tests/wycheproof.rs b/crypto/mlkem-lowmemory/tests/wycheproof.rs index f5570c6..a026b9b 100644 --- a/crypto/mlkem-lowmemory/tests/wycheproof.rs +++ b/crypto/mlkem-lowmemory/tests/wycheproof.rs @@ -24,7 +24,9 @@ #![allow(dead_code)] -use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; +use bouncycastle_core::key_material::{ + KeyMaterial512, KeyMaterialTrait, KeyType, do_hazardous_operations, +}; use bouncycastle_core::traits::{KEMDecapsulator, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; use bouncycastle_mlkem_lowmemory::{ @@ -567,9 +569,10 @@ impl MLKEMTestCase { } }; // allow an all-zero seed for testing - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match seed.set_security_strength(SecurityStrength::_256bit) { + do_hazardous_operations(&mut seed, |seed| seed.set_key_type(KeyType::Seed)).unwrap(); + match do_hazardous_operations(&mut seed, |seed| { + seed.set_security_strength(SecurityStrength::_256bit) + }) { Ok(_) => (), Err(e) => { if self.result == "invalid" { @@ -631,9 +634,10 @@ impl MLKEMTestCase { } }; // allow an all-zero seed for testing - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match seed.set_security_strength(SecurityStrength::_256bit) { + do_hazardous_operations(&mut seed, |seed| seed.set_key_type(KeyType::Seed)).unwrap(); + match do_hazardous_operations(&mut seed, |seed| { + seed.set_security_strength(SecurityStrength::_256bit) + }) { Ok(_) => (), Err(e) => { if self.result == "invalid" { @@ -695,9 +699,10 @@ impl MLKEMTestCase { } }; // allow an all-zero seed for testing - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match seed.set_security_strength(SecurityStrength::_256bit) { + do_hazardous_operations(&mut seed, |seed| seed.set_key_type(KeyType::Seed)).unwrap(); + match do_hazardous_operations(&mut seed, |seed| { + seed.set_security_strength(SecurityStrength::_256bit) + }) { Ok(_) => (), Err(e) => { if self.result == "invalid" { diff --git a/crypto/mlkem/src/mlkem.rs b/crypto/mlkem/src/mlkem.rs index d370ea4..f988f6f 100644 --- a/crypto/mlkem/src/mlkem.rs +++ b/crypto/mlkem/src/mlkem.rs @@ -140,7 +140,9 @@ use crate::mlkem_keys::{ 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::key_material::{ + KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, +}; use bouncycastle_core::traits::{ Algorithm, Hash, KEMDecapsulator, KEMEncapsulator, RNG, SecurityStrength, XOF, }; @@ -775,9 +777,9 @@ impl< let (ss, ct) = Self::encaps_internal(&pk.ek, Some(&pk.A_hat), m); let mut key = KeyMaterial::::from_bytes_as_type(&ss, KeyType::BytesFullEntropy)?; - key.allow_hazardous_operations(); - key.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize))?; - key.drop_hazardous_operations(); + do_hazardous_operations(&mut key, |key| { + key.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize)) + })?; Ok((key, ct)) } @@ -806,9 +808,9 @@ impl< let K = Self::decaps_internal(&sk.dk, Some(&sk.A_hat), ct.try_into().unwrap()); let mut key = KeyMaterial::::from_bytes_as_type(&K, KeyType::BytesFullEntropy)?; - key.allow_hazardous_operations(); - key.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize))?; - key.drop_hazardous_operations(); + do_hazardous_operations(&mut key, |key| { + key.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize)) + })?; Ok(key) } diff --git a/crypto/mlkem/src/mlkem_keys.rs b/crypto/mlkem/src/mlkem_keys.rs index 327878c..10fc00b 100644 --- a/crypto/mlkem/src/mlkem_keys.rs +++ b/crypto/mlkem/src/mlkem_keys.rs @@ -6,6 +6,7 @@ use crate::mlkem::{MLKEM768_PK_LEN, MLKEM768_SK_LEN, MLKEM768_k}; use crate::mlkem::{MLKEM1024_PK_LEN, MLKEM1024_SK_LEN, MLKEM1024_k}; use crate::{ML_KEM_512_NAME, ML_KEM_768_NAME, ML_KEM_1024_NAME}; use bouncycastle_core::errors::KEMError; +use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{Hash, KEMPrivateKey, KEMPublicKey, Secret, SecurityStrength}; use bouncycastle_sha3::SHA3_256; @@ -469,15 +470,17 @@ impl< tmp[..32].copy_from_slice(&self.seed_d.unwrap()); tmp[32..].copy_from_slice(&self.z); let mut seed = KeyMaterial::<64>::from_bytes_as_type(&tmp, KeyType::Seed).unwrap(); - seed.allow_hazardous_operations(); - seed.set_security_strength(match k { - 2 => SecurityStrength::_128bit, - 3 => SecurityStrength::_192bit, - 4 => SecurityStrength::_256bit, - _ => unreachable!("Invalid mlkem param set"), + + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_security_strength(match k { + 2 => SecurityStrength::_128bit, + 3 => SecurityStrength::_192bit, + 4 => SecurityStrength::_256bit, + _ => unreachable!("Invalid mlkem param set"), + }) }) .unwrap(); - seed.drop_hazardous_operations(); + Some(seed) } } diff --git a/crypto/mlkem/tests/bc_test_data.rs b/crypto/mlkem/tests/bc_test_data.rs index ef40434..bfb0a64 100644 --- a/crypto/mlkem/tests/bc_test_data.rs +++ b/crypto/mlkem/tests/bc_test_data.rs @@ -4,6 +4,7 @@ #[cfg(test)] mod bc_test_data { + use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ KEMDecapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength, @@ -155,10 +156,11 @@ mod bc_test_data { let mut seed = KeyMaterial512::from_bytes_as_type(&seed_bytes, KeyType::Seed).unwrap(); // for the purposes of the test cases, accept an all-zero seed - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - seed.set_security_strength(SecurityStrength::_256bit).unwrap(); - seed.drop_hazardous_operations(); + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed)?; + seed.set_security_strength(SecurityStrength::_256bit) + }) + .unwrap(); match self.parameter_set.as_str() { "ML-KEM-512" => { diff --git a/crypto/mlkem/tests/mlkem_tests.rs b/crypto/mlkem/tests/mlkem_tests.rs index d5aa274..1a9da27 100644 --- a/crypto/mlkem/tests/mlkem_tests.rs +++ b/crypto/mlkem/tests/mlkem_tests.rs @@ -2,6 +2,7 @@ #[cfg(test)] mod mlkem_tests { use bouncycastle_core::errors::KEMError; + use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength, XOF, @@ -322,8 +323,11 @@ mod mlkem_tests { assert_eq!(derived_pk.encode(), expected_pk_bytes.as_slice()); // success case KeyType: BytesFullEntropy - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::BytesFullEntropy).unwrap(); + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::BytesFullEntropy) + }) + .unwrap(); + _ = MLKEM512::keygen_from_seed(&seed).unwrap(); // Failure case: key type != Seed || BytesFullEntropy diff --git a/crypto/mlkem/tests/wycheproof.rs b/crypto/mlkem/tests/wycheproof.rs index a141e63..cb50268 100644 --- a/crypto/mlkem/tests/wycheproof.rs +++ b/crypto/mlkem/tests/wycheproof.rs @@ -20,6 +20,7 @@ #![allow(dead_code)] +use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{KEMDecapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength}; use bouncycastle_hex as hex; @@ -717,19 +718,21 @@ impl MLKEMTestCase { } }; // allow an all-zero seed for testing - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match seed.set_security_strength(SecurityStrength::_256bit) { - Ok(_) => (), - Err(e) => { - if self.result == "invalid" { - /* good */ - return; - } else { - panic!("{:?}", e) + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => Ok(()), + Err(e) => { + if self.result == "invalid" { + /* good */ + Ok(()) + } else { + panic!("{:?}", e) + } } } - } + }) + .unwrap(); let (ek, dk) = match MLKEM512::keygen_from_seed(&seed) { Ok((ek, dk)) => (ek, dk), @@ -781,19 +784,21 @@ impl MLKEMTestCase { } }; // allow an all-zero seed for testing - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match seed.set_security_strength(SecurityStrength::_256bit) { - Ok(_) => (), - Err(e) => { - if self.result == "invalid" { - /* good */ - return; - } else { - panic!("{:?}", e) + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => Ok(()), + Err(e) => { + if self.result == "invalid" { + /* good */ + Ok(()) + } else { + panic!("{:?}", e) + } } } - } + }) + .unwrap(); let (ek, dk) = match MLKEM768::keygen_from_seed(&seed) { Ok((ek, dk)) => (ek, dk), @@ -845,19 +850,21 @@ impl MLKEMTestCase { } }; // allow an all-zero seed for testing - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match seed.set_security_strength(SecurityStrength::_256bit) { - Ok(_) => (), - Err(e) => { - if self.result == "invalid" { - /* good */ - return; - } else { - panic!("{:?}", e) + key_material::do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed).unwrap(); + match seed.set_security_strength(SecurityStrength::_256bit) { + Ok(_) => Ok(()), + Err(e) => { + if self.result == "invalid" { + /* good */ + Ok(()) + } else { + panic!("{:?}", e) + } } } - } + }) + .unwrap(); let (ek, dk) = match MLKEM1024::keygen_from_seed(&seed) { Ok((ek, dk)) => (ek, dk), diff --git a/crypto/rng/src/hash_drbg80090a.rs b/crypto/rng/src/hash_drbg80090a.rs index ffac1db..6041606 100644 --- a/crypto/rng/src/hash_drbg80090a.rs +++ b/crypto/rng/src/hash_drbg80090a.rs @@ -6,7 +6,9 @@ use crate::Sp80090ADrbg; use bouncycastle_core::errors::{KeyMaterialError, RNGError}; -use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; +use bouncycastle_core::key_material::{ + KeyMaterial512, KeyMaterialTrait, KeyType, do_hazardous_operations, +}; use bouncycastle_core::traits::{Hash, HashAlgParams, RNG, SecurityStrength}; use bouncycastle_sha2::{SHA256, SHA512}; use bouncycastle_utils::min; @@ -141,20 +143,23 @@ impl HashDRBG80090A { /// Creates a new instance using the local OS RNG as a source of seed entropy. pub fn new_from_os() -> Self { let mut seed = KeyMaterial512::new(); - seed.allow_hazardous_operations(); - seed.set_key_type(KeyType::Seed).unwrap(); - match H::HASH { - SupportedHash::SHA256 => { - getrandom::fill(&mut seed.ref_to_bytes_mut().unwrap()[..32]).unwrap(); - seed.set_key_len(32).unwrap(); - seed.set_security_strength(SecurityStrength::_128bit).unwrap(); - } - SupportedHash::SHA512 => { - getrandom::fill(&mut seed.ref_to_bytes_mut().unwrap()).unwrap(); - seed.set_key_len(64).unwrap(); - seed.set_security_strength(SecurityStrength::_256bit).unwrap(); + do_hazardous_operations(&mut seed, |seed| { + seed.set_key_type(KeyType::Seed).unwrap(); + match H::HASH { + SupportedHash::SHA256 => { + getrandom::fill(&mut seed.ref_to_bytes_mut().unwrap()[..32]).unwrap(); + seed.set_key_len(32).unwrap(); + seed.set_security_strength(SecurityStrength::_128bit).unwrap(); + } + SupportedHash::SHA512 => { + getrandom::fill(&mut seed.ref_to_bytes_mut().unwrap()).unwrap(); + seed.set_key_len(64).unwrap(); + seed.set_security_strength(SecurityStrength::_256bit).unwrap(); + } } - } + Ok(()) + }) + .unwrap(); let mut rng = Self::new_unititialized(); let ss = seed.security_strength().clone(); @@ -462,15 +467,27 @@ impl Sp80090ADrbg for HashDRBG80090A { additional_input: &[u8], out: &mut impl KeyMaterialTrait, ) -> Result { - out.allow_hazardous_operations(); - let bytes_written = self.generate_out(additional_input, out.ref_to_bytes_mut().unwrap())?; - - out.set_key_len(bytes_written)?; - out.set_key_type(KeyType::BytesFullEntropy)?; - let new_security_strength = - min(&self.admin_info.strength, &SecurityStrength::from_bits(bytes_written * 8)).clone(); - out.set_security_strength(new_security_strength)?; - out.drop_hazardous_operations(); + let mut ret: Result = Ok(0); + do_hazardous_operations(out, |out| { + let out_ref = out.ref_to_bytes_mut()?; + ret = self.generate_out(additional_input, out_ref); + Ok(()) + })?; + + let bytes_written = match ret { + Err(e) => return Err(e), + Ok(bytes_written) => bytes_written, + }; + + do_hazardous_operations(out, |out| { + out.set_key_len(bytes_written)?; + out.set_key_type(KeyType::BytesFullEntropy)?; + let new_security_strength = + min(&self.admin_info.strength, &SecurityStrength::from_bits(bytes_written * 8)) + .clone(); + out.set_security_strength(new_security_strength)?; + Ok(()) + })?; Ok(bytes_written) } } diff --git a/crypto/rng/tests/hash_drbg80090a_tests.rs b/crypto/rng/tests/hash_drbg80090a_tests.rs index e4fd534..d019e40 100644 --- a/crypto/rng/tests/hash_drbg80090a_tests.rs +++ b/crypto/rng/tests/hash_drbg80090a_tests.rs @@ -286,7 +286,8 @@ mod tests { let mut out = KeyMaterial256::new(); match rng.generate_keymaterial_out(&[], &mut out) { Err(RNGError::Uninitialized) => { /*good*/ } - _ => panic!("Expected Uninitialized error"), + Err(e) => panic!("{:?}", e), + Ok(_) => panic!("Expected Uninitialized error"), } // Skipping tests for max lengths of seeds and personalization strings, because they're in the gigabyte range and that'll blow up the test suite. diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 21edef3..7f2d60a 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -1,6 +1,7 @@ use crate::SHA3Params; use crate::keccak::KeccakDigest; use bouncycastle_core::errors::{HashError, KDFError}; +use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{Hash, KDF, SecurityStrength}; use bouncycastle_utils::{max, min}; @@ -81,20 +82,34 @@ impl SHA3 { let mut key_type = self.kdf_key_type.clone(); let output_security_strength = self.kdf_security_strength.clone(); - output_key.allow_hazardous_operations(); - let bytes_written = self.do_final_out(output_key.ref_to_bytes_mut()?); - output_key.set_key_len(bytes_written)?; - - // since we've done some computation, the result will not actually be zeroized, even if all input key material was zeroized. + let mut bytes_written: usize = 0; + key_material::do_hazardous_operations(output_key, |output_key| { + bytes_written = self.do_final_out(output_key.ref_to_bytes_mut()?); + output_key.set_key_len(bytes_written)?; + Ok(()) + }) + .expect( + "both mut_ref_to_bytes() and set_key_len() should be infallible within a hazop block", + ); + + // since we've done some computation, the result will not actually be zeroized, + // even if all input key material was zeroized. if key_type == KeyType::Zeroized { key_type = KeyType::BytesLowEntropy; } - output_key.set_key_type(key_type)?; - output_key.set_security_strength( - min(&output_security_strength, &SecurityStrength::from_bits(bytes_written * 8)).clone(), - )?; - output_key.drop_hazardous_operations(); - output_key.truncate(min(&output_key.key_len(), &PARAMS::OUTPUT_LEN).clone())?; + key_material::do_hazardous_operations(&mut *output_key, |output_key| { + output_key.set_key_type(key_type)?; + output_key.set_security_strength( + min(&output_security_strength, &SecurityStrength::from_bits(bytes_written * 8)).clone(), + ) + }) + .expect( + "both set_key_type() and set_security_strength() should be infallible within a hazop block", + ); + + output_key + .set_key_len(min(&output_key.key_len(), &PARAMS::OUTPUT_LEN).clone()) + .expect("should be infallible to truncate key length"); Ok(bytes_written) } } diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index 3e64e6c..f934a44 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -1,6 +1,7 @@ use crate::SHAKEParams; -use crate::keccak::KeccakDigest; +use crate::keccak::{KeccakDigest, KeccakSize}; use bouncycastle_core::errors::{HashError, KDFError}; +use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, XOF}; use bouncycastle_utils::{max, min}; @@ -87,8 +88,13 @@ impl SHAKE { let mut output_key = KeyMaterial::<64>::new(); self.derive_key_out_final_internal(additional_input, &mut output_key)?; + // truncate // 128 => 32, 256 => 64 - output_key.truncate(2 * (PARAMS::SIZE as usize) / 8)?; + match PARAMS::SIZE { + KeccakSize::_128 => output_key.set_key_len(32).expect("truncate should be infallible"), + KeccakSize::_256 => output_key.set_key_len(64).expect("truncate should be infallible"), + _ => unreachable!(), + } Ok(Box::new(output_key)) } @@ -109,25 +115,25 @@ impl SHAKE { self.absorb(additional_input); - // let mut buf = [0u8; 64]; - output_key.allow_hazardous_operations(); - let bytes_written = self.squeeze_out( - output_key - .ref_to_bytes_mut() - .expect("We just set .allow_hazardous_operations(), so this should be fine."), - ); - output_key.set_key_len(bytes_written)?; + let mut bytes_written: usize = 0; + key_material::do_hazardous_operations(output_key, |output_key| { + bytes_written = self.squeeze_out( + output_key.ref_to_bytes_mut().expect("Infallible within do_hazardous_operations"), + ); + output_key.set_key_len(bytes_written) + })?; // since we've done some computation, the result will not actually be zeroized, even if all input key material was zeroized. if self.kdf_key_type == KeyType::Zeroized { self.kdf_key_type = KeyType::BytesLowEntropy; } - output_key.set_key_type(self.kdf_key_type)?; - output_key.set_security_strength( - min(&self.kdf_security_strength, &SecurityStrength::from_bits(bytes_written * 8)) - .clone(), - )?; - output_key.drop_hazardous_operations(); + key_material::do_hazardous_operations(output_key, |output_key| { + output_key.set_key_type(self.kdf_key_type)?; + output_key.set_security_strength( + min(&self.kdf_security_strength, &SecurityStrength::from_bits(bytes_written * 8)) + .clone(), + ) + })?; Ok(bytes_written) } } diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index 5fb5cae..0065726 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod sha3_tests { use super::sha3_test_helpers::*; + use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; @@ -344,7 +345,7 @@ mod sha3_tests { let input_seed = KeyMaterial256::from_bytes(&DUMMY_SEED_512[..32]).expect("Error happened"); let mut output_seed = SHA3_256::new().derive_key(&input_seed, b"nytimes.com").expect("Error happened"); - match output_seed.convert_key_type(KeyType::MACKey) { + match output_seed.set_key_type(KeyType::MACKey) { Ok(_) => { panic!( "Should have failed to convert key type because the input was BytesLowEntropy" @@ -358,10 +359,10 @@ mod sha3_tests { let mut output_seed = SHA3_256::new() .derive_key(&input_seed, b"some addtional input to the KDF") .expect("Error happened"); - output_seed.allow_hazardous_operations(); - output_seed.convert_key_type(KeyType::MACKey).unwrap(); - output_seed.drop_hazardous_operations(); // not strictly necessary because KeyMaterial - // will automatically drop allow_hazardous_conversions() after performing one. + key_material::do_hazardous_operations(&mut *output_seed, |output_seed| { + output_seed.set_key_type(KeyType::MACKey) + }) + .unwrap(); assert_eq!(output_seed.key_type(), KeyType::MACKey); // This works because we explicitly tag the input data as BytesFullEntropy. From 935c5c041bc42df7a2a84893438d79709034de43 Mon Sep 17 00:00:00 2001 From: officialfrancismendoza Date: Tue, 30 Jun 2026 11:51:14 +0700 Subject: [PATCH 18/35] Add missing tests from PR #43 --- alpha_0.1.2_release_notes.md | 5 + .../core-test-framework/src/fixed_seed_rng.rs | 95 +++++++++ crypto/core-test-framework/src/kem.rs | 59 ++++-- crypto/core-test-framework/src/lib.rs | 3 + crypto/core/src/errors.rs | 5 + crypto/core/src/traits.rs | 19 +- crypto/factory/src/rng_factory.rs | 4 +- crypto/mldsa-lowmemory/src/lib.rs | 2 +- crypto/mldsa-lowmemory/src/mldsa.rs | 36 ++-- crypto/mldsa-lowmemory/tests/mldsa_tests.rs | 89 +++++++- crypto/mldsa/src/lib.rs | 2 +- crypto/mldsa/src/mldsa.rs | 39 ++-- crypto/mldsa/tests/mldsa_tests.rs | 88 +++++++- crypto/mlkem-lowmemory/src/lib.rs | 2 +- crypto/mlkem-lowmemory/src/mlkem.rs | 48 +++-- crypto/mlkem-lowmemory/tests/mlkem_tests.rs | 189 ++++++++++++++++- crypto/mlkem/src/lib.rs | 2 +- crypto/mlkem/src/mlkem.rs | 64 ++++-- crypto/mlkem/tests/mlkem_tests.rs | 194 +++++++++++++++++- crypto/rng/src/hash_drbg80090a.rs | 14 +- crypto/rng/src/lib.rs | 8 +- crypto/rng/tests/hash_drbg80090a_tests.rs | 2 +- 22 files changed, 854 insertions(+), 115 deletions(-) create mode 100644 crypto/core-test-framework/src/fixed_seed_rng.rs diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index c59991b..5183a27 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -6,6 +6,11 @@ * Check the crate release checklist and run claude against the style guide (maybe Francis could cross-check me) * Run Crucible testing * Add factories for ML-DSA and ML-KEM (if we are keeping factories, see below) + * After merging the Signer/Verifier, Encrypter/Decrypter split, check if the keygen_from_rng() is still on the right + trait. +* Split the Signature trait into a Signer and a Verifier so that, for example, we can implement the verifier for MTC in + a different struct from the signer; or so that you can get FIPS compliance on old algorithms that are currently only + FIPS-allowed for verification of existing signatures but not for creation of new ones. * Check out Megan's email May 13 about KeyMaterial: "I was wondering if there might be scope for a closure based approach that could guarantee encapsulation of the state change from safe to hazardous back to safe again." * Go back to previous algs and apply memory optimization tricks like internal functions. And add a docs section "Memory diff --git a/crypto/core-test-framework/src/fixed_seed_rng.rs b/crypto/core-test-framework/src/fixed_seed_rng.rs new file mode 100644 index 0000000..944487d --- /dev/null +++ b/crypto/core-test-framework/src/fixed_seed_rng.rs @@ -0,0 +1,95 @@ +//! A deterministic fake [RNG] for reproducible tests. + +use bouncycastle_core::errors::{KeyMaterialError, RNGError}; +use bouncycastle_core::key_material; +use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; +use bouncycastle_core::traits::{RNG, SecurityStrength}; + +/// A test-only fake [RNG] that produces a fixed, fully deterministic byte stream. +/// +/// The stream is the `SEED_LEN`-byte seed repeated indefinitely. A single internal counter is +/// shared across every [RNG] method, so each byte handed out β€” whether through +/// [RNG::next_bytes_out], [RNG::next_bytes], [RNG::next_int], or [RNG::fill_keymaterial_out] β€” +/// advances the same stream. Two instances built from the same seed therefore emit identical +/// streams, which is what makes RNG-driven operations reproducible (and comparable against their +/// seed/`m`-driven internal counterparts) in tests. +/// +/// This is a deterministic stub for tests only; it is in no way a secure RNG. +pub struct FixedSeedRNG { + seed: [u8; SEED_LEN], + counter: usize, + security_strength: SecurityStrength, +} + +impl FixedSeedRNG { + /// Create an instance that emits `seed` repeated indefinitely, starting from its first byte. + pub fn new(seed: [u8; SEED_LEN]) -> Self { + Self { seed, counter: 0, security_strength: SecurityStrength::_256bit } + } + + /// Pull the next byte from the deterministic stream and advance the counter. + fn next_byte(&mut self) -> u8 { + let b = self.seed[self.counter % SEED_LEN]; + self.counter += 1; + b + } + + /// For testing purposes, set the security strength that this RNG will report + pub fn set_security_strength(&mut self, security_strength: SecurityStrength) { + self.security_strength = security_strength; + } +} + +impl RNG for FixedSeedRNG { + /// No-op: this fake RNG ignores reseeding, since its stream is fixed by construction. + fn add_seed_keymaterial( + &mut self, + _additional_seed: &dyn KeyMaterialTrait, + ) -> Result<(), RNGError> { + Ok(()) + } + + fn next_int(&mut self) -> Result { + let mut buf = [0u8; 4]; + for slot in buf.iter_mut() { + *slot = self.next_byte(); + } + Ok(u32::from_le_bytes(buf)) + } + + fn next_bytes(&mut self, len: usize) -> Result, RNGError> { + let mut out = vec![0u8; len]; + for slot in out.iter_mut() { + *slot = self.next_byte(); + } + Ok(out) + } + + fn next_bytes_out(&mut self, out: &mut [u8]) -> Result { + for slot in out.iter_mut() { + *slot = self.next_byte(); + } + Ok(out.len()) + } + + /// Fill `out` to capacity from the stream and mark it as a full-entropy 256-bit seed, + /// mirroring what a real DRBG's `generate_keymaterial_out` produces. A 256-bit security + /// strength is enough for every ML-KEM / ML-DSA parameter set. + fn fill_keymaterial_out(&mut self, out: &mut dyn KeyMaterialTrait) -> Result { + let mut len = 0; + key_material::do_hazardous_operations(out, |out| { + len = self + .next_bytes_out(out.ref_to_bytes_mut()?) + .map_err(|_| KeyMaterialError::GenericError("RNG failed to acquire next bytes."))?; + out.set_key_len(len)?; + out.set_key_type(KeyType::Seed)?; + out.set_security_strength(SecurityStrength::_256bit) + })?; + + Ok(len) + } + + fn security_strength(&self) -> SecurityStrength { + self.security_strength.clone() + } +} diff --git a/crypto/core-test-framework/src/kem.rs b/crypto/core-test-framework/src/kem.rs index 4509684..170c688 100644 --- a/crypto/core-test-framework/src/kem.rs +++ b/crypto/core-test-framework/src/kem.rs @@ -1,5 +1,8 @@ +use crate::FixedSeedRNG; use bouncycastle_core::errors::KEMError; -use bouncycastle_core::traits::{KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey}; +use bouncycastle_core::traits::{ + KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey, RNG, SecurityStrength, +}; pub struct TestFrameworkKEM { // Put any config options here @@ -24,8 +27,7 @@ impl TestFrameworkKEM { pub fn test_kem< PK: KEMPublicKey, SK: KEMPrivateKey, - ENCAPSULATOR: KEMEncapsulator, - DECAPSULATOR: KEMDecapsulator, + KEMAlg: KEMEncapsulator + KEMDecapsulator, const PK_LEN: usize, const SK_LEN: usize, const CT_LEN: usize, @@ -37,27 +39,45 @@ impl TestFrameworkKEM { ) { // Basic test let (pk, sk) = keygen().unwrap(); - let (ss, ct) = ENCAPSULATOR::encaps(&pk).unwrap(); - let ss1 = DECAPSULATOR::decaps(&sk, &ct).unwrap(); + let (ss, ct) = KEMAlg::encaps(&pk).unwrap(); + let ss1 = KEMAlg::decaps(&sk, &ct).unwrap(); assert_eq!(ss, ss1); + // Test that encaps_rng is deterministic in its RNG input: two encapsulations against the + // same public key, each fed an RNG that emits identical bytes, must produce the same + // shared secret and ciphertext. + { + let mut rng_a = FixedSeedRNG::new([0x5A; 64]); + let mut rng_b = FixedSeedRNG::new([0x5A; 64]); + let (ss_a, ct_a) = KEMAlg::encaps_rng(&pk, &mut rng_a).unwrap(); + let (ss_b, ct_b) = KEMAlg::encaps_rng(&pk, &mut rng_b).unwrap(); + assert_eq!( + ss_a, ss_b, + "encaps_rng shared secret must be deterministic given fixed RNG output" + ); + assert_eq!( + ct_a, ct_b, + "encaps_rng ciphertext must be deterministic given fixed RNG output" + ); + } + // Test non-determinism if !self.alg_is_deterministic { - let (ss1, ct1) = ENCAPSULATOR::encaps(&pk).unwrap(); - let (ss2, ct2) = ENCAPSULATOR::encaps(&pk).unwrap(); + let (ss1, ct1) = KEMAlg::encaps(&pk).unwrap(); + let (ss2, ct2) = KEMAlg::encaps(&pk).unwrap(); assert_ne!(ss1, ss2); assert_ne!(ct1, ct2); } // Test that decaps fails for broken ct value let (pk, sk) = keygen().unwrap(); - let (ss, mut ct) = ENCAPSULATOR::encaps(&pk).unwrap(); + let (ss, mut ct) = KEMAlg::encaps(&pk).unwrap(); ct[17] ^= 0xFF; if self.is_implicitly_rejecting { - let ss2 = DECAPSULATOR::decaps(&sk, &ct).unwrap(); + let ss2 = KEMAlg::decaps(&sk, &ct).unwrap(); assert_ne!(ss, ss2); } else { - match DECAPSULATOR::decaps(&sk, &ct) { + match KEMAlg::decaps(&sk, &ct) { Err(KEMError::DecapsulationFailed) => /* good */ { @@ -76,10 +96,10 @@ impl TestFrameworkKEM { // should throw an Err if self.is_implicitly_rejecting { - let ss2 = DECAPSULATOR::decaps(&sk, &ct_copy).unwrap(); + let ss2 = KEMAlg::decaps(&sk, &ct_copy).unwrap(); assert_ne!(ss, ss2); } else { - match DECAPSULATOR::decaps(&sk, &ct) { + match KEMAlg::decaps(&sk, &ct) { Err(KEMError::DecapsulationFailed) => /* good */ { @@ -94,9 +114,9 @@ impl TestFrameworkKEM { // test ct the wrong length let (pk, sk) = keygen().unwrap(); - let (_ss, ct) = ENCAPSULATOR::encaps(&pk).unwrap(); + let (_ss, ct) = KEMAlg::encaps(&pk).unwrap(); // too short - match DECAPSULATOR::decaps(&sk, &ct[..CT_LEN - 1]) { + match KEMAlg::decaps(&sk, &ct[..CT_LEN - 1]) { Err(KEMError::LengthError(_)) => { /* good */ } _ => panic!("This should have thrown an error but it didn't."), }; @@ -104,10 +124,19 @@ 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 DECAPSULATOR::decaps(&sk, &long_ct) { + match KEMAlg::decaps(&sk, &long_ct) { Err(KEMError::LengthError(_)) => { /* good */ } _ => panic!("This should have thrown an error but it didn't."), }; + + // encaps_rng should reject an RNG at a lower security level than the KEM + let mut no_security_rng = FixedSeedRNG::new([0x00; 64]); + no_security_rng.set_security_strength(SecurityStrength::None); + assert_eq!(no_security_rng.security_strength(), SecurityStrength::None); + match KEMAlg::encaps_rng(&pk, &mut no_security_rng) { + Err(KEMError::RNGError(_)) => { /* good */ } + _ => panic!("This should have thrown an error but it didn't."), + } } } diff --git a/crypto/core-test-framework/src/lib.rs b/crypto/core-test-framework/src/lib.rs index 92f7215..be769ae 100644 --- a/crypto/core-test-framework/src/lib.rs +++ b/crypto/core-test-framework/src/lib.rs @@ -15,6 +15,9 @@ pub mod kem; pub mod mac; pub mod signature; +mod fixed_seed_rng; +pub use fixed_seed_rng::FixedSeedRNG; + pub const DUMMY_SEED_512: &[u8; 512] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"; pub const DUMMY_SEED_1024: &[u8; 1024] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"; diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index b2eaf0c..1334cdb 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -64,6 +64,11 @@ pub enum RNGError { /// Indicates that the RNG cannot produce any more output until it has been reseeded with fresh entropy. ReseedRequired, + /// Thrown my algorithms attempting to use an RNG instance, for example for key generation or + /// other randomness required by the algorithm, but the provided RNG is at a lower security strength + /// than the algorithm requires. + SecurityStrengthInsufficientForAlgorithm, + KeyMaterialError(KeyMaterialError), } diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 595fc06..ab182e5 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -206,8 +206,16 @@ pub trait KEMEncapsulator< >: Sized { /// Performs an encapsulation against the given public key. + /// Sources randomness from the library's default OS-backed RNG. /// Returns the ciphertext and derived shared secret. fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; + /// Performs an encapsulation against the given public key. + /// Sources randomness from the provided RNG. + /// Returns the ciphertext and derived shared secret. + fn encaps_rng( + pk: &PK, + rng: &mut dyn RNG, + ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; } /// A Key Encapsulation Mechanism (KEM) is defined as a set of three operations: @@ -426,13 +434,18 @@ impl SecurityStrength { /// be used by applications that intend to submit to FIPS certification as it more closely aligns with the /// requirements of SP 800-90A. /// Note: this interface produces bytes. If you want a [KeyMaterialTrait], then use [KeyMaterial::from_rng]. -pub trait RNG: Default { +/// +/// Implementors are expected to also implement [Default] (default-construction should produce a +/// securely OS-seeded instance), but this is intentionally *not* a supertrait bound: requiring +/// `Default` would make `RNG` not dyn-compatible, and `&mut dyn RNG` is needed so RNG instances +/// can be handed around as trait objects. +pub trait RNG { // TODO: add back once we figure out streaming interaction with entropy sources. // fn add_seed_bytes(&mut self, additional_seed: &[u8]) -> Result<(), RNGError>; fn add_seed_keymaterial( &mut self, - additional_seed: impl KeyMaterialTrait, + additional_seed: &dyn KeyMaterialTrait, ) -> Result<(), RNGError>; fn next_int(&mut self) -> Result; @@ -443,7 +456,7 @@ pub trait RNG: Default { /// The entire output buffer is zeroized before the random bytes are written. fn next_bytes_out(&mut self, out: &mut [u8]) -> Result; - fn fill_keymaterial_out(&mut self, out: &mut impl KeyMaterialTrait) -> Result; + fn fill_keymaterial_out(&mut self, out: &mut dyn KeyMaterialTrait) -> Result; /// Returns the Security Strength of this RNG. fn security_strength(&self) -> SecurityStrength; diff --git a/crypto/factory/src/rng_factory.rs b/crypto/factory/src/rng_factory.rs index 1431c90..4e542e4 100644 --- a/crypto/factory/src/rng_factory.rs +++ b/crypto/factory/src/rng_factory.rs @@ -90,7 +90,7 @@ impl AlgorithmFactory for RNGFactory { impl RNG for RNGFactory { fn add_seed_keymaterial( &mut self, - additional_seed: impl KeyMaterialTrait, + additional_seed: &dyn KeyMaterialTrait, ) -> Result<(), RNGError> { match self { Self::HashDRBG_SHA256(rng) => rng.add_seed_keymaterial(additional_seed), @@ -121,7 +121,7 @@ impl RNG for RNGFactory { } } - fn fill_keymaterial_out(&mut self, out: &mut impl KeyMaterialTrait) -> Result { + fn fill_keymaterial_out(&mut self, out: &mut dyn KeyMaterialTrait) -> Result { match self { Self::HashDRBG_SHA256(rng) => rng.fill_keymaterial_out(out), Self::HashDRBG_SHA512(rng) => rng.fill_keymaterial_out(out), diff --git a/crypto/mldsa-lowmemory/src/lib.rs b/crypto/mldsa-lowmemory/src/lib.rs index 5c23325..64a91f1 100644 --- a/crypto/mldsa-lowmemory/src/lib.rs +++ b/crypto/mldsa-lowmemory/src/lib.rs @@ -128,7 +128,7 @@ //! ## Generating Keys //! //! ```rust -//! use bouncycastle_mldsa_lowmemory::MLDSA65; +//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MLDSATrait}; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); //! ``` diff --git a/crypto/mldsa-lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs index 5768e3f..f7124e1 100644 --- a/crypto/mldsa-lowmemory/src/mldsa.rs +++ b/crypto/mldsa-lowmemory/src/mldsa.rs @@ -322,7 +322,7 @@ use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, }; -use bouncycastle_core::errors::SignatureError; +use bouncycastle_core::errors::{RNGError, SignatureError}; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, XOF}; use bouncycastle_rng::HashDRBG_SHA512; @@ -718,21 +718,6 @@ 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(); - HashDRBG_SHA512::new_from_os().fill_keymaterial_out(&mut seed)?; - Self::keygen_internal(&seed) - } /// Performs the first step of key generation to transform the single provided seed into a set of internal intermediate seeds. /// /// Unlike other interfaces across the library that take an &impl KeyMaterial, this one @@ -1335,6 +1320,25 @@ pub trait MLDSATrait< const ETA: usize, >: Sized { + /// Runs a key generation using the library's default RNG, seeded from the OS. + /// In environments where the default OS based RNG is not available, use instead [MLDSA::keygen_from_rng] + /// and explicitly provide a [RNG] implementation, or use [MLDSATrait::keygen_from_seed] and provide the + /// private key seed directly. + fn keygen() -> Result<(PK, SK), SignatureError> { + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::keygen_from_rng(&mut os_rng) + } + /// Run a keygen using the provided RNG implementation. + // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG. + fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), SignatureError> { + // Source the seed from the provided RNG + if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; + } + let mut seed = KeyMaterial::<32>::new(); + rng.fill_keymaterial_out(&mut seed)?; + Self::keygen_from_seed(&seed) + } /// Imports a secret key from a seed. fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError>; /// Imports a secret key from both a seed and an encoded_sk. diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index 593b6f9..359b4c1 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -2,13 +2,14 @@ #[cfg(test)] mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; - use bouncycastle_core::errors::SignatureError; + use bouncycastle_core::errors::{RNGError, SignatureError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; + use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ @@ -19,6 +20,92 @@ mod mldsa_tests { use bouncycastle_mldsa_lowmemory::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; use bouncycastle_mldsa_lowmemory::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; use bouncycastle_mldsa_lowmemory::{MLDSAPrivateKeyTrait, MLDSAPublicKeyTrait, MLDSATrait}; + + #[test] + fn keygen_from_rng_tests() { + /* keygen from seed must match keygen from rng, for a fixed-seed rng */ + // Same arbitrary fixed seed as rfc9881_keygen. + let seed_bytes: [u8; 32] = + hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + .try_into() + .unwrap(); + + // The seed as fed directly to keygen_from_seed. + let seed = KeyMaterial256::from_bytes_as_type(&seed_bytes, KeyType::Seed).unwrap(); + + // ML-DSA-44 + let (pk_seed, sk_seed) = MLDSA44::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLDSA44::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-DSA-44 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-DSA-44 sk from RNG must match sk from seed"); + + // ML-DSA-65 + let (pk_seed, sk_seed) = MLDSA65::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLDSA65::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-DSA-65 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-DSA-65 sk from RNG must match sk from seed"); + + // ML-DSA-87 + let (pk_seed, sk_seed) = MLDSA87::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLDSA87::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-DSA-87 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-DSA-87 sk from RNG must match sk from seed"); + + /* Test that keygen rejects a seed from a weak RNG */ + + // MLDSA44 -- RNG too weak + let mut rng = FixedSeedRNG::new([0u8; 32]); + rng.set_security_strength(SecurityStrength::_112bit); + match MLDSA44::keygen_from_rng(&mut rng) { + Err(SignatureError::RNGError(RNGError::SecurityStrengthInsufficientForAlgorithm)) => { /* good */ + } + _ => { + panic!("should have returned RNGError::RNGSecurityStrengthInsufficientForAlgorithm") + } + } + + // MLDSA44 -- RNG just right + let mut rng = FixedSeedRNG::new([0u8; 32]); + rng.set_security_strength(SecurityStrength::_128bit); + _ = MLDSA44::keygen_from_rng(&mut rng).unwrap(); + + // MLDSA65 -- RNG too weak + let mut rng = FixedSeedRNG::new([0u8; 32]); + rng.set_security_strength(SecurityStrength::_128bit); + match MLDSA65::keygen_from_rng(&mut rng) { + Err(SignatureError::RNGError(RNGError::SecurityStrengthInsufficientForAlgorithm)) => { /* good */ + } + _ => { + panic!("should have returned RNGError::RNGSecurityStrengthInsufficientForAlgorithm") + } + } + + // MLDSA65 -- RNG just right + let mut rng = FixedSeedRNG::new([0u8; 32]); + rng.set_security_strength(SecurityStrength::_192bit); + _ = MLDSA65::keygen_from_rng(&mut rng).unwrap(); + + // MLDSA87 -- RNG too weak + let mut rng = FixedSeedRNG::new([0u8; 32]); + rng.set_security_strength(SecurityStrength::_192bit); + match MLDSA87::keygen_from_rng(&mut rng) { + Err(SignatureError::RNGError(RNGError::SecurityStrengthInsufficientForAlgorithm)) => { /* good */ + } + _ => { + panic!("should have returned RNGError::RNGSecurityStrengthInsufficientForAlgorithm") + } + } + + // MLDSA87 -- RNG just right + let mut rng = FixedSeedRNG::new([0u8; 32]); + rng.set_security_strength(SecurityStrength::_256bit); + _ = MLDSA87::keygen_from_rng(&mut rng).unwrap(); + } + #[test] fn test_framework_signature() { let tf = TestFrameworkSignature::new(false, true); diff --git a/crypto/mldsa/src/lib.rs b/crypto/mldsa/src/lib.rs index ba33b1d..7480c3c 100644 --- a/crypto/mldsa/src/lib.rs +++ b/crypto/mldsa/src/lib.rs @@ -14,7 +14,7 @@ //! ## Generating Keys //! //! ```rust -//! use bouncycastle_mldsa::MLDSA65; +//! use bouncycastle_mldsa::{MLDSA65, MLDSATrait}; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); //! ``` diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index 680c9ae..0d645ce 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -410,7 +410,7 @@ use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyExpanded, MLDSAPublicKeyExpanded, }; -use bouncycastle_core::errors::SignatureError; +use bouncycastle_core::errors::{RNGError, SignatureError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, XOF}; use bouncycastle_rng::HashDRBG_SHA512; @@ -728,21 +728,6 @@ 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(); - HashDRBG_SHA512::new_from_os().fill_keymaterial_out(&mut seed)?; - Self::keygen_internal(&seed) - } /// Implements Algorithm 6 of FIPS 204 /// Note: NIST has made a special exception in the FIPS 204 FAQ that this _internal function /// may in fact be exposed outside the crypto module. @@ -1057,7 +1042,7 @@ impl< const GAMMA1_MINUS_BETA: i32, const GAMMA2_MINUS_BETA: i32, const GAMMA1_MASK_LEN: usize, -> MLDSATrait +> MLDSATrait for MLDSA< PK_LEN, SK_LEN, @@ -1652,11 +1637,31 @@ pub trait MLDSATrait< PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, + const LAMBDA: i32, const k: usize, const l: usize, const ETA: usize, >: Sized { + /// Runs a key generation using the library's default RNG, seeded from the OS. + /// In environments where the default OS based RNG is not available, use instead [MLDSA::keygen_from_rng] + /// and explicitly provide a [RNG] implementation, or use [MLDSATrait::keygen_from_seed] and provide the + /// private key seed directly. + fn keygen() -> Result<(PK, SK), SignatureError> { + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::keygen_from_rng(&mut os_rng) + } + /// Run a keygen using the provided RNG implementation. + // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG. + fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), SignatureError> { + // Source the seed from the provided RNG + if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; + } + let mut seed = KeyMaterial256::new(); + rng.fill_keymaterial_out(&mut seed)?; + Self::keygen_from_seed(&seed) + } /// Imports a secret key from a seed. fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError>; /// Imports a secret key from both a seed and an encoded_sk. diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index 242800a..df312e2 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; - use bouncycastle_core::errors::SignatureError; + use bouncycastle_core::errors::{RNGError, SignatureError}; use bouncycastle_core::key_material::{ KeyMaterial256, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -10,6 +10,7 @@ mod mldsa_tests { RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; + use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ @@ -200,6 +201,91 @@ mod mldsa_tests { } } + #[test] + fn keygen_from_rng_tests() { + /* keygen from seed must match keygen from rng, for a fixed-seed rng */ + // Same arbitrary fixed seed as rfc9881_keygen. + let seed_bytes: [u8; 32] = + hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + .try_into() + .unwrap(); + + // The seed as fed directly to keygen_from_seed. + let seed = KeyMaterial256::from_bytes_as_type(&seed_bytes, KeyType::Seed).unwrap(); + + // ML-DSA-44 + let (pk_seed, sk_seed) = MLDSA44::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLDSA44::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-DSA-44 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-DSA-44 sk from RNG must match sk from seed"); + + // ML-DSA-65 + let (pk_seed, sk_seed) = MLDSA65::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLDSA65::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-DSA-65 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-DSA-65 sk from RNG must match sk from seed"); + + // ML-DSA-87 + let (pk_seed, sk_seed) = MLDSA87::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLDSA87::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-DSA-87 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-DSA-87 sk from RNG must match sk from seed"); + + /* Test that keygen rejects a seed from a weak RNG */ + + // MLDSA44 -- RNG too weak + let mut rng = FixedSeedRNG::new([0u8; 32]); + rng.set_security_strength(SecurityStrength::_112bit); + match MLDSA44::keygen_from_rng(&mut rng) { + Err(SignatureError::RNGError(RNGError::SecurityStrengthInsufficientForAlgorithm)) => { /* good */ + } + _ => { + panic!("should have returned RNGError::RNGSecurityStrengthInsufficientForAlgorithm") + } + } + + // MLDSA44 -- RNG just right + let mut rng = FixedSeedRNG::new([0u8; 32]); + rng.set_security_strength(SecurityStrength::_128bit); + _ = MLDSA44::keygen_from_rng(&mut rng).unwrap(); + + // MLDSA65 -- RNG too weak + let mut rng = FixedSeedRNG::new([0u8; 32]); + rng.set_security_strength(SecurityStrength::_128bit); + match MLDSA65::keygen_from_rng(&mut rng) { + Err(SignatureError::RNGError(RNGError::SecurityStrengthInsufficientForAlgorithm)) => { /* good */ + } + _ => { + panic!("should have returned RNGError::RNGSecurityStrengthInsufficientForAlgorithm") + } + } + + // MLDSA65 -- RNG just right + let mut rng = FixedSeedRNG::new([0u8; 32]); + rng.set_security_strength(SecurityStrength::_192bit); + _ = MLDSA65::keygen_from_rng(&mut rng).unwrap(); + + // MLDSA87 -- RNG too weak + let mut rng = FixedSeedRNG::new([0u8; 32]); + rng.set_security_strength(SecurityStrength::_192bit); + match MLDSA87::keygen_from_rng(&mut rng) { + Err(SignatureError::RNGError(RNGError::SecurityStrengthInsufficientForAlgorithm)) => { /* good */ + } + _ => { + panic!("should have returned RNGError::RNGSecurityStrengthInsufficientForAlgorithm") + } + } + + // MLDSA87 -- RNG just right + let mut rng = FixedSeedRNG::new([0u8; 32]); + rng.set_security_strength(SecurityStrength::_256bit); + _ = MLDSA87::keygen_from_rng(&mut rng).unwrap(); + } + #[test] fn keygen_error_cases() { /* diff --git a/crypto/mlkem-lowmemory/src/lib.rs b/crypto/mlkem-lowmemory/src/lib.rs index 6435f73..d371326 100644 --- a/crypto/mlkem-lowmemory/src/lib.rs +++ b/crypto/mlkem-lowmemory/src/lib.rs @@ -154,7 +154,7 @@ //! ## Generating Keys //! //! ```rust -//! use bouncycastle_mlkem_lowmemory::MLKEM768; +//! use bouncycastle_mlkem_lowmemory::{MLKEM768, MLKEMTrait}; //! //! let (pk, sk) = MLKEM768::keygen().unwrap(); //! ``` diff --git a/crypto/mlkem-lowmemory/src/mlkem.rs b/crypto/mlkem-lowmemory/src/mlkem.rs index 5e63c0b..aba4b83 100644 --- a/crypto/mlkem-lowmemory/src/mlkem.rs +++ b/crypto/mlkem-lowmemory/src/mlkem.rs @@ -12,7 +12,7 @@ use crate::mlkem_keys::{ use crate::mlkem_keys::{MLKEMPrivateKeyInternalTrait, MLKEMPrivateKeyTrait}; use crate::mlkem_keys::{MLKEMPublicKeyInternalTrait, MLKEMPublicKeyTrait}; use crate::polynomial::Polynomial; -use bouncycastle_core::errors::KEMError; +use bouncycastle_core::errors::{KEMError, RNGError}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -237,22 +237,6 @@ 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(); - HashDRBG_SHA512::new_from_os().fill_keymaterial_out(&mut seed)?; - // Self::keygen_internal(&seed) - Self::keygen_internal(&seed) - } /// Performs the first step of key generation to transform the single provided seed into a set of internal intermediate seeds. /// /// Unlike other interfaces across the library that take an &impl KeyMaterial, this one @@ -631,6 +615,22 @@ pub trait MLKEMTrait< const T_PACKED_LEN: usize, >: Sized { + /// Generates a fresh key pair. + fn keygen() -> Result<(PK, SK), KEMError> { + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::keygen_from_rng(&mut os_rng) + } + /// Run a keygen using the provided RNG implementation. + // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG. + fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), KEMError> { + // Source the seed from the provided RNG + if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; + } + let mut seed = KeyMaterial::<64>::new(); + rng.fill_keymaterial_out(&mut seed)?; + Self::keygen_from_seed(&seed) + } /// Imports a secret key from a seed. fn keygen_from_seed(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError>; /// Imports a secret key from both a seed and an encoded_sk. @@ -688,8 +688,20 @@ impl< > { fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::encaps_rng(pk, &mut os_rng) + } + + fn encaps_rng( + pk: &PK, + rng: &mut dyn RNG, + ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { + // Source the random message m from the provided RNG + if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; + } let mut m = [0u8; 32]; - HashDRBG_SHA512::new_from_os().next_bytes_out(&mut m)?; + rng.next_bytes_out(&mut m)?; let (ss_bytes, ct) = Self::encaps_internal(pk, m); diff --git a/crypto/mlkem-lowmemory/tests/mlkem_tests.rs b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs index 2922f13..8845bef 100644 --- a/crypto/mlkem-lowmemory/tests/mlkem_tests.rs +++ b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs @@ -1,13 +1,14 @@ /// This performs tests using the public interfaces of the crate. #[cfg(test)] mod mlkem_tests { - use bouncycastle_core::errors::KEMError; + use bouncycastle_core::errors::{KEMError, RNGError}; use bouncycastle_core::key_material::{ KeyMaterial512, KeyMaterialTrait, KeyType, do_hazardous_operations, }; use bouncycastle_core::traits::{ KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength, XOF, }; + use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; use bouncycastle_mlkem_lowmemory::mlkem::{ MLKEM512_FULL_SK_LEN, MLKEM768_FULL_SK_LEN, MLKEM1024_FULL_SK_LEN, @@ -50,9 +51,9 @@ mod mlkem_tests { let tf = TestFrameworkKEM::new(false, true); - tf.test_kem::(MLKEM512::keygen, false); - tf.test_kem::(MLKEM768::keygen, false); - tf.test_kem::(MLKEM1024::keygen, 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.. @@ -554,6 +555,186 @@ mod mlkem_tests { _ => panic!("Expected error for different key"), }; } + + #[test] + fn keygen_from_rng_tests() { + /* keygen from seed must match keygen from rng, for a fixed-seed rng */ + // An arbitrary fixed 64-byte seed (bytes 0x00..=0x3f). + let seed_bytes: [u8; 64] = hex::decode( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\ + 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + ) + .unwrap() + .try_into() + .unwrap(); + + // The seed as fed directly to keygen_from_seed. + let seed = KeyMaterial512::from_bytes_as_type(&seed_bytes, KeyType::Seed).unwrap(); + + // ML-KEM-512 + let (pk_seed, sk_seed) = MLKEM512::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLKEM512::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-KEM-512 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-KEM-512 sk from RNG must match sk from seed"); + + // ML-KEM-768 + let (pk_seed, sk_seed) = MLKEM768::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLKEM768::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-KEM-768 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-KEM-768 sk from RNG must match sk from seed"); + + // ML-KEM-1024 + let (pk_seed, sk_seed) = MLKEM1024::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLKEM1024::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-KEM-1024 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-KEM-1024 sk from RNG must match sk from seed"); + + /* Test that keygen rejects a seed from a weak RNG */ + + // ML-KEM-512 -- RNG too weak + let mut rng = FixedSeedRNG::new([0u8; 64]); + rng.set_security_strength(SecurityStrength::_112bit); + match MLKEM512::keygen_from_rng(&mut rng) { + Err(KEMError::RNGError(RNGError::SecurityStrengthInsufficientForAlgorithm)) => { /* good */ + } + _ => { + panic!("should have returned RNGError::RNGSecurityStrengthInsufficientForAlgorithm") + } + } + + // ML-KEM-512 -- RNG just right + let mut rng = FixedSeedRNG::new([0u8; 64]); + rng.set_security_strength(SecurityStrength::_128bit); + _ = MLKEM512::keygen_from_rng(&mut rng).unwrap(); + + // ML-KEM-768 -- RNG too weak + let mut rng = FixedSeedRNG::new([0u8; 64]); + rng.set_security_strength(SecurityStrength::_128bit); + match MLKEM768::keygen_from_rng(&mut rng) { + Err(KEMError::RNGError(RNGError::SecurityStrengthInsufficientForAlgorithm)) => { /* good */ + } + _ => { + panic!("should have returned RNGError::RNGSecurityStrengthInsufficientForAlgorithm") + } + } + + // ML-KEM-768 -- RNG just right + let mut rng = FixedSeedRNG::new([0u8; 64]); + rng.set_security_strength(SecurityStrength::_192bit); + _ = MLKEM768::keygen_from_rng(&mut rng).unwrap(); + + // ML-KEM-1024 -- RNG too weak + let mut rng = FixedSeedRNG::new([0u8; 64]); + rng.set_security_strength(SecurityStrength::_192bit); + match MLKEM1024::keygen_from_rng(&mut rng) { + Err(KEMError::RNGError(RNGError::SecurityStrengthInsufficientForAlgorithm)) => { /* good */ + } + _ => { + panic!("should have returned RNGError::RNGSecurityStrengthInsufficientForAlgorithm") + } + } + + // ML-KEM-1024 -- RNG just right + let mut rng = FixedSeedRNG::new([0u8; 64]); + rng.set_security_strength(SecurityStrength::_256bit); + _ = MLKEM1024::keygen_from_rng(&mut rng).unwrap(); + } + + #[test] + fn encaps_rng_tests() { + // Proves that `encaps_rng` is just `encaps_internal` with the message `m` sourced from the + // RNG: when the RNG hands back exactly the bytes that `m` would be, the two must produce the + // same shared secret and ciphertext. + // + // Determinism of `encaps_rng` itself is covered by the shared KEM test + // framework via `core_framework_tests`.) + + // An arbitrary fixed 64-byte seed; FixedSeedRNG::next_bytes_out hands back its leading + // 32 bytes as the encapsulation message `m`. + let seed_bytes: [u8; 64] = hex::decode( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\ + 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + ) + .unwrap() + .try_into() + .unwrap(); + let m: [u8; MLKEM_RND_LEN] = seed_bytes[..MLKEM_RND_LEN].try_into().unwrap(); + + // ML-KEM-512 + let (pk512, _sk) = MLKEM512::keygen().unwrap(); + let (ss_ref, ct_ref) = MLKEM512::encaps_internal(&pk512, m); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (ss, ct) = MLKEM512::encaps_rng(&pk512, &mut rng).unwrap(); + assert_eq!(ct, ct_ref, "ML-KEM-512 ciphertext must match encaps_internal"); + assert_eq!( + ss_ref, + ss.ref_to_bytes(), + "ML-KEM-512 shared secret must match encaps_internal" + ); + + // ML-KEM-768 + let (pk768, _sk) = MLKEM768::keygen().unwrap(); + let (ss_ref, ct_ref) = MLKEM768::encaps_internal(&pk768, m); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (ss, ct) = MLKEM768::encaps_rng(&pk768, &mut rng).unwrap(); + assert_eq!(ct, ct_ref, "ML-KEM-768 ciphertext must match encaps_internal"); + assert_eq!( + ss_ref, + ss.ref_to_bytes(), + "ML-KEM-768 shared secret must match encaps_internal" + ); + + // ML-KEM-1024 + let (pk1024, _sk) = MLKEM1024::keygen().unwrap(); + let (ss_ref, ct_ref) = MLKEM1024::encaps_internal(&pk1024, m); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (ss, ct) = MLKEM1024::encaps_rng(&pk1024, &mut rng).unwrap(); + assert_eq!(ct, ct_ref, "ML-KEM-1024 ciphertext must match encaps_internal"); + assert_eq!( + ss_ref, + ss.ref_to_bytes(), + "ML-KEM-1024 shared secret must match encaps_internal" + ); + + // Ensure that it rejects an RNG at a lower security level + let mut fake_rng = FixedSeedRNG::new([0u8; 64]); + + // fails + fake_rng.set_security_strength(SecurityStrength::_112bit); + match MLKEM512::encaps_rng(&pk512, &mut fake_rng) { + Err(KEMError::RNGError(_)) => { /* good */ } + _ => panic!("unexpected error"), + } + + // succeeds + fake_rng.set_security_strength(SecurityStrength::_128bit); + _ = MLKEM512::encaps_rng(&pk512, &mut fake_rng).unwrap(); + + // fails + fake_rng.set_security_strength(SecurityStrength::_128bit); + match MLKEM768::encaps_rng(&pk768, &mut fake_rng) { + Err(KEMError::RNGError(_)) => { /* good */ } + _ => panic!("unexpected error"), + } + + // succeeds + fake_rng.set_security_strength(SecurityStrength::_192bit); + _ = MLKEM768::encaps_rng(&pk768, &mut fake_rng).unwrap(); + + // fails + fake_rng.set_security_strength(SecurityStrength::_192bit); + match MLKEM1024::encaps_rng(&pk1024, &mut fake_rng) { + Err(KEMError::RNGError(_)) => { /* good */ } + _ => panic!("unexpected error"), + } + + // succeeds + fake_rng.set_security_strength(SecurityStrength::_256bit); + _ = MLKEM1024::encaps_rng(&pk1024, &mut fake_rng).unwrap(); + } } // struct Kat { diff --git a/crypto/mlkem/src/lib.rs b/crypto/mlkem/src/lib.rs index 3e4b9c6..c3dd524 100644 --- a/crypto/mlkem/src/lib.rs +++ b/crypto/mlkem/src/lib.rs @@ -45,7 +45,7 @@ //! ## Generating Keys //! //! ```rust -//! use bouncycastle_mlkem::MLKEM768; +//! use bouncycastle_mlkem::{MLKEM768, MLKEMTrait}; //! //! let (pk, sk) = MLKEM768::keygen().unwrap(); //! ``` diff --git a/crypto/mlkem/src/mlkem.rs b/crypto/mlkem/src/mlkem.rs index f988f6f..46d09a6 100644 --- a/crypto/mlkem/src/mlkem.rs +++ b/crypto/mlkem/src/mlkem.rs @@ -140,6 +140,7 @@ use crate::mlkem_keys::{ use crate::mlkem_keys::{MLKEMPrivateKeyInternalTrait, MLKEMPrivateKeyTrait}; use crate::polynomial::Polynomial; use bouncycastle_core::errors::KEMError; +use bouncycastle_core::errors::RNGError; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -150,8 +151,8 @@ 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 ***/ +/*** Constants ***/ /// pub const ML_KEM_512_NAME: &str = "ML-KEM-512"; /// @@ -321,21 +322,6 @@ 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(); - HashDRBG_SHA512::new_from_os().fill_keymaterial_out(&mut seed)?; - Self::keygen_internal(&seed) - } /// Algorithm 16 ML-KEM.KeyGen_internal(𝑑, 𝑧) /// Uses randomness to generate an encapsulation key and a corresponding decapsulation key. /// Input: randomness 𝑑 ∈ 𝔹32 . @@ -771,8 +757,20 @@ impl< fn encaps_for_expanded_key( pk: &MLKEMPublicKeyExpanded, ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::encaps_for_expanded_key_rng(pk, &mut os_rng) + } + + fn encaps_for_expanded_key_rng( + pk: &MLKEMPublicKeyExpanded, + rng: &mut dyn RNG, + ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { + // Source the random message m from the provided RNG + if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; + } let mut m = [0u8; 32]; - HashDRBG_SHA512::new_from_os().next_bytes_out(&mut m)?; + rng.next_bytes_out(&mut m)?; let (ss, ct) = Self::encaps_internal(&pk.ek, Some(&pk.A_hat), m); @@ -832,6 +830,22 @@ pub trait MLKEMTrait< const LAMBDA: i16, >: Sized { + /// Generates a fresh key pair. + fn keygen() -> Result<(PK, SK), KEMError> { + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::keygen_from_rng(&mut os_rng) + } + /// Run a keygen using the provided RNG implementation. + // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG. + fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), KEMError> { + // Source the seed from the provided RNG + if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) { + return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; + } + let mut seed = KeyMaterial::<64>::new(); + rng.fill_keymaterial_out(&mut seed)?; + Self::keygen_from_seed(&seed) + } /// Imports a secret key from a seed. fn keygen_from_seed(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError>; /// Imports a secret key from both a seed and an encoded_sk. @@ -859,6 +873,12 @@ pub trait MLKEMTrait< pk: &MLKEMPublicKeyExpanded, ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; + /// Same as [KEM::encaps], but acts on an [MLKEMPublicKeyExpanded] and uses a provided RNG. + fn encaps_for_expanded_key_rng( + pk: &MLKEMPublicKeyExpanded, + rng: &mut dyn RNG, + ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; + /// Same as [KEMDecapsulator::decaps], but acts on an [MLKEMPrivateKeyExpanded]. fn decaps_with_expanded_key( sk: &MLKEMPrivateKeyExpanded, @@ -893,7 +913,15 @@ impl< /// Output: shared secret key 𝐾 ∈ 𝔹32 . /// Output: ciphertext 𝑐 ∈ 𝔹32(π‘‘π‘’π‘˜+𝑑𝑣). fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { - Self::encaps_for_expanded_key(&MLKEMPublicKeyExpanded::::from(pk)) + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::encaps_rng(pk, &mut os_rng) + } + + fn encaps_rng( + pk: &PK, + rng: &mut dyn RNG, + ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { + Self::encaps_for_expanded_key_rng(&MLKEMPublicKeyExpanded::::from(pk), rng) } } diff --git a/crypto/mlkem/tests/mlkem_tests.rs b/crypto/mlkem/tests/mlkem_tests.rs index 1a9da27..b777c4c 100644 --- a/crypto/mlkem/tests/mlkem_tests.rs +++ b/crypto/mlkem/tests/mlkem_tests.rs @@ -1,12 +1,13 @@ /// This performs tests using the public interfaces of the crate. #[cfg(test)] mod mlkem_tests { - use bouncycastle_core::errors::KEMError; + use bouncycastle_core::errors::{KEMError, RNGError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey, SecurityStrength, XOF, }; + use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; use bouncycastle_mlkem::{MLKEM_RND_LEN, MLKEM512, MLKEM768, MLKEM1024, Polynomial}; use bouncycastle_mlkem::{ @@ -44,9 +45,9 @@ mod mlkem_tests { let tf = TestFrameworkKEM::new(false, true); - tf.test_kem::(MLKEM512::keygen, false); - tf.test_kem::(MLKEM768::keygen, false); - tf.test_kem::(MLKEM1024::keygen, 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.. @@ -639,6 +640,191 @@ mod mlkem_tests { }; assert_eq!(ss, ss1); } + + #[test] + fn keygen_from_rng_tests() { + /* keygen from seed must match keygen from rng, for a fixed-seed rng */ + // An arbitrary fixed 64-byte seed (bytes 0x00..=0x3f). + let seed_bytes: [u8; 64] = hex::decode( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\ + 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + ) + .unwrap() + .try_into() + .unwrap(); + + // The seed as fed directly to keygen_from_seed. + let seed = KeyMaterial512::from_bytes_as_type(&seed_bytes, KeyType::Seed).unwrap(); + + // ML-KEM-512 + let (pk_seed, sk_seed) = MLKEM512::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLKEM512::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-KEM-512 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-KEM-512 sk from RNG must match sk from seed"); + + // ML-KEM-768 + let (pk_seed, sk_seed) = MLKEM768::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLKEM768::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-KEM-768 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-KEM-768 sk from RNG must match sk from seed"); + + // ML-KEM-1024 + let (pk_seed, sk_seed) = MLKEM1024::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLKEM1024::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-KEM-1024 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-KEM-1024 sk from RNG must match sk from seed"); + + /* Test that keygen rejects a seed from a weak RNG */ + + // ML-KEM-512 -- RNG too weak + let mut rng = FixedSeedRNG::new([0u8; 64]); + rng.set_security_strength(SecurityStrength::_112bit); + match MLKEM512::keygen_from_rng(&mut rng) { + Err(KEMError::RNGError(RNGError::SecurityStrengthInsufficientForAlgorithm)) => { /* good */ + } + _ => { + panic!("should have returned RNGError::RNGSecurityStrengthInsufficientForAlgorithm") + } + } + + // ML-KEM-512 -- RNG just right + let mut rng = FixedSeedRNG::new([0u8; 64]); + rng.set_security_strength(SecurityStrength::_128bit); + _ = MLKEM512::keygen_from_rng(&mut rng).unwrap(); + + // ML-KEM-768 -- RNG too weak + let mut rng = FixedSeedRNG::new([0u8; 64]); + rng.set_security_strength(SecurityStrength::_128bit); + match MLKEM768::keygen_from_rng(&mut rng) { + Err(KEMError::RNGError(RNGError::SecurityStrengthInsufficientForAlgorithm)) => { /* good */ + } + _ => { + panic!("should have returned RNGError::RNGSecurityStrengthInsufficientForAlgorithm") + } + } + + // ML-KEM-768 -- RNG just right + let mut rng = FixedSeedRNG::new([0u8; 64]); + rng.set_security_strength(SecurityStrength::_192bit); + _ = MLKEM768::keygen_from_rng(&mut rng).unwrap(); + + // ML-KEM-1024 -- RNG too weak + let mut rng = FixedSeedRNG::new([0u8; 64]); + rng.set_security_strength(SecurityStrength::_192bit); + match MLKEM1024::keygen_from_rng(&mut rng) { + Err(KEMError::RNGError(RNGError::SecurityStrengthInsufficientForAlgorithm)) => { /* good */ + } + _ => { + panic!("should have returned RNGError::RNGSecurityStrengthInsufficientForAlgorithm") + } + } + + // ML-KEM-1024 -- RNG just right + let mut rng = FixedSeedRNG::new([0u8; 64]); + rng.set_security_strength(SecurityStrength::_256bit); + _ = MLKEM1024::keygen_from_rng(&mut rng).unwrap(); + } + + #[test] + fn encaps_rng_tests() { + use bouncycastle_mlkem::{ + // Prove that `encaps_for_expanded_key_rng` is just `encaps_internal` with the message `m` + // sourced from the RNG: when the RNG hands back exactly the bytes that `m` would be, the two + // must produce the same shared secret and ciphertext. + MLKEM512PublicKeyExpanded, + MLKEM768PublicKeyExpanded, + MLKEM1024PublicKeyExpanded, + }; + + // An arbitrary fixed 64-byte seed; FixedSeedRNG::next_bytes_out hands back its leading + // 32 bytes as the encapsulation message `m`. + let seed_bytes: [u8; 64] = hex::decode( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\ + 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + ) + .unwrap() + .try_into() + .unwrap(); + let m: [u8; MLKEM_RND_LEN] = seed_bytes[..MLKEM_RND_LEN].try_into().unwrap(); + + // ML-KEM-512 + let (pk512, _sk) = MLKEM512::keygen().unwrap(); + let (ss_ref, ct_ref) = MLKEM512::encaps_internal(&pk512, None, m); + let pk_expanded = MLKEM512PublicKeyExpanded::from(&pk512); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (ss, ct) = MLKEM512::encaps_for_expanded_key_rng(&pk_expanded, &mut rng).unwrap(); + assert_eq!(ct, ct_ref, "ML-KEM-512 ciphertext must match encaps_internal"); + assert_eq!( + ss_ref, + ss.ref_to_bytes(), + "ML-KEM-512 shared secret must match encaps_internal" + ); + + // ML-KEM-768 + let (pk768, _sk) = MLKEM768::keygen().unwrap(); + let (ss_ref, ct_ref) = MLKEM768::encaps_internal(&pk768, None, m); + let pk_expanded = MLKEM768PublicKeyExpanded::from(&pk768); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (ss, ct) = MLKEM768::encaps_for_expanded_key_rng(&pk_expanded, &mut rng).unwrap(); + assert_eq!(ct, ct_ref, "ML-KEM-768 ciphertext must match encaps_internal"); + assert_eq!( + ss_ref, + ss.ref_to_bytes(), + "ML-KEM-768 shared secret must match encaps_internal" + ); + + // ML-KEM-1024 + let (pk1024, _sk) = MLKEM1024::keygen().unwrap(); + let (ss_ref, ct_ref) = MLKEM1024::encaps_internal(&pk1024, None, m); + let pk_expanded = MLKEM1024PublicKeyExpanded::from(&pk1024); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (ss, ct) = MLKEM1024::encaps_for_expanded_key_rng(&pk_expanded, &mut rng).unwrap(); + assert_eq!(ct, ct_ref, "ML-KEM-1024 ciphertext must match encaps_internal"); + assert_eq!( + ss_ref, + ss.ref_to_bytes(), + "ML-KEM-1024 shared secret must match encaps_internal" + ); + + // Ensure that it rejects an RNG at a lower security level + let mut fake_rng = FixedSeedRNG::new([0u8; 64]); + + // fails + fake_rng.set_security_strength(SecurityStrength::_112bit); + match MLKEM512::encaps_rng(&pk512, &mut fake_rng) { + Err(KEMError::RNGError(_)) => { /* good */ } + _ => panic!("unexpected error"), + } + + // succeeds + fake_rng.set_security_strength(SecurityStrength::_128bit); + _ = MLKEM512::encaps_rng(&pk512, &mut fake_rng).unwrap(); + + // fails + fake_rng.set_security_strength(SecurityStrength::_128bit); + match MLKEM768::encaps_rng(&pk768, &mut fake_rng) { + Err(KEMError::RNGError(_)) => { /* good */ } + _ => panic!("unexpected error"), + } + + // succeeds + fake_rng.set_security_strength(SecurityStrength::_192bit); + _ = MLKEM768::encaps_rng(&pk768, &mut fake_rng).unwrap(); + + // fails + fake_rng.set_security_strength(SecurityStrength::_192bit); + match MLKEM1024::encaps_rng(&pk1024, &mut fake_rng) { + Err(KEMError::RNGError(_)) => { /* good */ } + _ => panic!("unexpected error"), + } + + // succeeds + fake_rng.set_security_strength(SecurityStrength::_256bit); + _ = MLKEM1024::encaps_rng(&pk1024, &mut fake_rng).unwrap(); + } } // struct Kat { diff --git a/crypto/rng/src/hash_drbg80090a.rs b/crypto/rng/src/hash_drbg80090a.rs index 6041606..d3b59b3 100644 --- a/crypto/rng/src/hash_drbg80090a.rs +++ b/crypto/rng/src/hash_drbg80090a.rs @@ -273,9 +273,9 @@ impl Sp80090ADrbg for HashDRBG80090A { Ok(()) } - fn reseed( + fn reseed( &mut self, - seed: &impl KeyMaterialTrait, + seed: &K, additional_input: &[u8], ) -> Result<(), RNGError> { // Hash_DRBG Reseed Process: @@ -462,10 +462,10 @@ impl Sp80090ADrbg for HashDRBG80090A { Ok(out.len()) } - fn generate_keymaterial_out( + fn generate_keymaterial_out( &mut self, additional_input: &[u8], - out: &mut impl KeyMaterialTrait, + out: &mut K, ) -> Result { let mut ret: Result = Ok(0); do_hazardous_operations(out, |out| { @@ -502,9 +502,9 @@ impl RNG for HashDRBG80090A { fn add_seed_keymaterial( &mut self, - additional_seed: impl KeyMaterialTrait, + additional_seed: &dyn KeyMaterialTrait, ) -> Result<(), RNGError> { - self.reseed(&additional_seed, "add_seed_keymaterial".as_bytes()) + self.reseed(additional_seed, "add_seed_keymaterial".as_bytes()) } fn next_int(&mut self) -> Result { @@ -523,7 +523,7 @@ impl RNG for HashDRBG80090A { self.generate_out("next_bytes_out".as_bytes(), out) } - fn fill_keymaterial_out(&mut self, out: &mut impl KeyMaterialTrait) -> Result { + fn fill_keymaterial_out(&mut self, out: &mut dyn KeyMaterialTrait) -> Result { self.generate_keymaterial_out("fill_keymaterial".as_bytes(), out) } diff --git a/crypto/rng/src/lib.rs b/crypto/rng/src/lib.rs index 43a65c1..19c60bc 100644 --- a/crypto/rng/src/lib.rs +++ b/crypto/rng/src/lib.rs @@ -92,9 +92,9 @@ pub trait Sp80090ADrbg { /// Reseeds the DRBG with the provided seed. /// TODO: this needs to be thought out to take some sort of EntropySource object that'll work well with DRBGs that require frequent reseeding. - fn reseed( + fn reseed( &mut self, - seed: &impl KeyMaterialTrait, + seed: &K, additional_input: &[u8], ) -> Result<(), RNGError>; @@ -125,10 +125,10 @@ pub trait Sp80090ADrbg { /// The output [KeyMaterialTrait] is filled to capacity. /// Throws a [RNGError::InsufficientSeedEntropy] if the capacity of the output KeyMaterial exceeds [SecurityStrength]. /// Retruns the number of bits output. - fn generate_keymaterial_out( + fn generate_keymaterial_out( &mut self, additional_input: &[u8], - out: &mut impl KeyMaterialTrait, + out: &mut K, ) -> Result; // TODO -- implement FIPS health tests diff --git a/crypto/rng/tests/hash_drbg80090a_tests.rs b/crypto/rng/tests/hash_drbg80090a_tests.rs index d019e40..c03f71b 100644 --- a/crypto/rng/tests/hash_drbg80090a_tests.rs +++ b/crypto/rng/tests/hash_drbg80090a_tests.rs @@ -302,7 +302,7 @@ mod tests { KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Seed).unwrap(); /* test add_seed_keymaterial */ - rng.add_seed_keymaterial(seed).unwrap(); + rng.add_seed_keymaterial(&seed).unwrap(); /* test next_int */ let out1: u32 = rng.next_int().unwrap(); From 4035351f994de6f9e32dc17b91465b69535b826e Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 30 Jun 2026 19:51:22 -0500 Subject: [PATCH 19/35] Renamed KeyType and deleted KeyMaterial::concatenate. Closes #47 --- alpha_0.1.2_release_notes.md | 2 + crypto/core-test-framework/src/kdf.rs | 40 ++-- crypto/core/src/key_material.rs | 85 +++----- crypto/core/src/traits.rs | 14 +- crypto/core/tests/key_material_tests.rs | 226 +++++++------------- crypto/hkdf/src/lib.rs | 16 +- crypto/hkdf/tests/hkdf_tests.rs | 95 ++++---- crypto/mldsa-lowmemory/src/mldsa_keys.rs | 2 +- crypto/mldsa-lowmemory/tests/mldsa_tests.rs | 2 +- crypto/mldsa/src/mldsa.rs | 4 +- crypto/mldsa/tests/mldsa_tests.rs | 2 +- crypto/mlkem-lowmemory/src/mlkem.rs | 4 +- crypto/mlkem-lowmemory/src/mlkem_keys.rs | 2 +- crypto/mlkem-lowmemory/tests/mlkem_tests.rs | 2 +- crypto/mlkem/src/mlkem.rs | 6 +- crypto/mlkem/tests/mlkem_tests.rs | 2 +- crypto/rng/src/hash_drbg80090a.rs | 2 +- crypto/sha3/src/lib.rs | 6 +- crypto/sha3/src/sha3.rs | 4 +- crypto/sha3/src/shake.rs | 4 +- crypto/sha3/tests/sha3_tests.rs | 41 ++-- crypto/sha3/tests/shake_tests.rs | 31 ++- crypto/utils/tests/test_utils.rs | 4 +- 23 files changed, 255 insertions(+), 341 deletions(-) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index c59991b..37ad679 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -42,6 +42,8 @@ preventing exposure of stale data in oversized output buffers or on early error returns. * Reworked the way KeyMaterial hazardous operations work; instead of a stateful .allow_hazardous_operations() / .drop_hazardous_operations(), it now uses a closure-based do_hazardous_operations(). Github issue #39. +* Renamed KeyMaterial::KeyType's and deleted KeyMaterial::concatenate in order to give a better intuition and + FIPS-alignment. * Github issues resolved: * #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/crypto/core-test-framework/src/kdf.rs b/crypto/core-test-framework/src/kdf.rs index 21d1b4a..cac4081 100644 --- a/crypto/core-test-framework/src/kdf.rs +++ b/crypto/core-test-framework/src/kdf.rs @@ -59,31 +59,31 @@ impl TestFrameworkKDF { assert_eq!(zeroized_key.key_type(), KeyType::Zeroized); let out_key = H::default().derive_key(&zeroized_key, &[0u8; 10]).unwrap(); // since we've done some computation, the result will not actually be zeroized, even if all input key material was zeroized. - assert_eq!(out_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(out_key.key_type(), KeyType::Unknown); assert_eq!(out_key.security_strength(), SecurityStrength::None); // BytesLowEntropy -> BytesLowEntropy let low_entropy_key = - KeyMaterial256::from_bytes_as_type(&[1u8; 16], KeyType::BytesLowEntropy).unwrap(); - assert_eq!(low_entropy_key.key_type(), KeyType::BytesLowEntropy); + KeyMaterial256::from_bytes_as_type(&[1u8; 16], KeyType::Unknown).unwrap(); + assert_eq!(low_entropy_key.key_type(), KeyType::Unknown); let out_key = H::default().derive_key(&low_entropy_key, &[0u8; 10]).unwrap(); - assert_eq!(out_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(out_key.key_type(), KeyType::Unknown); assert_eq!(out_key.security_strength(), SecurityStrength::None); // BytesFullEntropy -> BytesLowEntropy if not enough to fill the hash block let low_entropy_key = - KeyMaterial256::from_bytes_as_type(&[1u8; 6], KeyType::BytesFullEntropy).unwrap(); - assert_eq!(low_entropy_key.key_type(), KeyType::BytesFullEntropy); + KeyMaterial256::from_bytes_as_type(&[1u8; 6], KeyType::CryptographicRandom).unwrap(); + assert_eq!(low_entropy_key.key_type(), KeyType::CryptographicRandom); let out_key = H::default().derive_key(&low_entropy_key, &[0u8; 10]).unwrap(); - assert_eq!(out_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(out_key.key_type(), KeyType::Unknown); assert_eq!(out_key.security_strength(), SecurityStrength::None); // BytesFullEntropy -> BytesFullEntropy let full_entropy_key = - KeyMaterial512::from_bytes_as_type(&[1u8; 64], KeyType::BytesFullEntropy).unwrap(); - assert_eq!(full_entropy_key.key_type(), KeyType::BytesFullEntropy); + KeyMaterial512::from_bytes_as_type(&[1u8; 64], KeyType::CryptographicRandom).unwrap(); + assert_eq!(full_entropy_key.key_type(), KeyType::CryptographicRandom); let out_key = H::default().derive_key(&full_entropy_key, &[0u8; 10]).unwrap(); - assert_eq!(out_key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(out_key.key_type(), KeyType::CryptographicRandom); assert!(out_key.security_strength() > SecurityStrength::None); } @@ -141,35 +141,35 @@ impl TestFrameworkKDF { assert_eq!(zeroized_key.security_strength(), SecurityStrength::None); let keys = [&zeroized_key, &zeroized_key]; let out_key = H::default().derive_key_from_multiple(&keys, &[0u8; 10]).unwrap(); - assert_eq!(out_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(out_key.key_type(), KeyType::Unknown); assert_eq!(out_key.security_strength(), SecurityStrength::None); // BytesLowEntropy -> BytesLowEntropy let low_entropy_key = - KeyMaterial256::from_bytes_as_type(&[1u8; 16], KeyType::BytesLowEntropy).unwrap(); - assert_eq!(low_entropy_key.key_type(), KeyType::BytesLowEntropy); + KeyMaterial256::from_bytes_as_type(&[1u8; 16], KeyType::Unknown).unwrap(); + assert_eq!(low_entropy_key.key_type(), KeyType::Unknown); let keys = [&zeroized_key, &low_entropy_key]; let out_key = H::default().derive_key_from_multiple(&keys, &[0u8; 10]).unwrap(); - assert_eq!(out_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(out_key.key_type(), KeyType::Unknown); assert_eq!(out_key.security_strength(), SecurityStrength::None); // BytesFullEntropy -> BytesLowEntropy if not enough to fill the hash block let low_entropy_key = - KeyMaterial256::from_bytes_as_type(&[1u8; 6], KeyType::BytesFullEntropy).unwrap(); - assert_eq!(low_entropy_key.key_type(), KeyType::BytesFullEntropy); + KeyMaterial256::from_bytes_as_type(&[1u8; 6], KeyType::CryptographicRandom).unwrap(); + assert_eq!(low_entropy_key.key_type(), KeyType::CryptographicRandom); let keys = [&zeroized_key, &low_entropy_key]; let out_key = H::default().derive_key_from_multiple(&keys, &[0u8; 10]).unwrap(); - assert_eq!(out_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(out_key.key_type(), KeyType::Unknown); assert_eq!(out_key.security_strength(), SecurityStrength::None); // BytesFullEntropy -> BytesFullEntropy let zeroized64_key = KeyMaterial512::new(); let full_entropy_key = - KeyMaterial512::from_bytes_as_type(&[1u8; 64], KeyType::BytesFullEntropy).unwrap(); - assert_eq!(full_entropy_key.key_type(), KeyType::BytesFullEntropy); + KeyMaterial512::from_bytes_as_type(&[1u8; 64], KeyType::CryptographicRandom).unwrap(); + assert_eq!(full_entropy_key.key_type(), KeyType::CryptographicRandom); let keys = [&zeroized64_key, &full_entropy_key]; let out_key = H::default().derive_key_from_multiple(&keys, &[0u8; 10]).unwrap(); - assert_eq!(out_key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(out_key.key_type(), KeyType::CryptographicRandom); assert!(out_key.security_strength() > SecurityStrength::None); } } diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index 8535c84..2d1db76 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -85,13 +85,13 @@ pub trait KeyMaterialTrait: KeyMaterialInternalTrait { /// /// let key_bytes = [0u8; 16]; /// let mut key = KeyMaterial256::new(); - /// let res = key.set_bytes_as_type(&key_bytes, KeyType::BytesLowEntropy); + /// let res = key.set_bytes_as_type(&key_bytes, KeyType::Unknown); /// match res { /// Err(KeyMaterialError::ActingOnZeroizedKey) => { /// // Either figure out why your passed an all-zero key, /// // or set the key type manually, if that's what you intended. /// do_hazardous_operations(&mut key, |key| { - /// key.set_key_type(KeyType::BytesLowEntropy) + /// key.set_key_type(KeyType::Unknown) /// }).unwrap(); // probably you should do something more elegant than .unwrap in your code ;) /// }, /// Err(_) => { /* figure out what else went wrong */ }, @@ -103,7 +103,7 @@ pub trait KeyMaterialTrait: KeyMaterialInternalTrait { /// Since this zeroizes and resets the key material, this is considered a dangerous conversion. /// /// Will set the [SecurityStrength] automatically according to the following rules: - /// * If [KeyType] is [KeyType::Zeroized] or [KeyType::BytesLowEntropy] then it will be [SecurityStrength::None]. + /// * If [KeyType] is [KeyType::Zeroized] or [KeyType::Unknown] then it will be [SecurityStrength::None]. /// * Otherwise it will set it based on the length of the provided source bytes. fn set_bytes_as_type( &mut self, @@ -160,7 +160,7 @@ pub trait KeyMaterialTrait: KeyMaterialInternalTrait { /// /// # 🚨 Hazardous Operation🚨 /// Inside a [do_hazardous_operations] closure this will set the key to any [KeyType]. - /// Outside such a closure, only "safe" conversions are permitted: a [KeyType::BytesFullEntropy] + /// Outside such a closure, only "safe" conversions are permitted: a [KeyType::CryptographicRandom] /// key may be converted to any type, and any type may be converted to itself (a no-op). /// A hazardous conversion attempted outside a [do_hazardous_operations] closure returns /// [KeyMaterialError::HazardousOperationNotPermitted], and converting a [KeyType::Zeroized] key @@ -202,18 +202,6 @@ pub trait KeyMaterialTrait: KeyMaterialInternalTrait { /// hold a different key, potentially of a different length. fn zeroize(&mut self); - /// Adds the other KeyMaterial into this one, assuming there is space. - /// - /// Throws [KeyMaterialError::InvalidLength] if this object does not have enough space to add the other one. - /// - /// The resulting [KeyType] and security strength will be the lesser of the two keys. - /// In other words, concatenating two 128-bit full entropy keys generated at a 128-bit DRBG security level - /// will result in a 256-bit full entropy key still at the 128-bit DRBG security level. - /// Concatenating a full entropy key with a low entropy key will result in a low entropy key. - /// - /// Returns the new key_len. - fn concatenate(&mut self, other: &dyn KeyMaterialTrait) -> Result; - /// Perform a constant-time comparison between the two key material buffers, /// ignoring differences in capacity, [KeyType], [SecurityStrength], etc. fn equals(&self, other: &dyn KeyMaterialTrait) -> bool; @@ -238,11 +226,18 @@ pub enum KeyType { /// The KeyMaterial is zeroized and MUST NOT be used for any cryptographic operation in this state. Zeroized, - /// The KeyMaterial contains data of low or unknown entropy. - BytesLowEntropy, + /// The KeyMaterial contains non-zero data of unknown key type. + /// A KeyMaterial of key type Unknown will always have a [SecurityStrength] of [SecurityStrength::None]. + /// + /// This is the default KeyType for data loaded via [KeyMaterial::from_bytes]. + /// Promotion from Unknown to any other key type is considered to be a hazardous operation + /// and must be done within a [do_hazardous_operations] closure. + /// If you want to import key material directly into a known key type, use [KeyMaterial::from_bytes_as_type], + /// which does not require a hazardous operations closure. + Unknown, - /// The KeyMaterial contains data of full entropy and can be safely converted to any other full-entropy key type. - BytesFullEntropy, + /// The KeyMaterial contains data of full entropy and can be safely converted to any other key type. + CryptographicRandom, /// A seed for asymmetric private keys, RNGs, and other seed-based cryptographic objects. Seed, @@ -283,16 +278,16 @@ impl KeyMaterial { })?; key.key_len = KEY_LEN; - key.key_type = KeyType::BytesFullEntropy; + key.key_type = KeyType::CryptographicRandom; key.security_strength = rng.security_strength(); Ok(key) } /// Constructor. - /// Loads the provided data into a new KeyMaterial of type [KeyType::BytesLowEntropy]. + /// Loads the provided data into a new KeyMaterial of type [KeyType::Unknown]. /// It will detect if you give it all-zero source data and set the key type to [KeyType::Zeroized] instead. pub fn from_bytes(source: &[u8]) -> Result { - Self::from_bytes_as_type(source, KeyType::BytesLowEntropy) + Self::from_bytes_as_type(source, KeyType::Unknown) } /// Constructor. @@ -302,7 +297,7 @@ impl KeyMaterial { /// It will detect if you give it all-zero source data and set the key type to [KeyType::Zeroized] instead. /// /// Will set the [SecurityStrength] automatically according to the following rules: - /// * If [KeyType] is [KeyType::Zeroized] or [KeyType::BytesLowEntropy] then it will be [SecurityStrength::None]. + /// * If [KeyType] is [KeyType::Zeroized] or [KeyType::Unknown] then it will be [SecurityStrength::None]. /// * Otherwise it will set it based on the length of the provided source bytes. pub fn from_bytes_as_type(source: &[u8], key_type: KeyType) -> Result { let mut key_material = Self::default(); @@ -359,7 +354,7 @@ impl KeyMaterialTrait for KeyMaterial { self.key_type = new_key_type; do_hazardous_operations(self, |s| { - if new_key_type <= KeyType::BytesLowEntropy { + if new_key_type <= KeyType::Unknown { s.set_security_strength(SecurityStrength::None)?; } else { s.set_security_strength(SecurityStrength::from_bits(source.len() * 8))?; @@ -435,12 +430,12 @@ impl KeyMaterialTrait for KeyMaterial { KeyType::Zeroized => { return Err(KeyMaterialError::ActingOnZeroizedKey); } - KeyType::BytesFullEntropy => { + KeyType::CryptographicRandom => { // raw full entropy can be safely converted to anything. self.key_type = key_type; } - KeyType::BytesLowEntropy => match key_type { - KeyType::BytesLowEntropy => { /* No change */ } + KeyType::Unknown => match key_type { + KeyType::Unknown => { /* No change */ } _ => { return Err(KeyMaterialError::HazardousOperationNotPermitted); } @@ -482,7 +477,7 @@ impl KeyMaterialTrait for KeyMaterial { return Err(KeyMaterialError::HazardousOperationNotPermitted); }; - if self.key_type <= KeyType::BytesLowEntropy && strength > SecurityStrength::None { + if self.key_type <= KeyType::Unknown && strength > SecurityStrength::None { return Err(KeyMaterialError::SecurityStrength( "BytesLowEntropy keys cannot have a security strength other than None.", )); @@ -525,11 +520,11 @@ impl KeyMaterialTrait for KeyMaterial { } fn is_full_entropy(&self) -> bool { match self.key_type { - KeyType::BytesFullEntropy + KeyType::CryptographicRandom | KeyType::Seed | KeyType::MACKey | KeyType::SymmetricCipherKey => true, - KeyType::Zeroized | KeyType::BytesLowEntropy => false, + KeyType::Zeroized | KeyType::Unknown => false, } } @@ -539,18 +534,6 @@ impl KeyMaterialTrait for KeyMaterial { self.key_type = KeyType::Zeroized; } - fn concatenate(&mut self, other: &dyn KeyMaterialTrait) -> Result { - let new_key_len = self.key_len() + other.key_len(); - if self.key_len() + other.key_len() > KEY_LEN { - return Err(KeyMaterialError::InputDataLongerThanKeyCapacity); - } - self.buf[self.key_len..new_key_len].copy_from_slice(other.ref_to_bytes()); - self.key_len += other.key_len(); - self.key_type = min(&self.key_type, &other.key_type()).clone(); - self.security_strength = min(&self.security_strength, &other.security_strength()).clone(); - Ok(self.key_len()) - } - fn equals(&self, other: &dyn KeyMaterialTrait) -> bool { if self.key_len() != other.key_len() { return false; @@ -561,7 +544,7 @@ impl KeyMaterialTrait for KeyMaterial { /// Checks for equality of the key data (using a constant-time comparison), but does not check that /// the two keys have the same type. -/// Therefore, for example, two keys loaded from the same bytes, one with type [KeyType::BytesLowEntropy] and +/// Therefore, for example, two keys loaded from the same bytes, one with type [KeyType::Unknown] and /// the other with [KeyType::MACKey] will be considered equal. impl PartialEq for KeyMaterial { fn eq(&self, other: &Self) -> bool { @@ -582,18 +565,18 @@ impl PartialOrd for KeyType { KeyType::Zeroized => Some(Ordering::Equal), _ => Some(Ordering::Less), }, - KeyType::BytesLowEntropy => match other { + KeyType::Unknown => match other { KeyType::Zeroized => Some(Ordering::Greater), - KeyType::BytesLowEntropy => Some(Ordering::Equal), + KeyType::Unknown => Some(Ordering::Equal), _ => Some(Ordering::Less), }, - KeyType::BytesFullEntropy => match other { - KeyType::Zeroized | KeyType::BytesLowEntropy => Some(Ordering::Greater), - KeyType::BytesFullEntropy => Some(Ordering::Equal), + KeyType::CryptographicRandom => match other { + KeyType::Zeroized | KeyType::Unknown => Some(Ordering::Greater), + KeyType::CryptographicRandom => Some(Ordering::Equal), _ => Some(Ordering::Less), }, KeyType::Seed | KeyType::MACKey | KeyType::SymmetricCipherKey => match other { - KeyType::Zeroized | KeyType::BytesLowEntropy | KeyType::BytesFullEntropy => { + KeyType::Zeroized | KeyType::Unknown | KeyType::CryptographicRandom => { Some(Ordering::Greater) } KeyType::Seed | KeyType::MACKey | KeyType::SymmetricCipherKey => { @@ -736,7 +719,7 @@ impl KeyMaterialInternalTrait for KeyMaterial { /// // In this example, we initialize a KeyMateriol512 (64 bytes) with only 32 bytes of input. /// let mut key = KeyMaterial512::from_bytes_as_type( /// &[1u8; 32], -/// KeyType::BytesFullEntropy +/// KeyType::CryptographicRandom /// ).unwrap(); /// assert_eq!(key.key_len(), 32); /// diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 595fc06..dbf40ea 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -98,14 +98,14 @@ pub trait KDF: Default { /// /// ex.: /// - /// * [KeyType::BytesLowEntropy] -> [KeyType::BytesLowEntropy]) - /// * [KeyType::BytesFullEntropy] -> [KeyType::BytesFullEntropy]) + /// * [KeyType::Unknown] -> [KeyType::Unknown]) + /// * [KeyType::CryptographicRandom] -> [KeyType::CryptographicRandom]) /// * [KeyType::SymmetricCipherKey] -> [KeyType::SymmetricCipherKey]) /// - /// If provided with an input key, even if it is [KeyType::BytesFullEntropy], but that + /// If provided with an input key, even if it is [KeyType::CryptographicRandom], but that /// contains less key material than the internal block size of the KDF, then the KDF /// will not be considered properly seeded, and the output [KeyMaterial] will be set to - /// [KeyType::BytesLowEntropy] -- for example, seeding SHA3-256 with a [KeyMaterial] containing + /// [KeyType::Unknown] -- for example, seeding SHA3-256 with a [KeyMaterial] containing /// only 128 bits of key material. /// /// An implement can, and in most cases SHOULD, return a [HashError] if provided @@ -152,9 +152,9 @@ pub trait KDF: Default { /// /// Implementations can, and in most cases SHOULD, return a [KeyMaterial] of the same type as the /// strongest key, and SHOULD throw a [HashError] if all input keys are zeroized. - /// For example output a [KeyType::BytesFullEntropy] key whenever any one of - /// the input keys is a [KeyType::BytesFullEntropy] key. - /// As another example, combining a [KeyType::BytesLowEntropy] key with a [KeyType::MACKey] key + /// For example output a [KeyType::CryptographicRandom] key whenever any one of + /// the input keys is a [KeyType::CryptographicRandom] key. + /// As another example, combining a [KeyType::Unknown] key with a [KeyType::MACKey] key /// should return a [KeyType::MACKey]. /// /// Output length: this function will create a KeyMaterial populated with the default output length diff --git a/crypto/core/tests/key_material_tests.rs b/crypto/core/tests/key_material_tests.rs index 08ce397..0dfcf96 100644 --- a/crypto/core/tests/key_material_tests.rs +++ b/crypto/core/tests/key_material_tests.rs @@ -37,7 +37,7 @@ mod test_key_material { fn test_set_bytes_as_type() { let key_bytes = [0u8; 16]; let mut key = KeyMaterial256::new(); - let res = key.set_bytes_as_type(&key_bytes, KeyType::BytesLowEntropy); + let res = key.set_bytes_as_type(&key_bytes, KeyType::Unknown); match res { Ok(_) => { panic!("should have thrown a KeyMaterialError::ActingOnZeroizedKey error.") @@ -49,7 +49,7 @@ mod test_key_material { // but it'll allow it within tho do_hazardous closure. do_hazardous_operations(&mut key, |key| { - key.set_key_type(KeyType::BytesLowEntropy)?; + key.set_key_type(KeyType::Unknown)?; Ok(()) }) .unwrap(); @@ -58,18 +58,18 @@ mod test_key_material { panic!("should have thrown a KeyMaterialError::ActingOnZeroizedKey error.") } } - assert_eq!(key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(key.key_type(), KeyType::Unknown); assert_eq!(key.security_strength(), SecurityStrength::None); // but it'll allow it within tho do_hazardous closure. let key_bytes = [0u8; 16]; let mut key = KeyMaterial256::new(); do_hazardous_operations(&mut key, |key| { - key.set_bytes_as_type(&key_bytes, KeyType::BytesLowEntropy)?; + key.set_bytes_as_type(&key_bytes, KeyType::Unknown)?; Ok(()) }) .unwrap(); - assert_eq!(key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(key.key_type(), KeyType::Unknown); // nothing else requires setting hazardous operations. } @@ -159,16 +159,16 @@ mod test_key_material { fn from_bytes() { let key = KeyMaterial512::from_bytes(&DUMMY_KEY[..64]).unwrap(); assert_eq!(key.key_len(), 64); - assert_eq!(key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(key.key_type(), KeyType::Unknown); // Basic success case let key = - KeyMaterial256::from_bytes_as_type(&[1u8; 16], KeyType::BytesFullEntropy).unwrap(); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + KeyMaterial256::from_bytes_as_type(&[1u8; 16], KeyType::CryptographicRandom).unwrap(); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); assert_eq!(key.security_strength(), SecurityStrength::_128bit); // Success case: KeyType::BytesLowEntropy gets tagged with SecurityStrength::None. - let key = KeyMaterial256::from_bytes_as_type(&[1u8; 16], KeyType::BytesLowEntropy); + let key = KeyMaterial256::from_bytes_as_type(&[1u8; 16], KeyType::Unknown); assert_eq!(key.unwrap().security_strength(), SecurityStrength::None); } @@ -178,11 +178,11 @@ mod test_key_material { let key = KeyMaterial256::from_rng(&mut rng::DefaultRNG::default()).unwrap(); assert_eq!(key.key_len(), 32); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); let key = KeyMaterial512::from_rng(&mut rng::DefaultRNG::default()).unwrap(); assert_eq!(key.key_len(), 64); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); } #[test] @@ -249,7 +249,7 @@ mod test_key_material { // test security strength interactions with truncation let mut key = - KeyMaterial512::from_bytes_as_type(&[1u8; 64], KeyType::BytesFullEntropy).unwrap(); + KeyMaterial512::from_bytes_as_type(&[1u8; 64], KeyType::CryptographicRandom).unwrap(); assert_eq!(key.security_strength(), SecurityStrength::_256bit); key.set_key_len(16).unwrap(); assert_eq!(key.security_strength(), SecurityStrength::_128bit); @@ -260,7 +260,7 @@ mod test_key_material { // truncate should not raise the security level let mut key = - KeyMaterial512::from_bytes_as_type(&[1u8; 64], KeyType::BytesFullEntropy).unwrap(); + KeyMaterial512::from_bytes_as_type(&[1u8; 64], KeyType::CryptographicRandom).unwrap(); key.set_security_strength(SecurityStrength::_112bit).unwrap(); key.set_key_len(64).unwrap(); assert_eq!(key.security_strength(), SecurityStrength::_112bit); @@ -269,24 +269,24 @@ mod test_key_material { #[test] fn test_conversions() { let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - assert_eq!(key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(key.key_type(), KeyType::Unknown); assert!(!key.is_full_entropy()); // Note: can't use the usual assert_eq!() here because that requires PartialEq, but we're in a no_std context here. match key.key_type() { - KeyType::BytesLowEntropy => { /* good */ } + KeyType::Unknown => { /* good */ } _ => panic!("Expected BytesLowEntropy"), } // This should fail. - match key.set_key_type(KeyType::BytesFullEntropy) { + match key.set_key_type(KeyType::CryptographicRandom) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } - do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::BytesFullEntropy)) + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::CryptographicRandom)) .unwrap(); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); assert!(key.is_full_entropy()); // Now we can convert BytesFullEntropy -> SymmetricCipherKey outside of a hazop block @@ -294,13 +294,13 @@ mod test_key_material { Ok(()) => { /* good */ } _ => panic!("Expected Ok(())"), } - match key.set_key_type(KeyType::BytesFullEntropy) { + match key.set_key_type(KeyType::CryptographicRandom) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::BytesFullEntropy)) + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::CryptographicRandom)) .unwrap(); // Now we can convert BytesFullEntropy -> Seed outside of a hazop block @@ -312,14 +312,13 @@ mod test_key_material { // each KeyType can convert to itself let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::BytesLowEntropy)) - .unwrap(); - key.set_key_type(KeyType::BytesLowEntropy).unwrap(); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::Unknown)).unwrap(); + key.set_key_type(KeyType::Unknown).unwrap(); let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::BytesFullEntropy)) + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::CryptographicRandom)) .unwrap(); - key.set_key_type(KeyType::BytesFullEntropy).unwrap(); + key.set_key_type(KeyType::CryptographicRandom).unwrap(); let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::MACKey)).unwrap(); @@ -341,11 +340,11 @@ mod test_key_material { assert_eq!(zeroized_key.key_type(), KeyType::Zeroized); /* All conversions should fail. */ - match zeroized_key.set_key_type(KeyType::BytesLowEntropy) { + match zeroized_key.set_key_type(KeyType::Unknown) { Err(KeyMaterialError::ActingOnZeroizedKey) => { /* good */ } _ => panic!("Expected ActingOnZeroizedKey"), } - match zeroized_key.set_key_type(KeyType::BytesFullEntropy) { + match zeroized_key.set_key_type(KeyType::CryptographicRandom) { Err(KeyMaterialError::ActingOnZeroizedKey) => { /* good */ } _ => panic!("Expected ActingOnZeroizedKey"), } @@ -370,7 +369,7 @@ mod test_key_material { // But it's totally fine if you give it non-zero input data. let not_zero_key = KeyMaterial256::from_bytes(&[1u8; 19]).unwrap(); - assert_eq!(not_zero_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(not_zero_key.key_type(), KeyType::Unknown); // test .set_bytes_as_type() // it should detect if you give it all zero input data. @@ -400,13 +399,13 @@ mod test_key_material { /// Tests the conversions that should only be allowed if hazardous_conversions() has been set. fn test_hazardous_conversions_from_bytes() { let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - assert_eq!(key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(key.key_type(), KeyType::Unknown); /* All the non-hazardous conversions should work. */ // ... none /* All the hazardous conversions should fail. */ - match key.set_key_type(KeyType::BytesFullEntropy) { + match key.set_key_type(KeyType::CryptographicRandom) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } @@ -425,7 +424,7 @@ mod test_key_material { /* Should work if you allow hazardous conversions. */ key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::BytesFullEntropy)) + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::CryptographicRandom)) .unwrap(); key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); @@ -494,7 +493,7 @@ mod test_key_material { key.set_key_type(KeyType::MACKey).unwrap(); /* All the hazardous conversions should fail. */ - match key.set_key_type(KeyType::BytesFullEntropy) { + match key.set_key_type(KeyType::CryptographicRandom) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected HazardousConversion"), } @@ -515,51 +514,59 @@ mod test_key_material { #[test] fn test_security_strength() { let key = KeyMaterial512::from_bytes(DUMMY_KEY).unwrap(); - assert_eq!(key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(key.key_type(), KeyType::Unknown); assert_eq!(key.security_strength(), SecurityStrength::None); - let key = KeyMaterial512::from_bytes_as_type(DUMMY_KEY, KeyType::BytesFullEntropy).unwrap(); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + let key = + KeyMaterial512::from_bytes_as_type(DUMMY_KEY, KeyType::CryptographicRandom).unwrap(); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); assert_eq!(key.security_strength(), SecurityStrength::_256bit); - let key = KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::BytesFullEntropy) - .unwrap(); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + let key = + KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::CryptographicRandom) + .unwrap(); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); assert_eq!(key.security_strength(), SecurityStrength::_256bit); - let key = KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..31], KeyType::BytesFullEntropy) - .unwrap(); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + let key = + KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..31], KeyType::CryptographicRandom) + .unwrap(); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); assert_eq!(key.security_strength(), SecurityStrength::_192bit); - let key = KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..24], KeyType::BytesFullEntropy) - .unwrap(); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + let key = + KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..24], KeyType::CryptographicRandom) + .unwrap(); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); assert_eq!(key.security_strength(), SecurityStrength::_192bit); - let key = KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..16], KeyType::BytesFullEntropy) - .unwrap(); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + let key = + KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..16], KeyType::CryptographicRandom) + .unwrap(); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); assert_eq!(key.security_strength(), SecurityStrength::_128bit); - let key = KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..15], KeyType::BytesFullEntropy) - .unwrap(); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + let key = + KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..15], KeyType::CryptographicRandom) + .unwrap(); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); assert_eq!(key.security_strength(), SecurityStrength::_112bit); - let key = KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..14], KeyType::BytesFullEntropy) - .unwrap(); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + let key = + KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..14], KeyType::CryptographicRandom) + .unwrap(); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); assert_eq!(key.security_strength(), SecurityStrength::_112bit); - let key = KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..13], KeyType::BytesFullEntropy) - .unwrap(); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + let key = + KeyMaterial512::from_bytes_as_type(&DUMMY_KEY[..13], KeyType::CryptographicRandom) + .unwrap(); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); assert_eq!(key.security_strength(), SecurityStrength::None); // even if it's long enough, BytesLowEntropy or Zeroized always get ::None - let key = KeyMaterial512::from_bytes_as_type(DUMMY_KEY, KeyType::BytesLowEntropy).unwrap(); - assert_eq!(key.key_type(), KeyType::BytesLowEntropy); + let key = KeyMaterial512::from_bytes_as_type(DUMMY_KEY, KeyType::Unknown).unwrap(); + assert_eq!(key.key_type(), KeyType::Unknown); assert_eq!(key.key_len(), 64); assert_eq!(key.security_strength(), SecurityStrength::None); @@ -571,7 +578,7 @@ mod test_key_material { // test set_security_strength() // Can't increase the security level outside of a hazop block first. let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - assert_eq!(key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(key.key_type(), KeyType::Unknown); match key.set_security_strength(SecurityStrength::_128bit) { Err(KeyMaterialError::HazardousOperationNotPermitted) => { /* good */ } _ => panic!("Expected KeyMaterialError::HazardousOperationNotPermitted"), @@ -600,17 +607,17 @@ mod test_key_material { .unwrap(); // But it'll work if you set it to a full entropy type do_hazardous_operations(&mut key, |key| { - key.set_key_type(KeyType::BytesFullEntropy).unwrap(); + key.set_key_type(KeyType::CryptographicRandom).unwrap(); key.set_security_strength(SecurityStrength::_128bit) }) .unwrap(); - assert_eq!(key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(key.key_type(), KeyType::CryptographicRandom); assert_eq!(key.security_strength(), SecurityStrength::_128bit); // BytesLowEntropy keys cannot have a security strength other than None. // success let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); - assert_eq!(key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(key.key_type(), KeyType::Unknown); // setting to ::None should work .. even outside of a hazop block key.set_security_strength(SecurityStrength::None).unwrap(); // but to ::_128bit should fail @@ -648,86 +655,6 @@ mod test_key_material { .unwrap(); } - #[test] - fn test_concatenate() { - // intentionally half-full - let mut key1 = KeyMaterial256::from_bytes(&[1u8; 16]).unwrap(); - let key2 = KeyMaterial256::from_bytes(&[2u8; 16]).unwrap(); - assert_eq!(key1.key_len(), 16); - assert_eq!(key2.key_len(), 16); - - key1.concatenate(&key2).unwrap(); - assert_eq!(key1.key_len(), 32); - assert_eq!(key1.ref_to_bytes()[..16], [1u8; 16]); - assert_eq!(key1.ref_to_bytes()[16..], [2u8; 16]); - - let mut zeroized_key = KeyMaterial256::default(); - do_hazardous_operations(&mut zeroized_key, |zeroized_key| { - zeroized_key.set_key_len(8).unwrap(); - Ok(()) - }) - .unwrap(); - assert_eq!(zeroized_key.key_type(), KeyType::Zeroized); - assert_eq!(zeroized_key.key_len(), 8); - zeroized_key.concatenate(&key2).unwrap(); - assert_eq!(zeroized_key.key_len(), 24); - // The result takes the lesser (min) of the two key types: min(Zeroized, BytesLowEntropy). - // Folding in zeroized (uninitialized) bytes taints the whole buffer as Zeroized. - assert_eq!(zeroized_key.key_type(), KeyType::Zeroized); - assert_eq!(zeroized_key.security_strength(), SecurityStrength::None); - - // This should be symmetric, so test it in the other direction too. - let mut zeroized_key = KeyMaterial256::default(); - do_hazardous_operations(&mut zeroized_key, |zeroized_key| { - zeroized_key.set_key_len(8).unwrap(); - Ok(()) - }) - .unwrap(); - assert_eq!(zeroized_key.key_type(), KeyType::Zeroized); - assert_eq!(zeroized_key.key_len(), 8); - let mut key2 = KeyMaterial256::from_bytes(&[1u8; 16]).unwrap(); - key2.concatenate(&zeroized_key).unwrap(); - assert_eq!(key2.key_len(), 24); - // The result takes the lesser (min) of the two key types: min(BytesLowEntropy, Zeroized). - assert_eq!(key2.key_type(), KeyType::Zeroized); - assert_eq!(key2.security_strength(), SecurityStrength::None); - - // now try it with keys of different key types - let mut low_entropy_key = - KeyMaterial256::from_bytes_as_type(&[1u8; 16], KeyType::BytesLowEntropy).unwrap(); - let full_entropy_key = - KeyMaterial256::from_bytes_as_type(&[2u8; 16], KeyType::BytesFullEntropy).unwrap(); - low_entropy_key.concatenate(&full_entropy_key).unwrap(); - // Conservative model: concatenating a full-entropy key with a low-entropy key yields a - // low-entropy key. min(BytesLowEntropy, BytesFullEntropy) == BytesLowEntropy. - assert_eq!(low_entropy_key.key_type(), KeyType::BytesLowEntropy); - // min(None, _128bit) == None (and BytesLowEntropy keys must have strength None anyway). - assert_eq!(low_entropy_key.security_strength(), SecurityStrength::None); - - // and in the other direction too - let low_entropy_key = - KeyMaterial256::from_bytes_as_type(&[1u8; 16], KeyType::BytesLowEntropy).unwrap(); - let mut full_entropy_key = - KeyMaterial256::from_bytes_as_type(&[2u8; 16], KeyType::BytesFullEntropy).unwrap(); - full_entropy_key.concatenate(&low_entropy_key).unwrap(); - // min(BytesFullEntropy, BytesLowEntropy) == BytesLowEntropy. - assert_eq!(full_entropy_key.key_type(), KeyType::BytesLowEntropy); - // min(_128bit, None) == None. - assert_eq!(full_entropy_key.security_strength(), SecurityStrength::None); - - // now with full entropy keys at different security levels - let mut full_entropy_key_112 = - KeyMaterial512::from_bytes_as_type(&[1u8; 16], KeyType::BytesFullEntropy).unwrap(); - // Now we're gonna explictly tag it at the 112bit security level -- does not require allow_hazardous_operations(). - full_entropy_key_112.set_security_strength(SecurityStrength::_112bit).unwrap(); - let full_entropy_key = - KeyMaterial256::from_bytes_as_type(&[2u8; 32], KeyType::BytesFullEntropy).unwrap(); - full_entropy_key_112.concatenate(&full_entropy_key).unwrap(); - assert_eq!(full_entropy_key_112.key_type(), KeyType::BytesFullEntropy); - // The combined key keeps the lower of the two security strengths: min(_112bit, _256bit). - assert_eq!(full_entropy_key_112.security_strength(), SecurityStrength::_112bit); - } - #[test] fn eq() { // For context: @@ -751,17 +678,17 @@ mod test_key_material { // PartialEq ignores key_type: same bytes, different KeyType. Should be equal. let key_low = - KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::BytesLowEntropy).unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::Unknown).unwrap(); let key_mac = KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::MACKey).unwrap(); assert_eq!(key_low, key_mac); // PartialEq ignores security_strength: same bytes, different strength. Should be equal. let key_strong = - KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::CryptographicRandom) .unwrap(); let mut key_weak = - KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..32], KeyType::CryptographicRandom) .unwrap(); key_weak.set_security_strength(SecurityStrength::_128bit).unwrap(); assert_ne!(key_strong.security_strength(), key_weak.security_strength()); // strengths differ @@ -846,14 +773,13 @@ mod test_key_material { fn rank(kt: KeyType) -> u8 { match kt { Zeroized => 0, - BytesLowEntropy => 1, - BytesFullEntropy => 2, + Unknown => 1, + CryptographicRandom => 2, Seed | MACKey | SymmetricCipherKey => 3, } } - let all_types = - [Zeroized, BytesLowEntropy, BytesFullEntropy, Seed, MACKey, SymmetricCipherKey]; + let all_types = [Zeroized, Unknown, CryptographicRandom, Seed, MACKey, SymmetricCipherKey]; for &a in &all_types { for &b in &all_types { @@ -899,7 +825,7 @@ mod test_key_material { // 2. A real KeyMaterialError raised by a guarded op inside the closure propagates via `?`. // Raising to _256bit requires >= 32 bytes, but this key is only 16, so it fails. let mut short = - KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..16], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_KEY[..16], KeyType::CryptographicRandom) .unwrap(); let result = do_hazardous_operations(&mut short, |k| { k.set_security_strength(SecurityStrength::_256bit)?; diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 27c5607..f6920ef 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -235,7 +235,7 @@ impl HkdfEntropyTracker { /// Either [KeyMaterialTrait::BytesLowEntropy] or [KeyMaterialTrait::BytesFullEntropy] depending on /// whether enough input key material was provided for the internal hash function to have a full block. fn get_output_key_type(&self) -> KeyType { - if self.is_fully_seeded() { KeyType::BytesFullEntropy } else { KeyType::BytesLowEntropy } + if self.is_fully_seeded() { KeyType::CryptographicRandom } else { KeyType::Unknown } } } @@ -245,22 +245,22 @@ fn test_entropy_tracker() { let mut entropy = HkdfEntropyTracker::::new(); assert_eq!(entropy.get_entropy(), 0); - assert_eq!(entropy.get_output_key_type(), KeyType::BytesLowEntropy); + assert_eq!(entropy.get_output_key_type(), KeyType::Unknown); let key = KeyMaterial512::from_bytes_as_type( b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", - KeyType::BytesFullEntropy, + KeyType::CryptographicRandom, ) .unwrap(); entropy.credit_entropy(&key); assert_eq!(entropy.get_entropy(), 16); assert_eq!(entropy.is_fully_seeded(), false); - assert_eq!(entropy.get_output_key_type(), KeyType::BytesLowEntropy); + assert_eq!(entropy.get_output_key_type(), KeyType::Unknown); entropy.credit_entropy(&key); assert_eq!(entropy.get_entropy(), 32); assert_eq!(entropy.is_fully_seeded(), true); - assert_eq!(entropy.get_output_key_type(), KeyType::BytesFullEntropy); + assert_eq!(entropy.get_output_key_type(), KeyType::CryptographicRandom); } impl Default for HKDF { @@ -450,12 +450,12 @@ impl HKDF { // since we've done some computation, the result will not actually be zeroized, even if all input key material was zeroized. key_material::do_hazardous_operations(okm, |okm| { if prk.key_type() == KeyType::Zeroized { - okm.set_key_type(KeyType::BytesLowEntropy)?; + okm.set_key_type(KeyType::Unknown)?; } else { okm.set_key_type(prk.key_type().clone())?; } okm.set_key_len(bytes_written)?; - if okm.key_type() <= KeyType::BytesLowEntropy { + if okm.key_type() <= KeyType::Unknown { okm.set_security_strength(SecurityStrength::None) } else { okm.set_security_strength( @@ -589,7 +589,7 @@ impl HKDF { .map_err(|_| KeyMaterialError::GenericError("HMAC do_final_out failed"))?; okm.set_key_len(bytes_written)?; okm.set_key_type(output_key_type)?; - if output_key_type <= KeyType::BytesLowEntropy { + if output_key_type <= KeyType::Unknown { okm.set_security_strength(SecurityStrength::None) } else { okm.set_security_strength( diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index 4d598fa..84037ea 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -139,27 +139,27 @@ mod hkdf_tests { // not enough assert_eq!(key255.security_strength(), SecurityStrength::_192bit); let mut okm = HKDF_SHA256::extract(&key255, &zero_key).unwrap(); - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); assert_eq!(okm.security_strength(), SecurityStrength::None); _ = HKDF_SHA256::extract_and_expand_out(&key255, &zero_key, &[], 32, &mut okm).unwrap(); - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); assert_eq!(okm.security_strength(), SecurityStrength::None); // too much assert_eq!(key512.security_strength(), SecurityStrength::_256bit); let mut okm = HKDF_SHA256::extract(&key512, &zero_key).unwrap(); - assert_eq!(okm.key_type(), KeyType::BytesFullEntropy); + assert_eq!(okm.key_type(), KeyType::CryptographicRandom); // should get downgraded to match hash alg assert_eq!(okm.security_strength(), SecurityStrength::_128bit); _ = HKDF_SHA256::extract_and_expand_out(&key512, &zero_key, &[], 32, &mut okm).unwrap(); - assert_eq!(okm.key_type(), KeyType::BytesFullEntropy); + assert_eq!(okm.key_type(), KeyType::CryptographicRandom); assert_eq!(okm.security_strength(), SecurityStrength::_128bit); // just right let mut okm = HKDF_SHA256::extract(&key256, &zero_key).unwrap(); - assert_eq!(okm.key_type(), KeyType::BytesFullEntropy); + assert_eq!(okm.key_type(), KeyType::CryptographicRandom); _ = HKDF_SHA256::extract_and_expand_out(&key256, &zero_key, &[], 32, &mut okm).unwrap(); - assert_eq!(okm.key_type(), KeyType::BytesFullEntropy); + assert_eq!(okm.key_type(), KeyType::CryptographicRandom); // test the thresholds of HMAC-SHA512 let key511 = @@ -170,20 +170,19 @@ mod hkdf_tests { // not enough let mut okm = HKDF_SHA512::extract(&key511, &zero_key).unwrap(); - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); _ = HKDF_SHA512::extract_and_expand_out(&key511, &zero_key, &[], 32, &mut okm).unwrap(); - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); // just right let mut okm = HKDF_SHA512::extract(&key512, &zero_key).unwrap(); - assert_eq!(okm.key_type(), KeyType::BytesFullEntropy); + assert_eq!(okm.key_type(), KeyType::CryptographicRandom); _ = HKDF_SHA512::extract_and_expand_out(&key512, &zero_key, &[], 32, &mut okm).unwrap(); - assert_eq!(okm.key_type(), KeyType::BytesFullEntropy); + assert_eq!(okm.key_type(), KeyType::CryptographicRandom); // variable setup let low_entropy_key = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::BytesLowEntropy) - .unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Unknown).unwrap(); let mut okm = KeyMaterial256::new(); // failure case: should complain if low entropy bytes are provided @@ -262,16 +261,16 @@ mod hkdf_tests { ) .unwrap(); // okm should be tracked as LowEntropy - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); HKDF_SHA256::new().derive_key_out(&KeyMaterial0::new(), &[], &mut okm).unwrap(); // okm should be tracked as LowEntropy - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); let keys = [&KeyMaterial0::new(), &KeyMaterial0::new()]; HKDF_SHA256::new().derive_key_from_multiple_out(&keys, &[], &mut okm).unwrap(); // okm should be tracked as LowEntropy - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); // zero-length salt is allowed -- low entropy ikm _ = HKDF_SHA256::extract_and_expand_out( @@ -283,25 +282,27 @@ mod hkdf_tests { ) .unwrap(); // okm should be tracked as LowEntropy - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); HKDF_SHA256::new().derive_key_out(&low_entropy_key, &[], &mut okm).unwrap(); // okm should be tracked as LowEntropy - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); let keys = [&KeyMaterial256::new(), &low_entropy_key]; HKDF_SHA256::new().derive_key_from_multiple_out(&keys, &[], &mut okm).unwrap(); // okm should be tracked as LowEntropy - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); // salt and ikm are full-entropy, but not enough to seed the HKDF, according to FIPS // first, error case; not a MACKey let salt = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::BytesFullEntropy) - .unwrap(); - let ikm = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[8..16], KeyType::BytesFullEntropy) + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::CryptographicRandom) .unwrap(); + let ikm = KeyMaterial128::from_bytes_as_type( + &DUMMY_SEED_512[8..16], + KeyType::CryptographicRandom, + ) + .unwrap(); match HKDF_SHA256::extract_and_expand_out(&salt, &ikm, &[], 32, &mut okm) { Ok(_) => { @@ -318,7 +319,7 @@ mod hkdf_tests { // derive_key has a different behaviour here, since it hard-codes a zero salt as the HMAC key, which is valid, // it will produce output of Keytype::BytesLowEntropy _ = HKDF_SHA256::new().derive_key_out(&ikm, &[], &mut okm); - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); let keys = [&salt, &ikm]; match HKDF_SHA256::new().derive_key_from_multiple_out(&keys, &[], &mut okm) { @@ -336,57 +337,60 @@ mod hkdf_tests { // success case -- insufficient entropy returns KeyType::BytesLowEntropy let salt = KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::MACKey).unwrap(); - let ikm = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[8..16], KeyType::BytesFullEntropy) - .unwrap(); + let ikm = KeyMaterial128::from_bytes_as_type( + &DUMMY_SEED_512[8..16], + KeyType::CryptographicRandom, + ) + .unwrap(); _ = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, &[], 32, &mut okm); - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); _ = HKDF_SHA256::new().derive_key_out(&salt, &[], &mut okm); - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); let keys = [&salt, &ikm]; _ = HKDF_SHA256::new().derive_key_from_multiple_out(&keys, &[], &mut okm); - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); // success case -- sufficient entropy returns the highest input key type -- KeyType::BytesFullEntropy // Note that FIPS requires it to be seeded to a full internal block (which is, for example 512 bits for SHA256) // Note: will still return BytesFullEntropy because that one was first in the inputs. let salt = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::MACKey).unwrap(); - let ikm = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[32..64], KeyType::BytesFullEntropy) - .unwrap(); + let ikm = KeyMaterial256::from_bytes_as_type( + &DUMMY_SEED_512[32..64], + KeyType::CryptographicRandom, + ) + .unwrap(); _ = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, &[], 32, &mut okm); - assert_eq!(okm.key_type(), KeyType::BytesFullEntropy); + assert_eq!(okm.key_type(), KeyType::CryptographicRandom); let salt1 = KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::MACKey).unwrap(); _ = HKDF_SHA256::new().derive_key_out(&salt1, &[], &mut okm); - assert_eq!(okm.key_type(), KeyType::BytesFullEntropy); + assert_eq!(okm.key_type(), KeyType::CryptographicRandom); let keys = [&salt, &ikm]; _ = HKDF_SHA256::new().derive_key_from_multiple_out(&keys, &[], &mut okm); - assert_eq!(okm.key_type(), KeyType::BytesFullEntropy); + assert_eq!(okm.key_type(), KeyType::CryptographicRandom); // success case -- insufficient entropy due to key types -- KeyType::BytesLowEntropy // Note: will still return MACKey because that one was first in the inputs. let salt = KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::MACKey).unwrap(); let ikm = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[16..32], KeyType::BytesLowEntropy) - .unwrap(); + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[16..32], KeyType::Unknown).unwrap(); _ = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, &[], 32, &mut okm); - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); // no way to test this on derive_out let keys = [&salt, &ikm]; _ = HKDF_SHA256::new().derive_key_from_multiple_out(&keys, &[], &mut okm); - assert_eq!(okm.key_type(), KeyType::BytesLowEntropy); + assert_eq!(okm.key_type(), KeyType::Unknown); /* get_entropy */ // This requires using the stateful streaming API and check the amount of entropy it tracks after each addition. @@ -395,11 +399,12 @@ mod hkdf_tests { let salt64 = KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::MACKey).unwrap(); let low_entropy_key16 = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::BytesLowEntropy) - .unwrap(); - let full_entropy_key16 = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[16..32], KeyType::BytesFullEntropy) - .unwrap(); + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::Unknown).unwrap(); + let full_entropy_key16 = KeyMaterial128::from_bytes_as_type( + &DUMMY_SEED_512[16..32], + KeyType::CryptographicRandom, + ) + .unwrap(); // can't test with a low entropy salt because the salt has to be full entropy or zero. // but can test with a zeroized key @@ -531,7 +536,7 @@ mod hkdf_tests { let mut ikm_key = KeyMaterial::<100>::new(); key_material::do_hazardous_operations(&mut ikm_key, |ikm_key| { // just for testing, ignore the error about zeroized keys - ikm_key.set_bytes_as_type(&hex::decode(ikm).unwrap(), KeyType::BytesFullEntropy) + ikm_key.set_bytes_as_type(&hex::decode(ikm).unwrap(), KeyType::CryptographicRandom) }) .unwrap(); diff --git a/crypto/mldsa-lowmemory/src/mldsa_keys.rs b/crypto/mldsa-lowmemory/src/mldsa_keys.rs index d4b1c88..7a733fe 100644 --- a/crypto/mldsa-lowmemory/src/mldsa_keys.rs +++ b/crypto/mldsa-lowmemory/src/mldsa_keys.rs @@ -486,7 +486,7 @@ impl< /// Seed SecurityStrength must match algorithm security strength: 128-bit (ML-DSA-44), 192-bit (ML-DSA-65), or 256-bit (ML-DSA-87), /// otherwise it throws a SignatureError::KeyGenError("SecurityStrength". pub fn new(seed: &KeyMaterial<32>) -> Result { - if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::BytesFullEntropy) + if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::CryptographicRandom) || seed.key_len() != 32 { return Err(SignatureError::KeyGenError( diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index 593b6f9..184de0a 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -190,7 +190,7 @@ mod mldsa_tests { // success case KeyType: BytesFullEntropy key_material::do_hazardous_operations(&mut seed, |seed| { - seed.set_key_type(KeyType::BytesFullEntropy) + seed.set_key_type(KeyType::CryptographicRandom) }) .unwrap(); _ = MLDSA44::keygen_from_seed(&seed).unwrap(); diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index 680c9ae..efee573 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -753,7 +753,7 @@ impl< /// If you happen to have your seed in a larger KeyMaterial, you'll have to copy it using /// [KeyMaterialTrait::from_key] pub(crate) fn keygen_internal(seed: &KeyMaterial256) -> Result<(PK, SK), SignatureError> { - if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::BytesFullEntropy) + if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::CryptographicRandom) || seed.key_len() != 32 { return Err(SignatureError::KeyGenError( @@ -1249,7 +1249,7 @@ impl< // to avoid having all of it in memory at the same time, // we're gonna derive what we need as we need it. - if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::BytesFullEntropy) + if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::CryptographicRandom) || seed.key_len() != 32 { return Err(SignatureError::KeyGenError( diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index 242800a..45c812b 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -225,7 +225,7 @@ mod mldsa_tests { assert_eq!(derived_pk.encode(), expected_pk_bytes.as_slice()); // success case KeyType: BytesFullEntropy - do_hazardous_operations(&mut seed, |seed| seed.set_key_type(KeyType::BytesFullEntropy)) + do_hazardous_operations(&mut seed, |seed| seed.set_key_type(KeyType::CryptographicRandom)) .unwrap(); _ = MLDSA44::keygen_from_seed(&seed).unwrap(); diff --git a/crypto/mlkem-lowmemory/src/mlkem.rs b/crypto/mlkem-lowmemory/src/mlkem.rs index 5e63c0b..e5abc51 100644 --- a/crypto/mlkem-lowmemory/src/mlkem.rs +++ b/crypto/mlkem-lowmemory/src/mlkem.rs @@ -694,7 +694,7 @@ impl< let (ss_bytes, ct) = Self::encaps_internal(pk, m); let mut ss_keymaterial = - KeyMaterial::::from_bytes_as_type(&ss_bytes, KeyType::BytesFullEntropy)?; + KeyMaterial::::from_bytes_as_type(&ss_bytes, KeyType::CryptographicRandom)?; do_hazardous_operations(&mut ss_keymaterial, |ss_keymaterial| { ss_keymaterial.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize)) })?; @@ -749,7 +749,7 @@ impl< let ss_bytes = Self::decaps_internal(sk, ct.try_into().unwrap()); let mut ss_keymaterial = - KeyMaterial::::from_bytes_as_type(&ss_bytes, KeyType::BytesFullEntropy)?; + KeyMaterial::::from_bytes_as_type(&ss_bytes, KeyType::CryptographicRandom)?; do_hazardous_operations(&mut ss_keymaterial, |ss_keymaterial| { ss_keymaterial.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize)) })?; diff --git a/crypto/mlkem-lowmemory/src/mlkem_keys.rs b/crypto/mlkem-lowmemory/src/mlkem_keys.rs index 4c310ab..36d36bd 100644 --- a/crypto/mlkem-lowmemory/src/mlkem_keys.rs +++ b/crypto/mlkem-lowmemory/src/mlkem_keys.rs @@ -268,7 +268,7 @@ impl< /// Create a new MLKEMSeedPrivateKey from a 64-byte KeyMaterial. /// Seed SecurityStrength must match algorithm security strength: 128-bit (ML-KEM-512), 192-bit (ML-KEM-768), or 256-bit (ML-KEM-1024). pub fn new(seed: &KeyMaterial<64>) -> Result { - if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::BytesFullEntropy) + if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::CryptographicRandom) || seed.key_len() != 64 { return Err(KEMError::KeyGenError( diff --git a/crypto/mlkem-lowmemory/tests/mlkem_tests.rs b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs index 2922f13..7210d96 100644 --- a/crypto/mlkem-lowmemory/tests/mlkem_tests.rs +++ b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs @@ -338,7 +338,7 @@ mod mlkem_tests { assert_eq!(derived_pk.encode(), expected_pk_bytes.as_slice()); // success case KeyType: BytesFullEntropy - do_hazardous_operations(&mut seed, |seed| seed.set_key_type(KeyType::BytesFullEntropy)) + do_hazardous_operations(&mut seed, |seed| seed.set_key_type(KeyType::CryptographicRandom)) .unwrap(); _ = MLKEM512::keygen_from_seed(&seed).unwrap(); diff --git a/crypto/mlkem/src/mlkem.rs b/crypto/mlkem/src/mlkem.rs index f988f6f..28e1b0f 100644 --- a/crypto/mlkem/src/mlkem.rs +++ b/crypto/mlkem/src/mlkem.rs @@ -343,7 +343,7 @@ impl< /// Output: encapsulation key ek ∈ 𝔹384π‘˜+32 . /// Output: decapsulation key dk ∈ 𝔹768π‘˜+96 . pub(crate) fn keygen_internal(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError> { - if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::BytesFullEntropy) + if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::CryptographicRandom) || seed.key_len() != 64 { return Err(KEMError::KeyGenError( @@ -776,7 +776,7 @@ impl< let (ss, ct) = Self::encaps_internal(&pk.ek, Some(&pk.A_hat), m); - let mut key = KeyMaterial::::from_bytes_as_type(&ss, KeyType::BytesFullEntropy)?; + let mut key = KeyMaterial::::from_bytes_as_type(&ss, KeyType::CryptographicRandom)?; do_hazardous_operations(&mut key, |key| { key.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize)) })?; @@ -807,7 +807,7 @@ impl< /* the actual decaps operation */ let K = Self::decaps_internal(&sk.dk, Some(&sk.A_hat), ct.try_into().unwrap()); - let mut key = KeyMaterial::::from_bytes_as_type(&K, KeyType::BytesFullEntropy)?; + let mut key = KeyMaterial::::from_bytes_as_type(&K, KeyType::CryptographicRandom)?; do_hazardous_operations(&mut key, |key| { key.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize)) })?; diff --git a/crypto/mlkem/tests/mlkem_tests.rs b/crypto/mlkem/tests/mlkem_tests.rs index 1a9da27..b0e0c81 100644 --- a/crypto/mlkem/tests/mlkem_tests.rs +++ b/crypto/mlkem/tests/mlkem_tests.rs @@ -324,7 +324,7 @@ mod mlkem_tests { // success case KeyType: BytesFullEntropy key_material::do_hazardous_operations(&mut seed, |seed| { - seed.set_key_type(KeyType::BytesFullEntropy) + seed.set_key_type(KeyType::CryptographicRandom) }) .unwrap(); diff --git a/crypto/rng/src/hash_drbg80090a.rs b/crypto/rng/src/hash_drbg80090a.rs index 6041606..244f4b8 100644 --- a/crypto/rng/src/hash_drbg80090a.rs +++ b/crypto/rng/src/hash_drbg80090a.rs @@ -481,7 +481,7 @@ impl Sp80090ADrbg for HashDRBG80090A { do_hazardous_operations(out, |out| { out.set_key_len(bytes_written)?; - out.set_key_type(KeyType::BytesFullEntropy)?; + out.set_key_type(KeyType::CryptographicRandom)?; let new_security_strength = min(&self.admin_info.strength, &SecurityStrength::from_bits(bytes_written * 8)) .clone(); diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 42ecac4..7ef4a44 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -98,10 +98,10 @@ //! let output_key = sha3::SHA3_256::new().derive_key(&input_key, b"Additional input").unwrap(); //!``` //! In the previous example, since [KeyMaterial::from_bytes] cannot know the amount of entropy in the input data, -//! it automatically tags it as [KeyType::BytesLowEntropy], and thus [SHA3::derive_key] produces an output key -//! which also has type [KeyType::BytesLowEntropy]. +//! it automatically tags it as [KeyType::Unknown], and thus [SHA3::derive_key] produces an output key +//! which also has type [KeyType::Unknown]. //! This would also be the case even if the input had type -//! [KeyType::BytesFullEntropy] since the input [KeyMaterial] is 16 bytes but [SHA3_256] needs at least 32 bytes of +//! [KeyType::CryptographicRandom] since the input [KeyMaterial] is 16 bytes but [SHA3_256] needs at least 32 bytes of //! full-entropy input key material in order to be able to produce full entropy output key material. #![forbid(unsafe_code)] diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 7f2d60a..fff2439 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -74,7 +74,7 @@ impl SHA3 { // it requires full-entropy input that is at least block length. // TODO: citation needed, which NIST spec did I get this from? if self.kdf_entropy < PARAMS::OUTPUT_LEN { - self.kdf_key_type = min(&self.kdf_key_type, &KeyType::BytesLowEntropy).clone(); + self.kdf_key_type = min(&self.kdf_key_type, &KeyType::Unknown).clone(); self.kdf_security_strength = SecurityStrength::None; // BytesLowEntropy can't have a securtiy level. } @@ -95,7 +95,7 @@ impl SHA3 { // since we've done some computation, the result will not actually be zeroized, // even if all input key material was zeroized. if key_type == KeyType::Zeroized { - key_type = KeyType::BytesLowEntropy; + key_type = KeyType::Unknown; } key_material::do_hazardous_operations(&mut *output_key, |output_key| { output_key.set_key_type(key_type)?; diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index f934a44..fecff5a 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -109,7 +109,7 @@ impl SHAKE { // TODO: intuitivitely this makes sense since SHAKE256 and SHA3-256 are both KECCAK[512], and SHAKE128 is KECCAK[256], // TODO: but I would rather find an actual reference for this "fully-seeded" threshold. if self.kdf_entropy < 2 * (PARAMS::SIZE as usize) / 8 { - self.kdf_key_type = min(&self.kdf_key_type, &KeyType::BytesLowEntropy).clone(); + self.kdf_key_type = min(&self.kdf_key_type, &KeyType::Unknown).clone(); self.kdf_security_strength = SecurityStrength::None; // BytesLowEntropy can't have a securtiy level. } @@ -125,7 +125,7 @@ impl SHAKE { // since we've done some computation, the result will not actually be zeroized, even if all input key material was zeroized. if self.kdf_key_type == KeyType::Zeroized { - self.kdf_key_type = KeyType::BytesLowEntropy; + self.kdf_key_type = KeyType::Unknown; } key_material::do_hazardous_operations(output_key, |output_key| { output_key.set_key_type(self.kdf_key_type)?; diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index 0065726..1add04b 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -249,38 +249,38 @@ mod sha3_tests { // Exact entropy let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHA3_256::new().derive_key(&key_material, &[0u8; 0]).unwrap(); let expected_key = KeyMaterial256::from_bytes(b"\x05\x0a\x48\x73\x3b\xd5\xc2\x75\x6b\xa9\x5c\x58\x28\xcc\x83\xee\x16\xfa\xbc\xd3\xc0\x86\x88\x5b\x77\x44\xf8\x4a\x0f\x9e\x0d\x94").unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(derived_key.key_type(), KeyType::CryptographicRandom); assert_eq!(derived_key.security_strength(), SecurityStrength::_128bit); assert_eq!(derived_key.ref_to_bytes(), expected_key.ref_to_bytes()); // more entropy than needed -- single input key let key_material = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::BytesFullEntropy) + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHA3_256::new().derive_key(&key_material, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(derived_key.key_type(), KeyType::CryptographicRandom); assert_eq!(derived_key.security_strength(), SecurityStrength::_128bit); // more entropy than needed -- single input key // but if you use SHA512 then you get SecurityStrength::_256bit let key_material = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::BytesFullEntropy) + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHA3_512::new().derive_key(&key_material, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(derived_key.key_type(), KeyType::CryptographicRandom); assert_eq!(derived_key.security_strength(), SecurityStrength::_256bit); // more entropy than needed -- multiple input keys let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) .unwrap(); let keys = [&key_material, &key_material]; let derived_key = SHA3_256::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(derived_key.key_type(), KeyType::CryptographicRandom); assert_eq!(derived_key.security_strength(), SecurityStrength::_128bit); // more entropy than needed -- multiple input keys of different full-entropy types; @@ -300,41 +300,40 @@ mod sha3_tests { assert_eq!(key_material.key_type(), KeyType::Zeroized); // it should do it, but return a zeroized output key, regardless of the additional_input let derived_key = SHA3_256::new().derive_key(&key_material, &[1u8; 100]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(derived_key.key_type(), KeyType::Unknown); assert_eq!(derived_key.security_strength(), SecurityStrength::None); // less entropy than needed -- various permutations, but not exhaustive let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHA3_256::new().derive_key(&key_material, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(derived_key.key_type(), KeyType::Unknown); assert_eq!(derived_key.security_strength(), SecurityStrength::None); let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) .unwrap(); let keys = [&key_material, &key_material]; let derived_key = SHA3_512::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(derived_key.key_type(), KeyType::Unknown); assert_eq!(derived_key.security_strength(), SecurityStrength::None); let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHA3_224::new().derive_key(&key_material, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(derived_key.key_type(), KeyType::Unknown); assert_eq!(derived_key.security_strength(), SecurityStrength::None); let key_low_entropy = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::BytesLowEntropy) - .unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Unknown).unwrap(); let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) .unwrap(); let keys = [&key_material, &key_low_entropy]; let derived_key = SHA3_256::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(derived_key.key_type(), KeyType::Unknown); assert_eq!(derived_key.security_strength(), SecurityStrength::None); } @@ -368,11 +367,11 @@ mod sha3_tests { // This works because we explicitly tag the input data as BytesFullEntropy. // This is the preferred and better way to do it. let input_seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::CryptographicRandom) .expect("Error happened"); let output_seed = SHA3_256::new().derive_key(&input_seed, b"nytimes.com").expect("Error happened"); - assert_eq!(output_seed.key_type(), KeyType::BytesFullEntropy); + assert_eq!(output_seed.key_type(), KeyType::CryptographicRandom); } #[test] diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index e87ea0d..edbca2d 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -161,27 +161,27 @@ mod shake_tests { fn kdf_input_entropy() { // Exact entropy let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHAKE128::new().derive_key(&key_material, &[0u8; 0]).unwrap(); let expected_key = KeyMaterial256::from_bytes(b"\x06\x6a\x36\x1d\xc6\x75\xf8\x56\xce\xcd\xc0\x2b\x25\x21\x8a\x10\xce\xc0\xce\xcf\x79\x85\x9e\xc0\xfe\xc3\xd4\x09\xe5\x84\x7a\x92").unwrap(); assert_eq!(derived_key.ref_to_bytes(), expected_key.ref_to_bytes()); - assert_eq!(derived_key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(derived_key.key_type(), KeyType::CryptographicRandom); // more entropy than needed -- single input key let key_material = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::BytesFullEntropy) + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHAKE128::new().derive_key(&key_material, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(derived_key.key_type(), KeyType::CryptographicRandom); // // more entropy than needed -- multiple input keys let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) .unwrap(); let keys = [&key_material, &key_material]; let derived_key = SHAKE128::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesFullEntropy); + assert_eq!(derived_key.key_type(), KeyType::CryptographicRandom); // more entropy than needed -- multiple input keys of different full-entropy types; // should get the type of the first one @@ -197,33 +197,32 @@ mod shake_tests { // // less entropy than needed -- various permutations, but not exhaustive let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..31], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..31], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHAKE128::new().derive_key(&key_material, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(derived_key.key_type(), KeyType::Unknown); let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) .unwrap(); let keys = [&key_material, &key_material]; let derived_key = SHAKE256::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(derived_key.key_type(), KeyType::Unknown); let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHAKE128::new().derive_key(&key_material, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(derived_key.key_type(), KeyType::Unknown); let key_low_entropy = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::BytesLowEntropy) - .unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Unknown).unwrap(); let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::BytesFullEntropy) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) .unwrap(); let keys = [&key_material, &key_low_entropy]; let derived_key = SHAKE128::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); - assert_eq!(derived_key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(derived_key.key_type(), KeyType::Unknown); } #[test] diff --git a/crypto/utils/tests/test_utils.rs b/crypto/utils/tests/test_utils.rs index e239bbc..13fda29 100644 --- a/crypto/utils/tests/test_utils.rs +++ b/crypto/utils/tests/test_utils.rs @@ -16,7 +16,7 @@ fn test_max_min() { // Test with KeyMaterial KeyTypes assert_eq!( - *max(&KeyType::BytesLowEntropy, &KeyType::BytesFullEntropy), - KeyType::BytesFullEntropy + *max(&KeyType::Unknown, &KeyType::CryptographicRandom), + KeyType::CryptographicRandom ); } From 19ea35f074a1bbff9611306cff070da406c504af Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 30 Jun 2026 21:39:23 -0500 Subject: [PATCH 20/35] initial trait defn for SerializableState --- crypto/core-test-framework/src/lib.rs | 1 + .../src/serializable_state.rs | 43 +++++++++ crypto/core/src/errors.rs | 5 ++ crypto/core/src/lib.rs | 1 + crypto/core/src/serializable_state.rs | 90 +++++++++++++++++++ crypto/core/src/traits.rs | 28 +++++- 6 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 crypto/core-test-framework/src/serializable_state.rs create mode 100644 crypto/core/src/serializable_state.rs diff --git a/crypto/core-test-framework/src/lib.rs b/crypto/core-test-framework/src/lib.rs index be769ae..b169dfa 100644 --- a/crypto/core-test-framework/src/lib.rs +++ b/crypto/core-test-framework/src/lib.rs @@ -13,6 +13,7 @@ pub mod hash; pub mod kdf; pub mod kem; pub mod mac; +pub mod serializable_state; pub mod signature; mod fixed_seed_rng; diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/serializable_state.rs new file mode 100644 index 0000000..4968905 --- /dev/null +++ b/crypto/core-test-framework/src/serializable_state.rs @@ -0,0 +1,43 @@ +use bouncycastle_core::errors::CoreError; +use bouncycastle_core::serializable_state::{LIB_VERSION}; +use bouncycastle_core::traits::{SerializableState}; + +pub struct TestFrameworkSerializableState { } + +impl TestFrameworkSerializableState { + pub fn new() -> Self { + Self { } + } + + /// Test all the members of trait SerializableState. + /// + /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. + pub fn test>( + &self, + instance: &S, + ) { + // There's not a lot we can test here in the abstract, but we can test a few things to + // ensure that the SerializableState trait has been impl'd correctly. + + // You can serialize and then deserialize the state. + let serialized_state = instance.serialize_state(); + assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN); + + let _deserialized_state = S::from_serialized_state(serialized_state).unwrap(); + + + // The serialized state MUST include a prefix indicating the current version of the library. + assert_eq!(serialized_state[..3], LIB_VERSION); + + + // All implementations MUST reject a serialized state from lib ver 0.0.0 + // This doesn't really serve any purpose except testing that all impl's have properly + // used the helper functions. + let mut busted_serialized_state = serialized_state.clone(); + busted_serialized_state[..3].copy_from_slice(&[0, 0, 0]); + match S::from_serialized_state(busted_serialized_state) { + Err(CoreError::IncompatibleVersion) => { /* good */ }, + _ => { panic!("Expected IncompatibleVersion error") } + } + } +} diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index 1334cdb..e20afb2 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -1,3 +1,8 @@ +#[derive(Debug)] +pub enum CoreError { + IncompatibleVersion, +} + #[derive(Debug)] pub enum HashError { GenericError(&'static str), diff --git a/crypto/core/src/lib.rs b/crypto/core/src/lib.rs index 379e54c..9516907 100644 --- a/crypto/core/src/lib.rs +++ b/crypto/core/src/lib.rs @@ -7,4 +7,5 @@ pub mod errors; pub mod key_material; +pub mod serializable_state; pub mod traits; diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/serializable_state.rs new file mode 100644 index 0000000..c9ddb87 --- /dev/null +++ b/crypto/core/src/serializable_state.rs @@ -0,0 +1,90 @@ +//! Helper functions for standardizing serialization and deserialization of stateful objects. + +use crate::errors::CoreError; + +/// The current library version. +// There is almost certainly a more elegant way to do this. +pub const LIB_VERSION: [u8; 3] = [0, 1, 2]; + +/// Compare two library semantic versions in the standard C format: +/// * if a < b => -1 +///* if a == b => 0 +///* if a > b => 1 +pub fn cmp_lib_ver(a: &[u8; 3], b: &[u8; 3]) -> i8 { + if a[0] < b[0] { + -1 + } else if a[0] > b[0] { + 1 + } + // first component is equal + else if a[1] < b[1] { + -1 + } else if a[1] > b[1] { + 1 + } + // first two components are equal + else if a[2] < b[2] { + -1 + } else if a[2] > b[2] { + 1 + } + // all three components are equal + else { + 0 + } +} + +#[test] +fn test_cmp_lib_ver() { + assert_eq!(cmp_lib_ver(&[0, 2, 1], &[1, 1, 1]), -1); + assert_eq!(cmp_lib_ver(&[2, 1, 1], &[1, 1, 1]), 1); + assert_eq!(cmp_lib_ver(&[1, 0, 2], &[1, 1, 1]), -1); + assert_eq!(cmp_lib_ver(&[1, 2, 0], &[1, 1, 1]), 1); + assert_eq!(cmp_lib_ver(&[1, 1, 0], &[1, 1, 1]), -1); + assert_eq!(cmp_lib_ver(&[1, 1, 2], &[1, 1, 1]), 1); + assert_eq!(cmp_lib_ver(&[1, 1, 1], &[1, 1, 1]), 0); +} + +/// A helper for serializing an object's state +/// +/// The state array must have length SERIALIZED_LEN - 3 to account for adding the 3-byte symver tag. +pub fn add_lib_ver(state: &[u8]) -> [u8; SERIALIZED_LEN] { + assert_eq!(state.len(), SERIALIZED_LEN - 3); + + let mut out = [0u8; SERIALIZED_LEN]; + out[..3].copy_from_slice(&LIB_VERSION); + out[3..].copy_from_slice(state); + + out +} + +/// A helper for deserializing an object's state +/// +/// The state_out array must have length at least SERIALIZED_LEN - 3. +/// +/// Returns the number of bytes written to state_out, or a [CoreError::IncompatibleVersion] if the +/// serialized state contains a version header earlier than the specified `not_before` version. +/// +/// Note that for testability, this will always reject if the serialized state contains a version tag +/// of `[0,0,0]`. +pub fn remove_lib_ver( + state_in: &[u8; SERIALIZED_LEN], + state_out: &mut [u8], + not_before: Option<[u8; 3]>, +) -> Result { + assert!(state_out.len() >= SERIALIZED_LEN - 3); + + let ver: [u8; 3] = state_in[..3].try_into().unwrap(); + + let not_before = not_before.unwrap_or([0, 0, 0]); + + if cmp_lib_ver(&ver, ¬_before) < 0 { + return Err(CoreError::IncompatibleVersion); + }; + if ver == [0, 0, 0] { + return Err(CoreError::IncompatibleVersion); + }; + + state_out.copy_from_slice(&state_in[3..]); + Ok(SERIALIZED_LEN - 3) +} diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 81fa854..ddfd62e 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -1,6 +1,6 @@ //! Provides simplified abstracted APIs over classes of cryptigraphic primitives, such as Hash, KDF, etc. -use crate::errors::{HashError, KDFError, KEMError, MACError, RNGError, SignatureError}; +use crate::errors::{CoreError, HashError, KDFError, KEMError, MACError, RNGError, SignatureError}; use crate::key_material::KeyMaterialTrait; use core::fmt::{Debug, Display}; use core::marker::Sized; @@ -37,7 +37,6 @@ pub trait Hash: Default { /// Provide a chunk of data to be absorbed into the hashes. /// `data` can be of any length, including zero bytes. /// do_update() is intended to be used as part of a streaming interface, and so may by called multiple times. - // fn do_update(&mut self, data: &[u8]) -> Result<(), HashError>; fn do_update(&mut self, data: &[u8]); /// Finish absorbing input and produce the hashes output. @@ -470,6 +469,31 @@ pub trait RNG { #[allow(drop_bounds)] pub trait Secret: Drop + Debug + Display {} +/// Allows a stateful object to serialize its state so that it can be paused and resumed later, +/// potentially from a different host. +/// +/// This is intended for situations where an object is being used through its streaming API +/// (do_update, do_final) and the operation wants to be paused to a cache, for example while waiting +/// for network IO. +/// +/// This is not intended as a mechanism to clone the state of an object since in most cases `.clone()` +/// will be more straightforward. +pub trait SerializableState: Sized { + /// Serialize the state of the object. + /// + /// The serialized state MUST include a prefix indicating the version of the library that serialized it. + fn serialize_state(&self) -> [u8; SERIALIZED_STATE_LEN]; + + /// Create a new object from a serialized state. + /// + /// Deserializers SHOULD check the version and reject serialized states from incompatible versions. + /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its + /// deserializer should reject serialized states from that version or older. + fn from_serialized_state( + serialized_state: [u8; SERIALIZED_STATE_LEN], + ) -> Result; +} + /// 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 PHSigner< From 64365de735ce3c72fdb0fd92d5726fbba4f57ccd Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 30 Jun 2026 22:42:22 -0500 Subject: [PATCH 21/35] sha2 impls SerializableState --- crypto/core/src/errors.rs | 1 + crypto/core/src/serializable_state.rs | 31 ++++------ crypto/sha2/Cargo.toml | 1 + crypto/sha2/src/lib.rs | 6 +- crypto/sha2/src/sha256.rs | 81 +++++++++++++++++++++++---- crypto/sha2/src/sha512.rs | 81 ++++++++++++++++++++++++--- crypto/sha2/tests/sha2_tests.rs | 63 +++++++++++++++++++++ 7 files changed, 224 insertions(+), 40 deletions(-) diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index e20afb2..5051443 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -1,6 +1,7 @@ #[derive(Debug)] pub enum CoreError { IncompatibleVersion, + InvalidData, } #[derive(Debug)] diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/serializable_state.rs index c9ddb87..3015e24 100644 --- a/crypto/core/src/serializable_state.rs +++ b/crypto/core/src/serializable_state.rs @@ -45,17 +45,12 @@ fn test_cmp_lib_ver() { assert_eq!(cmp_lib_ver(&[1, 1, 1], &[1, 1, 1]), 0); } -/// A helper for serializing an object's state +/// Puts the library version into the first three bytes of the state array. /// -/// The state array must have length SERIALIZED_LEN - 3 to account for adding the 3-byte symver tag. -pub fn add_lib_ver(state: &[u8]) -> [u8; SERIALIZED_LEN] { - assert_eq!(state.len(), SERIALIZED_LEN - 3); - - let mut out = [0u8; SERIALIZED_LEN]; - out[..3].copy_from_slice(&LIB_VERSION); - out[3..].copy_from_slice(state); - - out +/// Hands back a slice to the same array, starting after the version tag. +pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN]) -> &mut [u8] { + state[..3].copy_from_slice(&LIB_VERSION); + &mut state[3..] } /// A helper for deserializing an object's state @@ -67,14 +62,13 @@ pub fn add_lib_ver(state: &[u8]) -> [u8; SERIALIZED /// /// Note that for testability, this will always reject if the serialized state contains a version tag /// of `[0,0,0]`. -pub fn remove_lib_ver( - state_in: &[u8; SERIALIZED_LEN], - state_out: &mut [u8], +/// +/// Hands back a slice to the same array, starting after the version tag. +pub fn check_lib_ver( + state: &[u8; SERIALIZED_LEN], not_before: Option<[u8; 3]>, -) -> Result { - assert!(state_out.len() >= SERIALIZED_LEN - 3); - - let ver: [u8; 3] = state_in[..3].try_into().unwrap(); +) -> Result<&[u8], CoreError> { + let ver: [u8; 3] = state[..3].try_into().unwrap(); let not_before = not_before.unwrap_or([0, 0, 0]); @@ -85,6 +79,5 @@ pub fn remove_lib_ver( return Err(CoreError::IncompatibleVersion); }; - state_out.copy_from_slice(&state_in[3..]); - Ok(SERIALIZED_LEN - 3) + Ok(&state[3..]) } diff --git a/crypto/sha2/Cargo.toml b/crypto/sha2/Cargo.toml index a620a6a..ae8a52d 100644 --- a/crypto/sha2/Cargo.toml +++ b/crypto/sha2/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true bouncycastle-utils.workspace = true +serde = { version = "1.0.228", features = ["derive"] } [dev-dependencies] criterion.workspace = true diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index b3582fc..d8a4544 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -41,7 +41,7 @@ mod sha256; mod sha512; pub use self::sha256::SHA256Internal; -pub use self::sha512::Sha512Internal; +pub use self::sha512::SHA512Internal; use bouncycastle_core::traits::{Algorithm, HashAlgParams, SecurityStrength}; /*** String constants ***/ @@ -53,8 +53,8 @@ pub const SHA512_NAME: &str = "SHA512"; /*** pub types ***/ pub type SHA224 = SHA256Internal; pub type SHA256 = SHA256Internal; -pub type SHA384 = Sha512Internal; -pub type SHA512 = Sha512Internal; +pub type SHA384 = SHA512Internal; +pub type SHA512 = SHA512Internal; /*** Param traits ***/ diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index 7be7f47..6995642 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -1,6 +1,7 @@ use crate::SHA2Params; -use bouncycastle_core::errors::HashError; -use bouncycastle_core::traits::{Hash, SecurityStrength}; +use bouncycastle_core::errors::{CoreError, HashError}; +use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; use bouncycastle_utils::min; use core::slice; @@ -45,11 +46,9 @@ fn theta1(x: u32) -> u32 { x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10) } -// todo -- cleanup -// #[derive(Clone, Copy)] #[derive(Clone)] pub(crate) struct Sha256State { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, h: [u32; 8], } @@ -63,7 +62,7 @@ impl Sha256State { pub(crate) fn new() -> Self { match PARAMS::OUTPUT_LEN * 8 { 224 => Self { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, h: [ 0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939, 0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4, @@ -145,11 +144,9 @@ impl Sha256State { } } -// todo -- cleanup -// #[derive(Clone, Copy)] #[derive(Clone)] pub struct SHA256Internal { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, state: Sha256State, byte_count: u64, x_buf: [u8; 64], @@ -166,7 +163,7 @@ impl Drop for SHA256Internal { impl SHA256Internal { pub fn new() -> Self { Self { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, state: Sha256State::::new(), byte_count: 0, x_buf: [0; 64], @@ -306,3 +303,67 @@ impl Hash for SHA256Internal { SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2) } } + +impl SerializableState<108> for SHA256Internal { + fn serialize_state(&self) -> [u8; 108] { + let mut out_to_return = [0u8; 108]; + + // insert the version tag + let out: &mut [u8; 105] = add_lib_ver(&mut out_to_return).try_into().unwrap(); + + // state.h: [u32; 8] + // 4 * 8 = 32 + for i in 0..8 { + out[i * 4..(i * 4) + 4].copy_from_slice(&self.state.h[i].to_le_bytes()); + } + + // byte_count: u64 + out[32..40].copy_from_slice(&self.byte_count.to_le_bytes()); + + // x_buf: [u8; 64] + out[40..104].copy_from_slice(&self.x_buf); + + // x_buf_off: usize + // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 64 + debug_assert!(self.x_buf_off < 64); + out[104] = self.x_buf_off as u8; + + out_to_return + } + + fn from_serialized_state(serialized_state: [u8; 108]) -> Result { + // check the version tag + // At the moment, we have no not_before version to specify. + let input: &[u8; 105] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + // state.h: [u32; 8] + // 4 * 8 = 32 + let mut h = [0u32; 8]; + for i in 0..8 { + h[i] = u32::from_le_bytes(input[i * 4..(i * 4) + 4].try_into().unwrap()); + } + + // byte_count: u64 + let byte_count: u64 = u64::from_le_bytes(input[32..40].try_into().unwrap()); + + // x_buf: [u8; 64] + let x_buf: [u8; 64] = input[40..104].try_into().unwrap(); + + // x_buf_off: usize + // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 64 + let x_buf_off: usize = input[104] as usize; + if x_buf_off >= 64 { + return Err(CoreError::InvalidData); + } + + // Construct the object + let state = Sha256State { _params: core::marker::PhantomData, h }; + Ok(SHA256Internal { + _params: core::marker::PhantomData, + state, + byte_count, + x_buf, + x_buf_off, + }) + } +} diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index b4ae990..49aed8b 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,6 +1,7 @@ -use crate::SHA2Params; -use bouncycastle_core::errors::HashError; -use bouncycastle_core::traits::{Hash, SecurityStrength}; +use crate::{SHA2Params}; +use bouncycastle_core::errors::{CoreError, HashError}; +use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; use bouncycastle_utils::min; use core::slice; @@ -160,7 +161,7 @@ impl Sha512State { // todo -- cleanup // #[derive(Clone, Copy)] #[derive(Clone)] -pub struct Sha512Internal { +pub struct SHA512Internal { _params: std::marker::PhantomData, state: Sha512State, byte_count: u64, // NOTE We only support 2^67 bits, not the full 2^128 @@ -168,13 +169,13 @@ pub struct Sha512Internal { x_buf_off: usize, } -impl Drop for Sha512Internal { +impl Drop for SHA512Internal { fn drop(&mut self) { self.x_buf.fill(0); } } -impl Sha512Internal { +impl SHA512Internal { pub fn new() -> Self { Self { _params: std::marker::PhantomData, @@ -186,13 +187,13 @@ impl Sha512Internal { } } -impl Default for Sha512Internal { +impl Default for SHA512Internal { fn default() -> Self { Self::new() } } -impl Hash for Sha512Internal { +impl Hash for SHA512Internal { /// As per FIPS 180-4 Figure 1 fn block_bitlen(&self) -> usize { 1024 @@ -317,3 +318,67 @@ impl Hash for Sha512Internal { SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2) } } + +impl SerializableState<204> for SHA512Internal { + fn serialize_state(&self) -> [u8; 204] { + let mut out_to_return = [0u8; 204]; + + // insert the version tag + let out: &mut [u8; 201] = add_lib_ver(&mut out_to_return).try_into().unwrap(); + + // state.h: [u64; 8] + // 8 * 8 = 64 + for i in 0..8 { + out[i * 8..(i * 8) + 8].copy_from_slice(&self.state.h[i].to_le_bytes()); + } + + // byte_count: u64 + out[64..72].copy_from_slice(&self.byte_count.to_le_bytes()); + + // x_buf: [u8; 128] + out[72..200].copy_from_slice(&self.x_buf); + + // x_buf_off: usize + // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 128 + debug_assert!(self.x_buf_off < 128); + out[200] = self.x_buf_off as u8; + + out_to_return + } + + fn from_serialized_state(serialized_state: [u8; 204]) -> Result { + // check the version tag + // At the moment, we have no not_before version to specify. + let input: &[u8; 201] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + // state.h: [u64; 8] + // 8 * 8 = 64 + let mut h = [0u64; 8]; + for i in 0..8 { + h[i] = u64::from_le_bytes(input[i * 8..(i * 8) + 8].try_into().unwrap()); + } + + // byte_count: u64 + let byte_count: u64 = u64::from_le_bytes(input[64..72].try_into().unwrap()); + + // x_buf: [u8; 128] + let x_buf: [u8; 128] = input[72..200].try_into().unwrap(); + + // x_buf_off: usize + // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 128 + let x_buf_off: usize = input[200] as usize; + if x_buf_off >= 128 { + return Err(CoreError::InvalidData); + } + + // Construct the object + let state = Sha512State { _params: core::marker::PhantomData, h }; + Ok(SHA512Internal { + _params: core::marker::PhantomData, + state, + byte_count, + x_buf, + x_buf_off, + }) + } +} diff --git a/crypto/sha2/tests/sha2_tests.rs b/crypto/sha2/tests/sha2_tests.rs index 7d3e239..442b94b 100644 --- a/crypto/sha2/tests/sha2_tests.rs +++ b/crypto/sha2/tests/sha2_tests.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod sha2_tests { + use bouncycastle_core::errors::CoreError; use bouncycastle_core::traits::{Algorithm, Hash, HashAlgParams, SecurityStrength}; use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::hash::TestFrameworkHash; @@ -89,4 +90,66 @@ mod sha2_tests { assert_eq!(SHA384::default().max_security_strength(), SecurityStrength::_192bit); assert_eq!(SHA512::default().max_security_strength(), SecurityStrength::_256bit); } + + #[test] + fn test_serializable_state() { + use bouncycastle_core::traits::SerializableState; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + + let str = "Colorless green ideas sleep furiously"; + + // SHA256 + let mut sha256 = SHA256::new(); + sha256.do_update(str.as_bytes()); + + // do the default tests + let test_framework = TestFrameworkSerializableState::new(); + test_framework.test(&sha256); + + // now let's serialize the in-progress state + let serialized_state = sha256.serialize_state(); + + // finish the hash + let output = sha256.do_final(); + + // then load from state and finish the hash and make sure we get the same thing + let sha2_from_state = SHA256::from_serialized_state(serialized_state).unwrap(); + let output2 = sha2_from_state.do_final(); + assert_eq!(output, output2); + + // also, give it a busted x_buf_off, just to satisfy mutants that that's been tested + let mut busted_state = serialized_state.clone(); + busted_state[3 + 104] = 65; + match SHA256::from_serialized_state(busted_state) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error"), + } + + // SHA512 + let mut sha512 = SHA512::new(); + sha512.do_update(str.as_bytes()); + + // do the default tests + let test_framework = TestFrameworkSerializableState::new(); + test_framework.test(&sha512); + + // now let's serialize the in-progress state + let serialized_state = sha512.serialize_state(); + + // finish the hash + let output = sha512.do_final(); + + // then load from state and finish the hash and make sure we get the same thing + let sha2_from_state = SHA512::from_serialized_state(serialized_state).unwrap(); + let output2 = sha2_from_state.do_final(); + assert_eq!(output, output2); + + // also, give it a busted x_buf_off, just to satisfy mutants that that's been tested + let mut busted_state = serialized_state.clone(); + busted_state[3 + 200] = 129; + match SHA512::from_serialized_state(busted_state) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error"), + } + } } From f75bc5d65e2417803cbc3a691ef30c1b2d632258 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 30 Jun 2026 22:43:54 -0500 Subject: [PATCH 22/35] rustfmt --- .../src/serializable_state.rs | 26 +++++++++---------- crypto/sha2/src/sha512.rs | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/serializable_state.rs index 4968905..f79c496 100644 --- a/crypto/core-test-framework/src/serializable_state.rs +++ b/crypto/core-test-framework/src/serializable_state.rs @@ -1,16 +1,16 @@ use bouncycastle_core::errors::CoreError; -use bouncycastle_core::serializable_state::{LIB_VERSION}; -use bouncycastle_core::traits::{SerializableState}; +use bouncycastle_core::serializable_state::LIB_VERSION; +use bouncycastle_core::traits::SerializableState; -pub struct TestFrameworkSerializableState { } +pub struct TestFrameworkSerializableState {} impl TestFrameworkSerializableState { pub fn new() -> Self { - Self { } + Self {} } /// Test all the members of trait SerializableState. - /// + /// /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. pub fn test>( &self, @@ -18,26 +18,26 @@ impl TestFrameworkSerializableState { ) { // There's not a lot we can test here in the abstract, but we can test a few things to // ensure that the SerializableState trait has been impl'd correctly. - + // You can serialize and then deserialize the state. let serialized_state = instance.serialize_state(); assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN); - + let _deserialized_state = S::from_serialized_state(serialized_state).unwrap(); - - + // The serialized state MUST include a prefix indicating the current version of the library. assert_eq!(serialized_state[..3], LIB_VERSION); - - + // All implementations MUST reject a serialized state from lib ver 0.0.0 // This doesn't really serve any purpose except testing that all impl's have properly // used the helper functions. let mut busted_serialized_state = serialized_state.clone(); busted_serialized_state[..3].copy_from_slice(&[0, 0, 0]); match S::from_serialized_state(busted_serialized_state) { - Err(CoreError::IncompatibleVersion) => { /* good */ }, - _ => { panic!("Expected IncompatibleVersion error") } + Err(CoreError::IncompatibleVersion) => { /* good */ } + _ => { + panic!("Expected IncompatibleVersion error") + } } } } diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 49aed8b..0c5ed13 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,4 +1,4 @@ -use crate::{SHA2Params}; +use crate::SHA2Params; use bouncycastle_core::errors::{CoreError, HashError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; From 54f93f65b03b233a2d230c44a0fc4c8d1934a1bc Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sun, 5 Jul 2026 22:49:25 -0500 Subject: [PATCH 23/35] Incorporating Nikola and David's feedback --- .../src/serializable_state.rs | 24 +++-- crypto/core/src/serializable_state.rs | 87 ++++++++++--------- crypto/core/src/traits.rs | 3 +- crypto/sha2/Cargo.toml | 1 - 4 files changed, 69 insertions(+), 46 deletions(-) diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/serializable_state.rs index f79c496..b8b7f63 100644 --- a/crypto/core-test-framework/src/serializable_state.rs +++ b/crypto/core-test-framework/src/serializable_state.rs @@ -1,5 +1,5 @@ use bouncycastle_core::errors::CoreError; -use bouncycastle_core::serializable_state::LIB_VERSION; +use bouncycastle_core::serializable_state::{LIB_VERSION, SemVer}; use bouncycastle_core::traits::SerializableState; pub struct TestFrameworkSerializableState {} @@ -26,14 +26,28 @@ impl TestFrameworkSerializableState { let _deserialized_state = S::from_serialized_state(serialized_state).unwrap(); // The serialized state MUST include a prefix indicating the current version of the library. - assert_eq!(serialized_state[..3], LIB_VERSION); + let state_sized: [u8; 3] = serialized_state[..3].try_into().unwrap(); + assert_eq!(SemVer::from(state_sized), LIB_VERSION); // All implementations MUST reject a serialized state from lib ver 0.0.0 // This doesn't really serve any purpose except testing that all impl's have properly // used the helper functions. - let mut busted_serialized_state = serialized_state.clone(); - busted_serialized_state[..3].copy_from_slice(&[0, 0, 0]); - match S::from_serialized_state(busted_serialized_state) { + let mut ver0_serialized_state = serialized_state.clone(); + ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); + match S::from_serialized_state(ver0_serialized_state) { + Err(CoreError::IncompatibleVersion) => { /* good */ } + _ => { + panic!("Expected IncompatibleVersion error") + } + } + + // All implementations MUST reject a serialized state from a future version. + let mut future_ver = LIB_VERSION; + future_ver.patch += 1; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + match S::from_serialized_state(futurever_serialized_state) { Err(CoreError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/serializable_state.rs index 3015e24..819a4f3 100644 --- a/crypto/core/src/serializable_state.rs +++ b/crypto/core/src/serializable_state.rs @@ -2,54 +2,56 @@ use crate::errors::CoreError; -/// The current library version. -// There is almost certainly a more elegant way to do this. -pub const LIB_VERSION: [u8; 3] = [0, 1, 2]; +/// A semantic library version, ordered by `major`, then `minor`, then `patch`. +/// +/// The field declaration order matters: the derived [`Ord`]/[`PartialOrd`] compare fields +/// lexicographically in declaration order, which is exactly semantic-version precedence. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct SemVer { + pub major: u8, + pub minor: u8, + pub patch: u8, + // A semantic version can often also take a suffix, e.g. "alpha", "beta", "rc1", etc. + // We're not going to model that here because it's not useful for versioning serialized states. +} -/// Compare two library semantic versions in the standard C format: -/// * if a < b => -1 -///* if a == b => 0 -///* if a > b => 1 -pub fn cmp_lib_ver(a: &[u8; 3], b: &[u8; 3]) -> i8 { - if a[0] < b[0] { - -1 - } else if a[0] > b[0] { - 1 +impl From<[u8; 3]> for SemVer { + fn from(v: [u8; 3]) -> Self { + SemVer { major: v[0], minor: v[1], patch: v[2] } } - // first component is equal - else if a[1] < b[1] { - -1 - } else if a[1] > b[1] { - 1 - } - // first two components are equal - else if a[2] < b[2] { - -1 - } else if a[2] > b[2] { - 1 - } - // all three components are equal - else { - 0 +} + +impl From for [u8; 3] { + fn from(v: SemVer) -> Self { + [v.major, v.minor, v.patch] } } +/// The current library version. +// There is almost certainly a more elegant way to do this. +pub const LIB_VERSION: SemVer = SemVer { major: 0, minor: 1, patch: 2 }; + #[test] fn test_cmp_lib_ver() { - assert_eq!(cmp_lib_ver(&[0, 2, 1], &[1, 1, 1]), -1); - assert_eq!(cmp_lib_ver(&[2, 1, 1], &[1, 1, 1]), 1); - assert_eq!(cmp_lib_ver(&[1, 0, 2], &[1, 1, 1]), -1); - assert_eq!(cmp_lib_ver(&[1, 2, 0], &[1, 1, 1]), 1); - assert_eq!(cmp_lib_ver(&[1, 1, 0], &[1, 1, 1]), -1); - assert_eq!(cmp_lib_ver(&[1, 1, 2], &[1, 1, 1]), 1); - assert_eq!(cmp_lib_ver(&[1, 1, 1], &[1, 1, 1]), 0); + use core::cmp::Ordering; + + assert!([0, 0, 0] < [0, 0, 1]); + + let cmp = |a: [u8; 3], b: [u8; 3]| SemVer::from(a).cmp(&SemVer::from(b)); + assert_eq!(cmp([0, 2, 1], [1, 1, 1]), Ordering::Less); + assert_eq!(cmp([2, 1, 1], [1, 1, 1]), Ordering::Greater); + assert_eq!(cmp([1, 0, 2], [1, 1, 1]), Ordering::Less); + assert_eq!(cmp([1, 2, 0], [1, 1, 1]), Ordering::Greater); + assert_eq!(cmp([1, 1, 0], [1, 1, 1]), Ordering::Less); + assert_eq!(cmp([1, 1, 2], [1, 1, 1]), Ordering::Greater); + assert_eq!(cmp([1, 1, 1], [1, 1, 1]), Ordering::Equal); } /// Puts the library version into the first three bytes of the state array. /// /// Hands back a slice to the same array, starting after the version tag. pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN]) -> &mut [u8] { - state[..3].copy_from_slice(&LIB_VERSION); + state[..3].copy_from_slice(&<[u8; 3]>::from(LIB_VERSION)); &mut state[3..] } @@ -68,16 +70,23 @@ pub fn check_lib_ver( state: &[u8; SERIALIZED_LEN], not_before: Option<[u8; 3]>, ) -> Result<&[u8], CoreError> { - let ver: [u8; 3] = state[..3].try_into().unwrap(); + let ver_bytes: [u8; 3] = state[..3].try_into().unwrap(); + let ver = SemVer::from(ver_bytes); - let not_before = not_before.unwrap_or([0, 0, 0]); + let not_before = SemVer::from(not_before.unwrap_or([0, 0, 0])); - if cmp_lib_ver(&ver, ¬_before) < 0 { + if ver < not_before { return Err(CoreError::IncompatibleVersion); }; - if ver == [0, 0, 0] { + // Nothing is ever compatible with [0,0,0] + if ver == SemVer::from([0, 0, 0]) { return Err(CoreError::IncompatibleVersion); }; + // Also not compatible with future versions. + if ver > LIB_VERSION { + return Err(CoreError::IncompatibleVersion); + } + Ok(&state[3..]) } diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index ddfd62e..9a06df2 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -486,7 +486,8 @@ pub trait SerializableState: Sized { /// Create a new object from a serialized state. /// - /// Deserializers SHOULD check the version and reject serialized states from incompatible versions. + /// Deserializers SHOULD check the version and reject serialized states from incompatible versions + /// (including rejecting serializations from a future version of the library). /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its /// deserializer should reject serialized states from that version or older. fn from_serialized_state( diff --git a/crypto/sha2/Cargo.toml b/crypto/sha2/Cargo.toml index ae8a52d..a620a6a 100644 --- a/crypto/sha2/Cargo.toml +++ b/crypto/sha2/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true bouncycastle-utils.workspace = true -serde = { version = "1.0.228", features = ["derive"] } [dev-dependencies] criterion.workspace = true From 46100d3c1a07faa9486632a5bfb5b3704273c816 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 6 Jul 2026 00:18:05 -0500 Subject: [PATCH 24/35] An in-progress SHA3 object can now be serialized --- crypto/core/src/key_material.rs | 36 +++++-- crypto/core/src/traits.rs | 31 +++++-- crypto/sha3/src/keccak.rs | 155 +++++++++++++++++++++++++++++-- crypto/sha3/src/lib.rs | 12 +++ crypto/sha3/src/sha3.rs | 53 ++++++++++- crypto/sha3/src/shake.rs | 53 ++++++++++- crypto/sha3/tests/sha3_tests.rs | 57 +++++++++++- crypto/sha3/tests/shake_tests.rs | 59 +++++++++++- 8 files changed, 429 insertions(+), 27 deletions(-) diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index 2d1db76..da8697e 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -50,7 +50,7 @@ //! //! See [do_hazardous_operations] for documentation and sample code. -use crate::errors::KeyMaterialError; +use crate::errors::{CoreError, KeyMaterialError}; use crate::traits::{RNG, Secret, SecurityStrength}; use bouncycastle_utils::{ct, min}; @@ -221,10 +221,15 @@ pub struct KeyMaterial { impl Secret for KeyMaterial {} +// The explicit `#[repr(u8)]` discriminants are the stable on-the-wire encoding used by +// `SerializableState` implementations (see the `TryFrom` impl below). Pin each value to its +// variant name: reordering variants is fine, but never reuse or renumber an existing discriminant, +// or previously-serialized states will be misread. #[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] pub enum KeyType { /// The KeyMaterial is zeroized and MUST NOT be used for any cryptographic operation in this state. - Zeroized, + Zeroized = 0, /// The KeyMaterial contains non-zero data of unknown key type. /// A KeyMaterial of key type Unknown will always have a [SecurityStrength] of [SecurityStrength::None]. @@ -234,19 +239,36 @@ pub enum KeyType { /// and must be done within a [do_hazardous_operations] closure. /// If you want to import key material directly into a known key type, use [KeyMaterial::from_bytes_as_type], /// which does not require a hazardous operations closure. - Unknown, + Unknown = 1, /// The KeyMaterial contains data of full entropy and can be safely converted to any other key type. - CryptographicRandom, + CryptographicRandom = 2, /// A seed for asymmetric private keys, RNGs, and other seed-based cryptographic objects. - Seed, + Seed = 3, /// A MAC key. - MACKey, + MACKey = 4, /// A key for a symmetric block or stream cipher. - SymmetricCipherKey, + SymmetricCipherKey = 5, +} + +impl TryFrom for KeyType { + type Error = CoreError; + + /// Inverse of `self as u8`; rejects unrecognized discriminants with [CoreError::InvalidData]. + fn try_from(value: u8) -> Result { + Ok(match value { + 0 => Self::Zeroized, + 1 => Self::Unknown, + 2 => Self::CryptographicRandom, + 3 => Self::Seed, + 4 => Self::MACKey, + 5 => Self::SymmetricCipherKey, + _ => return Err(CoreError::InvalidData), + }) + } } impl Default for KeyMaterial { diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 9a06df2..6ddc0d5 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -386,13 +386,32 @@ pub trait MAC: Sized { fn max_security_strength(&self) -> SecurityStrength; } -#[derive(Eq, PartialEq, PartialOrd, Clone, Debug)] +// The explicit `#[repr(u8)]` discriminants are the stable on-the-wire encoding used by +// `SerializableState` implementations (see the `TryFrom` impl below). +#[derive(Eq, PartialEq, PartialOrd, Clone, Copy, Debug)] +#[repr(u8)] pub enum SecurityStrength { - None, - _112bit, - _128bit, - _192bit, - _256bit, + None = 0, + _112bit = 1, + _128bit = 2, + _192bit = 3, + _256bit = 4, +} + +impl TryFrom for SecurityStrength { + type Error = CoreError; + + /// Inverse of `self as u8`; rejects unrecognized discriminants with [CoreError::InvalidData]. + fn try_from(value: u8) -> Result { + Ok(match value { + 0 => Self::None, + 1 => Self::_112bit, + 2 => Self::_128bit, + 3 => Self::_192bit, + 4 => Self::_256bit, + _ => return Err(CoreError::InvalidData), + }) + } } impl SecurityStrength { diff --git a/crypto/sha3/src/keccak.rs b/crypto/sha3/src/keccak.rs index 5e6f6a8..27fc702 100644 --- a/crypto/sha3/src/keccak.rs +++ b/crypto/sha3/src/keccak.rs @@ -1,4 +1,7 @@ -use bouncycastle_core::errors::HashError; +use bouncycastle_core::errors::{CoreError, HashError}; +use bouncycastle_core::key_material::KeyType; +use bouncycastle_core::traits::SecurityStrength; +use crate::{SHA3_224Params, SHA3_256Params, SHA3_384Params, SHA3_512Params, SHAKE128Params, SHAKE256Params, SHA3, SHAKE}; const KECCAK_ROUND_CONSTANTS: [u64; 24] = [ 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000, @@ -210,11 +213,6 @@ impl KeccakDigest { pub(super) fn new(size: KeccakSize) -> Self { let rate = 1600 - ((size as usize) << 1); - // todo I think this check is not needed since the fixed set of allowed sizes can't yield an invalid rate, but I'll leave this here for now. - // if rate == 0 || rate >= 1600 || (rate & 63) != 0 { - // return Err(HashError::InvalidLength("invalid rate value")); - // } - Self { state: KeccakState::new(rate), data_queue: [0u8; 192], @@ -343,6 +341,151 @@ impl KeccakDigest { } } +/*** State serialization ***/ +// +// The SHA3 and SHAKE public objects have identical state: a [KeccakDigest] plus three pieces of +// KDF metadata. The helpers below serialize that shared state so the `SerializableState` impls in +// `sha3.rs` and `shake.rs` are just thin wrappers that add/check the library version header. + +/// Number of bytes needed to serialize a [KeccakDigest]'s mutable state. +/// +/// The `rate` is intentionally NOT serialized: it is fully determined by the SHA3/SHAKE variant and +/// is re-supplied at deserialization time (see [KeccakDigest::from_serialized_state]). +/// +/// Layout (all integers little-endian): +/// [0 .. 200) state.buf [u64; 25] +/// [200 .. 392) data_queue [u8; 192] +/// [392 .. 400) bits_in_queue usize serialized as u64 +/// [400 .. 401) squeezing bool (0 or 1) +const KECCAK_SERIALIZED_LEN: usize = 200 + 192 + 8 + 1; + +/// Number of bytes needed to serialize the shared SHA3-family state (a variant tag, a [KeccakDigest], +/// plus the three KDF metadata fields), excluding the library version header. +/// +/// The leading variant tag distinguishes every SHA3/SHAKE variant β€” crucially including same-rate +/// pairs such as SHA3-256 and SHAKE256 β€” so a serialized state can never be deserialized into a +/// different algorithm (which would silently apply the wrong domain separation). +/// +/// Layout (all integers little-endian): +/// [0 .. 1) variant tag (see `STATE_TAG` on the param traits) +/// [1 .. 1 + KECCAK_SERIALIZED_LEN) keccak digest state +/// [.. + 1) kdf_key_type (1 byte enum tag) +/// [.. + 1) kdf_security_strength (1 byte enum tag) +/// [.. + 8) kdf_entropy usize serialized as u64 +pub(super) const SHA3_FAMILY_STATE_LEN: usize = 1 + KECCAK_SERIALIZED_LEN + 10; + +/// Total number of bytes in a serialized SHA3-family state, including the 3-byte library version +/// header prepended by [add_lib_ver]. This is the const generic used by the `SerializableState` +/// impls for SHA3 and SHAKE. +pub(super) const SHA3_FAMILY_SERIALIZED_STATE_LEN: usize = 3 + SHA3_FAMILY_STATE_LEN; + +impl KeccakDigest { + /// Serializes this digest's mutable state into `out`. The `rate` is deliberately omitted; see + /// [KECCAK_SERIALIZED_LEN]. + fn serialize_state(&self, out: &mut [u8; KECCAK_SERIALIZED_LEN]) { + // state.buf: [u64; 25] + for i in 0..25 { + out[i * 8..(i * 8) + 8].copy_from_slice(&self.state.buf[i].to_le_bytes()); + } + + // data_queue: [u8; 192] + out[200..392].copy_from_slice(&self.data_queue); + + // bits_in_queue: usize + out[392..400].copy_from_slice(&(self.bits_in_queue as u64).to_le_bytes()); + + // squeezing: bool + out[400] = self.squeezing as u8; + } + + /// Reconstructs a [KeccakDigest] from a state produced by [KeccakDigest::serialize_state]. + /// + /// `rate` is supplied by the caller (derived from its algorithm parameters) rather than read + /// from the serialized bytes, since the rate is fully determined by the SHA3/SHAKE variant. The + /// caller is responsible for having already verified the variant tag so that this `rate` is the + /// correct one for the serialized state. + fn from_serialized_state( + input: &[u8; KECCAK_SERIALIZED_LEN], + rate: usize, + ) -> Result { + // state.buf: [u64; 25] + let mut buf = [0u64; 25]; + for i in 0..25 { + buf[i] = u64::from_le_bytes(input[i * 8..(i * 8) + 8].try_into().unwrap()); + } + + // data_queue: [u8; 192] + let data_queue: [u8; 192] = input[200..392].try_into().unwrap(); + + // bits_in_queue: usize. It can never legitimately exceed the rate (which is at most 168 + // bytes, well within data_queue's 192-byte capacity). + let bits_in_queue = u64::from_le_bytes(input[392..400].try_into().unwrap()) as usize; + if bits_in_queue > rate { + return Err(CoreError::InvalidData); + } + + // squeezing: bool + let squeezing = match input[400] { + 0 => false, + 1 => true, + _ => return Err(CoreError::InvalidData), + }; + + Ok(Self { state: KeccakState { buf, rate }, data_queue, rate, bits_in_queue, squeezing }) + } +} + +/// Serializes the state shared by all SHA3-family objects (the `variant_tag`, a [KeccakDigest], plus +/// the three KDF metadata fields) into `out`. See [SHA3_FAMILY_STATE_LEN] for the layout. +pub(super) fn serialize_sha3_family_state( + out: &mut [u8; SHA3_FAMILY_STATE_LEN], + variant_tag: u8, + keccak: &KeccakDigest, + kdf_key_type: KeyType, + kdf_security_strength: SecurityStrength, + kdf_entropy: usize, +) { + out[0] = variant_tag; + + let keccak_out: &mut [u8; KECCAK_SERIALIZED_LEN] = + (&mut out[1..1 + KECCAK_SERIALIZED_LEN]).try_into().unwrap(); + keccak.serialize_state(keccak_out); + + out[1 + KECCAK_SERIALIZED_LEN] = kdf_key_type as u8; + out[1 + KECCAK_SERIALIZED_LEN + 1] = kdf_security_strength as u8; + out[1 + KECCAK_SERIALIZED_LEN + 2..1 + KECCAK_SERIALIZED_LEN + 10] + .copy_from_slice(&(kdf_entropy as u64).to_le_bytes()); +} + +/// Reconstructs the shared SHA3-family state from a buffer produced by [serialize_sha3_family_state]. +/// +/// `expected_variant_tag` and `rate` are both derived from the caller's algorithm parameters. The +/// tag is checked against the serialized one first: this is what prevents a state from one variant +/// being loaded into another (e.g. SHA3-256 vs SHAKE256, which share a rate but differ in domain +/// separation). Only once the tag matches is `rate` guaranteed to be the correct one to rebuild with. +pub(super) fn deserialize_sha3_family_state( + input: &[u8; SHA3_FAMILY_STATE_LEN], + expected_variant_tag: u8, + rate: usize, +) -> Result<(KeccakDigest, KeyType, SecurityStrength, usize), CoreError> { + if input[0] != expected_variant_tag { + return Err(CoreError::InvalidData); + } + + let keccak_in: &[u8; KECCAK_SERIALIZED_LEN] = + input[1..1 + KECCAK_SERIALIZED_LEN].try_into().unwrap(); + let keccak = KeccakDigest::from_serialized_state(keccak_in, rate)?; + + // KeyType and SecurityStrength each own their canonical 1-byte encoding (`as u8` / `TryFrom`). + let kdf_key_type = KeyType::try_from(input[1 + KECCAK_SERIALIZED_LEN])?; + let kdf_security_strength = SecurityStrength::try_from(input[1 + KECCAK_SERIALIZED_LEN + 1])?; + let kdf_entropy = u64::from_le_bytes( + input[1 + KECCAK_SERIALIZED_LEN + 2..1 + KECCAK_SERIALIZED_LEN + 10].try_into().unwrap(), + ) as usize; + + Ok((keccak, kdf_key_type, kdf_security_strength, kdf_entropy)) +} + #[cfg(test)] mod keccak_tests { use super::*; diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 7ef4a44..3f5a7ca 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -144,6 +144,10 @@ pub type SHAKE256 = SHAKE; /// Private trait on purpose so that only the NIST-approved params can be used. trait SHA3Params: HashAlgParams { const SIZE: KeccakSize; + /// A tag, unique across all SHA3 *and* SHAKE variants, identifying which variant produced a + /// serialized state. Distinguishing same-rate variants (e.g. SHA3-256 vs SHAKE256) requires + /// this to be distinct from every value used by [SHAKEParams::STATE_TAG]. Never reuse a value. + const STATE_TAG: u8; } // TODO: more elegant to macro this? @@ -168,6 +172,7 @@ impl HashAlgParams for SHA3_224Params { } impl SHA3Params for SHA3_224Params { const SIZE: KeccakSize = KeccakSize::_224; + const STATE_TAG: u8 = 1; } impl Algorithm for SHA3_256 { @@ -191,6 +196,7 @@ impl HashAlgParams for SHA3_256Params { } impl SHA3Params for SHA3_256Params { const SIZE: KeccakSize = KeccakSize::_256; + const STATE_TAG: u8 = 2; } pub struct SHA3_384Params; @@ -214,6 +220,7 @@ impl HashAlgParams for SHA3_384Params { } impl SHA3Params for SHA3_384Params { const SIZE: KeccakSize = KeccakSize::_384; + const STATE_TAG: u8 = 3; } pub struct SHA3_512Params; @@ -237,10 +244,13 @@ impl HashAlgParams for SHA3_512Params { } impl SHA3Params for SHA3_512Params { const SIZE: KeccakSize = KeccakSize::_512; + const STATE_TAG: u8 = 4; } trait SHAKEParams: Algorithm { const SIZE: KeccakSize; + /// See [SHA3Params::STATE_TAG]. Must be distinct from every SHA3 *and* SHAKE variant's tag. + const STATE_TAG: u8; } pub struct SHAKE128Params; impl Algorithm for SHAKE128Params { @@ -249,6 +259,7 @@ impl Algorithm for SHAKE128Params { } impl SHAKEParams for SHAKE128Params { const SIZE: KeccakSize = KeccakSize::_128; + const STATE_TAG: u8 = 5; } pub struct SHAKE256Params; @@ -258,4 +269,5 @@ impl Algorithm for SHAKE256Params { } impl SHAKEParams for SHAKE256Params { const SIZE: KeccakSize = KeccakSize::_256; + const STATE_TAG: u8 = 6; } diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index fff2439..2e4ca72 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -1,9 +1,13 @@ use crate::SHA3Params; -use crate::keccak::KeccakDigest; -use bouncycastle_core::errors::{HashError, KDFError}; +use crate::keccak::{ + KeccakDigest, SHA3_FAMILY_SERIALIZED_STATE_LEN, SHA3_FAMILY_STATE_LEN, + deserialize_sha3_family_state, serialize_sha3_family_state, +}; +use bouncycastle_core::errors::{CoreError, HashError, KDFError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Hash, KDF, SecurityStrength}; +use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::traits::{Hash, KDF, SecurityStrength, SerializableState}; use bouncycastle_utils::{max, min}; #[derive(Clone)] @@ -217,6 +221,49 @@ impl Hash for SHA3 { } } +impl SerializableState for SHA3 { + fn serialize_state(&self) -> [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN] { + let mut out_to_return = [0u8; SHA3_FAMILY_SERIALIZED_STATE_LEN]; + + // insert the version tag + let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = + add_lib_ver(&mut out_to_return).try_into().unwrap(); + + serialize_sha3_family_state( + out, + PARAMS::STATE_TAG, + &self.keccak, + self.kdf_key_type, + self.kdf_security_strength, + self.kdf_entropy, + ); + + out_to_return + } + + fn from_serialized_state( + serialized_state: [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN], + ) -> Result { + // check the version tag. At the moment, we have no not_before version to specify. + let input: &[u8; SHA3_FAMILY_STATE_LEN] = + check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + // The variant tag rejects states from any other SHA3/SHAKE variant; the rate is then the + // correct one to rebuild with (both are fully determined by the algorithm parameters). + let rate = 1600 - ((PARAMS::SIZE as usize) << 1); + let (keccak, kdf_key_type, kdf_security_strength, kdf_entropy) = + deserialize_sha3_family_state(input, PARAMS::STATE_TAG, rate)?; + + Ok(SHA3 { + _params: std::marker::PhantomData, + keccak, + kdf_key_type, + kdf_security_strength, + kdf_entropy, + }) + } +} + /// SHA3 is allowed to be used as a KDF in the form HASH(X) as per NIST SP 800-56C. impl KDF for SHA3 { /// Returns a [KeyMaterial]. diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index fecff5a..b2252b1 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -1,9 +1,13 @@ use crate::SHAKEParams; -use crate::keccak::{KeccakDigest, KeccakSize}; -use bouncycastle_core::errors::{HashError, KDFError}; +use crate::keccak::{ + KeccakDigest, KeccakSize, SHA3_FAMILY_SERIALIZED_STATE_LEN, SHA3_FAMILY_STATE_LEN, + deserialize_sha3_family_state, serialize_sha3_family_state, +}; +use bouncycastle_core::errors::{CoreError, HashError, KDFError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, XOF}; +use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, SerializableState, XOF}; use bouncycastle_utils::{max, min}; /// Note: FIPS 202 section 7 states: @@ -138,6 +142,49 @@ impl SHAKE { } } +impl SerializableState for SHAKE { + fn serialize_state(&self) -> [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN] { + let mut out_to_return = [0u8; SHA3_FAMILY_SERIALIZED_STATE_LEN]; + + // insert the version tag + let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = + add_lib_ver(&mut out_to_return).try_into().unwrap(); + + serialize_sha3_family_state( + out, + PARAMS::STATE_TAG, + &self.keccak, + self.kdf_key_type, + self.kdf_security_strength, + self.kdf_entropy, + ); + + out_to_return + } + + fn from_serialized_state( + serialized_state: [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN], + ) -> Result { + // check the version tag. At the moment, we have no not_before version to specify. + let input: &[u8; SHA3_FAMILY_STATE_LEN] = + check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + // The variant tag rejects states from any other SHA3/SHAKE variant; the rate is then the + // correct one to rebuild with (both are fully determined by the algorithm parameters). + let rate = 1600 - ((PARAMS::SIZE as usize) << 1); + let (keccak, kdf_key_type, kdf_security_strength, kdf_entropy) = + deserialize_sha3_family_state(input, PARAMS::STATE_TAG, rate)?; + + Ok(SHAKE { + _phantomdata: std::marker::PhantomData, + keccak, + kdf_key_type, + kdf_security_strength, + kdf_entropy, + }) + } +} + impl KDF for SHAKE { /// Returns a [KeyMaterial]. /// For the KDF to be considered "fully-seeded" and be capable of outputting full-entropy KeyMaterials, diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index 1add04b..0cdb21a 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -9,7 +9,7 @@ mod sha3_tests { use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::hash::TestFrameworkHash; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; - use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; + use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE256}; #[test] fn test_constants() { @@ -394,6 +394,61 @@ mod sha3_tests { run_test_vectors(read_test_vectors("tests/data/SHA3TestVectors.txt")); } + #[test] + fn test_serializable_state() { + use bouncycastle_core::errors::CoreError; + use bouncycastle_core::traits::SerializableState; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + + let str = "Colorless green ideas sleep furiously"; + + // A helper that exercises the full round-trip for one SHA3 variant. + fn round_trip>(mut hash: H, input: &[u8]) { + hash.do_update(input); + + // do the default trait-conformance tests + TestFrameworkSerializableState::new().test(&hash); + + // serialize the in-progress state, then finish the original + let serialized_state = hash.serialize_state(); + let expected = hash.do_final(); + + // rebuild from the serialized state and confirm it produces the same digest + let from_state = H::from_serialized_state(serialized_state).unwrap(); + assert_eq!(expected, from_state.do_final()); + + // a corrupt `squeezing` byte (last byte of the keccak state) must be rejected. + // Layout: 3 version bytes + variant tag(1) + [u64;25](200) + data_queue(192) + // + bits_in_queue(8) + squeezing(1) + let mut busted = serialized_state; + busted[3 + 1 + 400] = 42; + match H::from_serialized_state(busted) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error for a corrupt squeezing byte"), + } + } + + round_trip(SHA3_224::new(), str.as_bytes()); + round_trip(SHA3_256::new(), str.as_bytes()); + round_trip(SHA3_384::new(), str.as_bytes()); + round_trip(SHA3_512::new(), str.as_bytes()); + + // A state serialized by one variant must be rejected by a different variant (mismatched + // variant tag). SHA3-256 and SHAKE256 share the same rate (1088), so this cross-family case + // is only caught by the tag, not the rate -- it is the exact bug the tag exists to prevent. + let mut sha3_256 = SHA3_256::new(); + sha3_256.do_update(str.as_bytes()); + let serialized_256 = sha3_256.serialize_state(); + match SHA3_512::from_serialized_state(serialized_256) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error when loading a SHA3-256 state into SHA3-512"), + } + match SHAKE256::from_serialized_state(serialized_256) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error when loading a SHA3-256 state into SHAKE256"), + } + } + fn run_test_vectors(test_vectors: Vec) { for tc in test_vectors { match tc.algorithm { diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index edbca2d..d0611c7 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -9,7 +9,7 @@ mod shake_tests { use bouncycastle_core::traits::{KDF, SecurityStrength, XOF}; use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; - use bouncycastle_sha3::{SHAKE128, SHAKE256}; + use bouncycastle_sha3::{SHA3_256, SHAKE128, SHAKE256}; #[test] fn test_xof_partial_bit_output() { @@ -238,6 +238,63 @@ mod shake_tests { run_test_vectors(read_test_vectors("tests/data/SHAKETestVectors.txt")); } + #[test] + fn test_serializable_state() { + use bouncycastle_core::errors::CoreError; + use bouncycastle_core::traits::SerializableState; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + + let str = "Colorless green ideas sleep furiously"; + + // A helper that exercises the full round-trip for one SHAKE variant. + fn round_trip>(mut shake: X, input: &[u8]) { + shake.absorb(input); + + // do the default trait-conformance tests + TestFrameworkSerializableState::new().test(&shake); + + // serialize the in-progress (absorbing) state, then squeeze from the original + let serialized_state = shake.serialize_state(); + let expected = shake.squeeze(64); + + // rebuild from the serialized state and confirm it produces the same output + let mut from_state = X::from_serialized_state(serialized_state).unwrap(); + assert_eq!(expected, from_state.squeeze(64)); + + // a corrupt `squeezing` byte (last byte of the keccak state) must be rejected. + // Layout: 3 version bytes + variant tag(1) + [u64;25](200) + data_queue(192) + // + bits_in_queue(8) + squeezing(1) + let mut busted = serialized_state; + busted[3 + 1 + 400] = 42; + match X::from_serialized_state(busted) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error for a corrupt squeezing byte"), + } + } + + round_trip(SHAKE128::new(), str.as_bytes()); + round_trip(SHAKE256::new(), str.as_bytes()); + + // A state serialized by one variant must be rejected by a different variant (mismatched + // variant tag). The SHAKE256 -> SHA3-256 case is the important one: they share the same rate + // (1088), so only the variant tag distinguishes them. + let mut shake128 = SHAKE128::new(); + shake128.absorb(str.as_bytes()); + let serialized_128 = shake128.serialize_state(); + match SHAKE256::from_serialized_state(serialized_128) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error when loading a SHAKE128 state into SHAKE256"), + } + + let mut shake256 = SHAKE256::new(); + shake256.absorb(str.as_bytes()); + let serialized_256 = shake256.serialize_state(); + match SHA3_256::from_serialized_state(serialized_256) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error when loading a SHAKE256 state into SHA3-256"), + } + } + fn run_test_vectors(test_vectors: Vec) { for tc in test_vectors { //println!("SHA3-{} {}-bits", &tc.algorithm, &tc.bits); From 6b3ecec87005678a6e6cf40932e86a146a883680 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 6 Jul 2026 17:36:07 -0500 Subject: [PATCH 25/35] Impl'd SerializableState for MLDSA, and some quality-of-life fixes for the impl for SHA2 and SHA3. --- QUALITY_AND_STYLE.md | 4 + alpha_0.1.2_release_notes.md | 9 + .../src/serializable_state.rs | 17 +- crypto/core/src/errors.rs | 17 +- crypto/core/src/key_material.rs | 8 +- crypto/core/src/serializable_state.rs | 37 +- crypto/core/src/traits.rs | 46 ++- crypto/mldsa/src/hash_mldsa.rs | 106 +++--- crypto/mldsa/src/lib.rs | 2 + crypto/mldsa/src/mldsa.rs | 326 ++++++++++++------ crypto/mldsa/tests/mldsa_tests.rs | 90 ++++- crypto/mldsa/tests/wycheproof.rs | 48 +-- crypto/sha2/src/lib.rs | 39 ++- crypto/sha2/src/sha256.rs | 22 +- crypto/sha2/src/sha512.rs | 20 +- crypto/sha2/tests/sha2_tests.rs | 10 +- crypto/sha3/src/keccak.rs | 30 +- crypto/sha3/src/lib.rs | 40 +++ crypto/sha3/src/sha3.rs | 16 +- crypto/sha3/src/shake.rs | 14 +- crypto/sha3/tests/sha3_tests.rs | 12 +- crypto/sha3/tests/shake_tests.rs | 12 +- 22 files changed, 636 insertions(+), 289 deletions(-) diff --git a/QUALITY_AND_STYLE.md b/QUALITY_AND_STYLE.md index 2937ec7..77e9c08 100644 --- a/QUALITY_AND_STYLE.md +++ b/QUALITY_AND_STYLE.md @@ -12,6 +12,10 @@ lib.rs for all crates needs to contain: `#![forbid(missing_docs)]`, `#![no_std]` All primitives must be accompanied by a CLI in `/cli`. +All algorithms with a state that is exercised across multiple API calls -- typically through a `do_update()`, +`do_final()` pattern -- should implement SerializableState so that the user can pause the execution of this algorithm to +a cache and resume later. + # Quality ## Tests diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index ebe62c9..a298549 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -38,11 +38,20 @@ # 0.1.2 Features / Changelog +## Major features + * 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. +* New traits SerializeState and SerializeKeyedState allow algorithms with a streaming API (`do_update()` -> + `do_final()`) to be suspended to a small byte array and then resumed later, potentially from a different host. The + intended use case is if you are processing a large input that depends on one or more network round-trips and you wish + to suspect and potentially transfer to a new host while waiting for network IO. + +## Minor features / bug fixes + * 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. * Reworked the way KeyMaterial hazardous operations work; instead of a stateful .allow_hazardous_operations() / diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/serializable_state.rs index b8b7f63..2daea7d 100644 --- a/crypto/core-test-framework/src/serializable_state.rs +++ b/crypto/core-test-framework/src/serializable_state.rs @@ -1,4 +1,4 @@ -use bouncycastle_core::errors::CoreError; +use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::serializable_state::{LIB_VERSION, SemVer}; use bouncycastle_core::traits::SerializableState; @@ -12,15 +12,22 @@ impl TestFrameworkSerializableState { /// Test all the members of trait SerializableState. /// /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. - pub fn test>( + pub fn test< + const SERIALIZED_STATE_LEN: usize, + S: SerializableState + Clone, + >( &self, instance: &S, ) { // There's not a lot we can test here in the abstract, but we can test a few things to // ensure that the SerializableState trait has been impl'd correctly. + // we need to work on a clone because .serialize_state() moves self, which you can't do on a + // borrowed instance. + let instance_clone = instance.clone(); + // You can serialize and then deserialize the state. - let serialized_state = instance.serialize_state(); + let serialized_state = instance_clone.serialize_state(); assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN); let _deserialized_state = S::from_serialized_state(serialized_state).unwrap(); @@ -35,7 +42,7 @@ impl TestFrameworkSerializableState { let mut ver0_serialized_state = serialized_state.clone(); ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); match S::from_serialized_state(ver0_serialized_state) { - Err(CoreError::IncompatibleVersion) => { /* good */ } + Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") } @@ -48,7 +55,7 @@ impl TestFrameworkSerializableState { futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); match S::from_serialized_state(futurever_serialized_state) { - Err(CoreError::IncompatibleVersion) => { /* good */ } + Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") } diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index 5051443..43424f7 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -1,9 +1,3 @@ -#[derive(Debug)] -pub enum CoreError { - IncompatibleVersion, - InvalidData, -} - #[derive(Debug)] pub enum HashError { GenericError(&'static str), @@ -78,6 +72,17 @@ pub enum RNGError { KeyMaterialError(KeyMaterialError), } +#[derive(Debug)] +pub enum SerializedStateError { + /// The serialized state was produced by a library version incompatible with this one. + IncompatibleVersion, + /// The serialized state is malformed or corrupt. + InvalidData, + /// The key supplied to [crate::traits::SerializableKeyedState::from_serialized_state] does not + /// match the key the state was created with (it is bound to a different public-key hash `tr`). + IncorrectKey, +} + #[derive(Debug)] pub enum SignatureError { GenericError(&'static str), diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index da8697e..c730c26 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -50,7 +50,7 @@ //! //! See [do_hazardous_operations] for documentation and sample code. -use crate::errors::{CoreError, KeyMaterialError}; +use crate::errors::{KeyMaterialError, SerializedStateError}; use crate::traits::{RNG, Secret, SecurityStrength}; use bouncycastle_utils::{ct, min}; @@ -255,9 +255,9 @@ pub enum KeyType { } impl TryFrom for KeyType { - type Error = CoreError; + type Error = SerializedStateError; - /// Inverse of `self as u8`; rejects unrecognized discriminants with [CoreError::InvalidData]. + /// Inverse of `self as u8`; rejects unrecognized discriminants with [SerializedStateError::InvalidData]. fn try_from(value: u8) -> Result { Ok(match value { 0 => Self::Zeroized, @@ -266,7 +266,7 @@ impl TryFrom for KeyType { 3 => Self::Seed, 4 => Self::MACKey, 5 => Self::SymmetricCipherKey, - _ => return Err(CoreError::InvalidData), + _ => return Err(SerializedStateError::InvalidData), }) } } diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/serializable_state.rs index 819a4f3..420484a 100644 --- a/crypto/core/src/serializable_state.rs +++ b/crypto/core/src/serializable_state.rs @@ -1,6 +1,6 @@ //! Helper functions for standardizing serialization and deserialization of stateful objects. -use crate::errors::CoreError; +use crate::errors::SerializedStateError; /// A semantic library version, ordered by `major`, then `minor`, then `patch`. /// @@ -27,9 +27,28 @@ impl From for [u8; 3] { } } -/// The current library version. -// There is almost certainly a more elegant way to do this. -pub const LIB_VERSION: SemVer = SemVer { major: 0, minor: 1, patch: 2 }; +/// Parse a decimal ASCII string (a Cargo version component) into a u8 at compile time. +const fn parse_version_component(s: &str) -> u8 { + let bytes = s.as_bytes(); + let mut result: u8 = 0; + let mut i = 0; + while i < bytes.len() { + let d = bytes[i]; + assert!(d >= b'0' && d <= b'9', "version component must be numeric"); + // A component > 255 overflows u8 and fails the build (SemVer fields are u8 by design). + result = result * 10 + (d - b'0'); + i += 1; + } + result +} + +/// The current library version, taken from this crate's `Cargo.toml` at compile time (via Cargo's +/// `CARGO_PKG_VERSION_*` env vars) so it can never drift from the published version. +pub const LIB_VERSION: SemVer = SemVer { + major: parse_version_component(env!("CARGO_PKG_VERSION_MAJOR")), + minor: parse_version_component(env!("CARGO_PKG_VERSION_MINOR")), + patch: parse_version_component(env!("CARGO_PKG_VERSION_PATCH")), +}; #[test] fn test_cmp_lib_ver() { @@ -59,7 +78,7 @@ pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN] /// /// The state_out array must have length at least SERIALIZED_LEN - 3. /// -/// Returns the number of bytes written to state_out, or a [CoreError::IncompatibleVersion] if the +/// Returns the number of bytes written to state_out, or a [SerializedStateError::IncompatibleVersion] if the /// serialized state contains a version header earlier than the specified `not_before` version. /// /// Note that for testability, this will always reject if the serialized state contains a version tag @@ -69,23 +88,23 @@ pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN] pub fn check_lib_ver( state: &[u8; SERIALIZED_LEN], not_before: Option<[u8; 3]>, -) -> Result<&[u8], CoreError> { +) -> Result<&[u8], SerializedStateError> { let ver_bytes: [u8; 3] = state[..3].try_into().unwrap(); let ver = SemVer::from(ver_bytes); let not_before = SemVer::from(not_before.unwrap_or([0, 0, 0])); if ver < not_before { - return Err(CoreError::IncompatibleVersion); + return Err(SerializedStateError::IncompatibleVersion); }; // Nothing is ever compatible with [0,0,0] if ver == SemVer::from([0, 0, 0]) { - return Err(CoreError::IncompatibleVersion); + return Err(SerializedStateError::IncompatibleVersion); }; // Also not compatible with future versions. if ver > LIB_VERSION { - return Err(CoreError::IncompatibleVersion); + return Err(SerializedStateError::IncompatibleVersion); } Ok(&state[3..]) diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 6ddc0d5..8c774d9 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -1,6 +1,6 @@ //! Provides simplified abstracted APIs over classes of cryptigraphic primitives, such as Hash, KDF, etc. -use crate::errors::{CoreError, HashError, KDFError, KEMError, MACError, RNGError, SignatureError}; +use crate::errors::*; use crate::key_material::KeyMaterialTrait; use core::fmt::{Debug, Display}; use core::marker::Sized; @@ -399,9 +399,9 @@ pub enum SecurityStrength { } impl TryFrom for SecurityStrength { - type Error = CoreError; + type Error = SerializedStateError; - /// Inverse of `self as u8`; rejects unrecognized discriminants with [CoreError::InvalidData]. + /// Inverse of `self as u8`; rejects unrecognized discriminants with [SerializedStateError::InvalidData]. fn try_from(value: u8) -> Result { Ok(match value { 0 => Self::None, @@ -409,7 +409,7 @@ impl TryFrom for SecurityStrength { 2 => Self::_128bit, 3 => Self::_192bit, 4 => Self::_256bit, - _ => return Err(CoreError::InvalidData), + _ => return Err(SerializedStateError::InvalidData), }) } } @@ -497,11 +497,44 @@ pub trait Secret: Drop + Debug + Display {} /// /// This is not intended as a mechanism to clone the state of an object since in most cases `.clone()` /// will be more straightforward. +/// +/// The serialized state MAY contain short-term sensitive values such as nonces or IVs, +/// but it MUST NOT include a serialized private key. Keyed algorithms MUST instead impl +/// [SerializableKeyedState] which requires the key to be supplied independently at the time of deserialization. pub trait SerializableState: Sized { /// Serialize the state of the object. /// + /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization. + /// If you want to do this intentionally, then you will need to clone the object before serializing it. + /// + /// The serialized state MUST include a prefix indicating the version of the library that serialized it. + fn serialize_state(self) -> [u8; SERIALIZED_STATE_LEN]; + + /// Create a new object from a serialized state. + /// + /// Deserializers SHOULD check the version and reject serialized states from incompatible versions + /// (including rejecting serializations from a future version of the library). + /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its + /// deserializer should reject serialized states from that version or older. + fn from_serialized_state( + serialized_state: [u8; SERIALIZED_STATE_LEN], + ) -> Result; +} + +/// Similar to [SerializableState] in that it allows a stateful object to serialize its state so that +/// it can be paused and resumed later, potentially from a different host. +/// +/// The difference is that this trait is for keyed algorithms -- MACs, symmetric ciphers, signatures, etc -- +/// which require a private key. For security reasons, the private key is not included in the serialized state +/// and must be provided separately as part of the deserialization process. +pub trait SerializableKeyedState: Sized { + /// Serialize the state of the object. + /// + /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization. + /// If you want to do this intentionally, then you will need to clone the object before serializing it. + /// /// The serialized state MUST include a prefix indicating the version of the library that serialized it. - fn serialize_state(&self) -> [u8; SERIALIZED_STATE_LEN]; + fn serialize_state(self) -> [u8; SERIALIZED_STATE_LEN]; /// Create a new object from a serialized state. /// @@ -511,7 +544,8 @@ pub trait SerializableState: Sized { /// deserializer should reject serialized states from that version or older. fn from_serialized_state( serialized_state: [u8; SERIALIZED_STATE_LEN], - ) -> Result; + key: K, + ) -> Result; } /// Pre-Hashed Signer is an extension to [Signer] that adds functionality specific to signature diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 4b69c41..0cfd620 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -756,66 +756,52 @@ impl< }; match A_hat { - Some(A_hat) => { - if 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, - >::verify_mu_internal(pk, A_hat, &mu, sig_sized) - { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } - } - None => { - if 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, - >::verify_mu_internal(pk, &pk.A_hat(), &mu, sig_sized) - { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } - } + Some(A_hat) => 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, + >::verify_mu(pk, Some(A_hat), &mu, sig_sized), + None => 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, + >::verify_mu(pk, Some(&pk.A_hat()), &mu, sig_sized), } } } diff --git a/crypto/mldsa/src/lib.rs b/crypto/mldsa/src/lib.rs index 7480c3c..e0f2b4f 100644 --- a/crypto/mldsa/src/lib.rs +++ b/crypto/mldsa/src/lib.rs @@ -176,6 +176,8 @@ pub use mldsa::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; pub use mldsa::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; pub use mldsa::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; +pub use mldsa::MU_BUILDER_SERIALIZED_STATE_LEN; + pub use matrix::Matrix; // re-export just so it's visible to unit tests diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index 102293a..434777b 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -398,6 +398,81 @@ //! Err(e) => panic!("Something else went wrong: {:?}", e), //! } //! ``` +//! +//! # Suspending and resuming execution via SerializableState +//! +//! When signing or verifying a large message, it can be advantageous to be able to suspend the operation +//! to a cache and resume it later; for example if waiting for the message to stream over a slow network +//! connection. +//! +//! This can bo accomplished for both the ML-DSA signer and verifier through the [MuBuilder] object. +//! +//! Suspending an in-progress sign operation: +//! +//! ```rust +//! use bouncycastle_mldsa::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; +//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let (pk, sk) = MLDSA65::keygen().unwrap(); +//! +//! let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); +//! mb.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = mb.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! mb_resumed.do_update(msg_part2); +//! let mu: [u8; 64] = mb_resumed.do_final(); +//! +//! // Now we'll do the actual sign_mu operation +//! let sig = MLDSA65::sign_mu(&sk, None, &mu).unwrap(); +//! ``` +//! +//! Suspending an in-progress verify operation behaves exactly the same way: +//! +//! ```rust +//! use bouncycastle_mldsa::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; +//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! use bouncycastle_core::errors::SignatureError; +//! +//! let (pk, sk) = MLDSA65::keygen().unwrap(); +//! +//! // first, let's generate a signature to verify +//! let sig = MLDSA65::sign(&sk, b"The quick brown fox jumped over the lazy dog", None).unwrap(); +//! +//! // Now we'll verify it with a suspension in the middle +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); +//! mb.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = mb.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! mb_resumed.do_update(msg_part2); +//! let mu: [u8; 64] = mb_resumed.do_final(); +//! +//! // Now we'll do the actual verify_mu operation +//! match MLDSA65::verify_mu(&pk, Some(&pk.A_hat()), &mu, &sig) { +//! Ok(()) => println!("Signature is valid!"), +//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), +//! Err(e) => panic!("Something else went wrong: {:?}", e), +//! } +//! ``` use crate::aux_functions::{ expand_mask, expandA, expandS, make_hint_vecs, power_2_round_vec, sample_in_ball, sig_decode, @@ -410,11 +485,13 @@ use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyExpanded, MLDSAPublicKeyExpanded, }; -use bouncycastle_core::errors::{RNGError, SignatureError}; +use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, XOF}; +use bouncycastle_core::traits::{ + Algorithm, RNG, SecurityStrength, SerializableState, SignatureVerifier, Signer, XOF, +}; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha3::{SHAKE128, SHAKE256}; +use bouncycastle_sha3::{SHA3_SERIALIZED_STATE_LEN, SHAKE128, SHAKE256}; use core::marker::PhantomData; // imports needed just for docs @@ -1017,6 +1094,99 @@ impl< Ok(bytes_written) } + + /// Algorithm 8 ML-DSA.Verify_internal(π‘π‘˜, 𝑀′, 𝜎) + /// Internal function to verify a signature 𝜎 for a formatted message 𝑀′ . + /// Input: Public key π‘π‘˜ ∈ 𝔹32+32π‘˜(bitlen (π‘žβˆ’1)βˆ’π‘‘) and message 𝑀′ ∈ {0, 1}βˆ— . + /// Input: Signature 𝜎 ∈ π”Ήπœ†/4+β„“β‹…32β‹…(1+bitlen (𝛾1βˆ’1))+πœ”+π‘˜. + fn verify_internal( + pk: &PK, + A_hat: &Matrix, + mu: &[u8; 64], + sig: &[u8; SIG_LEN], + ) -> Result<(), SignatureError> { + // 1: (𝜌, 𝐭1) ← pkDecode(π‘π‘˜) + // Already done -- the pk struct is already decoded + + // 2: (𝑐_tilde, 𝐳, 𝐑) ← sigDecode(𝜎) + // β–· signer’s commitment hash c_tilde, response 𝐳, and hint 𝐑 + // 3: if 𝐑 = βŠ₯ then return false + let (c_tilde, z, h) = + sig_decode::(&sig) + .map_err(|_| SignatureError::SignatureVerificationFailed)?; + + // 13 (first half) return [[ ||𝐳||∞ < 𝛾1 βˆ’ 𝛽]] + if z.check_norm::() { + return Err(SignatureError::SignatureVerificationFailed); + } + + // 5: 𝐀 ← ExpandA(𝜌) + // β–· 𝐀 is generated and stored in NTT representation as 𝐀 + // We're doing an optimization where the user can pre-expand A_hat within the + // public key object for faster repeated encapsulations against this public key. + + // 6: π‘‘π‘Ÿ ← H(π‘π‘˜, 64) + // 7: πœ‡ ← (H(BytesToBits(π‘‘π‘Ÿ)||𝑀 β€², 64)) + // β–· message representative that may optionally be + // computed in a different cryptographic module + // skip because this function is being handed mu + + // 8: 𝑐 ∈ π‘…π‘ž ← SampleInBall(c_tilde) + let c_hat = { + let mut c = sample_in_ball::(&c_tilde); + c.ntt(); + + c + }; + + // 9: 𝐰′_approx ← NTTβˆ’1(𝐀_hat ∘ NTT(𝐳) βˆ’ NTT(𝑐) ∘ NTT(𝐭1 β‹… 2^𝑑)) + // broken out for clarity: + // NTTβˆ’1( + // 𝐀_hat ∘ NTT(𝐳) βˆ’ + // NTT(𝑐) ∘ NTT(𝐭1 β‹… 2^𝑑) + // ) + // β–· 𝐰'_approx = 𝐀𝐳 βˆ’ 𝑐𝐭1 β‹… 2^𝑑 + // weird nested scoping is to reduce peak stack memory usage + let w1p = { + let Az = { + let mut z_hat = z.clone(); + z_hat.ntt(); + A_hat.matrix_vector_ntt(&z_hat) + }; + let ct1 = { + // potential optimization -- pre-compute this on key load? + let mut t1_shift_hat = pk.t1().shift_left::(); + t1_shift_hat.ntt(); + t1_shift_hat.scalar_vector_ntt(&c_hat) + }; + let mut wp_approx = Az.sub_vector(&ct1); + wp_approx.inv_ntt(); + wp_approx.conditional_add_q(); + + // 10: 𝐰1β€² ← UseHint(𝐑, 𝐰'_approx) + // β–· reconstruction of signer’s commitment + use_hint_vecs::(&h, &wp_approx) + }; + // 12: 𝑐_tilde_p ← H(πœ‡||w1Encode(𝐰1'), πœ†/4) + // β–· hash it; this should match 𝑐_tilde + let c_tilde_p = { + let mut c_tilde_p = [0u8; LAMBDA_over_4]; + let mut hash = H::new(); + hash.absorb(mu); + w1p.w1_encode_and_hash::(&mut hash); + hash.squeeze_out(&mut c_tilde_p); + + c_tilde_p + }; + + // verification probably doesn't technically need to be constant-time, but why not? + // 13 (second half): return [[ ||𝐳||∞ < 𝛾1 βˆ’ 𝛽]] and [[𝑐 Μƒ = 𝑐′ ]] + if bouncycastle_utils::ct::ct_eq_bytes(&c_tilde, &c_tilde_p) { + Ok(()) + } else { + Err(SignatureError::SignatureVerificationFailed) + } + } } impl< @@ -1524,108 +1694,19 @@ impl< let sig: &[u8; SIG_LEN] = sig.try_into().map_err(|_| { SignatureError::LengthError("Signature value is not the correct length.") })?; - Self::verify_mu_internal(&pk.pk, &pk.A_hat(), &mu, sig) - .then_some(()) - .ok_or(SignatureError::SignatureVerificationFailed) + Self::verify_mu(&pk.pk, Some(&pk.A_hat()), &mu, sig) } - /// Algorithm 8 ML-DSA.Verify_internal(π‘π‘˜, 𝑀′, 𝜎) - /// Internal function to verify a signature 𝜎 for a formatted message 𝑀′ . - /// Input: Public key π‘π‘˜ ∈ 𝔹32+32π‘˜(bitlen (π‘žβˆ’1)βˆ’π‘‘) and message 𝑀′ ∈ {0, 1}βˆ— . - /// Input: Signature 𝜎 ∈ π”Ήπœ†/4+β„“β‹…32β‹…(1+bitlen (𝛾1βˆ’1))+πœ”+π‘˜. - fn verify_mu_internal( + fn verify_mu( pk: &PK, - A_hat: &Matrix, + A_hat: Option<&Matrix>, mu: &[u8; 64], sig: &[u8; SIG_LEN], - ) -> bool { - // 1: (𝜌, 𝐭1) ← pkDecode(π‘π‘˜) - // Already done -- the pk struct is already decoded - - // 2: (𝑐_tilde, 𝐳, 𝐑) ← sigDecode(𝜎) - // β–· signer’s commitment hash c_tilde, response 𝐳, and hint 𝐑 - // 3: if 𝐑 = βŠ₯ then return false - let (c_tilde, z, h) = match sig_decode::< - GAMMA1, - k, - l, - LAMBDA_over_4, - OMEGA, - POLY_Z_PACKED_LEN, - SIG_LEN, - >(&sig) - { - Ok((c_tilde, z, h)) => (c_tilde, z, h), - Err(_) => return false, - }; - - // 13 (first half) return [[ ||𝐳||∞ < 𝛾1 βˆ’ 𝛽]] - if z.check_norm::() { - return false; + ) -> Result<(), SignatureError> { + match A_hat { + Some(A_hat) => Self::verify_internal(pk, A_hat, mu, sig), + None => Self::verify_internal(pk, &pk.A_hat(), mu, sig), } - - // 5: 𝐀 ← ExpandA(𝜌) - // β–· 𝐀 is generated and stored in NTT representation as 𝐀 - // We're doing an optimization where the user can pre-expand A_hat within the - // public key object for faster repeated encapsulations against this public key. - - // 6: π‘‘π‘Ÿ ← H(π‘π‘˜, 64) - // 7: πœ‡ ← (H(BytesToBits(π‘‘π‘Ÿ)||𝑀 β€², 64)) - // β–· message representative that may optionally be - // computed in a different cryptographic module - // skip because this function is being handed mu - - // 8: 𝑐 ∈ π‘…π‘ž ← SampleInBall(c_tilde) - let c_hat = { - let mut c = sample_in_ball::(&c_tilde); - c.ntt(); - - c - }; - - // 9: 𝐰′_approx ← NTTβˆ’1(𝐀_hat ∘ NTT(𝐳) βˆ’ NTT(𝑐) ∘ NTT(𝐭1 β‹… 2^𝑑)) - // broken out for clarity: - // NTTβˆ’1( - // 𝐀_hat ∘ NTT(𝐳) βˆ’ - // NTT(𝑐) ∘ NTT(𝐭1 β‹… 2^𝑑) - // ) - // β–· 𝐰'_approx = 𝐀𝐳 βˆ’ 𝑐𝐭1 β‹… 2^𝑑 - // weird nested scoping is to reduce peak stack memory usage - let w1p = { - let Az = { - let mut z_hat = z.clone(); - z_hat.ntt(); - A_hat.matrix_vector_ntt(&z_hat) - }; - let ct1 = { - // potential optimization -- pre-compute this on key load? - let mut t1_shift_hat = pk.t1().shift_left::(); - t1_shift_hat.ntt(); - t1_shift_hat.scalar_vector_ntt(&c_hat) - }; - let mut wp_approx = Az.sub_vector(&ct1); - wp_approx.inv_ntt(); - wp_approx.conditional_add_q(); - - // 10: 𝐰1β€² ← UseHint(𝐑, 𝐰'_approx) - // β–· reconstruction of signer’s commitment - use_hint_vecs::(&h, &wp_approx) - }; - // 12: 𝑐_tilde_p ← H(πœ‡||w1Encode(𝐰1'), πœ†/4) - // β–· hash it; this should match 𝑐_tilde - let c_tilde_p = { - let mut c_tilde_p = [0u8; LAMBDA_over_4]; - let mut hash = H::new(); - hash.absorb(mu); - w1p.w1_encode_and_hash::(&mut hash); - hash.squeeze_out(&mut c_tilde_p); - - c_tilde_p - }; - - // verification probably doesn't technically need to be constant-time, but why not? - // 13 (second half): return [[ ||𝐳||∞ < 𝛾1 βˆ’ 𝛽]] and [[𝑐 Μƒ = 𝑐′ ]] - bouncycastle_utils::ct::ct_eq_bytes(&c_tilde, &c_tilde_p) } } @@ -1750,6 +1831,8 @@ pub trait MLDSATrait< /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both /// FIPS 204 itself, as well as subsequent FAQ documents. /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG. + /// + /// Optionally, takes a pre-expanded public matrix `A_hat`. fn sign_mu( sk: &SK, A_hat: Option<&Matrix>, @@ -1874,16 +1957,16 @@ pub trait MLDSATrait< ctx: Option<&[u8]>, sig: &[u8], ) -> Result<(), SignatureError>; - /// Algorithm 8 ML-DSA.Verify_internal(π‘π‘˜, 𝑀′, 𝜎) - /// Internal function to verify a signature 𝜎 for a formatted message 𝑀′ . - /// Input: Public key π‘π‘˜ ∈ 𝔹32+32π‘˜(bitlen (π‘žβˆ’1)βˆ’π‘‘) and message 𝑀′ ∈ {0, 1}βˆ— . - /// Input: Signature 𝜎 ∈ π”Ήπœ†/4+β„“β‹…32β‹…(1+bitlen (𝛾1βˆ’1))+πœ”+π‘˜. - fn verify_mu_internal( + /// Performs an ML-DSA signature verification using the provided external message representative `mu`. + /// This implements FIPS 204 Algorithm 8 with line 7 removed; a modification that is allowed by both + /// FIPS 204 itself, as well as subsequent FAQ documents. + /// Optionally, takes a pre-expanded public matrix `A_hat`. + fn verify_mu( pk: &PK, - A_hat: &Matrix, + A_hat: Option<&Matrix>, mu: &[u8; 64], sig: &[u8; SIG_LEN], - ) -> bool; + ) -> Result<(), SignatureError>; } impl< @@ -2067,9 +2150,7 @@ impl< let sig: &[u8; SIG_LEN] = sig.try_into().map_err(|_| { SignatureError::LengthError("Signature value is not the correct length.") })?; - Self::verify_mu_internal(pk, &pk.A_hat(), &mu, sig) - .then_some(()) - .ok_or(SignatureError::SignatureVerificationFailed) + Self::verify_mu(pk, Some(&pk.A_hat()), &mu, sig) } fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result { @@ -2097,9 +2178,7 @@ impl< let sig: &[u8; SIG_LEN] = sig.try_into().map_err(|_| { SignatureError::LengthError("Signature value is not the correct length.") })?; - Self::verify_mu_internal(pk, &pk.A_hat(), &mu, sig) - .then_some(()) - .ok_or(SignatureError::SignatureVerificationFailed) + Self::verify_mu(pk, Some(&pk.A_hat()), &mu, sig) } } @@ -2112,6 +2191,7 @@ impl< /// 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 [PHSigner::sign_ph]. +#[derive(Clone)] pub struct MuBuilder { h: H, } @@ -2177,3 +2257,25 @@ impl MuBuilder { mu } } + +/// The length, in bytes, of a serialized state of a [MuBuilder] object. +pub const MU_BUILDER_SERIALIZED_STATE_LEN: usize = SHA3_SERIALIZED_STATE_LEN; + +/// If you are processing a large input message into ML-DSA and want to pause the operation +/// -- maybe while waiting for slow network IO), you'll need to use [SerializableState]. +/// Serialization of the state of an in-progress ML-DSA instance is really just serialization +/// of the construction of the message representative mu, since no other part of the ML-DSA algorithm +/// has a pausable state. +// A [MuBuilder]'s (and by virtue, an ML-DSA instance's) entire mutable state is its inner SHAKE256 sponge, +// so serialization delegates directly to [SHAKE256]'s [SerializableState] impl. +impl SerializableState for MuBuilder { + fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { + self.h.serialize_state() + } + + fn from_serialized_state( + serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + ) -> Result { + Ok(MuBuilder { h: H::from_serialized_state(serialized_state)? }) + } +} diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index 73e1f16..8207a96 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -2,15 +2,17 @@ #[cfg(test)] mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; - use bouncycastle_core::errors::{RNGError, SignatureError}; + use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; use bouncycastle_core::key_material::{ KeyMaterial256, KeyMaterialTrait, KeyType, do_hazardous_operations, }; use bouncycastle_core::traits::{ - RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, + RNG, SecurityStrength, SerializableState, SignaturePrivateKey, SignaturePublicKey, + SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::FixedSeedRNG; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ @@ -974,6 +976,30 @@ mod mldsa_tests { mb.do_update(b"jumped over the lazy dog"); let mu6 = mb.do_final(); assert_eq!(mu1, mu6); + + // test SerializedState for MuBuilder + + // serializing and resuming after init + let mb = MuBuilder::do_init(&pk_expanded.compute_tr(), None).unwrap(); + let serialized_state = mb.serialize_state(); + + let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + mb_resumed.do_update(msg); + let mu_resumed = mb_resumed.do_final(); + assert_eq!(mu_resumed, mu1); + + // serializing and resuming partway through message consumption + let msg1 = b"The quick brown fox"; + let msg2 = b" jumped over the lazy dog"; + + let mut mb = MuBuilder::do_init(&pk_expanded.compute_tr(), None).unwrap(); + mb.do_update(msg1); + + let serialized_state = mb.serialize_state(); + let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + mb_resumed.do_update(msg2); + let mu_resumed2 = mb_resumed.do_final(); + assert_eq!(mu_resumed2, mu1); } #[test] @@ -997,6 +1023,66 @@ mod mldsa_tests { MLDSA44::sign_mu_deterministic_out(&sk, None, &mu, [1u8; 32], &mut sig_buf).unwrap(); MLDSA44::verify(&pk, msg, None, &sig_buf).unwrap(); } + + #[test] + fn mubuilder_serializable_state() { + // `tr` is the 64-byte public-key hash that seeds mu computation; its exact value doesn't + // matter for exercising serialization, only that it is fixed across the comparison. + let tr = [0x42u8; 64]; + let msg = b"Colorless green ideas sleep furiously"; + + // Put the builder into a mid-stream state (some, but not all, of the message absorbed). + let mut mb = MuBuilder::do_init(&tr, None).unwrap(); + mb.do_update(&msg[..10]); + + // Generic trait-conformance tests (version header present, [0,0,0] and future versions + // rejected). + TestFrameworkSerializableState::new().test(&mb); + + // Serialize the in-progress state, then finish the original. + let serialized_state = mb.clone().serialize_state(); + mb.do_update(&msg[10..]); + let expected_mu = mb.do_final(); + + // Rebuild from the serialized state, feed it the identical remaining input, and confirm it + // produces the same mu. + let mut from_state = MuBuilder::from_serialized_state(serialized_state).unwrap(); + from_state.do_update(&msg[10..]); + assert_eq!(expected_mu, from_state.do_final()); + + // Anchor correctness to the existing one-shot path: streaming must match compute_mu. + let one_shot = MuBuilder::compute_mu(&tr, msg, None).unwrap(); + assert_eq!(expected_mu, one_shot); + + // A corrupt SHAKE256 `squeezing` byte must be rejected. Layout of the delegated SHAKE256 + // state: 3 version bytes + variant tag(1) + [u64;25](200) + data_queue(192) + // + bits_in_queue(8) + squeezing(1) -> the squeezing byte is at offset 3 + 1 + 400. + let mut busted = serialized_state; + busted[3 + 1 + 400] = 42; + match MuBuilder::from_serialized_state(busted) { + Err(SerializedStateError::InvalidData) => { /* good */ } + _ => panic!("Expected an error for a corrupt squeezing byte"), + } + } + + #[test] + fn serializable_state_mubuilder_rejects_wrong_variant() { + use bouncycastle_core::traits::XOF; + use bouncycastle_sha3::SHAKE128; + + // A MuBuilder is always backed by SHAKE256. A serialized SHAKE128 state has the same length + // (both are SHA3-family states), so this would deserialize into the wrong sponge if the + // variant tag weren't checked -- SHAKE128 (tag 5) must be rejected by MuBuilder (SHAKE256, + // tag 6). + let mut shake128 = SHAKE128::new(); + shake128.absorb(b"Colorless green ideas sleep furiously"); + let serialized_128 = shake128.serialize_state(); + + match MuBuilder::from_serialized_state(serialized_128) { + Err(SerializedStateError::InvalidData) => { /* good */ } + _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), + } + } } struct Kat { diff --git a/crypto/mldsa/tests/wycheproof.rs b/crypto/mldsa/tests/wycheproof.rs index 523be36..93c9855 100644 --- a/crypto/mldsa/tests/wycheproof.rs +++ b/crypto/mldsa/tests/wycheproof.rs @@ -353,17 +353,17 @@ impl MLDSASignNoSeedTestCase { let sig = MLDSA44::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA44::verify_mu_internal( + let res = MLDSA44::verify_mu( &pk, - &pk.A_hat(), + Some(&pk.A_hat()), &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap(), ); if self.result == "valid" { - assert!(res); + res.unwrap() } else { - assert!(!res); + assert!(res.is_err()); }; } @@ -421,17 +421,17 @@ impl MLDSASignNoSeedTestCase { let sig = MLDSA65::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA65::verify_mu_internal( + let res = MLDSA65::verify_mu( &pk, - &pk.A_hat(), + Some(&pk.A_hat()), &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap(), ); if self.result == "valid" { - assert!(res); + res.unwrap() } else { - assert!(!res); + assert!(res.is_err()); }; } @@ -489,17 +489,17 @@ impl MLDSASignNoSeedTestCase { let sig = MLDSA87::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA87::verify_mu_internal( + let res = MLDSA87::verify_mu( &pk, - &pk.A_hat(), + Some(&pk.A_hat()), &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap(), ); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } } @@ -650,17 +650,17 @@ impl MLDSASignSeedTestCase { let sig = MLDSA44::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA44::verify_mu_internal( + let res = MLDSA44::verify_mu( &pk, - &pk.A_hat(), + Some(&pk.A_hat()), &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap(), ); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } @@ -742,17 +742,17 @@ impl MLDSASignSeedTestCase { let sig = MLDSA65::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA65::verify_mu_internal( + let res = MLDSA65::verify_mu( &pk, - &pk.A_hat(), + Some(&pk.A_hat()), &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap(), ); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } @@ -834,17 +834,17 @@ impl MLDSASignSeedTestCase { let sig = MLDSA87::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA87::verify_mu_internal( + let res = MLDSA87::verify_mu( &pk, - &pk.A_hat(), + Some(&pk.A_hat()), &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap(), ); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } } diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index d8a4544..d0bac1e 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -18,8 +18,8 @@ //! for example if input is received in chunks and not all available at the same time: //! //! ``` -//! use bouncycastle_core::traits::Hash; //! use bouncycastle_sha2 as sha2; +//! use bouncycastle_core::traits::Hash; //! //! let data: &[u8] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F //! \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F @@ -33,6 +33,36 @@ //! //! let output: Vec = sha2.do_final(); //! ``` +//! +//! # Suspending and resuming execution via SerializableState +//! +//! When hashing a large message, it can be advantageous to be able to suspend the operation +//! to a cache and resume it later; for example if waiting for the message to stream over a slow network +//! connection. +//! +//! For this reason, all SHA2 algorithms impl [SerializableState]. +//! +//! ```rust +//! use bouncycastle_sha2 as sha2; +//! use bouncycastle_core::traits::{Hash, SerializableState}; +//! +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let mut sha2 = sha2::SHA256::new(); +//! sha2.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = sha2.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! let mut sha2_resumed = sha2::SHA256::from_serialized_state(serialized_state).unwrap(); +//! sha2_resumed.do_update(msg_part2); +//! let h: Vec = sha2_resumed.do_final(); +//! ``` #![forbid(unsafe_code)] #![allow(private_bounds)] @@ -68,6 +98,7 @@ impl HashAlgParams for SHA224 { const OUTPUT_LEN: usize = 28; const BLOCK_LEN: usize = 64; } +#[derive(Clone)] pub struct SHA224Params; impl Algorithm for SHA224Params { const ALG_NAME: &'static str = SHA224_NAME; @@ -87,6 +118,7 @@ impl HashAlgParams for SHA256 { const OUTPUT_LEN: usize = 32; const BLOCK_LEN: usize = 64; } +#[derive(Clone)] pub struct SHA256Params; impl Algorithm for SHA256Params { const ALG_NAME: &'static str = SHA256_NAME; @@ -106,6 +138,7 @@ impl HashAlgParams for SHA384 { const OUTPUT_LEN: usize = 48; const BLOCK_LEN: usize = 128; } +#[derive(Clone)] pub struct SHA384Params; impl Algorithm for SHA384Params { const ALG_NAME: &'static str = SHA384_NAME; @@ -117,6 +150,7 @@ impl HashAlgParams for SHA384Params { } impl SHA2Params for SHA384Params {} +#[derive(Clone)] pub struct SHA512Params; impl Algorithm for SHA512 { const ALG_NAME: &'static str = SHA512_NAME; @@ -135,3 +169,6 @@ impl HashAlgParams for SHA512Params { const BLOCK_LEN: usize = 128; } impl SHA2Params for SHA512Params {} + +pub use sha256::SHA256_SERIALIZED_STATE_LEN; +pub use sha512::SHA512_SERIALIZED_STATE_LEN; diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index 6995642..bbd56e5 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -1,5 +1,5 @@ use crate::SHA2Params; -use bouncycastle_core::errors::{CoreError, HashError}; +use bouncycastle_core::errors::{HashError, SerializedStateError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; use bouncycastle_utils::min; @@ -304,9 +304,15 @@ impl Hash for SHA256Internal { } } -impl SerializableState<108> for SHA256Internal { - fn serialize_state(&self) -> [u8; 108] { - let mut out_to_return = [0u8; 108]; +/// The number of bytes produced by [SerializableState::serialize_state] for any SHA224 or SHA256 +/// object. +pub const SHA256_SERIALIZED_STATE_LEN: usize = 108; + +impl SerializableState for SHA256Internal { + fn serialize_state(self) -> [u8; SHA256_SERIALIZED_STATE_LEN] { + debug_assert_eq!(SHA256_SERIALIZED_STATE_LEN, 108); + + let mut out_to_return = [0u8; SHA256_SERIALIZED_STATE_LEN]; // insert the version tag let out: &mut [u8; 105] = add_lib_ver(&mut out_to_return).try_into().unwrap(); @@ -331,7 +337,11 @@ impl SerializableState<108> for SHA256Internal { out_to_return } - fn from_serialized_state(serialized_state: [u8; 108]) -> Result { + fn from_serialized_state( + serialized_state: [u8; SHA256_SERIALIZED_STATE_LEN], + ) -> Result { + debug_assert_eq!(SHA256_SERIALIZED_STATE_LEN, 108); + // check the version tag // At the moment, we have no not_before version to specify. let input: &[u8; 105] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); @@ -353,7 +363,7 @@ impl SerializableState<108> for SHA256Internal { // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 64 let x_buf_off: usize = input[104] as usize; if x_buf_off >= 64 { - return Err(CoreError::InvalidData); + return Err(SerializedStateError::InvalidData); } // Construct the object diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 0c5ed13..7b98b0e 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,5 +1,5 @@ use crate::SHA2Params; -use bouncycastle_core::errors::{CoreError, HashError}; +use bouncycastle_core::errors::{HashError, SerializedStateError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; use bouncycastle_utils::min; @@ -319,9 +319,15 @@ impl Hash for SHA512Internal { } } -impl SerializableState<204> for SHA512Internal { - fn serialize_state(&self) -> [u8; 204] { - let mut out_to_return = [0u8; 204]; +/// The number of bytes produced by [SerializableState::serialize_state] for any SHA384 or SHA512 +/// object. +pub const SHA512_SERIALIZED_STATE_LEN: usize = 204; + +impl SerializableState for SHA512Internal { + fn serialize_state(self) -> [u8; SHA512_SERIALIZED_STATE_LEN] { + debug_assert_eq!(SHA512_SERIALIZED_STATE_LEN, 204); + + let mut out_to_return = [0u8; SHA512_SERIALIZED_STATE_LEN]; // insert the version tag let out: &mut [u8; 201] = add_lib_ver(&mut out_to_return).try_into().unwrap(); @@ -346,7 +352,9 @@ impl SerializableState<204> for SHA512Internal { out_to_return } - fn from_serialized_state(serialized_state: [u8; 204]) -> Result { + fn from_serialized_state( + serialized_state: [u8; SHA512_SERIALIZED_STATE_LEN], + ) -> Result { // check the version tag // At the moment, we have no not_before version to specify. let input: &[u8; 201] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); @@ -368,7 +376,7 @@ impl SerializableState<204> for SHA512Internal { // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 128 let x_buf_off: usize = input[200] as usize; if x_buf_off >= 128 { - return Err(CoreError::InvalidData); + return Err(SerializedStateError::InvalidData); } // Construct the object diff --git a/crypto/sha2/tests/sha2_tests.rs b/crypto/sha2/tests/sha2_tests.rs index 442b94b..ad49cdd 100644 --- a/crypto/sha2/tests/sha2_tests.rs +++ b/crypto/sha2/tests/sha2_tests.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod sha2_tests { - use bouncycastle_core::errors::CoreError; + use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::traits::{Algorithm, Hash, HashAlgParams, SecurityStrength}; use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::hash::TestFrameworkHash; @@ -107,7 +107,7 @@ mod sha2_tests { test_framework.test(&sha256); // now let's serialize the in-progress state - let serialized_state = sha256.serialize_state(); + let serialized_state = sha256.clone().serialize_state(); // finish the hash let output = sha256.do_final(); @@ -121,7 +121,7 @@ mod sha2_tests { let mut busted_state = serialized_state.clone(); busted_state[3 + 104] = 65; match SHA256::from_serialized_state(busted_state) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error"), } @@ -134,7 +134,7 @@ mod sha2_tests { test_framework.test(&sha512); // now let's serialize the in-progress state - let serialized_state = sha512.serialize_state(); + let serialized_state = sha512.clone().serialize_state(); // finish the hash let output = sha512.do_final(); @@ -148,7 +148,7 @@ mod sha2_tests { let mut busted_state = serialized_state.clone(); busted_state[3 + 200] = 129; match SHA512::from_serialized_state(busted_state) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error"), } } diff --git a/crypto/sha3/src/keccak.rs b/crypto/sha3/src/keccak.rs index 27fc702..3609b88 100644 --- a/crypto/sha3/src/keccak.rs +++ b/crypto/sha3/src/keccak.rs @@ -1,7 +1,6 @@ -use bouncycastle_core::errors::{CoreError, HashError}; +use bouncycastle_core::errors::{HashError, SerializedStateError}; use bouncycastle_core::key_material::KeyType; use bouncycastle_core::traits::SecurityStrength; -use crate::{SHA3_224Params, SHA3_256Params, SHA3_384Params, SHA3_512Params, SHAKE128Params, SHAKE256Params, SHA3, SHAKE}; const KECCAK_ROUND_CONSTANTS: [u64; 24] = [ 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000, @@ -190,13 +189,14 @@ impl Drop for KeccakState { } } +// This is pub(crate) so that the SerializableState handlers can unpack it. #[derive(Clone)] -pub(super) struct KeccakDigest { +pub(crate) struct KeccakDigest { state: KeccakState, pub data_queue: [u8; 192], rate: usize, pub bits_in_queue: usize, - pub(super) squeezing: bool, + pub squeezing: bool, } #[derive(Clone)] @@ -372,12 +372,10 @@ const KECCAK_SERIALIZED_LEN: usize = 200 + 192 + 8 + 1; /// [.. + 1) kdf_key_type (1 byte enum tag) /// [.. + 1) kdf_security_strength (1 byte enum tag) /// [.. + 8) kdf_entropy usize serialized as u64 -pub(super) const SHA3_FAMILY_STATE_LEN: usize = 1 + KECCAK_SERIALIZED_LEN + 10; +pub(crate) const SHA3_FAMILY_STATE_LEN: usize = 1 + KECCAK_SERIALIZED_LEN + 10; -/// Total number of bytes in a serialized SHA3-family state, including the 3-byte library version -/// header prepended by [add_lib_ver]. This is the const generic used by the `SerializableState` -/// impls for SHA3 and SHAKE. -pub(super) const SHA3_FAMILY_SERIALIZED_STATE_LEN: usize = 3 + SHA3_FAMILY_STATE_LEN; +/// Total number of bytes in a serialized state of a SHA3 or SHAKE instance. +pub const SHA3_SERIALIZED_STATE_LEN: usize = 3 + SHA3_FAMILY_STATE_LEN; impl KeccakDigest { /// Serializes this digest's mutable state into `out`. The `rate` is deliberately omitted; see @@ -407,7 +405,7 @@ impl KeccakDigest { fn from_serialized_state( input: &[u8; KECCAK_SERIALIZED_LEN], rate: usize, - ) -> Result { + ) -> Result { // state.buf: [u64; 25] let mut buf = [0u64; 25]; for i in 0..25 { @@ -421,14 +419,14 @@ impl KeccakDigest { // bytes, well within data_queue's 192-byte capacity). let bits_in_queue = u64::from_le_bytes(input[392..400].try_into().unwrap()) as usize; if bits_in_queue > rate { - return Err(CoreError::InvalidData); + return Err(SerializedStateError::InvalidData); } // squeezing: bool let squeezing = match input[400] { 0 => false, 1 => true, - _ => return Err(CoreError::InvalidData), + _ => return Err(SerializedStateError::InvalidData), }; Ok(Self { state: KeccakState { buf, rate }, data_queue, rate, bits_in_queue, squeezing }) @@ -437,7 +435,7 @@ impl KeccakDigest { /// Serializes the state shared by all SHA3-family objects (the `variant_tag`, a [KeccakDigest], plus /// the three KDF metadata fields) into `out`. See [SHA3_FAMILY_STATE_LEN] for the layout. -pub(super) fn serialize_sha3_family_state( +pub(crate) fn serialize_sha3_family_state( out: &mut [u8; SHA3_FAMILY_STATE_LEN], variant_tag: u8, keccak: &KeccakDigest, @@ -463,13 +461,13 @@ pub(super) fn serialize_sha3_family_state( /// tag is checked against the serialized one first: this is what prevents a state from one variant /// being loaded into another (e.g. SHA3-256 vs SHAKE256, which share a rate but differ in domain /// separation). Only once the tag matches is `rate` guaranteed to be the correct one to rebuild with. -pub(super) fn deserialize_sha3_family_state( +pub(crate) fn deserialize_sha3_family_state( input: &[u8; SHA3_FAMILY_STATE_LEN], expected_variant_tag: u8, rate: usize, -) -> Result<(KeccakDigest, KeyType, SecurityStrength, usize), CoreError> { +) -> Result<(KeccakDigest, KeyType, SecurityStrength, usize), SerializedStateError> { if input[0] != expected_variant_tag { - return Err(CoreError::InvalidData); + return Err(SerializedStateError::InvalidData); } let keccak_in: &[u8; KECCAK_SERIALIZED_LEN] = diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 3f5a7ca..8a80639 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -103,6 +103,36 @@ //! This would also be the case even if the input had type //! [KeyType::CryptographicRandom] since the input [KeyMaterial] is 16 bytes but [SHA3_256] needs at least 32 bytes of //! full-entropy input key material in order to be able to produce full entropy output key material. +//! +//! # Suspending and resuming execution via SerializableState +//! +//! When hashing a large message, it can be advantageous to be able to suspend the operation +//! to a cache and resume it later; for example if waiting for the message to stream over a slow network +//! connection. +//! +//! For this reason, all SHA3 algorithms impl [SerializableState]. +//! +//!```rust +//! use bouncycastle_sha3 as sha3; +//! use bouncycastle_core::traits::{Hash, SerializableState}; +//! +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let mut sha3 = sha3::SHA3_256::new(); +//! sha3.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = sha3.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! let mut sha3_resumed = sha3::SHA3_256::from_serialized_state(serialized_state).unwrap(); +//! sha3_resumed.do_update(msg_part2); +//! let h: Vec = sha3_resumed.do_final(); +//! ``` #![forbid(unsafe_code)] #![allow(private_bounds)] @@ -132,6 +162,10 @@ pub const SHAKE256_NAME: &str = "SHAKE256"; /*** pub types ***/ pub use sha3::SHA3; pub use shake::SHAKE; + +/// The number of bytes produced by [SerializableState::serialize_state] for any SHA3 or SHAKE +/// object. +pub use keccak::SHA3_SERIALIZED_STATE_LEN; pub type SHA3_224 = SHA3; pub type SHA3_256 = SHA3; pub type SHA3_384 = SHA3; @@ -160,6 +194,7 @@ impl HashAlgParams for SHA3_224 { // const BLOCK_LEN: usize = 64; const BLOCK_LEN: usize = 144; // FIPS 202 Table 3 } +#[derive(Clone)] pub struct SHA3_224Params; impl Algorithm for SHA3_224Params { const ALG_NAME: &'static str = SHA3_224_NAME; @@ -184,6 +219,7 @@ impl HashAlgParams for SHA3_256 { // const BLOCK_LEN: usize = 64; const BLOCK_LEN: usize = 136; // FIPS 202 Table 3 } +#[derive(Clone)] pub struct SHA3_256Params; impl Algorithm for SHA3_256Params { const ALG_NAME: &'static str = SHA3_256_NAME; @@ -199,6 +235,7 @@ impl SHA3Params for SHA3_256Params { const STATE_TAG: u8 = 2; } +#[derive(Clone)] pub struct SHA3_384Params; impl Algorithm for SHA3_384 { const ALG_NAME: &'static str = SHA3_384_NAME; @@ -223,6 +260,7 @@ impl SHA3Params for SHA3_384Params { const STATE_TAG: u8 = 3; } +#[derive(Clone)] pub struct SHA3_512Params; impl Algorithm for SHA3_512 { const ALG_NAME: &'static str = SHA3_512_NAME; @@ -252,6 +290,7 @@ trait SHAKEParams: Algorithm { /// See [SHA3Params::STATE_TAG]. Must be distinct from every SHA3 *and* SHAKE variant's tag. const STATE_TAG: u8; } +#[derive(Clone)] pub struct SHAKE128Params; impl Algorithm for SHAKE128Params { const ALG_NAME: &'static str = SHAKE128_NAME; @@ -262,6 +301,7 @@ impl SHAKEParams for SHAKE128Params { const STATE_TAG: u8 = 5; } +#[derive(Clone)] pub struct SHAKE256Params; impl Algorithm for SHAKE256Params { const ALG_NAME: &'static str = SHAKE256_NAME; diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 2e4ca72..4d78f37 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -1,9 +1,9 @@ use crate::SHA3Params; use crate::keccak::{ - KeccakDigest, SHA3_FAMILY_SERIALIZED_STATE_LEN, SHA3_FAMILY_STATE_LEN, - deserialize_sha3_family_state, serialize_sha3_family_state, + KeccakDigest, SHA3_FAMILY_STATE_LEN, SHA3_SERIALIZED_STATE_LEN, deserialize_sha3_family_state, + serialize_sha3_family_state, }; -use bouncycastle_core::errors::{CoreError, HashError, KDFError}; +use bouncycastle_core::errors::{HashError, KDFError, SerializedStateError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; @@ -221,9 +221,9 @@ impl Hash for SHA3 { } } -impl SerializableState for SHA3 { - fn serialize_state(&self) -> [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN] { - let mut out_to_return = [0u8; SHA3_FAMILY_SERIALIZED_STATE_LEN]; +impl SerializableState for SHA3 { + fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { + let mut out_to_return = [0u8; SHA3_SERIALIZED_STATE_LEN]; // insert the version tag let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = @@ -242,8 +242,8 @@ impl SerializableState for } fn from_serialized_state( - serialized_state: [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN], - ) -> Result { + serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + ) -> Result { // check the version tag. At the moment, we have no not_before version to specify. let input: &[u8; SHA3_FAMILY_STATE_LEN] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index b2252b1..dd05d52 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -1,9 +1,9 @@ use crate::SHAKEParams; use crate::keccak::{ - KeccakDigest, KeccakSize, SHA3_FAMILY_SERIALIZED_STATE_LEN, SHA3_FAMILY_STATE_LEN, + KeccakDigest, KeccakSize, SHA3_FAMILY_STATE_LEN, SHA3_SERIALIZED_STATE_LEN, deserialize_sha3_family_state, serialize_sha3_family_state, }; -use bouncycastle_core::errors::{CoreError, HashError, KDFError}; +use bouncycastle_core::errors::{HashError, KDFError, SerializedStateError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; @@ -142,9 +142,9 @@ impl SHAKE { } } -impl SerializableState for SHAKE { - fn serialize_state(&self) -> [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN] { - let mut out_to_return = [0u8; SHA3_FAMILY_SERIALIZED_STATE_LEN]; +impl SerializableState for SHAKE { + fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { + let mut out_to_return = [0u8; SHA3_SERIALIZED_STATE_LEN]; // insert the version tag let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = @@ -163,8 +163,8 @@ impl SerializableState fo } fn from_serialized_state( - serialized_state: [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN], - ) -> Result { + serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + ) -> Result { // check the version tag. At the moment, we have no not_before version to specify. let input: &[u8; SHA3_FAMILY_STATE_LEN] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index 0cdb21a..a93704e 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -396,21 +396,21 @@ mod sha3_tests { #[test] fn test_serializable_state() { - use bouncycastle_core::errors::CoreError; + use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::traits::SerializableState; use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; let str = "Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one SHA3 variant. - fn round_trip>(mut hash: H, input: &[u8]) { + fn round_trip + Clone>(mut hash: H, input: &[u8]) { hash.do_update(input); // do the default trait-conformance tests TestFrameworkSerializableState::new().test(&hash); // serialize the in-progress state, then finish the original - let serialized_state = hash.serialize_state(); + let serialized_state = hash.clone().serialize_state(); let expected = hash.do_final(); // rebuild from the serialized state and confirm it produces the same digest @@ -423,7 +423,7 @@ mod sha3_tests { let mut busted = serialized_state; busted[3 + 1 + 400] = 42; match H::from_serialized_state(busted) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } } @@ -440,11 +440,11 @@ mod sha3_tests { sha3_256.do_update(str.as_bytes()); let serialized_256 = sha3_256.serialize_state(); match SHA3_512::from_serialized_state(serialized_256) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHA3-256 state into SHA3-512"), } match SHAKE256::from_serialized_state(serialized_256) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHA3-256 state into SHAKE256"), } } diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index d0611c7..10584f7 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -240,21 +240,21 @@ mod shake_tests { #[test] fn test_serializable_state() { - use bouncycastle_core::errors::CoreError; + use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::traits::SerializableState; use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; let str = "Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one SHAKE variant. - fn round_trip>(mut shake: X, input: &[u8]) { + fn round_trip + Clone>(mut shake: X, input: &[u8]) { shake.absorb(input); // do the default trait-conformance tests TestFrameworkSerializableState::new().test(&shake); // serialize the in-progress (absorbing) state, then squeeze from the original - let serialized_state = shake.serialize_state(); + let serialized_state = shake.clone().serialize_state(); let expected = shake.squeeze(64); // rebuild from the serialized state and confirm it produces the same output @@ -267,7 +267,7 @@ mod shake_tests { let mut busted = serialized_state; busted[3 + 1 + 400] = 42; match X::from_serialized_state(busted) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } } @@ -282,7 +282,7 @@ mod shake_tests { shake128.absorb(str.as_bytes()); let serialized_128 = shake128.serialize_state(); match SHAKE256::from_serialized_state(serialized_128) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into SHAKE256"), } @@ -290,7 +290,7 @@ mod shake_tests { shake256.absorb(str.as_bytes()); let serialized_256 = shake256.serialize_state(); match SHA3_256::from_serialized_state(serialized_256) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE256 state into SHA3-256"), } } From d7192dd701a5cd7beb031f0e35885b6f18c79700 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 6 Jul 2026 17:36:54 -0500 Subject: [PATCH 26/35] rustfmt --- crypto/sha3/tests/sha3_tests.rs | 5 ++++- crypto/sha3/tests/shake_tests.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index a93704e..ffff758 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -403,7 +403,10 @@ mod sha3_tests { let str = "Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one SHA3 variant. - fn round_trip + Clone>(mut hash: H, input: &[u8]) { + fn round_trip + Clone>( + mut hash: H, + input: &[u8], + ) { hash.do_update(input); // do the default trait-conformance tests diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index 10584f7..1e43b39 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -247,7 +247,10 @@ mod shake_tests { let str = "Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one SHAKE variant. - fn round_trip + Clone>(mut shake: X, input: &[u8]) { + fn round_trip + Clone>( + mut shake: X, + input: &[u8], + ) { shake.absorb(input); // do the default trait-conformance tests From 45ba74244a82dfbedebcd9146d3a2c5fea5819eb Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 6 Jul 2026 18:21:33 -0500 Subject: [PATCH 27/35] Impl'd SerializableState for MLDSA-lowmemory. --- crypto/mldsa-lowmemory/src/hash_mldsa.rs | 9 +- crypto/mldsa-lowmemory/src/lib.rs | 2 + crypto/mldsa-lowmemory/src/mldsa.rs | 139 ++++++++++++++++---- crypto/mldsa-lowmemory/tests/mldsa_tests.rs | 90 ++++++++++++- crypto/mldsa-lowmemory/tests/wycheproof.rs | 33 ++--- 5 files changed, 221 insertions(+), 52 deletions(-) diff --git a/crypto/mldsa-lowmemory/src/hash_mldsa.rs b/crypto/mldsa-lowmemory/src/hash_mldsa.rs index 1c9f9e8..179301c 100644 --- a/crypto/mldsa-lowmemory/src/hash_mldsa.rs +++ b/crypto/mldsa-lowmemory/src/hash_mldsa.rs @@ -1236,7 +1236,7 @@ impl< let mut mu = [0u8; MLDSA_MU_LEN]; _ = h.squeeze_out(&mut mu); - if MLDSA::< + MLDSA::< PK_LEN, SK_LEN, FULL_SK_LEN, @@ -1262,11 +1262,6 @@ impl< GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, GAMMA1_MASK_LEN, - >::verify_mu_internal(pk, &mu, sig_sized) - { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } + >::verify_mu(pk, &mu, sig_sized) } } diff --git a/crypto/mldsa-lowmemory/src/lib.rs b/crypto/mldsa-lowmemory/src/lib.rs index 64a91f1..5d2ed88 100644 --- a/crypto/mldsa-lowmemory/src/lib.rs +++ b/crypto/mldsa-lowmemory/src/lib.rs @@ -258,5 +258,7 @@ pub use mldsa::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; pub use mldsa::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; pub use mldsa::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; +pub use mldsa::MU_BUILDER_SERIALIZED_STATE_LEN; + // re-export just so it's visible to unit tests pub use polynomial::Polynomial; diff --git a/crypto/mldsa-lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs index f7124e1..befebd7 100644 --- a/crypto/mldsa-lowmemory/src/mldsa.rs +++ b/crypto/mldsa-lowmemory/src/mldsa.rs @@ -308,6 +308,81 @@ //! private key encoding (which is often called the "semi-expanded format" since the in-memory representation //! is still larger). //! Contact us if you need such a thing implemented. +//! +//! # Suspending and resuming execution via SerializableState +//! +//! When signing or verifying a large message, it can be advantageous to be able to suspend the operation +//! to a cache and resume it later; for example if waiting for the message to stream over a slow network +//! connection. +//! +//! This can bo accomplished for both the ML-DSA signer and verifier through the [MuBuilder] object. +//! +//! Suspending an in-progress sign operation: +//! +//! ```rust +//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; +//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let (pk, sk) = MLDSA65::keygen().unwrap(); +//! +//! let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); +//! mb.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = mb.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! mb_resumed.do_update(msg_part2); +//! let mu: [u8; 64] = mb_resumed.do_final(); +//! +//! // Now we'll do the actual sign_mu operation +//! let sig = MLDSA65::sign_mu(&sk, &mu).unwrap(); +//! ``` +//! +//! Suspending an in-progress verify operation behaves exactly the same way: +//! +//! ```rust +//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; +//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! use bouncycastle_core::errors::SignatureError; +//! +//! let (pk, sk) = MLDSA65::keygen().unwrap(); +//! +//! // first, let's generate a signature to verify +//! let sig = MLDSA65::sign(&sk, b"The quick brown fox jumped over the lazy dog", None).unwrap(); +//! +//! // Now we'll verify it with a suspension in the middle +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); +//! mb.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = mb.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! mb_resumed.do_update(msg_part2); +//! let mu: [u8; 64] = mb_resumed.do_final(); +//! +//! // Now we'll do the actual verify_mu operation +//! match MLDSA65::verify_mu(&pk, &mu, &sig) { +//! Ok(()) => println!("Signature is valid!"), +//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), +//! Err(e) => panic!("Something else went wrong: {:?}", e), +//! } +//! ``` use crate::aux_functions::{ bitlen_eta, bitpack_gamma1, sample_in_ball, unpack_c_tilde, unpack_h_row, @@ -322,11 +397,13 @@ use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, }; -use bouncycastle_core::errors::{RNGError, SignatureError}; +use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, XOF}; +use bouncycastle_core::traits::{ + Algorithm, RNG, SecurityStrength, SerializableState, SignatureVerifier, Signer, XOF, +}; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha3::{SHAKE128, SHAKE256}; +use bouncycastle_sha3::{SHA3_SERIALIZED_STATE_LEN, SHAKE128, SHAKE256}; use core::marker::PhantomData; // imports needed just for docs @@ -1210,7 +1287,7 @@ impl< /// Internal function to verify a signature 𝜎 for a formatted message 𝑀′ . /// Input: Public key π‘π‘˜ ∈ 𝔹32+32π‘˜(bitlen (π‘žβˆ’1)βˆ’π‘‘) and message 𝑀′ ∈ {0, 1}βˆ— . /// Input: Signature 𝜎 ∈ π”Ήπœ†/4+β„“β‹…32β‹…(1+bitlen (𝛾1βˆ’1))+πœ”+π‘˜. - fn verify_mu_internal(pk: &PK, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> bool { + fn verify_mu(pk: &PK, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> Result<(), SignatureError> { // 1: (𝜌, 𝐭1) ← pkDecode(π‘π‘˜) // Already done -- the pk struct is already decoded @@ -1247,7 +1324,7 @@ impl< } { Ok(wp_approx) => wp_approx, // means the norm check on z failed - Err(_) => return false, + Err(_) => return Err(SignatureError::SignatureVerificationFailed), }; let h_i = match unpack_h_row::< @@ -1262,7 +1339,7 @@ impl< { Some(h_i) => h_i, // means there were more than OMEGA bits set in the hint - None => return false, + None => return Err(SignatureError::SignatureVerificationFailed), }; // 10: 𝐰1β€² ← UseHint(𝐑, 𝐰'_approx) @@ -1278,7 +1355,11 @@ impl< // 13 (second half): return [[ ||𝐳||∞ < 𝛾1 βˆ’ 𝛽]] and [[𝑐 Μƒ = 𝑐′ ]] // note: the first half of this check (the norm check) is buried in unpack_z_row(), // which is called from compute_wp_approx_row() - bouncycastle_utils::ct::ct_eq_bytes(unpack_c_tilde::(sig), &c_tilde_p) + if bouncycastle_utils::ct::ct_eq_bytes(unpack_c_tilde::(sig), &c_tilde_p) { + Ok(()) + } else { + Err(SignatureError::SignatureVerificationFailed) + } } } @@ -1504,11 +1585,10 @@ pub trait MLDSATrait< seed: &KeyMaterial<32>, ctx: Option<&[u8]>, ) -> Result; - /// Algorithm 8 ML-DSA.Verify_internal(π‘π‘˜, 𝑀′, 𝜎) - /// Internal function to verify a signature 𝜎 for a formatted message 𝑀′ . - /// Input: Public key π‘π‘˜ ∈ 𝔹32+32π‘˜(bitlen (π‘žβˆ’1)βˆ’π‘‘) and message 𝑀′ ∈ {0, 1}βˆ— . - /// Input: Signature 𝜎 ∈ π”Ήπœ†/4+β„“β‹…32β‹…(1+bitlen (𝛾1βˆ’1))+πœ”+π‘˜. - fn verify_mu_internal(pk: &PK, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> bool; + /// Performs an ML-DSA signature verification using the provided external message representative `mu`. + /// This implements FIPS 204 Algorithm 8 with line 7 removed; a modification that is allowed by both + /// FIPS 204 itself, as well as subsequent FAQ documents. + fn verify_mu(pk: &PK, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> Result<(), SignatureError>; } impl< @@ -1746,11 +1826,7 @@ impl< if sig.len() != SIG_LEN { return Err(SignatureError::LengthError("Signature value is not the correct length.")); } - if Self::verify_mu_internal(pk, &mu, &sig.try_into().unwrap()) { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } + Self::verify_mu(pk, &mu, &sig.try_into().unwrap()) } fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result { @@ -1780,11 +1856,7 @@ impl< return Err(SignatureError::LengthError("Signature value is not the correct length.")); } - if Self::verify_mu_internal(&self.pk.unwrap(), &mu, &sig.try_into().unwrap()) { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } + Self::verify_mu(&self.pk.unwrap(), &mu, &sig.try_into().unwrap()) } } @@ -1797,6 +1869,7 @@ impl< /// 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 [PHSigner::sign_ph]. +#[derive(Clone)] pub struct MuBuilder { h: H, } @@ -1862,3 +1935,25 @@ impl MuBuilder { mu } } + +/// The length, in bytes, of a serialized state of a [MuBuilder] object. +pub const MU_BUILDER_SERIALIZED_STATE_LEN: usize = SHA3_SERIALIZED_STATE_LEN; + +/// If you are processing a large input message into ML-DSA and want to pause the operation +/// -- maybe while waiting for slow network IO), you'll need to use [SerializableState]. +/// Serialization of the state of an in-progress ML-DSA instance is really just serialization +/// of the construction of the message representative mu, since no other part of the ML-DSA algorithm +/// has a pausable state. +// A [MuBuilder]'s (and by virtue, an ML-DSA instance's) entire mutable state is its inner SHAKE256 sponge, +// so serialization delegates directly to [SHAKE256]'s [SerializableState] impl. +impl SerializableState for MuBuilder { + fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { + self.h.serialize_state() + } + + fn from_serialized_state( + serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + ) -> Result { + Ok(MuBuilder { h: H::from_serialized_state(serialized_state)? }) + } +} diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index f1cd7ac..b69cb67 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -2,14 +2,16 @@ #[cfg(test)] mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; - use bouncycastle_core::errors::{RNGError, SignatureError}; + use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, + RNG, SecurityStrength, SerializableState, SignaturePrivateKey, SignaturePublicKey, + SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::FixedSeedRNG; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ @@ -810,6 +812,90 @@ mod mldsa_tests { mb.do_update(b"jumped over the lazy dog"); let mu6 = mb.do_final(); assert_eq!(mu1, mu6); + + // test SerializedState for MuBuilder + + // serializing and resuming after init + let mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); + let serialized_state = mb.serialize_state(); + + let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + mb_resumed.do_update(msg); + let mu_resumed = mb_resumed.do_final(); + assert_eq!(mu_resumed, mu1); + + // serializing and resuming partway through message consumption + let msg1 = b"The quick brown fox"; + let msg2 = b" jumped over the lazy dog"; + + let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); + mb.do_update(msg1); + + let serialized_state = mb.serialize_state(); + let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + mb_resumed.do_update(msg2); + let mu_resumed2 = mb_resumed.do_final(); + assert_eq!(mu_resumed2, mu1); + } + + #[test] + fn mubuilder_serializable_state() { + // `tr` is the 64-byte public-key hash that seeds mu computation; its exact value doesn't + // matter for exercising serialization, only that it is fixed across the comparison. + let tr = [0x42u8; 64]; + let msg = b"Colorless green ideas sleep furiously"; + + // Put the builder into a mid-stream state (some, but not all, of the message absorbed). + let mut mb = MuBuilder::do_init(&tr, None).unwrap(); + mb.do_update(&msg[..10]); + + // Generic trait-conformance tests (version header present, [0,0,0] and future versions + // rejected). + TestFrameworkSerializableState::new().test(&mb); + + // Serialize the in-progress state, then finish the original. + let serialized_state = mb.clone().serialize_state(); + mb.do_update(&msg[10..]); + let expected_mu = mb.do_final(); + + // Rebuild from the serialized state, feed it the identical remaining input, and confirm it + // produces the same mu. + let mut from_state = MuBuilder::from_serialized_state(serialized_state).unwrap(); + from_state.do_update(&msg[10..]); + assert_eq!(expected_mu, from_state.do_final()); + + // Anchor correctness to the existing one-shot path: streaming must match compute_mu. + let one_shot = MuBuilder::compute_mu(&tr, msg, None).unwrap(); + assert_eq!(expected_mu, one_shot); + + // A corrupt SHAKE256 `squeezing` byte must be rejected. Layout of the delegated SHAKE256 + // state: 3 version bytes + variant tag(1) + [u64;25](200) + data_queue(192) + // + bits_in_queue(8) + squeezing(1) -> the squeezing byte is at offset 3 + 1 + 400. + let mut busted = serialized_state; + busted[3 + 1 + 400] = 42; + match MuBuilder::from_serialized_state(busted) { + Err(SerializedStateError::InvalidData) => { /* good */ } + _ => panic!("Expected an error for a corrupt squeezing byte"), + } + } + + #[test] + fn serializable_state_mubuilder_rejects_wrong_variant() { + use bouncycastle_core::traits::XOF; + use bouncycastle_sha3::SHAKE128; + + // A MuBuilder is always backed by SHAKE256. A serialized SHAKE128 state has the same length + // (both are SHA3-family states), so this would deserialize into the wrong sponge if the + // variant tag weren't checked -- SHAKE128 (tag 5) must be rejected by MuBuilder (SHAKE256, + // tag 6). + let mut shake128 = SHAKE128::new(); + shake128.absorb(b"Colorless green ideas sleep furiously"); + let serialized_128 = shake128.serialize_state(); + + match MuBuilder::from_serialized_state(serialized_128) { + Err(SerializedStateError::InvalidData) => { /* good */ } + _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), + } } #[test] diff --git a/crypto/mldsa-lowmemory/tests/wycheproof.rs b/crypto/mldsa-lowmemory/tests/wycheproof.rs index 3dcdedc..9a95f45 100644 --- a/crypto/mldsa-lowmemory/tests/wycheproof.rs +++ b/crypto/mldsa-lowmemory/tests/wycheproof.rs @@ -333,16 +333,13 @@ impl MLDSASignSeedTestCase { let sig = MLDSA44::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA44::verify_mu_internal( - &pk, - &mu, - &hex::decode(&self.sig).unwrap().try_into().unwrap(), - ); + let res = + MLDSA44::verify_mu(&pk, &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap()); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } @@ -430,16 +427,13 @@ impl MLDSASignSeedTestCase { let sig = MLDSA65::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA65::verify_mu_internal( - &pk, - &mu, - &hex::decode(&self.sig).unwrap().try_into().unwrap(), - ); + let res = + MLDSA65::verify_mu(&pk, &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap()); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } @@ -527,16 +521,13 @@ impl MLDSASignSeedTestCase { let sig = MLDSA87::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA87::verify_mu_internal( - &pk, - &mu, - &hex::decode(&self.sig).unwrap().try_into().unwrap(), - ); + let res = + MLDSA87::verify_mu(&pk, &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap()); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } } From d9b06bdce2b3cd86032c7b1c2cc13540aa5a71bf Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 6 Jul 2026 21:54:22 -0500 Subject: [PATCH 28/35] Impl'd SerializableKeyedState for HMAC. --- .../src/serializable_state.rs | 64 ++++++++- crypto/core/src/traits.rs | 7 +- crypto/hmac/src/lib.rs | 124 ++++++++++++++---- crypto/hmac/tests/hmac_tests.rs | 59 +++++++++ 4 files changed, 229 insertions(+), 25 deletions(-) diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/serializable_state.rs index 2daea7d..694019b 100644 --- a/crypto/core-test-framework/src/serializable_state.rs +++ b/crypto/core-test-framework/src/serializable_state.rs @@ -1,6 +1,6 @@ use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::serializable_state::{LIB_VERSION, SemVer}; -use bouncycastle_core::traits::SerializableState; +use bouncycastle_core::traits::{SerializableKeyedState, SerializableState}; pub struct TestFrameworkSerializableState {} @@ -62,3 +62,65 @@ impl TestFrameworkSerializableState { } } } + +pub struct TestFrameworkSerializableKeyedState {} + +impl TestFrameworkSerializableKeyedState { + pub fn new() -> Self { + Self {} + } + + /// Test all the members of trait SerializableState. + /// + /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. + pub fn test< + const SERIALIZED_STATE_LEN: usize, + S: SerializableKeyedState + Clone, + >( + &self, + instance: &S, + key: &S::Key, + ) { + // There's not a lot we can test here in the abstract, but we can test a few things to + // ensure that the SerializableState trait has been impl'd correctly. + + // we need to work on a clone because .serialize_state() moves self, which you can't do on a + // borrowed instance. + let instance_clone = instance.clone(); + + // You can serialize and then deserialize the state. + let serialized_state = instance_clone.serialize_state(); + assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN); + + let _deserialized_state = S::from_serialized_state(serialized_state, key).unwrap(); + + // The serialized state MUST include a prefix indicating the current version of the library. + let state_sized: [u8; 3] = serialized_state[..3].try_into().unwrap(); + assert_eq!(SemVer::from(state_sized), LIB_VERSION); + + // All implementations MUST reject a serialized state from lib ver 0.0.0 + // This doesn't really serve any purpose except testing that all impl's have properly + // used the helper functions. + let mut ver0_serialized_state = serialized_state.clone(); + ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); + match S::from_serialized_state(ver0_serialized_state, key) { + Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + _ => { + panic!("Expected IncompatibleVersion error") + } + } + + // All implementations MUST reject a serialized state from a future version. + let mut future_ver = LIB_VERSION; + future_ver.patch += 1; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + match S::from_serialized_state(futurever_serialized_state, key) { + Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + _ => { + panic!("Expected IncompatibleVersion error") + } + } + } +} diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 8c774d9..e2f9efc 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -527,7 +527,10 @@ pub trait SerializableState: Sized { /// The difference is that this trait is for keyed algorithms -- MACs, symmetric ciphers, signatures, etc -- /// which require a private key. For security reasons, the private key is not included in the serialized state /// and must be provided separately as part of the deserialization process. -pub trait SerializableKeyedState: Sized { +pub trait SerializableKeyedState: Sized { + /// The type of key that must be re-supplied to resume this object. + type Key: ?Sized; + /// Serialize the state of the object. /// /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization. @@ -544,7 +547,7 @@ pub trait SerializableKeyedState: Sized { /// deserializer should reject serialized states from that version or older. fn from_serialized_state( serialized_state: [u8; SERIALIZED_STATE_LEN], - key: K, + key: &Self::Key, ) -> Result; } diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index c63c249..70e5816 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -137,29 +137,58 @@ //! } //! ``` //! -//! # Request for feedback on fallability of this API -//! We have made an effort to reduce the number of fallibly APIs in the [MAC] trait; in particular -//! the APIs for the update and most of the finalize phases are infallible -- they just work. -//! However, we were not able to design it to completely avoid runtime error conditions, such as -//! providing [MAC::do_final_out] with a buffer that is too small to hold what NIST considered the smallest -//! valid MAC value for the given underlying hash function. +//! # Suspending and resuming execution via SerializableState //! -//! Also, the key strength and type checking in the initialization phase means that both [MAC::new] and -//! [MAC::new_allow_weak_key] have several runtime error conditions that they check for. +//! When MAC'ing a large message, it can be advantageous to be able to suspend the operation +//! to a cache and resume it later; for example if waiting for the message to stream over a slow network +//! connection. For this reason, all HMAC algorithms impl [SerializableKeyedState]. //! -//! All of this leads to application code that requires a lot more .unwrap(), .expect(), or match than we would -//! really like. +//! Note that since HMAC is a keyed +//! algorithm and we do not want to serialize the private key into the state, the trait structure forces you to +//! re-provide the same key when you resume the operation. Securely storing this key in the interim +//! is the responsibility of the caller. Note also that if you resume the HMAC with the wrong key, +//! `from_serialized_state` has no way to detect this, so the end result will be a broken MAC value +//! computed with different keys in the inner and outer pad. So make sure you resume with the same key! //! -//! We would love feedback on an alternate design for this API than carries less runtime error -//! conditions, without sacrificing the key strength checking that the metadata in the [KeyMaterialTrait] object allows. +//!```rust +//! use bouncycastle_hmac::HMAC_SHA256; +//! use bouncycastle_core::key_material::KeyMaterial256; +//! use bouncycastle_core::traits::{MAC, SerializableKeyedState}; +//! use bouncycastle_core::key_material::KeyType; +//! +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let key = KeyMaterial256::from_bytes_as_type( +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! KeyType::MACKey).unwrap(); +//! +//! let mut hmac = HMAC_SHA256::new(&key).unwrap(); +//! hmac.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = hmac.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! // Since the key is not serialized into the state, you have to store it somewhere and provide +//! // it to the deserializer. Make sure you store it securely! +//! let mut hmac_resumed = HMAC_SHA256::from_serialized_state(serialized_state, &key).unwrap(); +//! hmac_resumed.do_update(msg_part2); +//! let h: Vec = hmac_resumed.do_final(); +//! ``` #![forbid(unsafe_code)] #![allow(incomplete_features)] // because at time of writing, generic_const_exprs is not a stable feature #![feature(generic_const_exprs)] -use bouncycastle_core::errors::{KeyMaterialError, MACError}; +use bouncycastle_core::errors::{KeyMaterialError, MACError, SerializedStateError}; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Algorithm, Hash, MAC, SecurityStrength}; +use bouncycastle_core::traits::{ + Algorithm, Hash, MAC, SecurityStrength, SerializableKeyedState, SerializableState, +}; use bouncycastle_sha2::{SHA224, SHA256, SHA384, SHA512}; use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; use bouncycastle_utils::ct; @@ -283,6 +312,20 @@ impl HMAC { self.hasher.do_update(&padded) } + /// Loads the raw key bytes into `self.key` / `self.key_len`, pre-hashing them first if they + /// exceed the underlying hash's block length (per RFC 2104 Section 2). This does NOT absorb the + /// key into the hasher; that is done separately via [HMAC::pad_key_into_hasher]. + fn load_key_material(&mut self, key_bytes: &[u8]) { + if key_bytes.len() > self.hasher.block_bitlen() / 8 { + // then we have to pre-hash it -- use a new instance of the hasher rather than the internal one + HASH::default().hash_out(key_bytes, &mut self.key[..self.hasher.output_len()]); + self.key_len = self.hasher.output_len(); + } else { + self.key[..key_bytes.len()].copy_from_slice(key_bytes); + self.key_len = key_bytes.len(); + } + } + /// Private init so that users are forced to go through one of the public new methods and thus we /// don't need to track state errors. fn init(&mut self, key: &impl KeyMaterialTrait, allow_weak_keys: bool) -> Result<(), MACError> { @@ -300,14 +343,7 @@ impl HMAC { // length of the underlying hashes algorithm, we apply a hash invocation // over the key first. - if key.key_len() > self.hasher.block_bitlen() / 8 { - // then we have to pre-hash it -- use a new instance of the hasher rather than the internal one - HASH::default().hash_out(key.ref_to_bytes(), &mut self.key[..self.hasher.output_len()]); - self.key_len = self.hasher.output_len(); - } else { - self.key[..key.key_len()].copy_from_slice(key.ref_to_bytes()); - self.key_len = key.key_len(); - } + self.load_key_material(key.ref_to_bytes()); self.pad_key_into_hasher(IPAD_BYTE); @@ -420,3 +456,47 @@ impl MAC for HMAC { HASH::default().max_security_strength() } } + +/// HMAC is a keyed algorithm, so it implements [SerializableKeyedState] (rather than +/// [SerializableState]) for suspending and resuming in-progress operations. +/// The key is deliberately NOT written into the serialized +/// bytes and must be re-supplied at deserialization. +/// +/// The serialized state is exactly the inner hasher's state (which has already absorbed `K βŠ• ipad` +/// and any message chunks provided so far) β€” so this is a straight passthrough to the underlying hash's +/// [SerializableState] impl. The re-supplied key is needed to reconstruct the material for the outer +/// (`K βŠ• opad`) step at finalization. +/// +/// There is no way to detect a mismatched key on +/// resume: the caller MUST supply the same key the HMAC was created with, otherwise the resumed +/// operation will silently produce an incorrect MAC. +impl> + SerializableKeyedState for HMAC +{ + // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` + // rather than a single concrete key type. The key is only used (by reference) to reload the key + // bytes at from_serialized_state, so dynamic dispatch here is negligible. + type Key = dyn KeyMaterialTrait; + + fn serialize_state(self) -> [u8; HASH_STATE_LEN] { + // The key is intentionally excluded; the resumable state is just the inner hasher, which + // already carries the library version header from the hash's own SerializableState impl. + self.hasher.serialize_state() + } + + fn from_serialized_state( + serialized_state: [u8; HASH_STATE_LEN], + key: &dyn KeyMaterialTrait, + ) -> Result { + // Rebuild the inner hasher (version-compatibility is validated by the hash's impl). + let hasher = HASH::from_serialized_state(serialized_state)?; + + // Re-load the key material exactly as `new()` did (pre-hashing an over-length key), but do + // NOT re-absorb `K βŠ• ipad` β€” the deserialized hasher already contains it. The key is only + // needed for the outer `K βŠ• opad` step at finalization. + let mut hmac = HMAC { hasher, key: [0u8; LARGEST_HASHER_OUTPUT_LEN], key_len: 0 }; + hmac.load_key_material(key.ref_to_bytes()); + + Ok(hmac) + } +} diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index 862bfa1..944da5b 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -580,4 +580,63 @@ mod hmac_tests { ); } } + + #[test] + fn serializable_keyed_state() { + use bouncycastle_core::errors::SerializedStateError; + use bouncycastle_core::serializable_state::LIB_VERSION; + use bouncycastle_core::traits::SerializableKeyedState; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableKeyedState; + + let key = + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::MACKey).unwrap(); + let msg = b"Colorless green ideas sleep furiously"; + + // A helper that exercises the full round-trip for one HMAC variant. HMAC is keyed, so the + // key is NOT in the serialized state -- it is re-supplied (by reference) to + // from_serialized_state. + // The `+ 'static` on the trait object matches the associated type `type Key = dyn + // KeyMaterialTrait` (a bare `dyn` in an associated type defaults to `'static`). The concrete + // key types are owned, so they satisfy it. + fn round_trip( + mut hmac: H, + key: &(dyn KeyMaterialTrait + 'static), + input: &[u8], + ) where + H: MAC + Clone + SerializableKeyedState, + { + hmac.do_update(&input[..10]); + + // do the default trait-conformance tests + TestFrameworkSerializableKeyedState::new().test(&hmac, key); + + // serialize the in-progress state (on a clone), then finish the original + let serialized_state = hmac.clone().serialize_state(); + + // the serialized state carries the library version header (from the inner hash) + let header: [u8; 3] = serialized_state[..3].try_into().unwrap(); + assert_eq!(header, <[u8; 3]>::from(LIB_VERSION)); + + hmac.do_update(&input[10..]); + let expected = hmac.do_final(); + + // rebuild from the serialized state (re-supplying the key), feed the identical remaining + // input, and confirm the MAC matches + let mut from_state = H::from_serialized_state(serialized_state, key).unwrap(); + from_state.do_update(&input[10..]); + assert_eq!(expected, from_state.do_final()); + + // a state whose version header is zeroed must be rejected (delegated to the hash's impl) + let mut busted = serialized_state; + busted[..3].copy_from_slice(&[0, 0, 0]); + match H::from_serialized_state(busted, key) { + Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + _ => panic!("Expected IncompatibleVersion for a zeroed version header"), + } + } + + round_trip(HMAC_SHA256::new(&key).unwrap(), &key, msg); + round_trip(HMAC_SHA512::new(&key).unwrap(), &key, msg); + round_trip(HMAC_SHA3_256::new(&key).unwrap(), &key, msg); + } } From e464f684503309d536221f7b7d1ecfee8ca72159 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 7 Jul 2026 00:33:57 -0500 Subject: [PATCH 29/35] Fixed a bug in HMAC that could cause panics. --- crypto/hkdf/src/lib.rs | 8 ++-- crypto/hmac/src/lib.rs | 73 ++++++++++++++++++--------------- crypto/hmac/tests/hmac_tests.rs | 21 ++++++++++ 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index f6920ef..569c7f6 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -179,9 +179,10 @@ pub type HKDF_SHA256 = HKDF; pub type HKDF_SHA512 = HKDF; pub struct HKDF { - hmac: Option>, // Optional because we can't construct an HMAC until they give us a key + // Optional because we can't construct an HMAC until they give us a key // to initialize it with. // None should correspond to a state of Uninitialized. + hmac: Option>, entropy: HkdfEntropyTracker, state: HkdfStates, } @@ -410,7 +411,6 @@ impl HKDF { key_material::do_hazardous_operations(okm, |okm| { let out = okm.ref_to_bytes_mut()?; while i < N { - // todo: might need this to be new_allow_weak_key() let mut hmac = HMAC::::new(&prk_as_mac_key) .map_err(|_| KeyMaterialError::GenericError("HMAC initialization failed"))?; hmac.do_update(&T[..t_len]); @@ -430,7 +430,6 @@ impl HKDF { // On the last iteration, we don't take all of the output. let remaining = L - bytes_written; - // todo: might need this to be new_allow_weak_key() let mut hmac = HMAC::::new(&prk_as_mac_key)?; hmac.do_update(&T[..t_len]); hmac.do_update(info); @@ -489,8 +488,7 @@ impl HKDF { /// The output KeyMaterial will be of fixed size, with a capacity large enough to cover any /// underlying hash function, but the actual key length will be appropriate to the underlying hash function. /// - /// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero, - /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt. + /// Salt is optional; to omit it, provide a KeyMaterial0, which will cause HKDF to use the default all-zero salt. /// /// Returns the number of bits of entropy credited to this input key material. pub fn do_extract_init(&mut self, salt: &impl KeyMaterialTrait) -> Result { diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 70e5816..f1460f6 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -205,69 +205,76 @@ pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512"; /*** Type aliases ***/ #[allow(non_camel_case_types)] -pub type HMAC_SHA224 = HMAC; +pub type HMAC_SHA224 = HMAC; impl Algorithm for HMAC_SHA224 { const ALG_NAME: &'static str = HMAC_SHA224_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA256 = HMAC; +pub type HMAC_SHA256 = HMAC; impl Algorithm for HMAC_SHA256 { const ALG_NAME: &'static str = HMAC_SHA256_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA384 = HMAC; +pub type HMAC_SHA384 = HMAC; impl Algorithm for HMAC_SHA384 { const ALG_NAME: &'static str = HMAC_SHA384_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA512 = HMAC; +pub type HMAC_SHA512 = HMAC; impl Algorithm for HMAC_SHA512 { const ALG_NAME: &'static str = HMAC_SHA512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA3_224 = HMAC; +pub type HMAC_SHA3_224 = HMAC; impl Algorithm for HMAC_SHA3_224 { const ALG_NAME: &'static str = HMAC_SHA3_224_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA3_256 = HMAC; +pub type HMAC_SHA3_256 = HMAC; impl Algorithm for HMAC_SHA3_256 { const ALG_NAME: &'static str = HMAC_SHA3_256_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA3_384 = HMAC; +pub type HMAC_SHA3_384 = HMAC; impl Algorithm for HMAC_SHA3_384 { const ALG_NAME: &'static str = HMAC_SHA3_384_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA3_512 = HMAC; +pub type HMAC_SHA3_512 = HMAC; impl Algorithm for HMAC_SHA3_512 { const ALG_NAME: &'static str = HMAC_SHA3_512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } -// TODO: is there a rustacious way to extract this from HASH? -const LARGEST_HASHER_OUTPUT_LEN: usize = 64; +// The internal key buffer must be able to hold a key up to the *block length* of the underlying hash: +// per RFC 2104, a key no longer than the block is used verbatim (only longer keys are pre-hashed down +// to the output length). So the buffer size is a const parameter of the struct, set per hash to its +// block length by the type aliases below. Block lengths (bytes): SHA-224/256 = 64, SHA-384/512 = 128, +// SHA3-224 = 144, SHA3-256 = 136, SHA3-384 = 104, SHA3-512 = 72. +// +// The default is used only when `HMAC` is written without an explicit buffer size; it is the +// largest block length across all supported hashes, so it is always large enough. +const LARGEST_HASHER_BLOCK_LEN: usize = 144; // HMAC implements RFC 2104. #[derive(Clone)] -pub struct HMAC { +pub struct HMAC { hasher: HASH, - key: [u8; LARGEST_HASHER_OUTPUT_LEN], + key: [u8; KEY_BUF_LEN], key_len: usize, // Doing it this way to avoid needing a vec, so that this can be made no_std friendly. } @@ -286,21 +293,13 @@ const OPAD_BYTE: u8 = 0x5C; // be too strict about it, pub const MIN_FIPS_DIGEST_LEN: usize = 4; // 32 / 8; -impl HMAC { +impl HMAC { fn pad_key_into_hasher(&mut self, padding: u8) { // TODO: it would be nice to be able to statically extract the length of HASH and not need a Vec or over-sized array here. // TODO: make this no_std-friendly let mut padded = vec![0u8; self.hasher.block_bitlen() / 8]; - - // Per RFC 2104 Section 2, if the application key exceeds the block - // length of the underlying hashes algorithm, we apply a hash invocation - // over the key first. - // if self.key_len > self.hasher.block_bitlen() / 8 { - // HASH::default().hash_out(&self.key[..self.key_len], &mut padded[..self.hasher.output_len()])?; - // } else { - // TODO: does this need a guard for a key_len longer than the block length? + padded[..self.key_len].copy_from_slice(&self.key[..self.key_len]); - // } // XXX: easier way to xor over Vec? for entry in &mut padded { @@ -312,9 +311,10 @@ impl HMAC { self.hasher.do_update(&padded) } - /// Loads the raw key bytes into `self.key` / `self.key_len`, pre-hashing them first if they - /// exceed the underlying hash's block length (per RFC 2104 Section 2). This does NOT absorb the - /// key into the hasher; that is done separately via [HMAC::pad_key_into_hasher]. + /// Per RFC 2104 Section 2, if the application key exceeds the block + /// length of the underlying hashes algorithm, we apply a hash invocation + /// over the key first. + /// This does NOT absorb the key into the hasher; that is done separately via [HMAC::pad_key_into_hasher]. fn load_key_material(&mut self, key_bytes: &[u8]) { if key_bytes.len() > self.hasher.block_bitlen() / 8 { // then we have to pre-hash it -- use a new instance of the hasher rather than the internal one @@ -324,6 +324,12 @@ impl HMAC { self.key[..key_bytes.len()].copy_from_slice(key_bytes); self.key_len = key_bytes.len(); } + + // Just as a sanity-check. + assert!( + self.key_len <= KEY_BUF_LEN, + "Fatal error: Key length exceeds HMAC internal buffer length" + ); } /// Private init so that users are forced to go through one of the public new methods and thus we @@ -390,17 +396,15 @@ impl HMAC { // TODO: This is essentially a "batch mode" where you want to perform many MACs or Verifications with the same key // TODO: against different data. -impl MAC for HMAC { +impl MAC for HMAC { fn new(key: &impl KeyMaterialTrait) -> Result { - let mut hmac = - Self { hasher: HASH::default(), key: [0u8; LARGEST_HASHER_OUTPUT_LEN], key_len: 0 }; + let mut hmac = Self { hasher: HASH::default(), key: [0u8; KEY_BUF_LEN], key_len: 0 }; hmac.init(key, false)?; Ok(hmac) } fn new_allow_weak_key(key: &impl KeyMaterialTrait) -> Result { - let mut hmac = - Self { hasher: HASH::default(), key: [0u8; LARGEST_HASHER_OUTPUT_LEN], key_len: 0 }; + let mut hmac = Self { hasher: HASH::default(), key: [0u8; KEY_BUF_LEN], key_len: 0 }; hmac.init(key, true)?; Ok(hmac) } @@ -470,8 +474,11 @@ impl MAC for HMAC { /// There is no way to detect a mismatched key on /// resume: the caller MUST supply the same key the HMAC was created with, otherwise the resumed /// operation will silently produce an incorrect MAC. -impl> - SerializableKeyedState for HMAC +impl< + const HASH_STATE_LEN: usize, + const KEY_BUF_LEN: usize, + HASH: Hash + Default + SerializableState, +> SerializableKeyedState for HMAC { // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` // rather than a single concrete key type. The key is only used (by reference) to reload the key @@ -494,7 +501,7 @@ impl::from_bytes_as_type(&[0x0B; 127], KeyType::MACKey).unwrap(); + let mut mac = HMAC_SHA512::new(&key).unwrap(); + mac.do_update(b"Hi There"); + let tag = mac.do_final(); + assert!(HMAC_SHA512::new(&key).unwrap().verify(b"Hi There", &tag)); + + // SHA3-224 has the largest block (144 bytes); a 143-byte key exercises the top of the range. + let key = KeyMaterial::<200>::from_bytes_as_type(&[0x0B; 143], KeyType::MACKey).unwrap(); + let mut mac = HMAC_SHA3_224::new(&key).unwrap(); + mac.do_update(b"Hi There"); + let tag = mac.do_final(); + assert!(HMAC_SHA3_224::new(&key).unwrap().verify(b"Hi There", &tag)); + } + #[test] fn security_strength_tests() { // test: provided key has the correct length, but insufficient tagged security strength From 937d859a539a1f35ed8233d9c0103cab50dfa2ee Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 7 Jul 2026 14:01:13 -0500 Subject: [PATCH 30/35] Implemented Suspendable for HKDF and a bunch of other tidy-up. --- alpha_0.1.2_release_notes.md | 9 +- crypto/core-test-framework/src/lib.rs | 2 +- ...alizable_state.rs => suspendable_state.rs} | 33 ++-- crypto/core/src/errors.rs | 2 +- crypto/core/src/traits.rs | 40 ++-- crypto/hkdf/src/lib.rs | 180 +++++++++++++++++- crypto/hkdf/tests/hkdf_tests.rs | 53 +++++- crypto/hmac/src/lib.rs | 78 +++++--- crypto/hmac/tests/hmac_tests.rs | 18 +- crypto/mldsa-lowmemory/src/mldsa.rs | 32 ++-- crypto/mldsa-lowmemory/tests/mldsa_tests.rs | 29 +-- crypto/mldsa/src/mldsa.rs | 32 ++-- crypto/mldsa/tests/mldsa_tests.rs | 29 +-- crypto/sha2/src/lib.rs | 21 +- crypto/sha2/src/sha256.rs | 21 +- crypto/sha2/src/sha512.rs | 19 +- crypto/sha2/tests/sha2_tests.rs | 24 +-- crypto/sha3/src/keccak.rs | 4 +- crypto/sha3/src/lib.rs | 19 +- crypto/sha3/src/sha3.rs | 90 ++++----- crypto/sha3/src/shake.rs | 14 +- crypto/sha3/tests/sha3_tests.rs | 31 +-- crypto/sha3/tests/shake_tests.rs | 29 ++- 23 files changed, 530 insertions(+), 279 deletions(-) rename crypto/core-test-framework/src/{serializable_state.rs => suspendable_state.rs} (80%) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index a298549..327f19c 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -45,10 +45,11 @@ * 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. -* New traits SerializeState and SerializeKeyedState allow algorithms with a streaming API (`do_update()` -> - `do_final()`) to be suspended to a small byte array and then resumed later, potentially from a different host. The - intended use case is if you are processing a large input that depends on one or more network round-trips and you wish - to suspect and potentially transfer to a new host while waiting for network IO. +* New traits [Suspendable] and [SuspendableKeyed] allow algorithms with a streaming API (`do_update()` -> + `do_final()`) to be suspended to a small byte array and then resumed later, potentially from a different host and + potentially across versions of the library. The intended use case is if you are processing a large input that depends + on one or more network round-trips and you wish to suspend to a cache and potentially transfer to a new host while + waiting for network IO. ## Minor features / bug fixes diff --git a/crypto/core-test-framework/src/lib.rs b/crypto/core-test-framework/src/lib.rs index b169dfa..52ccc87 100644 --- a/crypto/core-test-framework/src/lib.rs +++ b/crypto/core-test-framework/src/lib.rs @@ -13,8 +13,8 @@ pub mod hash; pub mod kdf; pub mod kem; pub mod mac; -pub mod serializable_state; pub mod signature; +pub mod suspendable_state; mod fixed_seed_rng; pub use fixed_seed_rng::FixedSeedRNG; diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/suspendable_state.rs similarity index 80% rename from crypto/core-test-framework/src/serializable_state.rs rename to crypto/core-test-framework/src/suspendable_state.rs index 694019b..7be7c21 100644 --- a/crypto/core-test-framework/src/serializable_state.rs +++ b/crypto/core-test-framework/src/suspendable_state.rs @@ -1,10 +1,10 @@ use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::serializable_state::{LIB_VERSION, SemVer}; -use bouncycastle_core::traits::{SerializableKeyedState, SerializableState}; +use bouncycastle_core::traits::{Suspendable, SuspendableKeyed}; -pub struct TestFrameworkSerializableState {} +pub struct TestFrameworkSuspendableState {} -impl TestFrameworkSerializableState { +impl TestFrameworkSuspendableState { pub fn new() -> Self { Self {} } @@ -12,10 +12,7 @@ impl TestFrameworkSerializableState { /// Test all the members of trait SerializableState. /// /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. - pub fn test< - const SERIALIZED_STATE_LEN: usize, - S: SerializableState + Clone, - >( + pub fn test + Clone>( &self, instance: &S, ) { @@ -27,10 +24,10 @@ impl TestFrameworkSerializableState { let instance_clone = instance.clone(); // You can serialize and then deserialize the state. - let serialized_state = instance_clone.serialize_state(); + let serialized_state = instance_clone.suspend(); assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN); - let _deserialized_state = S::from_serialized_state(serialized_state).unwrap(); + let _deserialized_state = S::from_suspended(serialized_state).unwrap(); // The serialized state MUST include a prefix indicating the current version of the library. let state_sized: [u8; 3] = serialized_state[..3].try_into().unwrap(); @@ -41,7 +38,7 @@ impl TestFrameworkSerializableState { // used the helper functions. let mut ver0_serialized_state = serialized_state.clone(); ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); - match S::from_serialized_state(ver0_serialized_state) { + match S::from_suspended(ver0_serialized_state) { Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") @@ -54,7 +51,7 @@ impl TestFrameworkSerializableState { let mut futurever_serialized_state = serialized_state.clone(); futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); - match S::from_serialized_state(futurever_serialized_state) { + match S::from_suspended(futurever_serialized_state) { Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") @@ -63,9 +60,9 @@ impl TestFrameworkSerializableState { } } -pub struct TestFrameworkSerializableKeyedState {} +pub struct TestFrameworkSuspendableKeyedState {} -impl TestFrameworkSerializableKeyedState { +impl TestFrameworkSuspendableKeyedState { pub fn new() -> Self { Self {} } @@ -75,7 +72,7 @@ impl TestFrameworkSerializableKeyedState { /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. pub fn test< const SERIALIZED_STATE_LEN: usize, - S: SerializableKeyedState + Clone, + S: SuspendableKeyed + Clone, >( &self, instance: &S, @@ -89,10 +86,10 @@ impl TestFrameworkSerializableKeyedState { let instance_clone = instance.clone(); // You can serialize and then deserialize the state. - let serialized_state = instance_clone.serialize_state(); + let serialized_state = instance_clone.suspend(); assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN); - let _deserialized_state = S::from_serialized_state(serialized_state, key).unwrap(); + let _deserialized_state = S::from_suspended(serialized_state, key).unwrap(); // The serialized state MUST include a prefix indicating the current version of the library. let state_sized: [u8; 3] = serialized_state[..3].try_into().unwrap(); @@ -103,7 +100,7 @@ impl TestFrameworkSerializableKeyedState { // used the helper functions. let mut ver0_serialized_state = serialized_state.clone(); ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); - match S::from_serialized_state(ver0_serialized_state, key) { + match S::from_suspended(ver0_serialized_state, key) { Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") @@ -116,7 +113,7 @@ impl TestFrameworkSerializableKeyedState { let mut futurever_serialized_state = serialized_state.clone(); futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); - match S::from_serialized_state(futurever_serialized_state, key) { + match S::from_suspended(futurever_serialized_state, key) { Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index 43424f7..0241ae7 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -78,7 +78,7 @@ pub enum SerializedStateError { IncompatibleVersion, /// The serialized state is malformed or corrupt. InvalidData, - /// The key supplied to [crate::traits::SerializableKeyedState::from_serialized_state] does not + /// The key supplied to [crate::traits::SuspendableKeyed::from_suspended] does not /// match the key the state was created with (it is bound to a different public-key hash `tr`). IncorrectKey, } diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index e2f9efc..f05cb43 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -488,8 +488,8 @@ pub trait RNG { #[allow(drop_bounds)] pub trait Secret: Drop + Debug + Display {} -/// Allows a stateful object to serialize its state so that it can be paused and resumed later, -/// potentially from a different host. +/// Allows a stateful object to suspend its operation by serializing its state into a byte array +///so that it can be resumed later, potentially from a different host. /// /// This is intended for situations where an object is being used through its streaming API /// (do_update, do_final) and the operation wants to be paused to a cache, for example while waiting @@ -499,54 +499,56 @@ pub trait Secret: Drop + Debug + Display {} /// will be more straightforward. /// /// The serialized state MAY contain short-term sensitive values such as nonces or IVs, -/// but it MUST NOT include a serialized private key. Keyed algorithms MUST instead impl -/// [SerializableKeyedState] which requires the key to be supplied independently at the time of deserialization. -pub trait SerializableState: Sized { - /// Serialize the state of the object. +/// but it MUST NOT include a serialized private key. +/// Keyed algorithms MUST instead impl +/// [SuspendableKeyed] which requires the key to be supplied independently at the time of deserialization. +pub trait Suspendable: Sized { + /// Suspend operation by serializing out the state of the object. /// /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization. /// If you want to do this intentionally, then you will need to clone the object before serializing it. /// /// The serialized state MUST include a prefix indicating the version of the library that serialized it. - fn serialize_state(self) -> [u8; SERIALIZED_STATE_LEN]; + fn suspend(self) -> [u8; SERIALIZED_STATE_LEN]; - /// Create a new object from a serialized state. + /// Resume operation from a serialized state. /// /// Deserializers SHOULD check the version and reject serialized states from incompatible versions /// (including rejecting serializations from a future version of the library). /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its /// deserializer should reject serialized states from that version or older. - fn from_serialized_state( - serialized_state: [u8; SERIALIZED_STATE_LEN], + fn from_suspended( + state: [u8; SERIALIZED_STATE_LEN], ) -> Result; } -/// Similar to [SerializableState] in that it allows a stateful object to serialize its state so that -/// it can be paused and resumed later, potentially from a different host. +/// Similar to [Suspendable] in that it allows a stateful object to suspend its operation by +/// serializing its state into a byte array so that it can be resumed later, potentially from a different host. /// /// The difference is that this trait is for keyed algorithms -- MACs, symmetric ciphers, signatures, etc -- -/// which require a private key. For security reasons, the private key is not included in the serialized state +/// which require a private key in order to resume successfully. +/// For security reasons, the private key is not included in the serialized state /// and must be provided separately as part of the deserialization process. -pub trait SerializableKeyedState: Sized { +pub trait SuspendableKeyed: Sized { /// The type of key that must be re-supplied to resume this object. type Key: ?Sized; - /// Serialize the state of the object. + /// Suspend operation by serializing out the state of the object. /// /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization. /// If you want to do this intentionally, then you will need to clone the object before serializing it. /// /// The serialized state MUST include a prefix indicating the version of the library that serialized it. - fn serialize_state(self) -> [u8; SERIALIZED_STATE_LEN]; + fn suspend(self) -> [u8; SERIALIZED_STATE_LEN]; - /// Create a new object from a serialized state. + /// Resume operation from a serialized state and the key. /// /// Deserializers SHOULD check the version and reject serialized states from incompatible versions /// (including rejecting serializations from a future version of the library). /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its /// deserializer should reject serialized states from that version or older. - fn from_serialized_state( - serialized_state: [u8; SERIALIZED_STATE_LEN], + fn from_suspended( + state: [u8; SERIALIZED_STATE_LEN], key: &Self::Key, ) -> Result; } diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 569c7f6..20773b9 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -144,16 +144,63 @@ //! let mut okm = KeyMaterial::<200>::new(); //! let _bytes_written = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, info, 200, &mut okm).unwrap(); //! ``` +//! +//! # Suspending and resuming execution +//! +//! The *HKDF-Extract* phase supports a streaming API whereby any amount of additional input keying +//! material can be provided either via [HKDF::do_extract_update_key] -- which will +//! credit the entropy of the provided [KeyMaterial] -- or as raw uncredited bytes via +//! [HKDF::do_extract_update_bytes]. +//! +//! As such, The *HKDF-Extract* phase can be suspended to a cache and resumed later via the +//! [SuspendableKeyed] trait. +//! +//! The HKDF algorithm is keyed by a `salt`, which is required twice: once at initialization and again +//! during finalization. Suspension and resumption are supported via the [SuspendableKeyed] trait +//! which requires the caller to store the salt securely and provide it again during resumption. +//! Note that providing a different salt during resumption cannot be detected by the library and +//! would silently produce a different PRK. +//! +//! ```rust +//! use bouncycastle_hkdf::HKDF_SHA256; +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::traits::SuspendableKeyed; +//! +//! let salt = KeyMaterial256::from_bytes_as_type( +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! KeyType::MACKey).unwrap(); +//! let ikm_part1 = b"input keying material part 1"; +//! let ikm_part2 = b" ...and part 2"; +//! +//! let mut hkdf = HKDF_SHA256::new(); +//! hkdf.do_extract_init(&salt).unwrap(); +//! hkdf.do_extract_update_bytes(ikm_part1).unwrap(); +//! +//! // suspend the in-progress extract (the salt is NOT included in the serialized state) +//! let serialized_state = hkdf.suspend(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! // ... later, possibly on another host: resume from the serialized state by re-supplying +//! // the same salt (make sure you store it securely!). +//! let mut hkdf = HKDF_SHA256::from_suspended(serialized_state, &salt).unwrap(); +//! hkdf.do_extract_update_bytes(ikm_part2).unwrap(); +//! let _prk = hkdf.do_extract_final().unwrap(); +//! ``` #![forbid(unsafe_code)] -use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError}; +use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError, SerializedStateError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial0, KeyMaterial512, KeyMaterialTrait, KeyType, }; -use bouncycastle_core::traits::{Hash, HashAlgParams, KDF, MAC, SecurityStrength}; -use bouncycastle_hmac::HMAC; +use bouncycastle_core::traits::{ + Hash, HashAlgParams, KDF, MAC, SecurityStrength, SuspendableKeyed, +}; +use bouncycastle_hmac::{HMAC, SUSPENDED_HMAC_SHA256_STATE_LEN, SUSPENDED_HMAC_SHA512_STATE_LEN}; use bouncycastle_sha2::{SHA256, SHA512}; use bouncycastle_utils::{max, min}; use std::marker::PhantomData; @@ -163,7 +210,7 @@ use bouncycastle_core::traits::XOF; // end doc-only imports /*** Constants ***/ -// Slightly hacky, but set this to accomodate the underlying hash primitive with the largest output size. +// Slightly hacky, but set this to accommodate the underlying hash primitive with the largest output size. // Would be better to somehow pull that at compile time from H, but I'm not sure how to do that. const HMAC_BLOCK_LEN: usize = 64; @@ -178,6 +225,7 @@ pub type HKDF_SHA256 = HKDF; #[allow(non_camel_case_types)] pub type HKDF_SHA512 = HKDF; +#[derive(Clone)] pub struct HKDF { // Optional because we can't construct an HMAC until they give us a key // to initialize it with. @@ -190,18 +238,34 @@ pub struct HKDF { // Note: does not need to impl Drop because HKDF itself does not hold any sensitive state data. #[derive(Clone, Debug, PartialOrd, PartialEq)] +#[repr(u8)] enum HkdfStates { /// waiting for salt - Uninitialized, + Uninitialized = 0, /// Salt set, waiting for IKMs or do_final - Initialized, + Initialized = 1, /// [HKDF::do_extract_update_key] has been called, after which no more credited IKMs can be given. /// This is in conformance with NIST SP 800-133 which requires all keys to come before other inputs. - TakingAdditionalInfo, + TakingAdditionalInfo = 2, +} + +impl TryFrom for HkdfStates { + type Error = SerializedStateError; + + /// Inverse of `self as u8`; rejects unrecognized discriminants with [SerializedStateError::InvalidData]. + fn try_from(value: u8) -> Result { + Ok(match value { + 0 => Self::Uninitialized, + 1 => Self::Initialized, + 2 => Self::TakingAdditionalInfo, + _ => return Err(SerializedStateError::InvalidData), + }) + } } +#[derive(Clone)] struct HkdfEntropyTracker { _phantomhash: PhantomData, entropy: usize, @@ -711,3 +775,105 @@ impl KDF for HKDF { H::default().max_security_strength() } } + +/// Length in bytes of the serialized state of [HKDF_SHA256]. +pub const SUSPENDED_HKDF_SHA256_STATE_LEN: usize = SUSPENDED_HMAC_SHA256_STATE_LEN + 11; +/// Length in bytes of the serialized state of [HKDF_SHA512]. +pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_LEN + 11; + +/// HKDF is *keyed by its salt* -- the salt keys the extract-phase HMAC -- so it implements +/// [SuspendableKeyed] (not [SerializableState]). An in-progress +/// extract operation can be suspended and resumed, but the salt is NOT written into the serialized +/// state and must be re-supplied to [SuspendableKeyed::from_serialized_state]. +/// +/// Only the extract phase carries resumable state (expand is a one-shot static operation). As with +/// HMAC, resuming with the wrong salt cannot be detected and will silently produce a wrong PRK. +/// +/// Serialized layout: the 3-byte library version header comes first and is checked before anything +/// else is parsed; then, using `B` = the inner HMAC blob length: +/// [0 .. B) the inner HMAC's SerializableKeyedState blob (salt excluded); zeroed when absent +/// [B] inner-HMAC present flag (0 = extract not yet initialized) +/// [B + 1] state-machine tag (see `HkdfStates`) +/// [B + 2 .. B + 10) entropy counter (usize serialized as u64, little-endian) +/// [B + 10] accumulated security strength (1-byte tag) +/// The number of bytes produced by `SerializableKeyedState::serialize_state` for each HKDF variant: +/// the 3-byte library version header + 11 bytes of HKDF bookkeeping (HMAC-present flag, state tag, +/// entropy counter, security strength) + the inner HMAC's serialized-state blob. +macro_rules! impl_suspendable_keyed_state_for_hkdf { + // $hash: the concrete hash; $hmac_blob: the inner HMAC's serialized-state length for that hash; + // $total: the full HKDF serialized-state length (= 3 + 11 + $hmac_blob). + ($hash:ty, $serialized_hmac_len:expr, $serialized_hkdf_len:expr) => { + impl SuspendableKeyed<{ $serialized_hkdf_len }> for HKDF<$hash> { + // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` + // rather than a single concrete key type. The key is only used (by reference) to reload the key + // bytes at from_serialized_state, so dynamic dispatch here is negligible. + type Key = dyn KeyMaterialTrait; + + fn suspend(self) -> [u8; $serialized_hkdf_len] { + debug_assert_eq!($serialized_hkdf_len, $serialized_hmac_len + 11); + let mut state = [0u8; $serialized_hkdf_len]; + + // The inner HMAC blob goes first, which carries a lib version header. + if let Some(hmac) = self.hmac { + state[..$serialized_hmac_len].copy_from_slice(&hmac.suspend()); + state[$serialized_hmac_len] = 1; // present flag + } + + state[$serialized_hmac_len + 1] = self.state as u8; + state[$serialized_hmac_len + 2..$serialized_hmac_len + 10] + .copy_from_slice(&(self.entropy.entropy as u64).to_le_bytes()); + state[$serialized_hmac_len + 10] = self.entropy.security_strength as u8; + + state + } + + fn from_suspended( + state: [u8; $serialized_hkdf_len], + salt: &Self::Key, + ) -> Result { + // Rebuild the salt-keyed HMAC (first in the payload) by re-supplying the salt. + + // This double-dips on HMAC checking the version tag before going any further. + // If ever we need to version-reject HKDF separately from HMAC, then we'll need to add + // an explicit version check here, and change "None" to the oldest accepted version. + // _ = check_lib_ver(&serialized_state, None)?; + let hmac = match state[$serialized_hmac_len] { + 0 => None, + 1 => Some(HMAC::<$hash>::from_suspended( + state[..$serialized_hmac_len].try_into().unwrap(), + salt, + )?), + _ => return Err(SerializedStateError::InvalidData), + }; + + let hkdf_state = HkdfStates::try_from(state[$serialized_hmac_len + 1])?; + let entropy = u64::from_le_bytes( + state[$serialized_hmac_len + 2..$serialized_hmac_len + 10].try_into().unwrap(), + ) as usize; + let security_strength = + SecurityStrength::try_from(state[$serialized_hmac_len + 10])?; + + Ok(HKDF { + hmac, + entropy: HkdfEntropyTracker { + _phantomhash: PhantomData, + entropy, + security_strength, + }, + state: hkdf_state, + }) + } + } + }; +} + +impl_suspendable_keyed_state_for_hkdf!( + SHA256, + SUSPENDED_HMAC_SHA256_STATE_LEN, + SUSPENDED_HKDF_SHA256_STATE_LEN +); +impl_suspendable_keyed_state_for_hkdf!( + SHA512, + SUSPENDED_HMAC_SHA512_STATE_LEN, + SUSPENDED_HKDF_SHA512_STATE_LEN +); diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index 84037ea..7131943 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -11,7 +11,7 @@ mod hkdf_tests { use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; use bouncycastle_hex as hex; use bouncycastle_hkdf::{HKDF, HKDF_SHA256, HKDF_SHA512}; - use bouncycastle_sha2::SHA256; + use bouncycastle_sha2::{SHA256, SHA512}; use bouncycastle_utils::ct; #[test] @@ -739,4 +739,55 @@ mod hkdf_tests { &mut expected_key, ); } + #[test] + fn serializable_keyed_state() { + use bouncycastle_core::traits::{Hash, SuspendableKeyed}; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; + use bouncycastle_hkdf::{SUSPENDED_HKDF_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN}; + + // HKDF is keyed by its salt: the salt is NOT serialized and is re-supplied on resume. + let salt = + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::MACKey).unwrap(); + let ikm = &DUMMY_SEED_512[16..64]; + let (part1, part2) = ikm.split_at(20); + + // A helper that exercises the full round-trip for one HKDF variant. A concrete `&KeyMaterial128` + // works for `do_extract_init` (which wants a `Sized` `&impl KeyMaterialTrait`) and coerces to + // `&dyn KeyMaterialTrait` for the serialization APIs. + fn round_trip(salt: &KeyMaterial128, part1: &[u8], part2: &[u8]) + where + H: Hash + HashAlgParams + Default, + HKDF: Clone + SuspendableKeyed, + { + let hkdf = HKDF::::new(); + + // it can be serialized pre-init, which is kinda a no-op, but at least it works. + let serialized_state = hkdf.suspend(); + assert_eq!(serialized_state.len(), LEN); + let mut hkdf = HKDF::::from_suspended(serialized_state, salt).unwrap(); + + hkdf.do_extract_init(salt).unwrap(); + hkdf.do_extract_update_bytes(part1).unwrap(); + + // generic trait-conformance tests (version header present, [0,0,0]/future rejected) + TestFrameworkSuspendableKeyedState::new().test(&hkdf, salt); + + // serialize the in-progress extract state (on a clone), then finish the original + let serialized_state = hkdf.clone().suspend(); + assert_eq!(serialized_state.len(), LEN); + + hkdf.do_extract_update_bytes(part2).unwrap(); + let prk = hkdf.do_extract_final().unwrap(); + + // resume (re-supplying the salt), feed the identical remaining IKM, and compare PRKs + let mut resumed = HKDF::::from_suspended(serialized_state, salt).unwrap(); + resumed.do_extract_update_bytes(part2).unwrap(); + let prk_resumed = resumed.do_extract_final().unwrap(); + + assert_eq!(prk.ref_to_bytes(), prk_resumed.ref_to_bytes()); + } + + round_trip::(&salt, part1, part2); + round_trip::(&salt, part1, part2); + } } diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index f1460f6..043e3f0 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -137,11 +137,11 @@ //! } //! ``` //! -//! # Suspending and resuming execution via SerializableState +//! # Suspending and resuming execution //! //! When MAC'ing a large message, it can be advantageous to be able to suspend the operation //! to a cache and resume it later; for example if waiting for the message to stream over a slow network -//! connection. For this reason, all HMAC algorithms impl [SerializableKeyedState]. +//! connection. For this reason, all HMAC algorithms impl [SuspendableKeyed]. //! //! Note that since HMAC is a keyed //! algorithm and we do not want to serialize the private key into the state, the trait structure forces you to @@ -153,7 +153,7 @@ //!```rust //! use bouncycastle_hmac::HMAC_SHA256; //! use bouncycastle_core::key_material::KeyMaterial256; -//! use bouncycastle_core::traits::{MAC, SerializableKeyedState}; +//! use bouncycastle_core::traits::{MAC, SuspendableKeyed}; //! use bouncycastle_core::key_material::KeyType; //! //! let msg_part1 = b"The quick brown fox"; @@ -166,16 +166,16 @@ //! let mut hmac = HMAC_SHA256::new(&key).unwrap(); //! hmac.do_update(msg_part1); //! -//! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = hmac.serialize_state(); +//! // suspend the in-progress mac (the key is NOT included in the serialized state) +//! let serialized_state = hmac.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! // Since the key is not serialized into the state, you have to store it somewhere and provide -//! // it to the deserializer. Make sure you store it securely! -//! let mut hmac_resumed = HMAC_SHA256::from_serialized_state(serialized_state, &key).unwrap(); +//! // ... later, possibly on another host: resume from the serialized state by re-supplying +//! // the same salt (make sure you store it securely!). +//! let mut hmac_resumed = HMAC_SHA256::from_suspended(serialized_state, &key).unwrap(); //! hmac_resumed.do_update(msg_part2); //! let h: Vec = hmac_resumed.do_final(); //! ``` @@ -187,20 +187,30 @@ use bouncycastle_core::errors::{KeyMaterialError, MACError, SerializedStateError}; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Algorithm, Hash, MAC, SecurityStrength, SerializableKeyedState, SerializableState, + Algorithm, Hash, MAC, SecurityStrength, Suspendable, SuspendableKeyed, }; -use bouncycastle_sha2::{SHA224, SHA256, SHA384, SHA512}; -use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; +use bouncycastle_sha2::{ + SHA224, SHA256, SHA384, SHA512, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN, +}; +use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512, SUSPENDED_SHA3_STATE_LEN}; use bouncycastle_utils::ct; /*** String constants ***/ +/// pub const HMAC_SHA224_NAME: &str = "HMAC-SHA224"; +/// pub const HMAC_SHA256_NAME: &str = "HMAC-SHA256"; +/// pub const HMAC_SHA384_NAME: &str = "HMAC-SHA384"; +/// pub const HMAC_SHA512_NAME: &str = "HMAC-SHA512"; +/// pub const HMAC_SHA3_224_NAME: &str = "HMAC-SHA3-224"; +/// pub const HMAC_SHA3_256_NAME: &str = "HMAC-SHA3-256"; +/// pub const HMAC_SHA3_384_NAME: &str = "HMAC-SHA3-384"; +/// pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512"; /*** Type aliases ***/ @@ -298,7 +308,7 @@ impl HMAC { // TODO: it would be nice to be able to statically extract the length of HASH and not need a Vec or over-sized array here. // TODO: make this no_std-friendly let mut padded = vec![0u8; self.hasher.block_bitlen() / 8]; - + padded[..self.key_len].copy_from_slice(&self.key[..self.key_len]); // XXX: easier way to xor over Vec? @@ -324,7 +334,7 @@ impl HMAC { self.key[..key_bytes.len()].copy_from_slice(key_bytes); self.key_len = key_bytes.len(); } - + // Just as a sanity-check. assert!( self.key_len <= KEY_BUF_LEN, @@ -461,14 +471,34 @@ impl MAC for HMAC MAC for HMAC, -> SerializableKeyedState for HMAC + HASH: Hash + Default + Suspendable, +> SuspendableKeyed for HMAC { // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` // rather than a single concrete key type. The key is only used (by reference) to reload the key // bytes at from_serialized_state, so dynamic dispatch here is negligible. type Key = dyn KeyMaterialTrait; - fn serialize_state(self) -> [u8; HASH_STATE_LEN] { + fn suspend(self) -> [u8; HASH_STATE_LEN] { // The key is intentionally excluded; the resumable state is just the inner hasher, which // already carries the library version header from the hash's own SerializableState impl. - self.hasher.serialize_state() + self.hasher.suspend() } - fn from_serialized_state( - serialized_state: [u8; HASH_STATE_LEN], - key: &dyn KeyMaterialTrait, + fn from_suspended( + state: [u8; HASH_STATE_LEN], + key: &Self::Key, ) -> Result { // Rebuild the inner hasher (version-compatibility is validated by the hash's impl). - let hasher = HASH::from_serialized_state(serialized_state)?; + let hasher = HASH::from_suspended(state)?; // Re-load the key material exactly as `new()` did (pre-hashing an over-length key), but do // NOT re-absorb `K βŠ• ipad` β€” the deserialized hasher already contains it. The key is only diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index 4e2f9fc..0c5daa4 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -148,7 +148,7 @@ mod hmac_tests { fn long_key() { // Regression test: a key just under the maximum length before HMAC will hash it down. // (RFC 2104 only pre-hashes keys *longer* than the block). - // This test wil detect an overflow-write and panic on HMAC's internal key buffer. + // This test is designed to detect an overflow-write and panic on HMAC's internal key buffer. // SHA-512 has a 128-byte block, so use a 127-byte key let key = KeyMaterial::<200>::from_bytes_as_type(&[0x0B; 127], KeyType::MACKey).unwrap(); @@ -603,11 +603,11 @@ mod hmac_tests { } #[test] - fn serializable_keyed_state() { + fn suspendable_keyed_state() { use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::serializable_state::LIB_VERSION; - use bouncycastle_core::traits::SerializableKeyedState; - use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableKeyedState; + use bouncycastle_core::traits::SuspendableKeyed; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; let key = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::MACKey).unwrap(); @@ -624,15 +624,15 @@ mod hmac_tests { key: &(dyn KeyMaterialTrait + 'static), input: &[u8], ) where - H: MAC + Clone + SerializableKeyedState, + H: MAC + Clone + SuspendableKeyed, { hmac.do_update(&input[..10]); // do the default trait-conformance tests - TestFrameworkSerializableKeyedState::new().test(&hmac, key); + TestFrameworkSuspendableKeyedState::new().test(&hmac, key); // serialize the in-progress state (on a clone), then finish the original - let serialized_state = hmac.clone().serialize_state(); + let serialized_state = hmac.clone().suspend(); // the serialized state carries the library version header (from the inner hash) let header: [u8; 3] = serialized_state[..3].try_into().unwrap(); @@ -643,14 +643,14 @@ mod hmac_tests { // rebuild from the serialized state (re-supplying the key), feed the identical remaining // input, and confirm the MAC matches - let mut from_state = H::from_serialized_state(serialized_state, key).unwrap(); + let mut from_state = H::from_suspended(serialized_state, key).unwrap(); from_state.do_update(&input[10..]); assert_eq!(expected, from_state.do_final()); // a state whose version header is zeroed must be rejected (delegated to the hash's impl) let mut busted = serialized_state; busted[..3].copy_from_slice(&[0, 0, 0]); - match H::from_serialized_state(busted, key) { + match H::from_suspended(busted, key) { Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => panic!("Expected IncompatibleVersion for a zeroed version header"), } diff --git a/crypto/mldsa-lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs index befebd7..77254a3 100644 --- a/crypto/mldsa-lowmemory/src/mldsa.rs +++ b/crypto/mldsa-lowmemory/src/mldsa.rs @@ -321,7 +321,7 @@ //! //! ```rust //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; -//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! use bouncycastle_core::traits::{Signer, Suspendable}; //! //! let msg_part1 = b"The quick brown fox"; //! let msg_part2 = b" jumped over the lazy dog"; @@ -332,13 +332,13 @@ //! mb.do_update(msg_part1); //! //! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = mb.serialize_state(); +//! let serialized_state = mb.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); //! mb_resumed.do_update(msg_part2); //! let mu: [u8; 64] = mb_resumed.do_final(); //! @@ -350,7 +350,7 @@ //! //! ```rust //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; -//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! use bouncycastle_core::traits::{Signer, Suspendable}; //! use bouncycastle_core::errors::SignatureError; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -366,13 +366,13 @@ //! mb.do_update(msg_part1); //! //! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = mb.serialize_state(); +//! let serialized_state = mb.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); //! mb_resumed.do_update(msg_part2); //! let mu: [u8; 64] = mb_resumed.do_final(); //! @@ -400,10 +400,10 @@ use crate::{ use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ - Algorithm, RNG, SecurityStrength, SerializableState, SignatureVerifier, Signer, XOF, + Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha3::{SHA3_SERIALIZED_STATE_LEN, SHAKE128, SHAKE256}; +use bouncycastle_sha3::{SHAKE128, SHAKE256, SUSPENDED_SHA3_STATE_LEN}; use core::marker::PhantomData; // imports needed just for docs @@ -1937,23 +1937,23 @@ impl MuBuilder { } /// The length, in bytes, of a serialized state of a [MuBuilder] object. -pub const MU_BUILDER_SERIALIZED_STATE_LEN: usize = SHA3_SERIALIZED_STATE_LEN; +pub const MU_BUILDER_SERIALIZED_STATE_LEN: usize = SUSPENDED_SHA3_STATE_LEN; /// If you are processing a large input message into ML-DSA and want to pause the operation -/// -- maybe while waiting for slow network IO), you'll need to use [SerializableState]. +/// -- maybe while waiting for slow network IO), you'll need to use [Suspendable]. /// Serialization of the state of an in-progress ML-DSA instance is really just serialization /// of the construction of the message representative mu, since no other part of the ML-DSA algorithm /// has a pausable state. // A [MuBuilder]'s (and by virtue, an ML-DSA instance's) entire mutable state is its inner SHAKE256 sponge, // so serialization delegates directly to [SHAKE256]'s [SerializableState] impl. -impl SerializableState for MuBuilder { - fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { - self.h.serialize_state() +impl Suspendable for MuBuilder { + fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { + self.h.suspend() } - fn from_serialized_state( - serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], ) -> Result { - Ok(MuBuilder { h: H::from_serialized_state(serialized_state)? }) + Ok(MuBuilder { h: H::from_suspended(serialized_state)? }) } } diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index b69cb67..27e21c6 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -6,12 +6,11 @@ mod mldsa_tests { use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - RNG, SecurityStrength, SerializableState, SignaturePrivateKey, SignaturePublicKey, - SignatureVerifier, Signer, + RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, + Suspendable, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::FixedSeedRNG; - use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ @@ -817,9 +816,9 @@ mod mldsa_tests { // serializing and resuming after init let mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); - let serialized_state = mb.serialize_state(); + let serialized_state = mb.suspend(); - let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); mb_resumed.do_update(msg); let mu_resumed = mb_resumed.do_final(); assert_eq!(mu_resumed, mu1); @@ -831,15 +830,17 @@ mod mldsa_tests { let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); mb.do_update(msg1); - let serialized_state = mb.serialize_state(); - let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + let serialized_state = mb.suspend(); + let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); mb_resumed.do_update(msg2); let mu_resumed2 = mb_resumed.do_final(); assert_eq!(mu_resumed2, mu1); } #[test] - fn mubuilder_serializable_state() { + fn mubuilder_suspendable_state() { + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; + // `tr` is the 64-byte public-key hash that seeds mu computation; its exact value doesn't // matter for exercising serialization, only that it is fixed across the comparison. let tr = [0x42u8; 64]; @@ -851,16 +852,16 @@ mod mldsa_tests { // Generic trait-conformance tests (version header present, [0,0,0] and future versions // rejected). - TestFrameworkSerializableState::new().test(&mb); + TestFrameworkSuspendableState::new().test(&mb); // Serialize the in-progress state, then finish the original. - let serialized_state = mb.clone().serialize_state(); + let serialized_state = mb.clone().suspend(); mb.do_update(&msg[10..]); let expected_mu = mb.do_final(); // Rebuild from the serialized state, feed it the identical remaining input, and confirm it // produces the same mu. - let mut from_state = MuBuilder::from_serialized_state(serialized_state).unwrap(); + let mut from_state = MuBuilder::from_suspended(serialized_state).unwrap(); from_state.do_update(&msg[10..]); assert_eq!(expected_mu, from_state.do_final()); @@ -873,7 +874,7 @@ mod mldsa_tests { // + bits_in_queue(8) + squeezing(1) -> the squeezing byte is at offset 3 + 1 + 400. let mut busted = serialized_state; busted[3 + 1 + 400] = 42; - match MuBuilder::from_serialized_state(busted) { + match MuBuilder::from_suspended(busted) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } @@ -890,9 +891,9 @@ mod mldsa_tests { // tag 6). let mut shake128 = SHAKE128::new(); shake128.absorb(b"Colorless green ideas sleep furiously"); - let serialized_128 = shake128.serialize_state(); + let serialized_128 = shake128.suspend(); - match MuBuilder::from_serialized_state(serialized_128) { + match MuBuilder::from_suspended(serialized_128) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), } diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index 434777b..71a76eb 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -411,7 +411,7 @@ //! //! ```rust //! use bouncycastle_mldsa::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; -//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! use bouncycastle_core::traits::{Signer, Suspendable}; //! //! let msg_part1 = b"The quick brown fox"; //! let msg_part2 = b" jumped over the lazy dog"; @@ -422,13 +422,13 @@ //! mb.do_update(msg_part1); //! //! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = mb.serialize_state(); +//! let serialized_state = mb.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); //! mb_resumed.do_update(msg_part2); //! let mu: [u8; 64] = mb_resumed.do_final(); //! @@ -440,7 +440,7 @@ //! //! ```rust //! use bouncycastle_mldsa::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; -//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! use bouncycastle_core::traits::{Signer, Suspendable}; //! use bouncycastle_core::errors::SignatureError; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -456,13 +456,13 @@ //! mb.do_update(msg_part1); //! //! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = mb.serialize_state(); +//! let serialized_state = mb.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); //! mb_resumed.do_update(msg_part2); //! let mu: [u8; 64] = mb_resumed.do_final(); //! @@ -488,10 +488,10 @@ use crate::{ use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Algorithm, RNG, SecurityStrength, SerializableState, SignatureVerifier, Signer, XOF, + Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha3::{SHA3_SERIALIZED_STATE_LEN, SHAKE128, SHAKE256}; +use bouncycastle_sha3::{SHAKE128, SHAKE256, SUSPENDED_SHA3_STATE_LEN}; use core::marker::PhantomData; // imports needed just for docs @@ -2259,23 +2259,23 @@ impl MuBuilder { } /// The length, in bytes, of a serialized state of a [MuBuilder] object. -pub const MU_BUILDER_SERIALIZED_STATE_LEN: usize = SHA3_SERIALIZED_STATE_LEN; +pub const MU_BUILDER_SERIALIZED_STATE_LEN: usize = SUSPENDED_SHA3_STATE_LEN; /// If you are processing a large input message into ML-DSA and want to pause the operation -/// -- maybe while waiting for slow network IO), you'll need to use [SerializableState]. +/// -- maybe while waiting for slow network IO), you'll need to use [Suspendable]. /// Serialization of the state of an in-progress ML-DSA instance is really just serialization /// of the construction of the message representative mu, since no other part of the ML-DSA algorithm /// has a pausable state. // A [MuBuilder]'s (and by virtue, an ML-DSA instance's) entire mutable state is its inner SHAKE256 sponge, // so serialization delegates directly to [SHAKE256]'s [SerializableState] impl. -impl SerializableState for MuBuilder { - fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { - self.h.serialize_state() +impl Suspendable for MuBuilder { + fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { + self.h.suspend() } - fn from_serialized_state( - serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], ) -> Result { - Ok(MuBuilder { h: H::from_serialized_state(serialized_state)? }) + Ok(MuBuilder { h: H::from_suspended(serialized_state)? }) } } diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index 8207a96..adc5c18 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -7,12 +7,11 @@ mod mldsa_tests { KeyMaterial256, KeyMaterialTrait, KeyType, do_hazardous_operations, }; use bouncycastle_core::traits::{ - RNG, SecurityStrength, SerializableState, SignaturePrivateKey, SignaturePublicKey, - SignatureVerifier, Signer, + RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, + Suspendable, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::FixedSeedRNG; - use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ @@ -981,9 +980,9 @@ mod mldsa_tests { // serializing and resuming after init let mb = MuBuilder::do_init(&pk_expanded.compute_tr(), None).unwrap(); - let serialized_state = mb.serialize_state(); + let serialized_state = mb.suspend(); - let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); mb_resumed.do_update(msg); let mu_resumed = mb_resumed.do_final(); assert_eq!(mu_resumed, mu1); @@ -995,8 +994,8 @@ mod mldsa_tests { let mut mb = MuBuilder::do_init(&pk_expanded.compute_tr(), None).unwrap(); mb.do_update(msg1); - let serialized_state = mb.serialize_state(); - let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + let serialized_state = mb.suspend(); + let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); mb_resumed.do_update(msg2); let mu_resumed2 = mb_resumed.do_final(); assert_eq!(mu_resumed2, mu1); @@ -1025,7 +1024,9 @@ mod mldsa_tests { } #[test] - fn mubuilder_serializable_state() { + fn mubuilder_suspendable_state() { + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; + // `tr` is the 64-byte public-key hash that seeds mu computation; its exact value doesn't // matter for exercising serialization, only that it is fixed across the comparison. let tr = [0x42u8; 64]; @@ -1037,16 +1038,16 @@ mod mldsa_tests { // Generic trait-conformance tests (version header present, [0,0,0] and future versions // rejected). - TestFrameworkSerializableState::new().test(&mb); + TestFrameworkSuspendableState::new().test(&mb); // Serialize the in-progress state, then finish the original. - let serialized_state = mb.clone().serialize_state(); + let serialized_state = mb.clone().suspend(); mb.do_update(&msg[10..]); let expected_mu = mb.do_final(); // Rebuild from the serialized state, feed it the identical remaining input, and confirm it // produces the same mu. - let mut from_state = MuBuilder::from_serialized_state(serialized_state).unwrap(); + let mut from_state = MuBuilder::from_suspended(serialized_state).unwrap(); from_state.do_update(&msg[10..]); assert_eq!(expected_mu, from_state.do_final()); @@ -1059,7 +1060,7 @@ mod mldsa_tests { // + bits_in_queue(8) + squeezing(1) -> the squeezing byte is at offset 3 + 1 + 400. let mut busted = serialized_state; busted[3 + 1 + 400] = 42; - match MuBuilder::from_serialized_state(busted) { + match MuBuilder::from_suspended(busted) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } @@ -1076,9 +1077,9 @@ mod mldsa_tests { // tag 6). let mut shake128 = SHAKE128::new(); shake128.absorb(b"Colorless green ideas sleep furiously"); - let serialized_128 = shake128.serialize_state(); + let serialized_128 = shake128.suspend(); - match MuBuilder::from_serialized_state(serialized_128) { + match MuBuilder::from_suspended(serialized_128) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), } diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index d0bac1e..687817e 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -34,17 +34,17 @@ //! let output: Vec = sha2.do_final(); //! ``` //! -//! # Suspending and resuming execution via SerializableState +//! # Suspending and resuming execution //! //! When hashing a large message, it can be advantageous to be able to suspend the operation //! to a cache and resume it later; for example if waiting for the message to stream over a slow network //! connection. //! -//! For this reason, all SHA2 algorithms impl [SerializableState]. +//! For this reason, all SHA2 algorithms impl [Suspendable]. //! //! ```rust //! use bouncycastle_sha2 as sha2; -//! use bouncycastle_core::traits::{Hash, SerializableState}; +//! use bouncycastle_core::traits::{Hash, Suspendable}; //! //! let msg_part1 = b"The quick brown fox"; //! let msg_part2 = b" jumped over the lazy dog"; @@ -52,14 +52,15 @@ //! let mut sha2 = sha2::SHA256::new(); //! sha2.do_update(msg_part1); //! -//! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = sha2.serialize_state(); +//! // suspend the in-progress extract while "waiting" for the second part of the message. +//! let serialized_state = sha2.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! let mut sha2_resumed = sha2::SHA256::from_serialized_state(serialized_state).unwrap(); +//! // ... later, possibly on another host: resume from the serialized state. +//! let mut sha2_resumed = sha2::SHA256::from_suspended(serialized_state).unwrap(); //! sha2_resumed.do_update(msg_part2); //! let h: Vec = sha2_resumed.do_final(); //! ``` @@ -74,6 +75,10 @@ pub use self::sha256::SHA256Internal; pub use self::sha512::SHA512Internal; use bouncycastle_core::traits::{Algorithm, HashAlgParams, SecurityStrength}; +/*** Imports needed for docs ***/ +#[allow(unused_imports)] +use bouncycastle_core::traits::Suspendable; + /*** String constants ***/ pub const SHA224_NAME: &str = "SHA224"; pub const SHA256_NAME: &str = "SHA256"; @@ -170,5 +175,5 @@ impl HashAlgParams for SHA512Params { } impl SHA2Params for SHA512Params {} -pub use sha256::SHA256_SERIALIZED_STATE_LEN; -pub use sha512::SHA512_SERIALIZED_STATE_LEN; +pub use sha256::SUSPENDED_SHA256_STATE_LEN; +pub use sha512::SUSPENDED_SHA512_STATE_LEN; diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index bbd56e5..12bbdcd 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -1,7 +1,7 @@ use crate::SHA2Params; use bouncycastle_core::errors::{HashError, SerializedStateError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; +use bouncycastle_core::traits::{Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; @@ -304,15 +304,14 @@ impl Hash for SHA256Internal { } } -/// The number of bytes produced by [SerializableState::serialize_state] for any SHA224 or SHA256 -/// object. -pub const SHA256_SERIALIZED_STATE_LEN: usize = 108; +/// Length in bytes of the serialized state of SHA224 and SHA256. +pub const SUSPENDED_SHA256_STATE_LEN: usize = 108; -impl SerializableState for SHA256Internal { - fn serialize_state(self) -> [u8; SHA256_SERIALIZED_STATE_LEN] { - debug_assert_eq!(SHA256_SERIALIZED_STATE_LEN, 108); +impl Suspendable for SHA256Internal { + fn suspend(self) -> [u8; SUSPENDED_SHA256_STATE_LEN] { + debug_assert_eq!(SUSPENDED_SHA256_STATE_LEN, 108); - let mut out_to_return = [0u8; SHA256_SERIALIZED_STATE_LEN]; + let mut out_to_return = [0u8; SUSPENDED_SHA256_STATE_LEN]; // insert the version tag let out: &mut [u8; 105] = add_lib_ver(&mut out_to_return).try_into().unwrap(); @@ -337,10 +336,10 @@ impl SerializableState for SHA2 out_to_return } - fn from_serialized_state( - serialized_state: [u8; SHA256_SERIALIZED_STATE_LEN], + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA256_STATE_LEN], ) -> Result { - debug_assert_eq!(SHA256_SERIALIZED_STATE_LEN, 108); + debug_assert_eq!(SUSPENDED_SHA256_STATE_LEN, 108); // check the version tag // At the moment, we have no not_before version to specify. diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 7b98b0e..89c4af2 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,7 +1,7 @@ use crate::SHA2Params; use bouncycastle_core::errors::{HashError, SerializedStateError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; +use bouncycastle_core::traits::{Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; @@ -319,15 +319,14 @@ impl Hash for SHA512Internal { } } -/// The number of bytes produced by [SerializableState::serialize_state] for any SHA384 or SHA512 -/// object. -pub const SHA512_SERIALIZED_STATE_LEN: usize = 204; +/// Length in bytes of the serialized state of SHA384 and SHA512. +pub const SUSPENDED_SHA512_STATE_LEN: usize = 204; -impl SerializableState for SHA512Internal { - fn serialize_state(self) -> [u8; SHA512_SERIALIZED_STATE_LEN] { - debug_assert_eq!(SHA512_SERIALIZED_STATE_LEN, 204); +impl Suspendable for SHA512Internal { + fn suspend(self) -> [u8; SUSPENDED_SHA512_STATE_LEN] { + debug_assert_eq!(SUSPENDED_SHA512_STATE_LEN, 204); - let mut out_to_return = [0u8; SHA512_SERIALIZED_STATE_LEN]; + let mut out_to_return = [0u8; SUSPENDED_SHA512_STATE_LEN]; // insert the version tag let out: &mut [u8; 201] = add_lib_ver(&mut out_to_return).try_into().unwrap(); @@ -352,8 +351,8 @@ impl SerializableState for SHA5 out_to_return } - fn from_serialized_state( - serialized_state: [u8; SHA512_SERIALIZED_STATE_LEN], + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA512_STATE_LEN], ) -> Result { // check the version tag // At the moment, we have no not_before version to specify. diff --git a/crypto/sha2/tests/sha2_tests.rs b/crypto/sha2/tests/sha2_tests.rs index ad49cdd..d6f2103 100644 --- a/crypto/sha2/tests/sha2_tests.rs +++ b/crypto/sha2/tests/sha2_tests.rs @@ -92,9 +92,9 @@ mod sha2_tests { } #[test] - fn test_serializable_state() { - use bouncycastle_core::traits::SerializableState; - use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + fn suspendable_state() { + use bouncycastle_core::traits::Suspendable; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; let str = "Colorless green ideas sleep furiously"; @@ -103,24 +103,25 @@ mod sha2_tests { sha256.do_update(str.as_bytes()); // do the default tests - let test_framework = TestFrameworkSerializableState::new(); + let test_framework = TestFrameworkSuspendableState::new(); test_framework.test(&sha256); // now let's serialize the in-progress state - let serialized_state = sha256.clone().serialize_state(); + let serialized_state = sha256.clone().suspend(); + assert_eq!(serialized_state.len(), SUSPENDED_SHA256_STATE_LEN); // finish the hash let output = sha256.do_final(); // then load from state and finish the hash and make sure we get the same thing - let sha2_from_state = SHA256::from_serialized_state(serialized_state).unwrap(); + let sha2_from_state = SHA256::from_suspended(serialized_state).unwrap(); let output2 = sha2_from_state.do_final(); assert_eq!(output, output2); // also, give it a busted x_buf_off, just to satisfy mutants that that's been tested let mut busted_state = serialized_state.clone(); busted_state[3 + 104] = 65; - match SHA256::from_serialized_state(busted_state) { + match SHA256::from_suspended(busted_state) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error"), } @@ -130,24 +131,25 @@ mod sha2_tests { sha512.do_update(str.as_bytes()); // do the default tests - let test_framework = TestFrameworkSerializableState::new(); + let test_framework = TestFrameworkSuspendableState::new(); test_framework.test(&sha512); // now let's serialize the in-progress state - let serialized_state = sha512.clone().serialize_state(); + let serialized_state = sha512.clone().suspend(); + assert_eq!(serialized_state.len(), SUSPENDED_SHA512_STATE_LEN); // finish the hash let output = sha512.do_final(); // then load from state and finish the hash and make sure we get the same thing - let sha2_from_state = SHA512::from_serialized_state(serialized_state).unwrap(); + let sha2_from_state = SHA512::from_suspended(serialized_state).unwrap(); let output2 = sha2_from_state.do_final(); assert_eq!(output, output2); // also, give it a busted x_buf_off, just to satisfy mutants that that's been tested let mut busted_state = serialized_state.clone(); busted_state[3 + 200] = 129; - match SHA512::from_serialized_state(busted_state) { + match SHA512::from_suspended(busted_state) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error"), } diff --git a/crypto/sha3/src/keccak.rs b/crypto/sha3/src/keccak.rs index 3609b88..c887e16 100644 --- a/crypto/sha3/src/keccak.rs +++ b/crypto/sha3/src/keccak.rs @@ -374,8 +374,8 @@ const KECCAK_SERIALIZED_LEN: usize = 200 + 192 + 8 + 1; /// [.. + 8) kdf_entropy usize serialized as u64 pub(crate) const SHA3_FAMILY_STATE_LEN: usize = 1 + KECCAK_SERIALIZED_LEN + 10; -/// Total number of bytes in a serialized state of a SHA3 or SHAKE instance. -pub const SHA3_SERIALIZED_STATE_LEN: usize = 3 + SHA3_FAMILY_STATE_LEN; +/// Length in bytes of the serialized state of a SHA3 or SHAKE instance. +pub const SUSPENDED_SHA3_STATE_LEN: usize = 3 + SHA3_FAMILY_STATE_LEN; impl KeccakDigest { /// Serializes this digest's mutable state into `out`. The `rate` is deliberately omitted; see diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 8a80639..902c8ed 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -104,17 +104,17 @@ //! [KeyType::CryptographicRandom] since the input [KeyMaterial] is 16 bytes but [SHA3_256] needs at least 32 bytes of //! full-entropy input key material in order to be able to produce full entropy output key material. //! -//! # Suspending and resuming execution via SerializableState +//! # Suspending and resuming execution //! //! When hashing a large message, it can be advantageous to be able to suspend the operation //! to a cache and resume it later; for example if waiting for the message to stream over a slow network //! connection. //! -//! For this reason, all SHA3 algorithms impl [SerializableState]. +//! For this reason, all SHA3 algorithms impl [Suspendable]. //! //!```rust //! use bouncycastle_sha3 as sha3; -//! use bouncycastle_core::traits::{Hash, SerializableState}; +//! use bouncycastle_core::traits::{Hash, Suspendable}; //! //! let msg_part1 = b"The quick brown fox"; //! let msg_part2 = b" jumped over the lazy dog"; @@ -122,14 +122,15 @@ //! let mut sha3 = sha3::SHA3_256::new(); //! sha3.do_update(msg_part1); //! -//! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = sha3.serialize_state(); +//! // suspend the in-progress extract while "waiting" for the second part of the message. +//! let serialized_state = sha3.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! let mut sha3_resumed = sha3::SHA3_256::from_serialized_state(serialized_state).unwrap(); +//! // ... later, possibly on another host: resume from the serialized state. +//! let mut sha3_resumed = sha3::SHA3_256::from_suspended(serialized_state).unwrap(); //! sha3_resumed.do_update(msg_part2); //! let h: Vec = sha3_resumed.do_final(); //! ``` @@ -144,7 +145,7 @@ use bouncycastle_core::traits::{Algorithm, HashAlgParams, SecurityStrength}; #[allow(unused_imports)] use bouncycastle_core::key_material::{KeyMaterial, KeyType}; #[allow(unused_imports)] -use bouncycastle_core::traits::{Hash, KDF, XOF}; +use bouncycastle_core::traits::{Hash, KDF, Suspendable, XOF}; // end of doc-only imports mod keccak; @@ -163,9 +164,7 @@ pub const SHAKE256_NAME: &str = "SHAKE256"; pub use sha3::SHA3; pub use shake::SHAKE; -/// The number of bytes produced by [SerializableState::serialize_state] for any SHA3 or SHAKE -/// object. -pub use keccak::SHA3_SERIALIZED_STATE_LEN; +pub use keccak::SUSPENDED_SHA3_STATE_LEN; pub type SHA3_224 = SHA3; pub type SHA3_256 = SHA3; pub type SHA3_384 = SHA3; diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 4d78f37..1bfde8a 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -1,13 +1,13 @@ use crate::SHA3Params; use crate::keccak::{ - KeccakDigest, SHA3_FAMILY_STATE_LEN, SHA3_SERIALIZED_STATE_LEN, deserialize_sha3_family_state, + KeccakDigest, SHA3_FAMILY_STATE_LEN, SUSPENDED_SHA3_STATE_LEN, deserialize_sha3_family_state, serialize_sha3_family_state, }; use bouncycastle_core::errors::{HashError, KDFError, SerializedStateError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Hash, KDF, SecurityStrength, SerializableState}; +use bouncycastle_core::traits::{Hash, KDF, SecurityStrength, Suspendable}; use bouncycastle_utils::{max, min}; #[derive(Clone)] @@ -221,49 +221,6 @@ impl Hash for SHA3 { } } -impl SerializableState for SHA3 { - fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { - let mut out_to_return = [0u8; SHA3_SERIALIZED_STATE_LEN]; - - // insert the version tag - let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = - add_lib_ver(&mut out_to_return).try_into().unwrap(); - - serialize_sha3_family_state( - out, - PARAMS::STATE_TAG, - &self.keccak, - self.kdf_key_type, - self.kdf_security_strength, - self.kdf_entropy, - ); - - out_to_return - } - - fn from_serialized_state( - serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], - ) -> Result { - // check the version tag. At the moment, we have no not_before version to specify. - let input: &[u8; SHA3_FAMILY_STATE_LEN] = - check_lib_ver(&serialized_state, None)?.try_into().unwrap(); - - // The variant tag rejects states from any other SHA3/SHAKE variant; the rate is then the - // correct one to rebuild with (both are fully determined by the algorithm parameters). - let rate = 1600 - ((PARAMS::SIZE as usize) << 1); - let (keccak, kdf_key_type, kdf_security_strength, kdf_entropy) = - deserialize_sha3_family_state(input, PARAMS::STATE_TAG, rate)?; - - Ok(SHA3 { - _params: std::marker::PhantomData, - keccak, - kdf_key_type, - kdf_security_strength, - kdf_entropy, - }) - } -} - /// SHA3 is allowed to be used as a KDF in the form HASH(X) as per NIST SP 800-56C. impl KDF for SHA3 { /// Returns a [KeyMaterial]. @@ -317,3 +274,46 @@ impl KDF for SHA3 { SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2) } } + +impl Suspendable for SHA3 { + fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_SHA3_STATE_LEN]; + + // insert the version tag + let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = + add_lib_ver(&mut out_to_return).try_into().unwrap(); + + serialize_sha3_family_state( + out, + PARAMS::STATE_TAG, + &self.keccak, + self.kdf_key_type, + self.kdf_security_strength, + self.kdf_entropy, + ); + + out_to_return + } + + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], + ) -> Result { + // check the version tag. At the moment, we have no not_before version to specify. + let input: &[u8; SHA3_FAMILY_STATE_LEN] = + check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + // The variant tag rejects states from any other SHA3/SHAKE variant; the rate is then the + // correct one to rebuild with (both are fully determined by the algorithm parameters). + let rate = 1600 - ((PARAMS::SIZE as usize) << 1); + let (keccak, kdf_key_type, kdf_security_strength, kdf_entropy) = + deserialize_sha3_family_state(input, PARAMS::STATE_TAG, rate)?; + + Ok(SHA3 { + _params: std::marker::PhantomData, + keccak, + kdf_key_type, + kdf_security_strength, + kdf_entropy, + }) + } +} diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index dd05d52..31868ec 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -1,13 +1,13 @@ use crate::SHAKEParams; use crate::keccak::{ - KeccakDigest, KeccakSize, SHA3_FAMILY_STATE_LEN, SHA3_SERIALIZED_STATE_LEN, + KeccakDigest, KeccakSize, SHA3_FAMILY_STATE_LEN, SUSPENDED_SHA3_STATE_LEN, deserialize_sha3_family_state, serialize_sha3_family_state, }; use bouncycastle_core::errors::{HashError, KDFError, SerializedStateError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, SerializableState, XOF}; +use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, Suspendable, XOF}; use bouncycastle_utils::{max, min}; /// Note: FIPS 202 section 7 states: @@ -142,9 +142,9 @@ impl SHAKE { } } -impl SerializableState for SHAKE { - fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { - let mut out_to_return = [0u8; SHA3_SERIALIZED_STATE_LEN]; +impl Suspendable for SHAKE { + fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_SHA3_STATE_LEN]; // insert the version tag let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = @@ -162,8 +162,8 @@ impl SerializableState for SHAKE out_to_return } - fn from_serialized_state( - serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], ) -> Result { // check the version tag. At the moment, we have no not_before version to specify. let input: &[u8; SHA3_FAMILY_STATE_LEN] = diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index ffff758..3bbe67b 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -9,7 +9,9 @@ mod sha3_tests { use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::hash::TestFrameworkHash; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; - use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE256}; + use bouncycastle_sha3::{ + SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE256, SUSPENDED_SHA3_STATE_LEN, + }; #[test] fn test_constants() { @@ -395,29 +397,28 @@ mod sha3_tests { } #[test] - fn test_serializable_state() { + fn serializable_state() { use bouncycastle_core::errors::SerializedStateError; - use bouncycastle_core::traits::SerializableState; - use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + use bouncycastle_core::traits::Suspendable; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; let str = "Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one SHA3 variant. - fn round_trip + Clone>( - mut hash: H, - input: &[u8], - ) { + fn round_trip + Clone>(mut hash: H, input: &[u8]) { hash.do_update(input); // do the default trait-conformance tests - TestFrameworkSerializableState::new().test(&hash); + TestFrameworkSuspendableState::new().test(&hash); // serialize the in-progress state, then finish the original - let serialized_state = hash.clone().serialize_state(); + let serialized_state = hash.clone().suspend(); + assert_eq!(serialized_state.len(), SUSPENDED_SHA3_STATE_LEN); + let expected = hash.do_final(); // rebuild from the serialized state and confirm it produces the same digest - let from_state = H::from_serialized_state(serialized_state).unwrap(); + let from_state = H::from_suspended(serialized_state).unwrap(); assert_eq!(expected, from_state.do_final()); // a corrupt `squeezing` byte (last byte of the keccak state) must be rejected. @@ -425,7 +426,7 @@ mod sha3_tests { // + bits_in_queue(8) + squeezing(1) let mut busted = serialized_state; busted[3 + 1 + 400] = 42; - match H::from_serialized_state(busted) { + match H::from_suspended(busted) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } @@ -441,12 +442,12 @@ mod sha3_tests { // is only caught by the tag, not the rate -- it is the exact bug the tag exists to prevent. let mut sha3_256 = SHA3_256::new(); sha3_256.do_update(str.as_bytes()); - let serialized_256 = sha3_256.serialize_state(); - match SHA3_512::from_serialized_state(serialized_256) { + let serialized_256 = sha3_256.suspend(); + match SHA3_512::from_suspended(serialized_256) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHA3-256 state into SHA3-512"), } - match SHAKE256::from_serialized_state(serialized_256) { + match SHAKE256::from_suspended(serialized_256) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHA3-256 state into SHAKE256"), } diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index 1e43b39..6fb8612 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -226,7 +226,7 @@ mod shake_tests { } #[test] - fn test_security_strength() { + fn security_strength() { assert_eq!(KDF::max_security_strength(&SHAKE128::default()), SecurityStrength::_128bit); assert_eq!(XOF::max_security_strength(&SHAKE128::default()), SecurityStrength::_128bit); assert_eq!(KDF::max_security_strength(&SHAKE256::default()), SecurityStrength::_256bit); @@ -239,29 +239,26 @@ mod shake_tests { } #[test] - fn test_serializable_state() { + fn suspendable_state() { use bouncycastle_core::errors::SerializedStateError; - use bouncycastle_core::traits::SerializableState; - use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + use bouncycastle_core::traits::Suspendable; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; let str = "Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one SHAKE variant. - fn round_trip + Clone>( - mut shake: X, - input: &[u8], - ) { + fn round_trip + Clone>(mut shake: X, input: &[u8]) { shake.absorb(input); // do the default trait-conformance tests - TestFrameworkSerializableState::new().test(&shake); + TestFrameworkSuspendableState::new().test(&shake); // serialize the in-progress (absorbing) state, then squeeze from the original - let serialized_state = shake.clone().serialize_state(); + let serialized_state = shake.clone().suspend(); let expected = shake.squeeze(64); // rebuild from the serialized state and confirm it produces the same output - let mut from_state = X::from_serialized_state(serialized_state).unwrap(); + let mut from_state = X::from_suspended(serialized_state).unwrap(); assert_eq!(expected, from_state.squeeze(64)); // a corrupt `squeezing` byte (last byte of the keccak state) must be rejected. @@ -269,7 +266,7 @@ mod shake_tests { // + bits_in_queue(8) + squeezing(1) let mut busted = serialized_state; busted[3 + 1 + 400] = 42; - match X::from_serialized_state(busted) { + match X::from_suspended(busted) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } @@ -283,16 +280,16 @@ mod shake_tests { // (1088), so only the variant tag distinguishes them. let mut shake128 = SHAKE128::new(); shake128.absorb(str.as_bytes()); - let serialized_128 = shake128.serialize_state(); - match SHAKE256::from_serialized_state(serialized_128) { + let serialized_128 = shake128.suspend(); + match SHAKE256::from_suspended(serialized_128) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into SHAKE256"), } let mut shake256 = SHAKE256::new(); shake256.absorb(str.as_bytes()); - let serialized_256 = shake256.serialize_state(); - match SHA3_256::from_serialized_state(serialized_256) { + let serialized_256 = shake256.suspend(); + match SHA3_256::from_suspended(serialized_256) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE256 state into SHA3-256"), } From 1dacaa27a5801512f2cf1a28ee08515fde38cef3 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 7 Jul 2026 21:33:27 -0500 Subject: [PATCH 31/35] rustfmt --- crypto/core/src/traits.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index f05cb43..6b4cddd 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -517,9 +517,7 @@ pub trait Suspendable: Sized { /// (including rejecting serializations from a future version of the library). /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its /// deserializer should reject serialized states from that version or older. - fn from_suspended( - state: [u8; SERIALIZED_STATE_LEN], - ) -> Result; + fn from_suspended(state: [u8; SERIALIZED_STATE_LEN]) -> Result; } /// Similar to [Suspendable] in that it allows a stateful object to suspend its operation by From 07889fcc9cc260bebc1c468929752b3c78ba822f Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 8 Jul 2026 18:33:18 -0500 Subject: [PATCH 32/35] HMAC now impl's Drop like it should. --- .../src/suspendable_state.rs | 10 +-- crypto/core/src/errors.rs | 2 +- crypto/core/src/key_material.rs | 8 +-- crypto/core/src/serializable_state.rs | 12 ++-- crypto/core/src/traits.rs | 12 ++-- crypto/factory/src/hash_factory.rs | 8 ++- crypto/hkdf/src/lib.rs | 23 +++++-- crypto/hkdf/tests/hkdf_tests.rs | 44 ++++++++++++- crypto/hmac/src/lib.rs | 41 +++++++++--- crypto/hmac/tests/hmac_tests.rs | 62 +++++++++++++------ crypto/mldsa-lowmemory/src/mldsa.rs | 4 +- crypto/mldsa-lowmemory/tests/mldsa_tests.rs | 6 +- crypto/mldsa/src/mldsa.rs | 4 +- crypto/mldsa/tests/mldsa_tests.rs | 6 +- crypto/sha2/src/lib.rs | 16 ----- crypto/sha2/src/sha256.rs | 13 ++-- crypto/sha2/src/sha512.rs | 13 ++-- crypto/sha2/tests/sha2_tests.rs | 6 +- crypto/sha3/src/keccak.rs | 12 ++-- crypto/sha3/src/lib.rs | 17 ----- crypto/sha3/src/sha3.rs | 11 +++- crypto/sha3/src/shake.rs | 4 +- crypto/sha3/tests/sha3_tests.rs | 8 +-- crypto/sha3/tests/shake_tests.rs | 20 ++++-- 24 files changed, 230 insertions(+), 132 deletions(-) diff --git a/crypto/core-test-framework/src/suspendable_state.rs b/crypto/core-test-framework/src/suspendable_state.rs index 7be7c21..c432aa8 100644 --- a/crypto/core-test-framework/src/suspendable_state.rs +++ b/crypto/core-test-framework/src/suspendable_state.rs @@ -1,4 +1,4 @@ -use bouncycastle_core::errors::SerializedStateError; +use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::serializable_state::{LIB_VERSION, SemVer}; use bouncycastle_core::traits::{Suspendable, SuspendableKeyed}; @@ -39,7 +39,7 @@ impl TestFrameworkSuspendableState { let mut ver0_serialized_state = serialized_state.clone(); ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); match S::from_suspended(ver0_serialized_state) { - Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + Err(SuspendableError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") } @@ -52,7 +52,7 @@ impl TestFrameworkSuspendableState { futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); match S::from_suspended(futurever_serialized_state) { - Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + Err(SuspendableError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") } @@ -101,7 +101,7 @@ impl TestFrameworkSuspendableKeyedState { let mut ver0_serialized_state = serialized_state.clone(); ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); match S::from_suspended(ver0_serialized_state, key) { - Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + Err(SuspendableError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") } @@ -114,7 +114,7 @@ impl TestFrameworkSuspendableKeyedState { futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); match S::from_suspended(futurever_serialized_state, key) { - Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + Err(SuspendableError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") } diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index 0241ae7..806380e 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -73,7 +73,7 @@ pub enum RNGError { } #[derive(Debug)] -pub enum SerializedStateError { +pub enum SuspendableError { /// The serialized state was produced by a library version incompatible with this one. IncompatibleVersion, /// The serialized state is malformed or corrupt. diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index c730c26..ece984c 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -50,7 +50,7 @@ //! //! See [do_hazardous_operations] for documentation and sample code. -use crate::errors::{KeyMaterialError, SerializedStateError}; +use crate::errors::{KeyMaterialError, SuspendableError}; use crate::traits::{RNG, Secret, SecurityStrength}; use bouncycastle_utils::{ct, min}; @@ -255,9 +255,9 @@ pub enum KeyType { } impl TryFrom for KeyType { - type Error = SerializedStateError; + type Error = SuspendableError; - /// Inverse of `self as u8`; rejects unrecognized discriminants with [SerializedStateError::InvalidData]. + /// Inverse of `self as u8`; rejects unrecognized discriminants with [SuspendableError::InvalidData]. fn try_from(value: u8) -> Result { Ok(match value { 0 => Self::Zeroized, @@ -266,7 +266,7 @@ impl TryFrom for KeyType { 3 => Self::Seed, 4 => Self::MACKey, 5 => Self::SymmetricCipherKey, - _ => return Err(SerializedStateError::InvalidData), + _ => return Err(SuspendableError::InvalidData), }) } } diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/serializable_state.rs index 420484a..615cfc3 100644 --- a/crypto/core/src/serializable_state.rs +++ b/crypto/core/src/serializable_state.rs @@ -1,6 +1,6 @@ //! Helper functions for standardizing serialization and deserialization of stateful objects. -use crate::errors::SerializedStateError; +use crate::errors::SuspendableError; /// A semantic library version, ordered by `major`, then `minor`, then `patch`. /// @@ -78,7 +78,7 @@ pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN] /// /// The state_out array must have length at least SERIALIZED_LEN - 3. /// -/// Returns the number of bytes written to state_out, or a [SerializedStateError::IncompatibleVersion] if the +/// Returns the number of bytes written to state_out, or a [SuspendableError::IncompatibleVersion] if the /// serialized state contains a version header earlier than the specified `not_before` version. /// /// Note that for testability, this will always reject if the serialized state contains a version tag @@ -88,23 +88,23 @@ pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN] pub fn check_lib_ver( state: &[u8; SERIALIZED_LEN], not_before: Option<[u8; 3]>, -) -> Result<&[u8], SerializedStateError> { +) -> Result<&[u8], SuspendableError> { let ver_bytes: [u8; 3] = state[..3].try_into().unwrap(); let ver = SemVer::from(ver_bytes); let not_before = SemVer::from(not_before.unwrap_or([0, 0, 0])); if ver < not_before { - return Err(SerializedStateError::IncompatibleVersion); + return Err(SuspendableError::IncompatibleVersion); }; // Nothing is ever compatible with [0,0,0] if ver == SemVer::from([0, 0, 0]) { - return Err(SerializedStateError::IncompatibleVersion); + return Err(SuspendableError::IncompatibleVersion); }; // Also not compatible with future versions. if ver > LIB_VERSION { - return Err(SerializedStateError::IncompatibleVersion); + return Err(SuspendableError::IncompatibleVersion); } Ok(&state[3..]) diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 6b4cddd..3086630 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -17,7 +17,7 @@ pub trait Algorithm { const MAX_SECURITY_STRENGTH: SecurityStrength; } -pub trait Hash: Default { +pub trait Hash: Algorithm + Default { /// The size of the internal block in bits -- needed by functions such as HMAC to compute security parameters. fn block_bitlen(&self) -> usize; @@ -399,9 +399,9 @@ pub enum SecurityStrength { } impl TryFrom for SecurityStrength { - type Error = SerializedStateError; + type Error = SuspendableError; - /// Inverse of `self as u8`; rejects unrecognized discriminants with [SerializedStateError::InvalidData]. + /// Inverse of `self as u8`; rejects unrecognized discriminants with [SuspendableError::InvalidData]. fn try_from(value: u8) -> Result { Ok(match value { 0 => Self::None, @@ -409,7 +409,7 @@ impl TryFrom for SecurityStrength { 2 => Self::_128bit, 3 => Self::_192bit, 4 => Self::_256bit, - _ => return Err(SerializedStateError::InvalidData), + _ => return Err(SuspendableError::InvalidData), }) } } @@ -517,7 +517,7 @@ pub trait Suspendable: Sized { /// (including rejecting serializations from a future version of the library). /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its /// deserializer should reject serialized states from that version or older. - fn from_suspended(state: [u8; SERIALIZED_STATE_LEN]) -> Result; + fn from_suspended(state: [u8; SERIALIZED_STATE_LEN]) -> Result; } /// Similar to [Suspendable] in that it allows a stateful object to suspend its operation by @@ -548,7 +548,7 @@ pub trait SuspendableKeyed: Sized { fn from_suspended( state: [u8; SERIALIZED_STATE_LEN], key: &Self::Key, - ) -> Result; + ) -> Result; } /// Pre-Hashed Signer is an extension to [Signer] that adds functionality specific to signature diff --git a/crypto/factory/src/hash_factory.rs b/crypto/factory/src/hash_factory.rs index 052f4b5..34793de 100644 --- a/crypto/factory/src/hash_factory.rs +++ b/crypto/factory/src/hash_factory.rs @@ -29,7 +29,7 @@ use crate::{AlgorithmFactory, FactoryError}; use crate::{DEFAULT, DEFAULT_128_BIT, DEFAULT_256_BIT}; use bouncycastle_core::errors::HashError; -use bouncycastle_core::traits::{Hash, SecurityStrength}; +use bouncycastle_core::traits::{Algorithm, Hash, SecurityStrength}; use bouncycastle_sha2 as sha2; use bouncycastle_sha2::{SHA224_NAME, SHA256_NAME, SHA384_NAME, SHA512_NAME}; use bouncycastle_sha3 as sha3; @@ -83,6 +83,12 @@ impl AlgorithmFactory for HashFactory { } } +// TODO -- this does't work. Perhaps Algorithm needs to be re-worked so that these are functions instead? +impl Algorithm for HashFactory { + const ALG_NAME: &'static str = "TODO"; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::None; +} + impl Hash for HashFactory { fn block_bitlen(&self) -> usize { match self { diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 20773b9..5c436d0 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -192,7 +192,7 @@ #![forbid(unsafe_code)] -use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError, SerializedStateError}; +use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial0, KeyMaterial512, KeyMaterialTrait, KeyType, @@ -252,15 +252,15 @@ enum HkdfStates { } impl TryFrom for HkdfStates { - type Error = SerializedStateError; + type Error = SuspendableError; - /// Inverse of `self as u8`; rejects unrecognized discriminants with [SerializedStateError::InvalidData]. + /// Inverse of `self as u8`; rejects unrecognized discriminants with [SuspendableError::InvalidData]. fn try_from(value: u8) -> Result { Ok(match value { 0 => Self::Uninitialized, 1 => Self::Initialized, 2 => Self::TakingAdditionalInfo, - _ => return Err(SerializedStateError::InvalidData), + _ => return Err(SuspendableError::InvalidData), }) } } @@ -830,7 +830,7 @@ macro_rules! impl_suspendable_keyed_state_for_hkdf { fn from_suspended( state: [u8; $serialized_hkdf_len], salt: &Self::Key, - ) -> Result { + ) -> Result { // Rebuild the salt-keyed HMAC (first in the payload) by re-supplying the salt. // This double-dips on HMAC checking the version tag before going any further. @@ -843,10 +843,21 @@ macro_rules! impl_suspendable_keyed_state_for_hkdf { state[..$serialized_hmac_len].try_into().unwrap(), salt, )?), - _ => return Err(SerializedStateError::InvalidData), + _ => return Err(SuspendableError::InvalidData), }; let hkdf_state = HkdfStates::try_from(state[$serialized_hmac_len + 1])?; + + // check that the hkdf_state aligns with the presence of an hmac + if + // an hmac object should not be present in the init state. + (hmac.is_some() && hkdf_state == HkdfStates::Uninitialized) || + // any other state must have an hmac object. + (hmac.is_none() && hkdf_state != HkdfStates::Uninitialized) + { + return Err(SuspendableError::InvalidData); + } + let entropy = u64::from_le_bytes( state[$serialized_hmac_len + 2..$serialized_hmac_len + 10].try_into().unwrap(), ) as usize; diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index 7131943..448c222 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod hkdf_tests { - use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError}; + use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial0, KeyMaterial128, KeyMaterial256, KeyMaterial512, @@ -571,7 +571,8 @@ mod hkdf_tests { assert_eq!(okm_key.ref_to_bytes().len(), L); assert_eq!(okm_key.ref_to_bytes(), hex::decode(okm).unwrap()); } - Err(KDFError::KeyMaterialError(_)) => { /* some of the rfc5896 test vectors are in fact low entropy, so just skip */ + Err(KDFError::KeyMaterialError(_)) => { + /* some of the rfc5896 test vectors are in fact low entropy, so just skip */ } Err(_) => panic!("Should have returned a MACError::KeyMaterialError."), } @@ -689,7 +690,7 @@ mod hkdf_tests { // SP800-56Cr2 tcId 1 let mut salt = KeyMaterial::<128>::new(); // have to do it this way for it to accept a zeroized key - key_material::do_hazardous_operations(&mut salt, |salt|{ + key_material::do_hazardous_operations(&mut salt, |salt| { salt.set_bytes_as_type(&hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), KeyType::MACKey) }).unwrap(); @@ -789,5 +790,42 @@ mod hkdf_tests { round_trip::(&salt, part1, part2); round_trip::(&salt, part1, part2); + + // Test the guard for invalid states + // testing just on HKDF_SHA256 + + const UNINITIALIZED: u8 = 0; // HkdfStates::Uninitialized + const INITIALIZED: u8 = 1; // HkdfStates::Initialized + let present_idx = SUSPENDED_HKDF_SHA256_STATE_LEN - 11; + let state_idx = SUSPENDED_HKDF_SHA256_STATE_LEN - 10; + + // Case 1: no HMAC present but state claims Initialized -> reject. + // construct a valid, pre-init state: no HMAC (flag = 0), state = Uninitialized. + let valid_uninitialized = HKDF_SHA256::new().suspend(); + assert_eq!(valid_uninitialized[present_idx], 0); + assert_eq!(valid_uninitialized[state_idx], UNINITIALIZED); + + let mut corrupt = valid_uninitialized; + corrupt[state_idx] = INITIALIZED; + assert!(matches!( + HKDF_SHA256::from_suspended(corrupt, &salt), + Err(SuspendableError::InvalidData) + )); + + // Case 2: HMAC present but state claims Uninitialized -> reject. + // construct a valid, mid-extract state: HMAC present (flag = 1), state = Initialized. + let mut hkdf = HKDF_SHA256::new(); + hkdf.do_extract_init(&salt).unwrap(); + hkdf.do_extract_update_bytes(ikm).unwrap(); + let valid_initialized = hkdf.suspend(); + assert_eq!(valid_initialized[present_idx], 1); + assert_ne!(valid_initialized[state_idx], UNINITIALIZED); + + let mut corrupt = valid_initialized; + corrupt[state_idx] = UNINITIALIZED; + assert!(matches!( + HKDF_SHA256::from_suspended(corrupt, &salt), + Err(SuspendableError::InvalidData) + )); } } diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 043e3f0..cd59def 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -184,16 +184,17 @@ #![allow(incomplete_features)] // because at time of writing, generic_const_exprs is not a stable feature #![feature(generic_const_exprs)] -use bouncycastle_core::errors::{KeyMaterialError, MACError, SerializedStateError}; +use bouncycastle_core::errors::{KeyMaterialError, MACError, SuspendableError}; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Algorithm, Hash, MAC, SecurityStrength, Suspendable, SuspendableKeyed, + Algorithm, Hash, MAC, Secret, SecurityStrength, Suspendable, SuspendableKeyed, }; use bouncycastle_sha2::{ SHA224, SHA256, SHA384, SHA512, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN, }; use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512, SUSPENDED_SHA3_STATE_LEN}; use bouncycastle_utils::ct; +use core::fmt::{Debug, Display, Formatter}; /*** String constants ***/ /// @@ -288,6 +289,28 @@ pub struct HMAC Secret for HMAC {} + +impl Drop for HMAC { + fn drop(&mut self) { + self.key.fill(0); + self.key_len = 0; + } +} + +impl Debug for HMAC { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "HMAC-{} instance", HASH::ALG_NAME,) + } +} + +impl Display for HMAC { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "HMAC-{} instance", HASH::ALG_NAME,) + } +} + // See definitions in RFC 2104 Section 2. const IPAD_BYTE: u8 = 0x36; const OPAD_BYTE: u8 = 0x5C; @@ -391,13 +414,15 @@ impl HMAC { // invalid outer hashes. // TODO: rework this to be no_std friendly (ie no vec!) let mut ihash = vec![0u8; self.hasher.output_len()]; - self.hasher.do_final_out(&mut ihash); + // `HMAC` implements `Drop` (required by `Secret`), so we cannot move `self.hasher` out + // directly. Swap in a fresh default and consume the taken-out hasher instead. + core::mem::take(&mut self.hasher).do_final_out(&mut ihash); // ohash self.hasher = HASH::default(); self.pad_key_into_hasher(OPAD_BYTE); self.hasher.do_update(&ihash); - Ok(self.hasher.do_final_out(out)) + Ok(core::mem::take(&mut self.hasher).do_final_out(out)) } } @@ -515,16 +540,18 @@ impl< // bytes at from_serialized_state, so dynamic dispatch here is negligible. type Key = dyn KeyMaterialTrait; - fn suspend(self) -> [u8; HASH_STATE_LEN] { + fn suspend(mut self) -> [u8; HASH_STATE_LEN] { // The key is intentionally excluded; the resumable state is just the inner hasher, which // already carries the library version header from the hash's own SerializableState impl. - self.hasher.suspend() + // `HMAC` implements `Drop` (required by `Secret`), so move the hasher out via `mem::take` + // rather than a direct partial move. + core::mem::take(&mut self.hasher).suspend() } fn from_suspended( state: [u8; HASH_STATE_LEN], key: &Self::Key, - ) -> Result { + ) -> Result { // Rebuild the inner hasher (version-compatibility is validated by the hash's impl). let hasher = HASH::from_suspended(state)?; diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index 0c5daa4..4921fef 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -59,8 +59,8 @@ mod hmac_tests { #[test] fn test_type_aliases() { let key = KeyMaterial512::from_bytes_as_type( - b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", - KeyType::MACKey).unwrap(); + b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + KeyType::MACKey).unwrap(); _ = HMAC::::new(&key).unwrap(); _ = HMAC_SHA224::new(&key).unwrap(); @@ -330,14 +330,14 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), - &hex::decode("7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea").unwrap() + &hex::decode("7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), - &hex::decode("6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a").unwrap() + &hex::decode("6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a").unwrap(), ); // RFC4231 Test Case 5 -- Test with a truncation of output to 128 bits. @@ -363,7 +363,7 @@ mod hmac_tests { // of SHA-384 and SHA-512) test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", - &hex::decode("3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1").unwrap() + &hex::decode("3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1").unwrap(), ); } @@ -411,14 +411,14 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), - &hex::decode("773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe").unwrap() + &hex::decode("773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), - &hex::decode("82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b").unwrap() + &hex::decode("82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b").unwrap(), ); // RFC4231 Test Case 5 -- Test with a truncation of output to 128 bits. @@ -435,14 +435,14 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", - &hex::decode("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54").unwrap() + &hex::decode("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54").unwrap(), ); // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", - &hex::decode("9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2").unwrap() + &hex::decode("9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2").unwrap(), ); } @@ -486,14 +486,14 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), - &hex::decode("88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27").unwrap() + &hex::decode("88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), - &hex::decode("3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb").unwrap() + &hex::decode("3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb").unwrap(), ); // RFC4231 Test Case 5 -- Test with a truncation of output to 128 bits. @@ -512,14 +512,14 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", - &hex::decode("4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952").unwrap() + &hex::decode("4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952").unwrap(), ); // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", - &hex::decode("6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e").unwrap() + &hex::decode("6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e").unwrap(), ); } @@ -564,14 +564,14 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), - &hex::decode("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb").unwrap() + &hex::decode("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), - &hex::decode("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd").unwrap() + &hex::decode("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd").unwrap(), ); // RFC4231 Test Case 5 -- Test with a truncation of output to 128 bits. @@ -590,21 +590,21 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", - &hex::decode("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598").unwrap() + &hex::decode("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598").unwrap(), ); // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", - &hex::decode("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58").unwrap() + &hex::decode("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58").unwrap(), ); } } #[test] fn suspendable_keyed_state() { - use bouncycastle_core::errors::SerializedStateError; + use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::serializable_state::LIB_VERSION; use bouncycastle_core::traits::SuspendableKeyed; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; @@ -651,7 +651,7 @@ mod hmac_tests { let mut busted = serialized_state; busted[..3].copy_from_slice(&[0, 0, 0]); match H::from_suspended(busted, key) { - Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + Err(SuspendableError::IncompatibleVersion) => { /* good */ } _ => panic!("Expected IncompatibleVersion for a zeroed version header"), } } @@ -659,5 +659,29 @@ mod hmac_tests { round_trip(HMAC_SHA256::new(&key).unwrap(), &key, msg); round_trip(HMAC_SHA512::new(&key).unwrap(), &key, msg); round_trip(HMAC_SHA3_256::new(&key).unwrap(), &key, msg); + + // test suspend / resume with a key larger than block size + let long_key = + KeyMaterial::<200>::from_bytes_as_type(&DUMMY_SEED_512[..200], KeyType::MACKey) + .unwrap(); + round_trip(HMAC_SHA256::new(&long_key).unwrap(), &long_key, msg); + } + + /// Tests that no private data is displayed + #[test] + fn test_display() { + let key = KeyMaterial256::from_bytes_as_type( + &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), + KeyType::MACKey, + ) + .unwrap(); + let hmac = HMAC_SHA256::new(&key).unwrap(); + + // test fmt + let fmt_str = format!("{}", &hmac); + assert_eq!(fmt_str, "HMAC-SHA256 instance"); + + // test debug + assert_eq!(format!("{:?}", &hmac), "HMAC-SHA256 instance"); } } diff --git a/crypto/mldsa-lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs index 77254a3..843a858 100644 --- a/crypto/mldsa-lowmemory/src/mldsa.rs +++ b/crypto/mldsa-lowmemory/src/mldsa.rs @@ -397,7 +397,7 @@ use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, }; -use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; +use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError}; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF, @@ -1953,7 +1953,7 @@ impl Suspendable for MuBuilder { fn from_suspended( serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], - ) -> Result { + ) -> Result { Ok(MuBuilder { h: H::from_suspended(serialized_state)? }) } } diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index 27e21c6..c195e63 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; - use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; + use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ @@ -875,7 +875,7 @@ mod mldsa_tests { let mut busted = serialized_state; busted[3 + 1 + 400] = 42; match MuBuilder::from_suspended(busted) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } } @@ -894,7 +894,7 @@ mod mldsa_tests { let serialized_128 = shake128.suspend(); match MuBuilder::from_suspended(serialized_128) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), } } diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index 71a76eb..0869c44 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -485,7 +485,7 @@ use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyExpanded, MLDSAPublicKeyExpanded, }; -use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; +use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF, @@ -2275,7 +2275,7 @@ impl Suspendable for MuBuilder { fn from_suspended( serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], - ) -> Result { + ) -> Result { Ok(MuBuilder { h: H::from_suspended(serialized_state)? }) } } diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index adc5c18..d46bf98 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; - use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; + use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError}; use bouncycastle_core::key_material::{ KeyMaterial256, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -1061,7 +1061,7 @@ mod mldsa_tests { let mut busted = serialized_state; busted[3 + 1 + 400] = 42; match MuBuilder::from_suspended(busted) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } } @@ -1080,7 +1080,7 @@ mod mldsa_tests { let serialized_128 = shake128.suspend(); match MuBuilder::from_suspended(serialized_128) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), } } diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index 687817e..fb8d933 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -95,10 +95,6 @@ pub type SHA512 = SHA512Internal; trait SHA2Params: HashAlgParams {} -impl Algorithm for SHA224 { - const ALG_NAME: &'static str = SHA224_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; -} impl HashAlgParams for SHA224 { const OUTPUT_LEN: usize = 28; const BLOCK_LEN: usize = 64; @@ -115,10 +111,6 @@ impl HashAlgParams for SHA224Params { } impl SHA2Params for SHA224Params {} -impl Algorithm for SHA256 { - const ALG_NAME: &'static str = SHA256_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} impl HashAlgParams for SHA256 { const OUTPUT_LEN: usize = 32; const BLOCK_LEN: usize = 64; @@ -135,10 +127,6 @@ impl HashAlgParams for SHA256Params { } impl SHA2Params for SHA256Params {} -impl Algorithm for SHA384 { - const ALG_NAME: &'static str = SHA384_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; -} impl HashAlgParams for SHA384 { const OUTPUT_LEN: usize = 48; const BLOCK_LEN: usize = 128; @@ -157,10 +145,6 @@ impl SHA2Params for SHA384Params {} #[derive(Clone)] pub struct SHA512Params; -impl Algorithm for SHA512 { - const ALG_NAME: &'static str = SHA512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; -} impl HashAlgParams for SHA512 { const OUTPUT_LEN: usize = 64; const BLOCK_LEN: usize = 128; diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index 12bbdcd..26fd556 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -1,7 +1,7 @@ use crate::SHA2Params; -use bouncycastle_core::errors::{HashError, SerializedStateError}; +use bouncycastle_core::errors::{HashError, SuspendableError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Hash, SecurityStrength, Suspendable}; +use bouncycastle_core::traits::{Algorithm, Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; @@ -178,6 +178,11 @@ impl Default for SHA256Internal { } } +impl Algorithm for SHA256Internal { + const ALG_NAME: &'static str = PARAMS::ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; +} + impl Hash for SHA256Internal { /// As per FIPS 180-4 Figure 1 fn block_bitlen(&self) -> usize { @@ -338,7 +343,7 @@ impl Suspendable for SHA256Inter fn from_suspended( serialized_state: [u8; SUSPENDED_SHA256_STATE_LEN], - ) -> Result { + ) -> Result { debug_assert_eq!(SUSPENDED_SHA256_STATE_LEN, 108); // check the version tag @@ -362,7 +367,7 @@ impl Suspendable for SHA256Inter // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 64 let x_buf_off: usize = input[104] as usize; if x_buf_off >= 64 { - return Err(SerializedStateError::InvalidData); + return Err(SuspendableError::InvalidData); } // Construct the object diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 89c4af2..4d2a7f4 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,7 +1,7 @@ use crate::SHA2Params; -use bouncycastle_core::errors::{HashError, SerializedStateError}; +use bouncycastle_core::errors::{HashError, SuspendableError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Hash, SecurityStrength, Suspendable}; +use bouncycastle_core::traits::{Algorithm, Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; @@ -193,6 +193,11 @@ impl Default for SHA512Internal { } } +impl Algorithm for SHA512Internal { + const ALG_NAME: &'static str = PARAMS::ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; +} + impl Hash for SHA512Internal { /// As per FIPS 180-4 Figure 1 fn block_bitlen(&self) -> usize { @@ -353,7 +358,7 @@ impl Suspendable for SHA512Inter fn from_suspended( serialized_state: [u8; SUSPENDED_SHA512_STATE_LEN], - ) -> Result { + ) -> Result { // check the version tag // At the moment, we have no not_before version to specify. let input: &[u8; 201] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); @@ -375,7 +380,7 @@ impl Suspendable for SHA512Inter // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 128 let x_buf_off: usize = input[200] as usize; if x_buf_off >= 128 { - return Err(SerializedStateError::InvalidData); + return Err(SuspendableError::InvalidData); } // Construct the object diff --git a/crypto/sha2/tests/sha2_tests.rs b/crypto/sha2/tests/sha2_tests.rs index d6f2103..38b4e2f 100644 --- a/crypto/sha2/tests/sha2_tests.rs +++ b/crypto/sha2/tests/sha2_tests.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod sha2_tests { - use bouncycastle_core::errors::SerializedStateError; + use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::traits::{Algorithm, Hash, HashAlgParams, SecurityStrength}; use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::hash::TestFrameworkHash; @@ -122,7 +122,7 @@ mod sha2_tests { let mut busted_state = serialized_state.clone(); busted_state[3 + 104] = 65; match SHA256::from_suspended(busted_state) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error"), } @@ -150,7 +150,7 @@ mod sha2_tests { let mut busted_state = serialized_state.clone(); busted_state[3 + 200] = 129; match SHA512::from_suspended(busted_state) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error"), } } diff --git a/crypto/sha3/src/keccak.rs b/crypto/sha3/src/keccak.rs index c887e16..fd63544 100644 --- a/crypto/sha3/src/keccak.rs +++ b/crypto/sha3/src/keccak.rs @@ -1,4 +1,4 @@ -use bouncycastle_core::errors::{HashError, SerializedStateError}; +use bouncycastle_core::errors::{HashError, SuspendableError}; use bouncycastle_core::key_material::KeyType; use bouncycastle_core::traits::SecurityStrength; @@ -405,7 +405,7 @@ impl KeccakDigest { fn from_serialized_state( input: &[u8; KECCAK_SERIALIZED_LEN], rate: usize, - ) -> Result { + ) -> Result { // state.buf: [u64; 25] let mut buf = [0u64; 25]; for i in 0..25 { @@ -419,14 +419,14 @@ impl KeccakDigest { // bytes, well within data_queue's 192-byte capacity). let bits_in_queue = u64::from_le_bytes(input[392..400].try_into().unwrap()) as usize; if bits_in_queue > rate { - return Err(SerializedStateError::InvalidData); + return Err(SuspendableError::InvalidData); } // squeezing: bool let squeezing = match input[400] { 0 => false, 1 => true, - _ => return Err(SerializedStateError::InvalidData), + _ => return Err(SuspendableError::InvalidData), }; Ok(Self { state: KeccakState { buf, rate }, data_queue, rate, bits_in_queue, squeezing }) @@ -465,9 +465,9 @@ pub(crate) fn deserialize_sha3_family_state( input: &[u8; SHA3_FAMILY_STATE_LEN], expected_variant_tag: u8, rate: usize, -) -> Result<(KeccakDigest, KeyType, SecurityStrength, usize), SerializedStateError> { +) -> Result<(KeccakDigest, KeyType, SecurityStrength, usize), SuspendableError> { if input[0] != expected_variant_tag { - return Err(SerializedStateError::InvalidData); + return Err(SuspendableError::InvalidData); } let keccak_in: &[u8; KECCAK_SERIALIZED_LEN] = diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 902c8ed..16262dd 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -183,11 +183,6 @@ trait SHA3Params: HashAlgParams { const STATE_TAG: u8; } -// TODO: more elegant to macro this? -impl Algorithm for SHA3_224 { - const ALG_NAME: &'static str = SHA3_224_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; -} impl HashAlgParams for SHA3_224 { const OUTPUT_LEN: usize = 28; // const BLOCK_LEN: usize = 64; @@ -209,10 +204,6 @@ impl SHA3Params for SHA3_224Params { const STATE_TAG: u8 = 1; } -impl Algorithm for SHA3_256 { - const ALG_NAME: &'static str = SHA3_256_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} impl HashAlgParams for SHA3_256 { const OUTPUT_LEN: usize = 32; // const BLOCK_LEN: usize = 64; @@ -236,10 +227,6 @@ impl SHA3Params for SHA3_256Params { #[derive(Clone)] pub struct SHA3_384Params; -impl Algorithm for SHA3_384 { - const ALG_NAME: &'static str = SHA3_384_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; -} impl HashAlgParams for SHA3_384 { const OUTPUT_LEN: usize = 48; // const BLOCK_LEN: usize = 128; @@ -261,10 +248,6 @@ impl SHA3Params for SHA3_384Params { #[derive(Clone)] pub struct SHA3_512Params; -impl Algorithm for SHA3_512 { - const ALG_NAME: &'static str = SHA3_512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; -} impl HashAlgParams for SHA3_512 { const OUTPUT_LEN: usize = 64; // const BLOCK_LEN: usize = 128; diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 1bfde8a..42933e6 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -3,11 +3,11 @@ use crate::keccak::{ KeccakDigest, SHA3_FAMILY_STATE_LEN, SUSPENDED_SHA3_STATE_LEN, deserialize_sha3_family_state, serialize_sha3_family_state, }; -use bouncycastle_core::errors::{HashError, KDFError, SerializedStateError}; +use bouncycastle_core::errors::{HashError, KDFError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Hash, KDF, SecurityStrength, Suspendable}; +use bouncycastle_core::traits::{Algorithm, Hash, KDF, SecurityStrength, Suspendable}; use bouncycastle_utils::{max, min}; #[derive(Clone)] @@ -124,6 +124,11 @@ impl Default for SHA3 { } } +impl Algorithm for SHA3 { + const ALG_NAME: &'static str = PARAMS::ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; +} + impl Hash for SHA3 { /// As per FIPS 202 Table 3. /// Required, for example, to compute the pad lengths in HMAC. @@ -297,7 +302,7 @@ impl Suspendable for SHA3 fn from_suspended( serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], - ) -> Result { + ) -> Result { // check the version tag. At the moment, we have no not_before version to specify. let input: &[u8; SHA3_FAMILY_STATE_LEN] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index 31868ec..e1b3aef 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -3,7 +3,7 @@ use crate::keccak::{ KeccakDigest, KeccakSize, SHA3_FAMILY_STATE_LEN, SUSPENDED_SHA3_STATE_LEN, deserialize_sha3_family_state, serialize_sha3_family_state, }; -use bouncycastle_core::errors::{HashError, KDFError, SerializedStateError}; +use bouncycastle_core::errors::{HashError, KDFError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; @@ -164,7 +164,7 @@ impl Suspendable for SHAKE Result { + ) -> Result { // check the version tag. At the moment, we have no not_before version to specify. let input: &[u8; SHA3_FAMILY_STATE_LEN] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index 3bbe67b..535e2dd 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -398,7 +398,7 @@ mod sha3_tests { #[test] fn serializable_state() { - use bouncycastle_core::errors::SerializedStateError; + use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::traits::Suspendable; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; @@ -427,7 +427,7 @@ mod sha3_tests { let mut busted = serialized_state; busted[3 + 1 + 400] = 42; match H::from_suspended(busted) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } } @@ -444,11 +444,11 @@ mod sha3_tests { sha3_256.do_update(str.as_bytes()); let serialized_256 = sha3_256.suspend(); match SHA3_512::from_suspended(serialized_256) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHA3-256 state into SHA3-512"), } match SHAKE256::from_suspended(serialized_256) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHA3-256 state into SHAKE256"), } } diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index 6fb8612..0140874 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -240,7 +240,7 @@ mod shake_tests { #[test] fn suspendable_state() { - use bouncycastle_core::errors::SerializedStateError; + use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::traits::Suspendable; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; @@ -253,7 +253,17 @@ mod shake_tests { // do the default trait-conformance tests TestFrameworkSuspendableState::new().test(&shake); - // serialize the in-progress (absorbing) state, then squeeze from the original + // Test #1 + // serialize the in-progress (absorbing) state, then squeeze from the original and compare + let serialized_state = shake.clone().suspend(); + let expected = shake.squeeze(64); + + // rebuild from the serialized state and confirm it produces the same output + let mut from_state = X::from_suspended(serialized_state).unwrap(); + assert_eq!(expected, from_state.squeeze(64)); + + // Test #2 + // serialize the in-progress (squeezing) state, then squeeze more from the original and compare let serialized_state = shake.clone().suspend(); let expected = shake.squeeze(64); @@ -267,7 +277,7 @@ mod shake_tests { let mut busted = serialized_state; busted[3 + 1 + 400] = 42; match X::from_suspended(busted) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } } @@ -282,7 +292,7 @@ mod shake_tests { shake128.absorb(str.as_bytes()); let serialized_128 = shake128.suspend(); match SHAKE256::from_suspended(serialized_128) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into SHAKE256"), } @@ -290,7 +300,7 @@ mod shake_tests { shake256.absorb(str.as_bytes()); let serialized_256 = shake256.suspend(); match SHA3_256::from_suspended(serialized_256) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE256 state into SHA3-256"), } } From 3c8d64b0730e97f4d4448f75ac8c3a964e0a2806 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 8 Jul 2026 19:06:46 -0500 Subject: [PATCH 33/35] Adjusted the Suspendable version checker to allow future versions from the same patch stream --- .../src/suspendable_state.rs | 66 +++++++++++++++++-- crypto/core/src/errors.rs | 3 - crypto/core/src/lib.rs | 2 +- ...alizable_state.rs => suspendable_state.rs} | 19 ++++-- crypto/hmac/tests/hmac_tests.rs | 2 +- crypto/sha2/src/sha256.rs | 2 +- crypto/sha2/src/sha512.rs | 2 +- crypto/sha3/src/sha3.rs | 2 +- crypto/sha3/src/shake.rs | 2 +- 9 files changed, 80 insertions(+), 20 deletions(-) rename crypto/core/src/{serializable_state.rs => suspendable_state.rs} (82%) diff --git a/crypto/core-test-framework/src/suspendable_state.rs b/crypto/core-test-framework/src/suspendable_state.rs index c432aa8..677ed4e 100644 --- a/crypto/core-test-framework/src/suspendable_state.rs +++ b/crypto/core-test-framework/src/suspendable_state.rs @@ -1,5 +1,5 @@ use bouncycastle_core::errors::SuspendableError; -use bouncycastle_core::serializable_state::{LIB_VERSION, SemVer}; +use bouncycastle_core::suspendable_state::{LIB_VERSION, SemVer}; use bouncycastle_core::traits::{Suspendable, SuspendableKeyed}; pub struct TestFrameworkSuspendableState {} @@ -45,9 +45,21 @@ impl TestFrameworkSuspendableState { } } - // All implementations MUST reject a serialized state from a future version. + // All implementations MUST reject a serialized state from a future MAJOR or MINOR version. let mut future_ver = LIB_VERSION; - future_ver.patch += 1; + future_ver.major += 1; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + match S::from_suspended(futurever_serialized_state) { + Err(SuspendableError::IncompatibleVersion) => { /* good */ } + _ => { + panic!("Expected IncompatibleVersion error") + } + } + + let mut future_ver = LIB_VERSION; + future_ver.minor += 1; let mut futurever_serialized_state = serialized_state.clone(); futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); @@ -57,6 +69,22 @@ impl TestFrameworkSuspendableState { panic!("Expected IncompatibleVersion error") } } + + // but should accept anything on the same patch stream. + let mut future_ver = LIB_VERSION; + future_ver.patch += 1; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + let _deserialized_state = S::from_suspended(futurever_serialized_state).unwrap(); + + // ... even up to patch 255 + let mut future_ver = LIB_VERSION; + future_ver.patch = 255; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + let _deserialized_state = S::from_suspended(futurever_serialized_state).unwrap(); } } @@ -107,9 +135,21 @@ impl TestFrameworkSuspendableKeyedState { } } - // All implementations MUST reject a serialized state from a future version. + // All implementations MUST reject a serialized state from a future MAJOR or MINOR version. let mut future_ver = LIB_VERSION; - future_ver.patch += 1; + future_ver.major += 1; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + match S::from_suspended(futurever_serialized_state, key) { + Err(SuspendableError::IncompatibleVersion) => { /* good */ } + _ => { + panic!("Expected IncompatibleVersion error") + } + } + + let mut future_ver = LIB_VERSION; + future_ver.minor += 1; let mut futurever_serialized_state = serialized_state.clone(); futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); @@ -119,5 +159,21 @@ impl TestFrameworkSuspendableKeyedState { panic!("Expected IncompatibleVersion error") } } + + // but should accept anything on the same patch stream. + let mut future_ver = LIB_VERSION; + future_ver.patch += 1; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + let _deserialized_state = S::from_suspended(futurever_serialized_state, key).unwrap(); + + // ... even up to patch 255 + let mut future_ver = LIB_VERSION; + future_ver.patch = 255; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + let _deserialized_state = S::from_suspended(futurever_serialized_state, key).unwrap(); } } diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index 806380e..a079aad 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -78,9 +78,6 @@ pub enum SuspendableError { IncompatibleVersion, /// The serialized state is malformed or corrupt. InvalidData, - /// The key supplied to [crate::traits::SuspendableKeyed::from_suspended] does not - /// match the key the state was created with (it is bound to a different public-key hash `tr`). - IncorrectKey, } #[derive(Debug)] diff --git a/crypto/core/src/lib.rs b/crypto/core/src/lib.rs index 9516907..66bff60 100644 --- a/crypto/core/src/lib.rs +++ b/crypto/core/src/lib.rs @@ -7,5 +7,5 @@ pub mod errors; pub mod key_material; -pub mod serializable_state; +pub mod suspendable_state; pub mod traits; diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/suspendable_state.rs similarity index 82% rename from crypto/core/src/serializable_state.rs rename to crypto/core/src/suspendable_state.rs index 615cfc3..a668676 100644 --- a/crypto/core/src/serializable_state.rs +++ b/crypto/core/src/suspendable_state.rs @@ -42,8 +42,8 @@ const fn parse_version_component(s: &str) -> u8 { result } -/// The current library version, taken from this crate's `Cargo.toml` at compile time (via Cargo's -/// `CARGO_PKG_VERSION_*` env vars) so it can never drift from the published version. +/// The current library version -- ie the version of the *bouncycastle-core* crate -- at compile time (via Cargo's +/// `CARGO_PKG_VERSION_*` env vars). pub const LIB_VERSION: SemVer = SemVer { major: parse_version_component(env!("CARGO_PKG_VERSION_MAJOR")), minor: parse_version_component(env!("CARGO_PKG_VERSION_MINOR")), @@ -78,8 +78,9 @@ pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN] /// /// The state_out array must have length at least SERIALIZED_LEN - 3. /// -/// Returns the number of bytes written to state_out, or a [SuspendableError::IncompatibleVersion] if the -/// serialized state contains a version header earlier than the specified `not_before` version. +/// Returns the number of bytes written to state_out, or a [SuspendableError::IncompatibleVersion] if +/// the version of the serialized state is earlier than the specified `not_before` version, or +/// is a future MAJOR or MINOR version (but future PATCH versions are ok). /// /// Note that for testability, this will always reject if the serialized state contains a version tag /// of `[0,0,0]`. @@ -89,6 +90,10 @@ pub fn check_lib_ver( state: &[u8; SERIALIZED_LEN], not_before: Option<[u8; 3]>, ) -> Result<&[u8], SuspendableError> { + // the .unwrap is infallible after the guard check + if state.len() < 3 { + return Err(SuspendableError::InvalidData); + } let ver_bytes: [u8; 3] = state[..3].try_into().unwrap(); let ver = SemVer::from(ver_bytes); @@ -102,8 +107,10 @@ pub fn check_lib_ver( return Err(SuspendableError::IncompatibleVersion); }; - // Also not compatible with future versions. - if ver > LIB_VERSION { + // Check if state was produced by a later MAJOR or MINOR version; + // a future version on the same patch stream is ok (if not, then we've broken the rules of semantic versioning); + let patch_stream = SemVer::from([LIB_VERSION.major, LIB_VERSION.minor, 255]); + if ver > patch_stream { return Err(SuspendableError::IncompatibleVersion); } diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index 4921fef..817a89d 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -605,7 +605,7 @@ mod hmac_tests { #[test] fn suspendable_keyed_state() { use bouncycastle_core::errors::SuspendableError; - use bouncycastle_core::serializable_state::LIB_VERSION; + use bouncycastle_core::suspendable_state::LIB_VERSION; use bouncycastle_core::traits::SuspendableKeyed; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index 26fd556..cd92454 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -1,6 +1,6 @@ use crate::SHA2Params; use bouncycastle_core::errors::{HashError, SuspendableError}; -use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Algorithm, Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 4d2a7f4..3a031bc 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,6 +1,6 @@ use crate::SHA2Params; use bouncycastle_core::errors::{HashError, SuspendableError}; -use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Algorithm, Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 42933e6..705e741 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -6,7 +6,7 @@ use crate::keccak::{ use bouncycastle_core::errors::{HashError, KDFError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Algorithm, Hash, KDF, SecurityStrength, Suspendable}; use bouncycastle_utils::{max, min}; diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index e1b3aef..551e6fc 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -6,7 +6,7 @@ use crate::keccak::{ use bouncycastle_core::errors::{HashError, KDFError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, Suspendable, XOF}; use bouncycastle_utils::{max, min}; From fd17f91a2e3c91d2cf7ac51d5e6596b6ffea04a1 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 8 Jul 2026 19:41:12 -0500 Subject: [PATCH 34/35] HKDF now explicitly carries a lib_ver tag --- crypto/hkdf/src/lib.rs | 71 +++++++++++++++++++-------------- crypto/hkdf/tests/hkdf_tests.rs | 3 +- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 5c436d0..13ec778 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -197,6 +197,7 @@ use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial0, KeyMaterial512, KeyMaterialTrait, KeyType, }; +use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{ Hash, HashAlgParams, KDF, MAC, SecurityStrength, SuspendableKeyed, }; @@ -777,9 +778,9 @@ impl KDF for HKDF { } /// Length in bytes of the serialized state of [HKDF_SHA256]. -pub const SUSPENDED_HKDF_SHA256_STATE_LEN: usize = SUSPENDED_HMAC_SHA256_STATE_LEN + 11; +pub const SUSPENDED_HKDF_SHA256_STATE_LEN: usize = SUSPENDED_HMAC_SHA256_STATE_LEN + 14; /// Length in bytes of the serialized state of [HKDF_SHA512]. -pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_LEN + 11; +pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_LEN + 14; /// HKDF is *keyed by its salt* -- the salt keys the extract-phase HMAC -- so it implements /// [SuspendableKeyed] (not [SerializableState]). An in-progress @@ -789,19 +790,21 @@ pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_L /// Only the extract phase carries resumable state (expand is a one-shot static operation). As with /// HMAC, resuming with the wrong salt cannot be detected and will silently produce a wrong PRK. /// -/// Serialized layout: the 3-byte library version header comes first and is checked before anything -/// else is parsed; then, using `B` = the inner HMAC blob length: -/// [0 .. B) the inner HMAC's SerializableKeyedState blob (salt excluded); zeroed when absent -/// [B] inner-HMAC present flag (0 = extract not yet initialized) -/// [B + 1] state-machine tag (see `HkdfStates`) -/// [B + 2 .. B + 10) entropy counter (usize serialized as u64, little-endian) -/// [B + 10] accumulated security strength (1-byte tag) -/// The number of bytes produced by `SerializableKeyedState::serialize_state` for each HKDF variant: -/// the 3-byte library version header + 11 bytes of HKDF bookkeeping (HMAC-present flag, state tag, -/// entropy counter, security strength) + the inner HMAC's serialized-state blob. +/// Serialized layout: HKDF writes its own 3-byte library version header first and checks it before +/// parsing anything else. This matters because the inner HMAC blob (which carries its own header) is +/// absent before extract is initialized -- without HKDF's own header, a pre-init state would have no +/// version tag at all. Using `B` = the inner HMAC blob length: +/// [0 .. 3) HKDF library version header (checked on resume) +/// [3] inner-HMAC present flag (0 = extract not yet initialized) +/// [4 .. 4 + B) the inner HMAC's SuspendableKeyed blob (salt excluded); zeroed when absent +/// [4 + B] state-machine tag (see `HkdfStates`) +/// [5 + B .. 13 + B) entropy counter (usize serialized as u64, little-endian) +/// [13 + B] accumulated security strength (1-byte tag) +/// So the total per HKDF variant is the 3-byte version header + 11 bytes of HKDF bookkeeping +/// (present flag, state tag, entropy counter, security strength) + the inner HMAC's blob = `B + 14`. macro_rules! impl_suspendable_keyed_state_for_hkdf { - // $hash: the concrete hash; $hmac_blob: the inner HMAC's serialized-state length for that hash; - // $total: the full HKDF serialized-state length (= 3 + 11 + $hmac_blob). + // $hash: the concrete hash; $serialized_hmac_len: the inner HMAC's serialized-state length for that + // hash; $serialized_hkdf_len: the full HKDF serialized-state length (= 3 + 11 + $serialized_hmac_len). ($hash:ty, $serialized_hmac_len:expr, $serialized_hkdf_len:expr) => { impl SuspendableKeyed<{ $serialized_hkdf_len }> for HKDF<$hash> { // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` @@ -810,19 +813,27 @@ macro_rules! impl_suspendable_keyed_state_for_hkdf { type Key = dyn KeyMaterialTrait; fn suspend(self) -> [u8; $serialized_hkdf_len] { - debug_assert_eq!($serialized_hkdf_len, $serialized_hmac_len + 11); + debug_assert_eq!($serialized_hkdf_len, $serialized_hmac_len + 14); let mut state = [0u8; $serialized_hkdf_len]; - // The inner HMAC blob goes first, which carries a lib version header. + // HKDF's own library version header comes first: the inner HMAC blob is absent before + // extract is initialized, so we can't rely on its header being present. + add_lib_ver(&mut state); + + // The present flag, then (when present) the inner salt-keyed HMAC blob right after it. if let Some(hmac) = self.hmac { - state[..$serialized_hmac_len].copy_from_slice(&hmac.suspend()); - state[$serialized_hmac_len] = 1; // present flag + state[3] = 1; // present flag + state[4..4 + $serialized_hmac_len].copy_from_slice(&hmac.suspend()); } + // else None: + // the presence flag = 0 + // the content = [u8; 0] + // which is how it already is, so nothing to do. - state[$serialized_hmac_len + 1] = self.state as u8; - state[$serialized_hmac_len + 2..$serialized_hmac_len + 10] + state[4 + $serialized_hmac_len] = self.state as u8; + state[5 + $serialized_hmac_len..13 + $serialized_hmac_len] .copy_from_slice(&(self.entropy.entropy as u64).to_le_bytes()); - state[$serialized_hmac_len + 10] = self.entropy.security_strength as u8; + state[13 + $serialized_hmac_len] = self.entropy.security_strength as u8; state } @@ -831,22 +842,20 @@ macro_rules! impl_suspendable_keyed_state_for_hkdf { state: [u8; $serialized_hkdf_len], salt: &Self::Key, ) -> Result { - // Rebuild the salt-keyed HMAC (first in the payload) by re-supplying the salt. + // Check HKDF's own version header before parsing anything else. + check_lib_ver(&state, None)?; - // This double-dips on HMAC checking the version tag before going any further. - // If ever we need to version-reject HKDF separately from HMAC, then we'll need to add - // an explicit version check here, and change "None" to the oldest accepted version. - // _ = check_lib_ver(&serialized_state, None)?; - let hmac = match state[$serialized_hmac_len] { + // Rebuild the salt-keyed HMAC (when present) by re-supplying the salt. + let hmac = match state[3] { 0 => None, 1 => Some(HMAC::<$hash>::from_suspended( - state[..$serialized_hmac_len].try_into().unwrap(), + state[4..4 + $serialized_hmac_len].try_into().unwrap(), salt, )?), _ => return Err(SuspendableError::InvalidData), }; - let hkdf_state = HkdfStates::try_from(state[$serialized_hmac_len + 1])?; + let hkdf_state = HkdfStates::try_from(state[4 + $serialized_hmac_len])?; // check that the hkdf_state aligns with the presence of an hmac if @@ -859,10 +868,10 @@ macro_rules! impl_suspendable_keyed_state_for_hkdf { } let entropy = u64::from_le_bytes( - state[$serialized_hmac_len + 2..$serialized_hmac_len + 10].try_into().unwrap(), + state[5 + $serialized_hmac_len..13 + $serialized_hmac_len].try_into().unwrap(), ) as usize; let security_strength = - SecurityStrength::try_from(state[$serialized_hmac_len + 10])?; + SecurityStrength::try_from(state[13 + $serialized_hmac_len])?; Ok(HKDF { hmac, diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index 448c222..fee0444 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -796,7 +796,8 @@ mod hkdf_tests { const UNINITIALIZED: u8 = 0; // HkdfStates::Uninitialized const INITIALIZED: u8 = 1; // HkdfStates::Initialized - let present_idx = SUSPENDED_HKDF_SHA256_STATE_LEN - 11; + // Layout: [version(3) | present flag | hmac blob | state | entropy(8) | strength]. + let present_idx = 3; let state_idx = SUSPENDED_HKDF_SHA256_STATE_LEN - 10; // Case 1: no HMAC present but state claims Initialized -> reject. From 03a0d93af99e4f3695c5c10c9da0fc0cfdd77f7a Mon Sep 17 00:00:00 2001 From: officialfrancismendoza Date: Thu, 9 Jul 2026 19:13:01 +0700 Subject: [PATCH 35/35] Feature branch diversion from feature/serialize_state where we changed everything from u8 to using const array --- Cargo.toml | 22 ++-- crypto/base64/Cargo.toml | 6 + crypto/base64/src/lib.rs | 19 +++- crypto/core-test-framework/Cargo.toml | 3 +- crypto/core-test-framework/src/hash.rs | 14 +++ crypto/core-test-framework/src/mac.rs | 16 +++ crypto/core/Cargo.toml | 7 ++ crypto/core/src/lib.rs | 10 +- crypto/core/src/traits.rs | 134 +++++++++++++++++++++- crypto/factory/Cargo.toml | 17 ++- crypto/hex/Cargo.toml | 6 + crypto/hex/src/lib.rs | 14 +++ crypto/hkdf/Cargo.toml | 11 ++ crypto/hkdf/src/lib.rs | 21 +++- crypto/hmac/Cargo.toml | 14 +++ crypto/hmac/src/lib.rs | 53 ++++++--- crypto/rng/Cargo.toml | 8 ++ crypto/rng/src/hash_drbg80090a.rs | 2 + crypto/rng/src/lib.rs | 3 + crypto/sha2/Cargo.toml | 6 + crypto/sha2/src/lib.rs | 7 ++ crypto/sha2/src/sha256.rs | 8 +- crypto/sha2/src/sha512.rs | 16 ++- crypto/sha3/Cargo.toml | 6 + crypto/sha3/src/lib.rs | 7 ++ crypto/sha3/src/sha3.rs | 20 +++- crypto/sha3/src/shake.rs | 26 ++++- recommendations.md | 149 +++++++++++++++++++++++++ 28 files changed, 571 insertions(+), 54 deletions(-) create mode 100644 recommendations.md diff --git a/Cargo.toml b/Cargo.toml index 4301f8f..9ce118b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,19 +9,22 @@ edition = "2024" # *** Internal Dependencies *** bouncycastle = { path = "./", version = "0.1.1" } bouncycastle-base64 = { path = "./crypto/base64", version = "0.1.1" } -bouncycastle-core = { path = "crypto/core", version = "0.1.1" } +# `default-features = false` here (rather than on each member) is required so that individual +# crates can opt out of the `alloc` feature for `no_std` builds; members re-enable it through +# their own `alloc` feature. Building the whole workspace keeps `alloc` on via feature unification. +bouncycastle-core = { path = "crypto/core", version = "0.1.1", default-features = false } bouncycastle-core-test-framework = { path = "./crypto/core-test-framework", version = "0.1.1" } bouncycastle-factory = { path = "./crypto/factory", version = "0.1.1" } bouncycastle-hex = { path = "./crypto/hex", version = "0.1.1" } bouncycastle-hkdf = { path = "./crypto/hkdf", version = "0.1.1" } -bouncycastle-hmac = { path = "./crypto/hmac", version = "0.1.1" } +bouncycastle-hmac = { path = "./crypto/hmac", version = "0.1.1", default-features = false } bouncycastle-mlkem = { path = "./crypto/mlkem", version = "0.1.2" } bouncycastle-mlkem-lowmemory = { path = "./crypto/mlkem-lowmemory", version = "0.1.2" } bouncycastle-mldsa = { path = "./crypto/mldsa", version = "0.1.2" } bouncycastle-mldsa-lowmemory = { path = "./crypto/mldsa-lowmemory", version = "0.1.2" } bouncycastle-rng = { path = "./crypto/rng", version = "0.1.1" } -bouncycastle-sha2 = { path = "./crypto/sha2", version = "0.1.1" } -bouncycastle-sha3 = { path = "./crypto/sha3", version = "0.1.1" } +bouncycastle-sha2 = { path = "./crypto/sha2", version = "0.1.1", default-features = false } +bouncycastle-sha3 = { path = "./crypto/sha3", version = "0.1.1", default-features = false } bouncycastle-utils = { path = "./crypto/utils", version = "0.1.1" } @@ -39,17 +42,20 @@ name = "bouncycastle" version = "0.1.2" edition.workspace = true +# The umbrella crate re-exports the whole library for downstream users who want everything in one +# dependency, so it enables `alloc` on the primitive crates whose workspace deps default it off. +# (base64/hex/hkdf/rng/factory already default `alloc` on.) [dependencies] bouncycastle-base64.workspace = true -bouncycastle-core.workspace = true +bouncycastle-core = { workspace = true, features = ["alloc"] } bouncycastle-factory.workspace = true bouncycastle-hex.workspace = true bouncycastle-hkdf.workspace = true -bouncycastle-hmac.workspace = true +bouncycastle-hmac = { workspace = true, features = ["alloc"] } bouncycastle-mldsa.workspace = true bouncycastle-mldsa-lowmemory.workspace = true bouncycastle-mlkem.workspace = true bouncycastle-mlkem-lowmemory.workspace = true bouncycastle-rng.workspace = true -bouncycastle-sha2.workspace = true -bouncycastle-sha3.workspace = true \ No newline at end of file +bouncycastle-sha2 = { workspace = true, features = ["alloc"] } +bouncycastle-sha3 = { workspace = true, features = ["alloc"] } \ No newline at end of file diff --git a/crypto/base64/Cargo.toml b/crypto/base64/Cargo.toml index 13411b9..2da0a8a 100644 --- a/crypto/base64/Cargo.toml +++ b/crypto/base64/Cargo.toml @@ -3,6 +3,12 @@ name = "bouncycastle-base64" version = "0.1.1" edition.workspace = true +[features] +# `alloc` enables the (entire) `String`/`Vec`-returning encoder/decoder API. On by default; a +# `no_std` build without it exposes only the type definitions (there are no streaming `_out` variants). +default = ["alloc"] +alloc = [] + [dependencies] bouncycastle-core.workspace = true bouncycastle-utils.workspace = true diff --git a/crypto/base64/src/lib.rs b/crypto/base64/src/lib.rs index b3e57d8..2f55313 100644 --- a/crypto/base64/src/lib.rs +++ b/crypto/base64/src/lib.rs @@ -81,14 +81,27 @@ // /// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=" // URLSafe, +#![cfg_attr(not(test), no_std)] + +// The entire base64 encoder/decoder API returns `String`/`Vec`, so it lives behind the +// default-on `alloc` feature. base64 is inherently an allocating codec (there are currently no +// streaming `_out` variants), so a `no_std` build without `alloc` exposes only the type definitions. +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "alloc")] +use alloc::{string::String, vec, vec::Vec}; + +#[cfg(feature = "alloc")] use bouncycastle_utils::ct::Condition; /// One-shot encode from bytes to a base64-encoded string using a constant-time implementation. +#[cfg(feature = "alloc")] pub fn encode>(input: T) -> String { Base64Encoder::new().do_final(input) } /// One-shot decode from a base64-encoded string to bytes using a constant-time implementation. +#[cfg(feature = "alloc")] pub fn decode>(input: T) -> Result, Base64Error> { Base64Decoder::new(true).do_final(input) } @@ -107,11 +120,13 @@ pub enum Base64Error { } /// The stateful base64 encoder that supports streaming. +#[cfg(feature = "alloc")] pub struct Base64Encoder { buf: [u8; 3], vals_in_buf: usize, } +#[cfg(feature = "alloc")] impl Base64Encoder { /// Create a new instance. pub fn new() -> Self { @@ -182,7 +197,7 @@ impl Base64Encoder { if self.vals_in_buf == 1 { out_buf[2] = b'='; } - out.push_str(std::str::from_utf8(&out_buf).unwrap()); + out.push_str(core::str::from_utf8(&out_buf).unwrap()); } out } @@ -202,12 +217,14 @@ impl Base64Encoder { } /// The stateful base64 decoder that supports streaming. +#[cfg(feature = "alloc")] pub struct Base64Decoder { buf: [u8; 4], vals_in_buf: usize, skip_whitespace: bool, } +#[cfg(feature = "alloc")] impl Base64Decoder { /// Create a new instance. pub fn new(skip_whitespace: bool) -> Self { diff --git a/crypto/core-test-framework/Cargo.toml b/crypto/core-test-framework/Cargo.toml index 49ae93b..37161c0 100644 --- a/crypto/core-test-framework/Cargo.toml +++ b/crypto/core-test-framework/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.1" edition.workspace = true [dependencies] -bouncycastle-core.workspace = true +# The KAT test framework exercises the `Vec`/`Box`-returning trait APIs, so it always needs `alloc`. +bouncycastle-core = { workspace = true, features = ["alloc"] } [dev-dependencies] \ No newline at end of file diff --git a/crypto/core-test-framework/src/hash.rs b/crypto/core-test-framework/src/hash.rs index 8bcc000..337d8d8 100644 --- a/crypto/core-test-framework/src/hash.rs +++ b/crypto/core-test-framework/src/hash.rs @@ -30,6 +30,20 @@ impl TestFrameworkHash { H::default().hash_out(input, &mut output_buf); assert_eq!(output_buf, expected_output); + /*** fn hash_array(self, data: &[u8]) -> [u8; N] (no_std alternative) ***/ + // Use N = 64, the maximum output length across all hashes; hash_array zero-pads the tail + // beyond output_len, so the digest lands in the first OUTPUT_LEN bytes. + let arr: [u8; 64] = H::default().hash_array(input); + assert_eq!(&arr[..H::OUTPUT_LEN], expected_output, "hash_array digest mismatch"); + assert!(arr[H::OUTPUT_LEN..].iter().all(|&b| b == 0), "hash_array tail not zero-padded"); + + /*** fn do_final_array(self) -> [u8; N] (no_std alternative) ***/ + let mut message_digest = H::default(); + message_digest.do_update(input); + let arr: [u8; 64] = message_digest.do_final_array(); + assert_eq!(&arr[..H::OUTPUT_LEN], expected_output, "do_final_array digest mismatch"); + assert!(arr[H::OUTPUT_LEN..].iter().all(|&b| b == 0), "do_final_array tail not zero-padded"); + /*** fn do_update(&mut self, data: &[u8]) -> Result<(), HashError> ***/ /*** fn do_final(self) -> Result, HashError> **/ diff --git a/crypto/core-test-framework/src/mac.rs b/crypto/core-test-framework/src/mac.rs index a87d7f6..c8f7809 100644 --- a/crypto/core-test-framework/src/mac.rs +++ b/crypto/core-test-framework/src/mac.rs @@ -60,6 +60,22 @@ impl TestFrameworkMAC { // Test .output_len() assert_eq!(output_len, out.len()); + // Test ::mac_array() and ::do_final_array() (no_std alternatives). + // N = 64 is >= every supported MAC output length (and >= the FIPS minimum), so the tag lands + // in the first output_len bytes with a zero-padded tail. + let arr: [u8; 64] = M::new_allow_weak_key(key).unwrap().mac_array(input).unwrap(); + assert_eq!(&arr[..expected_output.len()], expected_output, "mac_array digest mismatch"); + assert!(arr[expected_output.len()..].iter().all(|&b| b == 0), "mac_array tail not zero-padded"); + + let mut mac = M::new_allow_weak_key(key).unwrap(); + mac.do_update(input); + let arr: [u8; 64] = mac.do_final_array().unwrap(); + assert_eq!(&arr[..expected_output.len()], expected_output, "do_final_array digest mismatch"); + assert!( + arr[expected_output.len()..].iter().all(|&b| b == 0), + "do_final_array tail not zero-padded" + ); + // Test .init(), .do_update(), .do_mac_final_out() let mut mac = M::new_allow_weak_key(key).unwrap(); mac.do_update(input); diff --git a/crypto/core/Cargo.toml b/crypto/core/Cargo.toml index 2b9c969..2079bbb 100644 --- a/crypto/core/Cargo.toml +++ b/crypto/core/Cargo.toml @@ -3,6 +3,13 @@ name = "bouncycastle-core" version = "0.1.1" edition.workspace = true +[features] +# `alloc` pulls in the `Vec`/`Box`-returning convenience APIs. It is on by default so that +# regular (std) builds are unaffected. `no_std` users who cannot allocate should build with +# `default-features = false` and use the `*_out(&mut [u8])` / `*_array::()` APIs instead. +default = ["alloc"] +alloc = [] + [dependencies] bouncycastle-utils.workspace = true diff --git a/crypto/core/src/lib.rs b/crypto/core/src/lib.rs index 66bff60..df19778 100644 --- a/crypto/core/src/lib.rs +++ b/crypto/core/src/lib.rs @@ -1,10 +1,14 @@ //! This crate defines the core traits and types used by the rest of the bc-rust.test library. -// todo -- this is the goal, but first need to remove all the Vec in favour of compile-time array sizing. -// #![no_std] - +#![cfg_attr(not(test), no_std)] #![forbid(unsafe_code)] +// The `Vec`/`Box`-returning convenience APIs live behind the (default-on) `alloc` feature. +// When it is enabled we pull in the `alloc` crate; `no_std` users who disable it get the +// allocation-free `*_out(&mut [u8])` and `*_array::()` APIs only. +#[cfg(feature = "alloc")] +extern crate alloc; + pub mod errors; pub mod key_material; pub mod suspendable_state; diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 3086630..606df88 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -5,6 +5,14 @@ use crate::key_material::KeyMaterialTrait; use core::fmt::{Debug, Display}; use core::marker::Sized; +// `Vec`/`Box` come from the `alloc` crate under `#![no_std]` and are gated behind the (default-on) +// `alloc` feature. When the feature is disabled, use the allocation-free alternatives: the +// `*_out(&mut [u8])` fill methods, or the `*_array::()` methods that return a fixed-size array. +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + // Imports needed for docs #[allow(unused_imports)] use crate::key_material::KeyMaterial; @@ -26,8 +34,23 @@ pub trait Hash: Algorithm + Default { /// A static one-shot API that hashes the provided data. /// `data` can be of any length, including zero bytes. + #[cfg(feature = "alloc")] fn hash(self, data: &[u8]) -> Vec; + /// A static one-shot, `no_std`-friendly API that hashes the provided data and returns the digest + /// in a caller-sized array. This is the allocation-free counterpart to [Hash::hash]. + /// + /// `N` should equal [Hash::output_len]; the same truncation / zero-padding rules as + /// [Hash::hash_out] apply if `N` differs from the output length. + fn hash_array(self, data: &[u8]) -> [u8; N] + where + Self: Sized, + { + let mut output = [0u8; N]; + let _ = self.hash_out(data, &mut output); + output + } + /// A static one-shot API that hashes the provided data into the provided output slice. /// `data` can be of any length, including zero bytes. /// The entire output buffer is zeroized before the hash output is written. @@ -41,9 +64,24 @@ pub trait Hash: Algorithm + Default { /// Finish absorbing input and produce the hashes output. /// Consumes self, so this must be the final call to this object. - // fn do_final(self) -> Result, HashError>; + #[cfg(feature = "alloc")] fn do_final(self) -> Vec; + /// Finish absorbing input and produce the hashes output in a caller-sized array. + /// This is the allocation-free, `no_std`-friendly counterpart to [Hash::do_final]. + /// Consumes self, so this must be the final call to this object. + /// + /// `N` should equal [Hash::output_len]; the same truncation / zero-padding rules as + /// [Hash::do_final_out] apply if `N` differs from the output length. + fn do_final_array(self) -> [u8; N] + where + Self: Sized, + { + let mut output = [0u8; N]; + let _ = self.do_final_out(&mut output); + output + } + /// Finish absorbing input and produce the hashes output. /// Consumes self, so this must be the final call to this object. /// @@ -58,12 +96,31 @@ pub trait Hash: Algorithm + Default { /// The same as [Hash::do_final], but allows for supplying a partial byte as the last input. /// Assumes that the input is in the least significant bits (big endian). + #[cfg(feature = "alloc")] fn do_final_partial_bits( self, partial_byte: u8, num_partial_bits: usize, ) -> Result, HashError>; + /// The same as [Hash::do_final_partial_bits], but returns the output in a caller-sized array. + /// This is the allocation-free, `no_std`-friendly counterpart. + /// + /// `N` should equal [Hash::output_len]; the same truncation / zero-padding rules as + /// [Hash::do_final_partial_bits_out] apply if `N` differs from the output length. + fn do_final_partial_bits_array( + self, + partial_byte: u8, + num_partial_bits: usize, + ) -> Result<[u8; N], HashError> + where + Self: Sized, + { + let mut output = [0u8; N]; + self.do_final_partial_bits_out(partial_byte, num_partial_bits, &mut output)?; + Ok(output) + } + /// The same as [Hash::do_final_out], but allows for supplying a partial byte as the last input. /// Assumes that the input is in the least significant bits (big endian). /// will be placed in the first [Hash::output_len] bytes. @@ -118,6 +175,9 @@ pub trait KDF: Default { /// /// Output length: this function will create a KeyMaterial populated with the default output length /// of the underlying hash primitive. + /// + /// `no_std` alternative: use [KDF::derive_key_out] to fill a caller-provided [KeyMaterial]. + #[cfg(feature = "alloc")] fn derive_key( self, key: &impl KeyMaterialTrait, @@ -158,6 +218,9 @@ pub trait KDF: Default { /// /// Output length: this function will create a KeyMaterial populated with the default output length /// of the underlying hash primitive. + /// + /// `no_std` alternative: use [KDF::derive_key_from_multiple_out] to fill a caller-provided [KeyMaterial]. + #[cfg(feature = "alloc")] fn derive_key_from_multiple( self, keys: &[&impl KeyMaterialTrait], @@ -333,8 +396,23 @@ pub trait MAC: Sized { /// ```text /// MACError::KeyMaterialError(KeyMaterialError::SecurityStrength("HMAC::init(): provided key has a lower security strength than the instantiated HMAC") /// ``` + #[cfg(feature = "alloc")] fn mac(self, data: &[u8]) -> Vec; + /// One-shot, `no_std`-friendly API that computes a MAC for the provided data and returns it in a + /// caller-sized array. This is the allocation-free counterpart to [MAC::mac]. + /// + /// `N` should equal [MAC::output_len]; the same rules as [MAC::mac_out] apply (including the + /// possible [MACError::InvalidLength] for undersized buffers). + fn mac_array(self, data: &[u8]) -> Result<[u8; N], MACError> + where + Self: Sized, + { + let mut out = [0u8; N]; + self.mac_out(data, &mut out)?; + Ok(out) + } + /// One-shot API that computes a MAC for the provided data and writes it into the provided output slice. /// `data` can be of any length, including zero bytes. /// @@ -363,8 +441,23 @@ pub trait MAC: Sized { /// do_update() is intended to be used as part of a streaming interface, and so may by called multiple times. fn do_update(&mut self, data: &[u8]); + #[cfg(feature = "alloc")] fn do_final(self) -> Vec; + /// The allocation-free, `no_std`-friendly counterpart to [MAC::do_final]: returns the MAC value + /// in a caller-sized array. Consumes self, so this must be the final call to this object. + /// + /// `N` should equal [MAC::output_len]; the same rules as [MAC::do_final_out] apply (including the + /// possible [MACError::InvalidLength] for undersized buffers). + fn do_final_array(self) -> Result<[u8; N], MACError> + where + Self: Sized, + { + let mut out = [0u8; N]; + self.do_final_out(&mut out)?; + Ok(out) + } + /// Depending on the underlying MAC implementation, NIST may require that the library enforce /// a minimum length on the mac output value. See documentation for the underlying implementation /// to see conditions under which it throws [MACError::InvalidLength]. @@ -468,8 +561,23 @@ pub trait RNG { fn next_int(&mut self) -> Result; /// Returns the number of requested bytes. + #[cfg(feature = "alloc")] fn next_bytes(&mut self, len: usize) -> Result, RNGError>; + /// The allocation-free, `no_std`-friendly counterpart to [RNG::next_bytes]: returns `N` random + /// bytes in a fixed-size array. + /// + /// Bounded `where Self: Sized` so that adding this generic method does not make [RNG] + /// dyn-incompatible: `&mut dyn RNG` is used widely (e.g. [KEMEncapsulator::encaps_rng]). + fn next_bytes_array(&mut self) -> Result<[u8; N], RNGError> + where + Self: Sized, + { + let mut out = [0u8; N]; + self.next_bytes_out(&mut out)?; + Ok(out) + } + /// Returns the number of bytes written. /// The entire output buffer is zeroized before the random bytes are written. fn next_bytes_out(&mut self, out: &mut [u8]) -> Result; @@ -782,8 +890,20 @@ pub trait SignatureVerifier< /// distinguishing attacks should consider adding a cryptographic salt to diversify the inputs. pub trait XOF: Default { /// A static one-shot API that digests the input data and produces `result_len` bytes of output. + #[cfg(feature = "alloc")] fn hash_xof(self, data: &[u8], result_len: usize) -> Vec; + /// The allocation-free, `no_std`-friendly counterpart to [XOF::hash_xof]: digests the input data + /// and produces exactly `N` bytes of output in a fixed-size array. + fn hash_xof_array(self, data: &[u8]) -> [u8; N] + where + Self: Sized, + { + let mut output = [0u8; N]; + let _ = self.hash_xof_out(data, &mut output); + output + } + /// A static one-shot API that digests the input data and produces `result_len` bytes of output. /// Fills the provided output slice. /// The entire output buffer is zeroized before the output is written. @@ -799,8 +919,20 @@ pub trait XOF: Default { ) -> Result<(), HashError>; /// Can be called multiple times. + #[cfg(feature = "alloc")] fn squeeze(&mut self, num_bytes: usize) -> Vec; + /// The allocation-free, `no_std`-friendly counterpart to [XOF::squeeze]: squeezes exactly `N` + /// bytes into a fixed-size array. Can be called multiple times. + fn squeeze_array(&mut self) -> [u8; N] + where + Self: Sized, + { + let mut output = [0u8; N]; + let _ = self.squeeze_out(&mut output); + output + } + /// Can be called multiple times. /// Fills the provided output slice. /// The entire output buffer is zeroized before the output is written. diff --git a/crypto/factory/Cargo.toml b/crypto/factory/Cargo.toml index b869561..85ae5e1 100644 --- a/crypto/factory/Cargo.toml +++ b/crypto/factory/Cargo.toml @@ -3,13 +3,18 @@ name = "bouncycastle-factory" version = "0.1.1" edition.workspace = true +# The factory is a convenience aggregation layer (and includes RNGFactory, which wraps the std-only +# `rng` crate), so it always requires `alloc`: it enables the `alloc` feature on every internal +# dependency unconditionally. Users needing `no_std` should depend on the individual primitive crates +# directly with `default-features = false`. Note the factory types still expose the allocation-free +# `*_array::()` / `*_out(&mut [u8])` APIs via the core trait default methods. [dependencies] -bouncycastle-core.workspace = true -bouncycastle-hkdf.workspace = true -bouncycastle-hmac.workspace = true -bouncycastle-sha2.workspace = true -bouncycastle-sha3.workspace = true -bouncycastle-rng.workspace = true +bouncycastle-core = { workspace = true, features = ["alloc"] } +bouncycastle-hkdf = { workspace = true, features = ["alloc"] } +bouncycastle-hmac = { workspace = true, features = ["alloc"] } +bouncycastle-sha2 = { workspace = true, features = ["alloc"] } +bouncycastle-sha3 = { workspace = true, features = ["alloc"] } +bouncycastle-rng = { workspace = true, features = ["alloc"] } [dev-dependencies] bouncycastle-core-test-framework.workspace = true diff --git a/crypto/hex/Cargo.toml b/crypto/hex/Cargo.toml index f2514e7..94b3df5 100644 --- a/crypto/hex/Cargo.toml +++ b/crypto/hex/Cargo.toml @@ -3,6 +3,12 @@ name = "bouncycastle-hex" version = "0.1.1" edition.workspace = true +[features] +# `alloc` enables the `String`-returning `encode` and `Vec`-returning `decode`. The allocation-free +# `encode_out`/`decode_out` (fill a caller `&mut [u8]`) work in `no_std` without it. On by default. +default = ["alloc"] +alloc = [] + [dependencies] bouncycastle-utils.workspace = true diff --git a/crypto/hex/src/lib.rs b/crypto/hex/src/lib.rs index 6fcf787..fc22660 100644 --- a/crypto/hex/src/lib.rs +++ b/crypto/hex/src/lib.rs @@ -20,8 +20,16 @@ //! //! The decoder ignores whitespace and "\x". +#![cfg_attr(not(test), no_std)] #![forbid(unsafe_code)] +// `encode` (returns `String`) and `decode` (returns `Vec`) live behind the default-on `alloc` +// feature. `no_std` users should use `encode_out` / `decode_out`, which fill a caller `&mut [u8]`. +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "alloc")] +use alloc::{string::String, vec, vec::Vec}; + use bouncycastle_utils::ct::Condition; #[derive(Debug)] @@ -32,6 +40,9 @@ pub enum HexError { } /// One-shot encode from bytes to a hex-encoded string using a constant-time implementation. +/// +/// `no_std` alternative: [encode_out], which writes into a caller-provided `&mut [u8]`. +#[cfg(feature = "alloc")] pub fn encode>(input: T) -> String { let mut out = vec![0u8; input.as_ref().len() * 2]; encode_out(input.as_ref(), &mut out).unwrap(); @@ -76,6 +87,9 @@ pub fn encode_out>(input: T, out: &mut [u8]) -> Result>(input: T) -> Result, HexError> { let inref = input.as_ref(); let mut out: Vec = vec![0u8; inref.len() / 2]; diff --git a/crypto/hkdf/Cargo.toml b/crypto/hkdf/Cargo.toml index 97ab8d3..1cc9875 100644 --- a/crypto/hkdf/Cargo.toml +++ b/crypto/hkdf/Cargo.toml @@ -3,6 +3,17 @@ name = "bouncycastle-hkdf" version = "0.1.1" edition.workspace = true +[features] +# `alloc` enables the `Box`-returning `derive_key`/`derive_key_from_multiple`. +# Forwarded to every internal dependency to keep the `alloc` gates consistent across the graph. +# On by default; disable with `default-features = false` for `no_std` use. +default = ["alloc"] +alloc = [ + "bouncycastle-core/alloc", + "bouncycastle-hmac/alloc", + "bouncycastle-sha2/alloc", +] + [dependencies] bouncycastle-core.workspace = true bouncycastle-hmac.workspace = true diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 13ec778..612f655 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -190,13 +190,21 @@ //! let _prk = hkdf.do_extract_final().unwrap(); //! ``` +#![cfg_attr(not(test), no_std)] #![forbid(unsafe_code)] +// The `Box`-returning `derive_key` / `derive_key_from_multiple` live behind the +// default-on `alloc` feature. `no_std` users should use the `derive_key_out` / `derive_key_from_multiple_out` +// APIs that fill a caller-provided `KeyMaterial` instead. +#[cfg(feature = "alloc")] +extern crate alloc; + use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError, SuspendableError}; use bouncycastle_core::key_material; -use bouncycastle_core::key_material::{ - KeyMaterial, KeyMaterial0, KeyMaterial512, KeyMaterialTrait, KeyType, -}; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial0, KeyMaterialTrait, KeyType}; +// Only used by the alloc-gated `derive_key` / `derive_key_from_multiple`. +#[cfg(feature = "alloc")] +use bouncycastle_core::key_material::KeyMaterial512; use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{ Hash, HashAlgParams, KDF, MAC, SecurityStrength, SuspendableKeyed, @@ -204,7 +212,10 @@ use bouncycastle_core::traits::{ use bouncycastle_hmac::{HMAC, SUSPENDED_HMAC_SHA256_STATE_LEN, SUSPENDED_HMAC_SHA512_STATE_LEN}; use bouncycastle_sha2::{SHA256, SHA512}; use bouncycastle_utils::{max, min}; -use std::marker::PhantomData; +use core::marker::PhantomData; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; // Imports needed only for docs #[allow(unused_imports)] use bouncycastle_core::traits::XOF; @@ -680,6 +691,7 @@ impl HKDF { impl KDF for HKDF { /// This invokes [HKDF::extract_and_expand_out] with a zero salt and using the provided key as ikm. /// This provides a fixed-length output, which may be truncated as needed. + #[cfg(feature = "alloc")] fn derive_key( self, key: &impl KeyMaterialTrait, @@ -718,6 +730,7 @@ impl KDF for HKDF { /// Therefore, derive_key_from_multiple(&[KeyMaterial0::new(), &key], &info) is equivalent to derive_key(&key, &info). /// /// This provides a fixed-length output, which may be truncated as needed. + #[cfg(feature = "alloc")] fn derive_key_from_multiple( self, keys: &[&impl KeyMaterialTrait], diff --git a/crypto/hmac/Cargo.toml b/crypto/hmac/Cargo.toml index 6f8155f..6e83d4a 100644 --- a/crypto/hmac/Cargo.toml +++ b/crypto/hmac/Cargo.toml @@ -3,6 +3,20 @@ name = "bouncycastle-hmac" version = "0.1.1" edition.workspace = true +[features] +# `alloc` enables the `Vec`-returning convenience APIs (`mac`, `do_final`) and turns on the same +# feature in bouncycastle-core. The allocation-free streaming / `_out` / `_array::()` APIs work +# without it. On by default; disable with `default-features = false` for `no_std` use. +default = ["alloc"] +# Forward `alloc` to every internal dependency so that the `alloc` gates stay consistent across the +# dependency graph (otherwise a dep's default features could turn on `core/alloc` while hmac's own +# `alloc` is off, leaving the gated trait method unimplemented). +alloc = [ + "bouncycastle-core/alloc", + "bouncycastle-sha2/alloc", + "bouncycastle-sha3/alloc", +] + [dependencies] bouncycastle-core.workspace = true bouncycastle-sha2.workspace = true diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index cd59def..e45b439 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -180,10 +180,17 @@ //! let h: Vec = hmac_resumed.do_final(); //! ``` +#![cfg_attr(not(test), no_std)] #![forbid(unsafe_code)] #![allow(incomplete_features)] // because at time of writing, generic_const_exprs is not a stable feature #![feature(generic_const_exprs)] +// The `Vec`-returning convenience APIs (`mac`, `do_final`) live behind the default-on `alloc` +// feature. The streaming API and the `mac_out`/`do_final_out`/`mac_array::()` APIs are all +// allocation-free and work in `no_std` without it. +#[cfg(feature = "alloc")] +extern crate alloc; + use bouncycastle_core::errors::{KeyMaterialError, MACError, SuspendableError}; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ @@ -196,6 +203,9 @@ use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512, SUSPENDED_SHA3_S use bouncycastle_utils::ct; use core::fmt::{Debug, Display, Formatter}; +#[cfg(feature = "alloc")] +use alloc::{vec, vec::Vec}; + /*** String constants ***/ /// pub const HMAC_SHA224_NAME: &str = "HMAC-SHA224"; @@ -281,6 +291,10 @@ impl Algorithm for HMAC_SHA3_512 { // largest block length across all supported hashes, so it is always large enough. const LARGEST_HASHER_BLOCK_LEN: usize = 144; +// The largest output length across all supported hashes (SHA-512 / SHA3-512 = 64 bytes). Used to +// size allocation-free stack scratch buffers so that the streaming / `_out` APIs work in `no_std`. +const LARGEST_HASHER_OUTPUT_LEN: usize = 64; + // HMAC implements RFC 2104. #[derive(Clone)] pub struct HMAC { @@ -300,13 +314,13 @@ impl Drop for HMAC Debug for HMAC { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { write!(f, "HMAC-{} instance", HASH::ALG_NAME,) } } impl Display for HMAC { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { write!(f, "HMAC-{} instance", HASH::ALG_NAME,) } } @@ -328,20 +342,24 @@ pub const MIN_FIPS_DIGEST_LEN: usize = 4; // 32 / 8; impl HMAC { fn pad_key_into_hasher(&mut self, padding: u8) { - // TODO: it would be nice to be able to statically extract the length of HASH and not need a Vec or over-sized array here. - // TODO: make this no_std-friendly - let mut padded = vec![0u8; self.hasher.block_bitlen() / 8]; + // Allocation-free scratch: the padded key block is exactly `block_len` bytes (`block_len` is + // never larger than [LARGEST_HASHER_BLOCK_LEN]), so use an oversized stack buffer sliced down + // to `block_len`. This keeps the streaming API `no_std`-friendly (no `Vec`). + let block_len = self.hasher.block_bitlen() / 8; + let mut padded_buf = [0u8; LARGEST_HASHER_BLOCK_LEN]; + let padded = &mut padded_buf[..block_len]; padded[..self.key_len].copy_from_slice(&self.key[..self.key_len]); - // XXX: easier way to xor over Vec? - for entry in &mut padded { + // Per RFC 2104 Section 2, the key is zero-padded to the block length and then XORed with the + // pad byte over the entire block. + for entry in padded.iter_mut() { *entry ^= padding; } // Per RFC 2104 Section 2, write the padded key into the stream prior // to any other data. - self.hasher.do_update(&padded) + self.hasher.do_update(padded) } /// Per RFC 2104 Section 2, if the application key exceeds the block @@ -412,16 +430,19 @@ impl HMAC { // scratch pad here: if we're truncating the output but not // truncating the underlying hashes, we'd lose bytes and compute an // invalid outer hashes. - // TODO: rework this to be no_std friendly (ie no vec!) - let mut ihash = vec![0u8; self.hasher.output_len()]; + // Allocation-free scratch: the inner digest is exactly `output_len` bytes (never larger than + // [LARGEST_HASHER_OUTPUT_LEN]), so use an oversized stack buffer sliced down to `output_len`. + let out_len = self.hasher.output_len(); + let mut ihash_buf = [0u8; LARGEST_HASHER_OUTPUT_LEN]; + let ihash = &mut ihash_buf[..out_len]; // `HMAC` implements `Drop` (required by `Secret`), so we cannot move `self.hasher` out // directly. Swap in a fresh default and consume the taken-out hasher instead. - core::mem::take(&mut self.hasher).do_final_out(&mut ihash); + core::mem::take(&mut self.hasher).do_final_out(ihash); // ohash self.hasher = HASH::default(); self.pad_key_into_hasher(OPAD_BYTE); - self.hasher.do_update(&ihash); + self.hasher.do_update(ihash); Ok(core::mem::take(&mut self.hasher).do_final_out(out)) } } @@ -448,6 +469,7 @@ impl MAC for HMAC Vec { let mut out = vec![0_u8; self.hasher.output_len()]; let bytes_written = self.mac_out(data, &mut out).expect("HMAC::mac(): should not have failed because we gave it a sufficiently large output buffer to meet FIPS rules."); @@ -470,6 +492,7 @@ impl MAC for HMAC Vec { let mut out = vec![0_u8; self.hasher.output_len()]; self.do_final_internal_out(&mut out).expect("HMAC::do_final(): should not have failed because we gave it a sufficiently large output buffer to meet FIPS rules."); @@ -483,8 +506,10 @@ impl MAC for HMAC bool { - let mut out = vec![0_u8; HASH::default().output_len()]; - let output_len = self.do_final_internal_out(&mut out).expect("HMAC::do_final(): should not have failed because we gave it a sufficiently large output buffer to meet FIPS rules."); + // Allocation-free scratch buffer (see [LARGEST_HASHER_OUTPUT_LEN]). + let mut out_buf = [0_u8; LARGEST_HASHER_OUTPUT_LEN]; + let out = &mut out_buf[..HASH::default().output_len()]; + let output_len = self.do_final_internal_out(out).expect("HMAC::do_final(): should not have failed because we gave it a sufficiently large output buffer to meet FIPS rules."); if mac.len() != output_len { return false; } diff --git a/crypto/rng/Cargo.toml b/crypto/rng/Cargo.toml index 9b7bfd9..4147f1c 100644 --- a/crypto/rng/Cargo.toml +++ b/crypto/rng/Cargo.toml @@ -3,6 +3,14 @@ name = "bouncycastle-rng" version = "0.1.1" edition.workspace = true +[features] +# `alloc` enables the `Vec`-returning `next_bytes` / `generate` APIs. Kept consistent with the +# (gated) `RNG::next_bytes` trait method. Note this crate is not `no_std` (it needs OS entropy via +# `getrandom`); the feature only toggles the `Vec`-returning convenience methods. On by default. +default = ["alloc"] +# Forward to every internal dep so the `alloc` gates stay consistent across the graph. +alloc = ["bouncycastle-core/alloc", "bouncycastle-sha2/alloc"] + [dependencies] bouncycastle-core.workspace = true bouncycastle-sha2.workspace = true diff --git a/crypto/rng/src/hash_drbg80090a.rs b/crypto/rng/src/hash_drbg80090a.rs index 6a96060..f1821ee 100644 --- a/crypto/rng/src/hash_drbg80090a.rs +++ b/crypto/rng/src/hash_drbg80090a.rs @@ -350,6 +350,7 @@ impl Sp80090ADrbg for HashDRBG80090A { Ok(()) } + #[cfg(feature = "alloc")] fn generate(&mut self, additional_input: &[u8], len: usize) -> Result, RNGError> { let mut out = vec![0u8; len]; self.generate_out(additional_input, &mut out)?; @@ -513,6 +514,7 @@ impl RNG for HashDRBG80090A { Ok(u32::from_le_bytes(out)) } + #[cfg(feature = "alloc")] fn next_bytes(&mut self, len: usize) -> Result, RNGError> { self.generate("next_bytes".as_bytes(), len) } diff --git a/crypto/rng/src/lib.rs b/crypto/rng/src/lib.rs index 19c60bc..9ed81ec 100644 --- a/crypto/rng/src/lib.rs +++ b/crypto/rng/src/lib.rs @@ -113,6 +113,9 @@ pub trait Sp80090ADrbg { /// not pass FIPS certification. /// /// Throws a [RNGError::InsufficientSeedEntropy] if `len` exceeds [SecurityStrength]. + /// + /// `no_std` / no-alloc alternative: [Sp80090ADrbg::generate_out]. + #[cfg(feature = "alloc")] fn generate(&mut self, additional_input: &[u8], len: usize) -> Result, RNGError>; /// As per [Sp80090ADrbg::generate], but writes to the provided output slice. diff --git a/crypto/sha2/Cargo.toml b/crypto/sha2/Cargo.toml index a620a6a..b1de7f4 100644 --- a/crypto/sha2/Cargo.toml +++ b/crypto/sha2/Cargo.toml @@ -3,6 +3,12 @@ name = "bouncycastle-sha2" version = "0.1.1" edition.workspace = true +[features] +# `alloc` enables the `Vec`-returning convenience APIs (and turns on the same feature in +# bouncycastle-core). On by default; disable with `default-features = false` for `no_std` use. +default = ["alloc"] +alloc = ["bouncycastle-core/alloc"] + [dependencies] bouncycastle-core.workspace = true bouncycastle-utils.workspace = true diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index fb8d933..36c095b 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -65,9 +65,16 @@ //! let h: Vec = sha2_resumed.do_final(); //! ``` +#![cfg_attr(not(test), no_std)] #![forbid(unsafe_code)] #![allow(private_bounds)] +// The `Vec`-returning convenience APIs (`hash`, `do_final`, ...) live behind the default-on `alloc` +// feature. `no_std` users without an allocator should use the `*_out(&mut [u8])` / `*_array::()` +// APIs from the [bouncycastle_core::traits::Hash] trait instead. +#[cfg(feature = "alloc")] +extern crate alloc; + mod sha256; mod sha512; diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index cd92454..eec1db8 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -5,6 +5,9 @@ use bouncycastle_core::traits::{Algorithm, Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; +#[cfg(feature = "alloc")] +use alloc::{vec, vec::Vec}; + const SHA256_K: [u32; 64] = [ 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, @@ -69,7 +72,7 @@ impl Sha256State { ], }, 256 => Self { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, h: [ 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, @@ -193,6 +196,7 @@ impl Hash for SHA256Internal { PARAMS::OUTPUT_LEN } + #[cfg(feature = "alloc")] fn hash(self, data: &[u8]) -> Vec { let mut output = vec![0u8; PARAMS::OUTPUT_LEN]; self.hash_out(data, &mut output); @@ -239,6 +243,7 @@ impl Hash for SHA256Internal { self.x_buf_off = remaining; } + #[cfg(feature = "alloc")] fn do_final(self) -> Vec { let mut output = vec![0u8; PARAMS::OUTPUT_LEN]; self.do_final_out(&mut output); @@ -282,6 +287,7 @@ impl Hash for SHA256Internal { /// TODO: This is defined in FIPS 180-4 s. 5.1.2 /// TODO: /// TODO: Could implement if there is demand. + #[cfg(feature = "alloc")] #[allow(unused)] fn do_final_partial_bits( self, diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 3a031bc..10fe255 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -5,6 +5,9 @@ use bouncycastle_core::traits::{Algorithm, Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; +#[cfg(feature = "alloc")] +use alloc::{vec, vec::Vec}; + const SHA512_K: [u64; 80] = [ 0x428A2F98D728AE22, 0x7137449123EF65CD, 0xB5C0FBCFEC4D3B2F, 0xE9B5DBA58189DBBC, 0x3956C25BF348B538, 0x59F111F1B605D019, 0x923F82A4AF194F9B, 0xAB1C5ED5DA6D8118, @@ -62,7 +65,7 @@ fn theta1(x: u64) -> u64 { // #[derive(Clone, Copy)] #[derive(Clone)] pub(crate) struct Sha512State { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, h: [u64; 8], } @@ -76,14 +79,14 @@ impl Sha512State { pub(crate) fn new() -> Self { match PARAMS::OUTPUT_LEN * 8 { 384 => Self { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, h: [ 0xCBBB9D5DC1059ED8, 0x629A292A367CD507, 0x9159015A3070DD17, 0x152FECD8F70E5939, 0x67332667FFC00B31, 0x8EB44A8768581511, 0xDB0C2E0D64F98FA7, 0x47B5481DBEFA4FA4, ], }, 512 => Self { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, h: [ 0x6A09E667F3BCC908, 0xBB67AE8584CAA73B, 0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1, 0x510E527FADE682D1, 0x9B05688C2B3E6C1F, 0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179, @@ -162,7 +165,7 @@ impl Sha512State { // #[derive(Clone, Copy)] #[derive(Clone)] pub struct SHA512Internal { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, state: Sha512State, byte_count: u64, // NOTE We only support 2^67 bits, not the full 2^128 x_buf: [u8; 128], @@ -178,7 +181,7 @@ impl Drop for SHA512Internal { impl SHA512Internal { pub fn new() -> Self { Self { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, state: Sha512State::::new(), byte_count: 0, x_buf: [0; 128], @@ -208,6 +211,7 @@ impl Hash for SHA512Internal { PARAMS::OUTPUT_LEN } + #[cfg(feature = "alloc")] fn hash(self, data: &[u8]) -> Vec { let mut output = vec![0u8; self.output_len()]; self.hash_out(data, &mut output); @@ -253,6 +257,7 @@ impl Hash for SHA512Internal { self.x_buf_off = remaining; } + #[cfg(feature = "alloc")] fn do_final(self) -> Vec { let mut output = vec![0u8; PARAMS::OUTPUT_LEN]; self.do_final_out(&mut output); @@ -297,6 +302,7 @@ impl Hash for SHA512Internal { /// TODO: This is defined in FIPS 180-4 s. 5.1.2 /// TODO: /// TODO: Could implement if there is demand. + #[cfg(feature = "alloc")] #[allow(unused)] fn do_final_partial_bits( self, diff --git a/crypto/sha3/Cargo.toml b/crypto/sha3/Cargo.toml index df5b28a..1de1ad7 100644 --- a/crypto/sha3/Cargo.toml +++ b/crypto/sha3/Cargo.toml @@ -3,6 +3,12 @@ name = "bouncycastle-sha3" version = "0.1.1" edition.workspace = true +[features] +# `alloc` enables the `Vec`/`Box`-returning convenience APIs (and turns on the same feature in +# bouncycastle-core). On by default; disable with `default-features = false` for `no_std` use. +default = ["alloc"] +alloc = ["bouncycastle-core/alloc"] + [dependencies] bouncycastle-core.workspace = true bouncycastle-utils.workspace = true diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 16262dd..5dc832e 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -135,9 +135,16 @@ //! let h: Vec = sha3_resumed.do_final(); //! ``` +#![cfg_attr(not(test), no_std)] #![forbid(unsafe_code)] #![allow(private_bounds)] +// The `Vec`/`Box`-returning convenience APIs (`hash`, `do_final`, `hash_xof`, `squeeze`, `derive_key`, +// ...) live behind the default-on `alloc` feature. `no_std` users without an allocator should use the +// `*_out(&mut [u8])` / `*_array::()` APIs instead. +#[cfg(feature = "alloc")] +extern crate alloc; + use crate::keccak::KeccakSize; use bouncycastle_core::traits::{Algorithm, HashAlgParams, SecurityStrength}; diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 705e741..ac150c6 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -5,14 +5,20 @@ use crate::keccak::{ }; use bouncycastle_core::errors::{HashError, KDFError, SuspendableError}; use bouncycastle_core::key_material; -use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; +// `KeyMaterial` (the concrete type) is only used by the alloc-gated `derive_key_final_internal`. +#[cfg(feature = "alloc")] +use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Algorithm, Hash, KDF, SecurityStrength, Suspendable}; use bouncycastle_utils::{max, min}; +#[cfg(feature = "alloc")] +use alloc::{boxed::Box, vec, vec::Vec}; + #[derive(Clone)] pub struct SHA3 { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, keccak: KeccakDigest, kdf_key_type: KeyType, kdf_security_strength: SecurityStrength, @@ -24,7 +30,7 @@ pub struct SHA3 { impl SHA3 { pub fn new() -> Self { Self { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, keccak: KeccakDigest::new(PARAMS::SIZE), kdf_key_type: KeyType::Zeroized, kdf_security_strength: SecurityStrength::None, @@ -59,6 +65,7 @@ impl SHA3 { self.do_update(key.ref_to_bytes()) } + #[cfg(feature = "alloc")] fn derive_key_final_internal( self, additional_input: &[u8], @@ -140,6 +147,7 @@ impl Hash for SHA3 { PARAMS::OUTPUT_LEN } + #[cfg(feature = "alloc")] fn hash(self, data: &[u8]) -> Vec { let mut output: Vec = vec![0u8; PARAMS::OUTPUT_LEN]; _ = self.hash_internal(data, &mut output[..]); @@ -156,6 +164,7 @@ impl Hash for SHA3 { self.keccak.absorb(data) } + #[cfg(feature = "alloc")] fn do_final(self) -> Vec { let dbg_rslt_len = self.output_len(); let mut output: Vec = vec![0u8; self.output_len()]; @@ -182,6 +191,7 @@ impl Hash for SHA3 { bytes_written } + #[cfg(feature = "alloc")] fn do_final_partial_bits( self, partial_byte: u8, @@ -231,6 +241,7 @@ impl KDF for SHA3 { /// Returns a [KeyMaterial]. /// For the KDF to be considered "fully-seeded" and be capable of outputting full-entropy KeyMaterials, /// it requires full-entropy input that is at least the bit size (ie 256 bits for SHA3-256, etc). + #[cfg(feature = "alloc")] fn derive_key( mut self, key: &impl KeyMaterialTrait, @@ -251,6 +262,7 @@ impl KDF for SHA3 { self.derive_key_out_final_internal(additional_input, output_key) } + #[cfg(feature = "alloc")] fn derive_key_from_multiple( mut self, keys: &[&impl KeyMaterialTrait], @@ -314,7 +326,7 @@ impl Suspendable for SHA3 deserialize_sha3_family_state(input, PARAMS::STATE_TAG, rate)?; Ok(SHA3 { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, keccak, kdf_key_type, kdf_security_strength, diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index 551e6fc..e3dab39 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -1,15 +1,23 @@ use crate::SHAKEParams; use crate::keccak::{ - KeccakDigest, KeccakSize, SHA3_FAMILY_STATE_LEN, SUSPENDED_SHA3_STATE_LEN, - deserialize_sha3_family_state, serialize_sha3_family_state, + KeccakDigest, SHA3_FAMILY_STATE_LEN, SUSPENDED_SHA3_STATE_LEN, deserialize_sha3_family_state, + serialize_sha3_family_state, }; use bouncycastle_core::errors::{HashError, KDFError, SuspendableError}; use bouncycastle_core::key_material; -use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; +// `KeyMaterial` and `KeccakSize` are only used by the alloc-gated `derive_key_final_internal`. +#[cfg(feature = "alloc")] +use crate::keccak::KeccakSize; +#[cfg(feature = "alloc")] +use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, Suspendable, XOF}; use bouncycastle_utils::{max, min}; +#[cfg(feature = "alloc")] +use alloc::{boxed::Box, vec, vec::Vec}; + /// Note: FIPS 202 section 7 states: /// /// "SHAKE128 and SHAKE256 are approved XOFs, whose approved uses will be specified in @@ -26,7 +34,7 @@ use bouncycastle_utils::{max, min}; /// as such if the provided message includes the requested length, SHAKE does not implement the [Hash] trait. #[derive(Clone)] pub struct SHAKE { - _phantomdata: std::marker::PhantomData, + _phantomdata: core::marker::PhantomData, keccak: KeccakDigest, kdf_key_type: KeyType, kdf_security_strength: SecurityStrength, @@ -43,7 +51,7 @@ impl Algorithm for SHAKE { impl SHAKE { pub fn new() -> Self { Self { - _phantomdata: std::marker::PhantomData, + _phantomdata: core::marker::PhantomData, keccak: KeccakDigest::new(PARAMS::SIZE), kdf_key_type: KeyType::Zeroized, kdf_security_strength: SecurityStrength::None, @@ -52,6 +60,7 @@ impl SHAKE { } /// Swallows errors and simply returns an empty Vec if the hashes fails for whatever reason. + #[cfg(feature = "alloc")] fn hash_internal(mut self, data: &[u8], result_len: usize) -> Vec { self.absorb(data); self.squeeze(result_len) @@ -83,6 +92,7 @@ impl SHAKE { self.absorb(key.ref_to_bytes()) } + #[cfg(feature = "alloc")] fn derive_key_final_internal( mut self, additional_input: &[u8], @@ -176,7 +186,7 @@ impl Suspendable for SHAKE KDF for SHAKE { /// To produce longer keys, use [KDF::derive_key_out]. /// To produce shorter keys, either use [KDF::derive_key_out] or truncate this result down with /// [KeyMaterial::truncate]. + #[cfg(feature = "alloc")] fn derive_key( mut self, key: &impl KeyMaterialTrait, @@ -220,6 +231,7 @@ impl KDF for SHAKE { /// Returns a 32 byte key for SHAKE128 and a 64 byte key for SHAKE256. /// To produce longer keys, use [KDF::derive_key_out]. /// To produce shorter keys, either use [KDF::derive_key_out] or truncate this result down with [KeyMaterial::truncate]. + #[cfg(feature = "alloc")] fn derive_key_from_multiple( mut self, keys: &[&impl KeyMaterialTrait], @@ -255,6 +267,7 @@ impl Default for SHAKE { } impl XOF for SHAKE { + #[cfg(feature = "alloc")] fn hash_xof(self, data: &[u8], result_len: usize) -> Vec { self.hash_internal(data, result_len) } @@ -294,6 +307,7 @@ impl XOF for SHAKE { Ok(()) } + #[cfg(feature = "alloc")] fn squeeze(&mut self, num_bytes: usize) -> Vec { let mut out: Vec = vec![0u8; num_bytes]; self.squeeze_out(&mut out); diff --git a/recommendations.md b/recommendations.md new file mode 100644 index 0000000..2b0df5a --- /dev/null +++ b/recommendations.md @@ -0,0 +1,149 @@ +# PR #48 β€” Round 2: Comments to Post + +Each item = file, line, the exact code it anchors to, and the comment to paste. Ordered by priority. + +--- + +## 1. Keccak deserialization can panic on corrupt state (blocking) + +**File:** `crypto/sha3/src/keccak.rs` +**Line:** 420–422 (inside `KeccakDigest::from_serialized_state`) + +```rust +let bits_in_queue = u64::from_le_bytes(input[392..400].try_into().unwrap()) as usize; +if bits_in_queue > rate { + return Err(SuspendableError::InvalidData); +} +``` + +**Comment to post:** +> This only rejects `bits_in_queue > rate`. A corrupt/tampered state with `squeezing == false` and an +> odd `bits_in_queue` (or `bits_in_queue == rate`) still deserializes here, then panics on the next +> call: `absorb` hits `panic!("attempt to absorb with odd length queue")` (line 227), and +> `pad_and_switch_to_squeezing_phase` trips `debug_assert!(bits_in_queue < rate)`. Per the trait +> contract, corrupt input must return `InvalidData`, not panic. Please tighten to: +> ```rust +> if bits_in_queue > rate || (!/* squeezing */ && (bits_in_queue & 7 != 0 || bits_in_queue == rate)) { +> return Err(SuspendableError::InvalidData); +> } +> ``` +> (i.e. move this check below the `squeezing` parse and gate the extra conditions on `!squeezing`), and +> add a negative test β€” the current test only corrupts the `squeezing` byte, not `bits_in_queue`. + +--- + +## 2. `HashFactory` ships a knowingly-broken `Algorithm` impl (blocking) + +**File:** `crypto/factory/src/hash_factory.rs` +**Line:** 86–90 + +```rust +// TODO -- this does't work. Perhaps Algorithm needs to be re-worked so that these are functions instead? +impl Algorithm for HashFactory { + const ALG_NAME: &'static str = "TODO"; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::None; +} +``` + +**Comment to post:** +> This public `impl Algorithm for HashFactory` ships `ALG_NAME = "TODO"` and +> `MAX_SECURITY_STRENGTH = None`, with a "this does't work" TODO. It was only added to satisfy the new +> `Hash: Algorithm` supertrait bound (so HMAC's new `Display`/`Debug` can read `HASH::ALG_NAME`), but +> `Algorithm`'s associated **consts** can't express a per-variant value for a runtime factory enum β€” +> hence the stub. Please don't merge a knowingly-wrong public `Algorithm` impl. Suggest sourcing HMAC's +> display name from HMAC's own `Algorithm` alias (or a small `fn alg_name(&self)`) instead of widening +> the `Hash` supertrait, so `HashFactory` doesn't need this impl at all. + +--- + +## 3. Add a "Breaking changes" changelog entry (three breaking items) + +**File:** `alpha_0.1.2_release_notes.md` +**Line:** 54 (add a section under `## Minor features / bug fixes`, or a new `## Breaking changes`) + +```markdown +## Minor features / bug fixes +``` + +The three breaking items live at: +- `crypto/sha2/src/lib.rs:75` β€” `pub use self::sha512::SHA512Internal;` (was `Sha512Internal`) +- `crypto/core/src/traits.rs:20` β€” `pub trait Hash: Algorithm + Default {` (new supertrait) +- `MLDSATrait::verify_mu_internal(->bool)` β†’ public `verify_mu(->Result)` (mldsa & mldsa-lowmemory) + +**Comment to post:** +> Please add a "Breaking changes" section. This PR has three source-breaking public changes not listed: +> (1) `Sha512Internal` β†’ `SHA512Internal` (sha2/src/lib.rs:75); (2) `Hash` now requires `Algorithm` as +> a supertrait (core/src/traits.rs:20), so every external `Hash` impl must now also impl `Algorithm`; +> (3) the public `MLDSATrait` method `verify_mu_internal(->bool)` became `verify_mu(->Result)`. + +--- + +## 4. `LIB_VERSION` is `core`'s version (0.1.1) and is the only compat gate + +**File:** `crypto/core/Cargo.toml` +**Line:** 3 + +```toml +version = "0.1.1" +``` + +Related anchor β€” **File:** `crypto/core/src/suspendable_state.rs`, **Line:** 112–113: + +```rust +let patch_stream = SemVer::from([LIB_VERSION.major, LIB_VERSION.minor, 255]); +if ver > patch_stream { +``` + +**Comment to post:** +> `LIB_VERSION` comes from `bouncycastle-core`, which is still `0.1.1` here while the release is +> `0.1.2` β€” so every serialized state gets stamped `0.1.1`. Two asks: (a) reconcile `core`'s version +> with the release; (b) since this stamp is the *only* compat gate and the policy (suspendable_state.rs:112) +> accepts any future patch on the same major.minor, any change to a serialized layout MUST bump `core`'s +> **minor** (never just the patch) or old readers will silently misparse newer blobs. Worth a one-line +> maintainer note next to `check_lib_ver`. + +--- + +## 5. (Optional, style) Uncommented infallible `.unwrap()`s in serialization paths + +QUALITY_AND_STYLE requires each `.unwrap()` to carry a justification. These fixed-size sliceβ†’array +unwraps are infallible by const construction but uncommented: + +- `crypto/sha2/src/sha256.rs:322` β€” `let out: &mut [u8; 105] = add_lib_ver(&mut out_to_return).try_into().unwrap();` +- `crypto/sha2/src/sha256.rs:351` β€” `let input: &[u8; 105] = check_lib_ver(&serialized_state, None)?.try_into().unwrap();` +- `crypto/sha2/src/sha512.rs:337` β€” `add_lib_ver(&mut out_to_return).try_into().unwrap();` +- `crypto/sha2/src/sha512.rs:364` β€” `check_lib_ver(&serialized_state, None)?.try_into().unwrap();` +- `crypto/hkdf/src/lib.rs:852` and `:871` β€” `state[..].try_into().unwrap()` + +**Comment to post (put on sha256.rs:322, reference the rest):** +> Style nit per QUALITY_AND_STYLE: these `.try_into().unwrap()`s are infallible by const sizing but +> lack the required justification comment. Suggest a one-line `// infallible: slice is exactly N bytes +> by const construction` on each (same pattern in sha512.rs:337/364 and hkdf/src/lib.rs:852/871), and +> a `quality_stats.sh` before/after to confirm the fallibility count didn't rise. + +--- + +## 6. (Separate / pre-existing) wycheproof sign harness fails on the `Randomized` case + +**File:** `crypto/mldsa/tests/wycheproof.rs` +**Line:** 353 (and identically 421, 489, and the `sign_seed` equivalents) + +```rust +let sig = MLDSA44::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); +assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); +``` + +**Comment to post:** +> Not introduced by this PR (pre-existing on base), but the 6 `*_sign_*` wycheproof tests fail because +> the harness signs every case with a hardcoded all-zero `rnd` and byte-compares to `sig`, while each +> file's one `Randomized`-flagged case (tcId 73/90/78/109/69/100) was generated with a non-zero `rnd` +> (per wycheproof `doc/mldsa.md` lines 33–36). The `MLDSASign*TestCase` struct doesn't parse `rnd`. +> Fix: skip cases flagged `Randomized` / carrying an `rnd` field, or parse `rnd` and pass it through. +> Worth a tracking issue so the suite goes green. + +--- + +## Just reply "resolved" (no code-anchored comment needed) + +F1 (HKDF hmac/state consistency), F3 (HKDF version header), F5 (HMAC `Secret`/`Drop`), F6 +(`IncorrectKey` removed), F9 (version policy) β€” all addressed in round 2; close them with a reply.