diff --git a/Cargo.lock b/Cargo.lock index ac3abf08ea..db23450948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8019,6 +8019,7 @@ dependencies = [ "futures", "nym-bin-common", "rand_chacha 0.3.1", + "rand_chacha 0.9.0", "tokio", "tracing", ] diff --git a/Cargo.toml b/Cargo.toml index 9836d57c07..4e28dda315 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -326,6 +326,7 @@ quote = "1" rand = "0.8.5" rand09 = { package = "rand", version = "=0.9.2" } rand_chacha = "0.3" +rand_chacha09 = { package = "rand_chacha", version = "=0.9.0" } rand_core = "0.6.3" rand_distr = "0.4" rayon = "1.5.1" diff --git a/common/nym-kkt-ciphersuite/src/lib.rs b/common/nym-kkt-ciphersuite/src/lib.rs index 1c4a29a791..e7dcb8529c 100644 --- a/common/nym-kkt-ciphersuite/src/lib.rs +++ b/common/nym-kkt-ciphersuite/src/lib.rs @@ -209,9 +209,11 @@ impl SignatureScheme { #[strum(serialize_all = "lowercase")] #[repr(u8)] pub enum KEM { + #[deprecated] XWing = 0, MlKem768 = 1, McEliece = 2, + #[deprecated] X25519 = 255, } @@ -268,6 +270,34 @@ impl Ciphersuite { } } + #[must_use] + pub fn with_kem(mut self, kem: KEM) -> Self { + self.kem = kem; + self.encapsulation_key_length = kem.encapsulation_key_length(); + self + } + + #[must_use] + pub fn with_signature_scheme(mut self, signature_scheme: SignatureScheme) -> Self { + self.signature_scheme = signature_scheme; + self.signing_key_length = signature_scheme.signing_key_length(); + self.verification_key_length = signature_scheme.verification_key_length(); + self.signature_length = signature_scheme.signature_length(); + self + } + + #[must_use] + pub fn with_hash_function(mut self, hash_function: HashFunction) -> Self { + self.hash_function = hash_function; + self + } + + #[must_use] + pub fn with_hash_length(mut self, hash_length: HashLength) -> Self { + self.hash_length = hash_length; + self + } + pub fn kem_key_len(&self) -> usize { self.encapsulation_key_length } diff --git a/common/nym-kkt/src/key_utils.rs b/common/nym-kkt/src/key_utils.rs index df27f2cdd6..39c8e1479b 100644 --- a/common/nym-kkt/src/key_utils.rs +++ b/common/nym-kkt/src/key_utils.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use libcrux_kem::{MlKem768PrivateKey, MlKem768PublicKey}; +use libcrux_ml_kem::mlkem768::MlKem768KeyPair; use libcrux_psq::handshake::types::DHKeyPair; use nym_kkt_ciphersuite::{DEFAULT_HASH_LEN, HashFunction, KeyDigests}; use rand09::{CryptoRng, RngCore}; @@ -12,27 +13,19 @@ where DHKeyPair::new(rng) } -pub fn generate_keypair_mlkem(rng: &mut R) -> (MlKem768PrivateKey, MlKem768PublicKey) +pub fn generate_keypair_mlkem(rng: &mut R) -> MlKem768KeyPair where R: RngCore + CryptoRng, { - libcrux_ml_kem::mlkem768::rand::generate_key_pair(rng).into_parts() + libcrux_ml_kem::mlkem768::rand::generate_key_pair(rng) } // (decapsulation_key, encapsulation_key) -pub fn generate_keypair_mceliece( - rng: &mut R, -) -> ( - libcrux_psq::classic_mceliece::SecretKey, - libcrux_psq::classic_mceliece::PublicKey, -) +pub fn generate_keypair_mceliece(rng: &mut R) -> libcrux_psq::classic_mceliece::KeyPair where - // this is annoying because mceliece lib uses rand 0.8.5... R: RngCore + CryptoRng, { - let kp = libcrux_psq::classic_mceliece::KeyPair::generate_key_pair(rng); - - (kp.sk, kp.pk) + libcrux_psq::classic_mceliece::KeyPair::generate_key_pair(rng) } pub fn hash_key_bytes( diff --git a/common/nym-kkt/src/keys.rs b/common/nym-kkt/src/keys.rs new file mode 100644 index 0000000000..161d2d86b0 --- /dev/null +++ b/common/nym-kkt/src/keys.rs @@ -0,0 +1,55 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use libcrux_ml_kem::mlkem768::{MlKem768KeyPair, MlKem768PrivateKey, MlKem768PublicKey}; +use libcrux_psq::classic_mceliece; +use nym_kkt_ciphersuite::KEM; +use std::fmt::{Debug, Formatter}; + +/// Wrapper around keys used for the KEM exchange +pub struct KEMKeys { + mc_eliece: classic_mceliece::KeyPair, + ml_kem768: MlKem768KeyPair, +} + +impl Debug for KEMKeys { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KEMKeys") + .field("mc_eliece", &"") + .field("ml_kem768", &"") + .finish() + } +} + +impl KEMKeys { + pub fn new(mc_eliece: classic_mceliece::KeyPair, ml_kem768: MlKem768KeyPair) -> Self { + Self { + mc_eliece, + ml_kem768, + } + } + + pub fn encoded_encapsulation_key(&self, kem: KEM) -> Option<&[u8]> { + match kem { + KEM::McEliece => Some(self.mc_eliece.pk.as_ref()), + KEM::MlKem768 => Some(self.ml_kem768.pk()), + _ => None, + } + } + + pub fn mc_eliece_encapsulation_key(&self) -> &classic_mceliece::PublicKey { + &self.mc_eliece.pk + } + + pub fn ml_kem768_encapsulation_key(&self) -> &MlKem768PublicKey { + self.ml_kem768.public_key() + } + + pub fn mc_eliece_decapsulation_key(&self) -> &classic_mceliece::SecretKey { + &self.mc_eliece.sk + } + + pub fn ml_kem768_decapsulation_key(&self) -> &MlKem768PrivateKey { + self.ml_kem768.private_key() + } +} diff --git a/common/nym-kkt/src/lib.rs b/common/nym-kkt/src/lib.rs index 066df57daf..1c853d815b 100644 --- a/common/nym-kkt/src/lib.rs +++ b/common/nym-kkt/src/lib.rs @@ -7,6 +7,7 @@ pub mod error; pub mod frame; pub mod initiator; pub mod key_utils; +pub mod keys; pub mod masked_byte; pub mod rekey; pub mod responder; @@ -44,6 +45,7 @@ mod test { // generate kem public keys let responder_mlkem_keypair = generate_keypair_mlkem(&mut rng); + let responder_mceliece_keypair = generate_keypair_mceliece(&mut rng); let r_dir_hash_mlkem = hash_encapsulation_key( diff --git a/common/nym-kkt/src/responder.rs b/common/nym-kkt/src/responder.rs index 7fb5edce4d..3015cf6b30 100644 --- a/common/nym-kkt/src/responder.rs +++ b/common/nym-kkt/src/responder.rs @@ -8,6 +8,7 @@ use crate::{ }; use libcrux_psq::handshake::types::DHKeyPair; use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, KEM, SignatureScheme}; + pub struct KKTResponder<'a> { x25519_keypair: &'a DHKeyPair, mlkem_encapsulation_key: Option<&'a libcrux_kem::MlKem768PublicKey>, @@ -29,41 +30,40 @@ impl<'a> KKTResponder<'a> { supported_hash_functions.iter().copied().collect(); if hash_functions.is_empty() { - Err(KKTError::FunctionInputError { + return Err(KKTError::FunctionInputError { info: "Did not provide a supported HashFunction when instaciating a KKTResponder", + }); + } + + let signature_schemes: HashSet = + supported_signature_schemes.iter().copied().collect(); + + if signature_schemes.is_empty() { + return Err(KKTError::FunctionInputError { + info: "Did not provide a supported SignatureScheme when instaciating a KKTResponder", + }); + } + + let outer_protocol_versions: HashSet = + supported_outer_protocol_versions.iter().copied().collect(); + + if outer_protocol_versions.is_empty() { + Err(KKTError::FunctionInputError { + info: "Did not provide a supported outer protocol version when instaciating a KKTResponder", + }) + } else if mlkem_encapsulation_key.is_none() && mceliece_encapsulation_key.is_none() { + Err(KKTError::FunctionInputError { + info: "Did not provide an encapsulation key when instanciating a KKTResponder.", }) } else { - let signature_schemes: HashSet = - supported_signature_schemes.iter().copied().collect(); - - if signature_schemes.is_empty() { - Err(KKTError::FunctionInputError { - info: "Did not provide a supported SignatureScheme when instaciating a KKTResponder", - }) - } else { - let outer_protocol_versions: HashSet = - supported_outer_protocol_versions.iter().copied().collect(); - - if outer_protocol_versions.is_empty() { - Err(KKTError::FunctionInputError { - info: "Did not provide a supported outer protocol version when instaciating a KKTResponder", - }) - } else if mlkem_encapsulation_key.is_none() && mceliece_encapsulation_key.is_none() - { - Err(KKTError::FunctionInputError { - info: "Did not provide an encapsulation key when instanciating a KKTResponder.", - }) - } else { - Ok(Self { - x25519_keypair, - mlkem_encapsulation_key, - mceliece_encapsulation_key, - supported_hash_functions: hash_functions, - supported_signature_schemes: signature_schemes, - supported_outer_protocol_versions: outer_protocol_versions, - }) - } - } + Ok(Self { + x25519_keypair, + mlkem_encapsulation_key, + mceliece_encapsulation_key, + supported_hash_functions: hash_functions, + supported_signature_schemes: signature_schemes, + supported_outer_protocol_versions: outer_protocol_versions, + }) } } fn supported_protocol_versions(&self) -> Vec { diff --git a/common/nym-lp/src/peer.rs b/common/nym-lp/src/peer.rs index 785c5842e9..b1e3bf11e1 100644 --- a/common/nym-lp/src/peer.rs +++ b/common/nym-lp/src/peer.rs @@ -2,11 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ClientHelloData, LpError}; -use libcrux_psq::handshake::types::{DHKeyPair, DHPrivateKey, DHPublicKey}; +use libcrux_psq::handshake::types::{DHKeyPair, DHPublicKey}; use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_kkt_ciphersuite::{ - Ciphersuite, HashFunction, HashLength, KEM, KEMKeyDigests, SignatureScheme, SigningKeyDigests, +use nym_kkt::key_utils::{ + generate_keypair_mceliece, generate_keypair_mlkem, generate_keypair_x25519, }; +use nym_kkt::keys::KEMKeys; +use nym_kkt_ciphersuite::{Ciphersuite, KEM, KEMKeyDigests, SignatureScheme, SigningKeyDigests}; +use nym_test_utils::helpers::{deterministic_rng, seeded_rng_09}; use std::collections::HashMap; use std::fmt::Debug; use std::sync::Arc; @@ -24,25 +27,19 @@ pub struct LpLocalPeer { pub(crate) x25519: Arc, /// Local KEM keys used for PSQ - // pub(crate) kem_keypairs: HashMap>, - pub(crate) kem_keypairs: HashMap>, + pub(crate) kem_keypairs: Option>, } impl LpLocalPeer { pub fn new( ciphersuite: Ciphersuite, ed25519: Arc, - x25519: Arc, + x25519: Arc, ) -> Self { - // TODO: make nicer conversion (without cloning) + error handling - let initiator_libcrux_x25519_private_key = - DHPrivateKey::from_bytes(x25519.private_key().as_bytes()).unwrap(); - let initiator_x25519_keypair = DHKeyPair::from(initiator_libcrux_x25519_private_key); - LpLocalPeer { ciphersuite, ed25519, - x25519: Arc::new(initiator_x25519_keypair), + x25519, kem_keypairs: Default::default(), } } @@ -56,11 +53,10 @@ impl LpLocalPeer { // ) } - pub fn with_kem_keypair(mut self, keypair: Arc<()>) -> Self { - todo!() - // let kem = keypair.kem(); - // self.kem_keypairs.insert(kem, keypair); - // self + #[must_use] + pub fn with_kem_keys(mut self, kem_keys: Arc) -> Self { + self.kem_keypairs = Some(kem_keys); + self } pub fn ed25519(&self) -> &Arc { @@ -79,34 +75,33 @@ impl LpLocalPeer { // .ok_or(LpError::ResponderWithMissingKEMKey) // } - pub fn kem_key(&self, kem: KEM) -> Option<&Arc<()>> { - self.kem_keypairs.get(&kem) - } - /// Convert this `LpLocalPeer` into a valid `LpRemotePeer` that can be used within tests #[doc(hidden)] pub fn as_remote(&self) -> LpRemotePeer { - todo!() - // let mut expected_signing_key_digests = HashMap::new(); - // expected_signing_key_digests.insert( - // SignatureScheme::Ed25519, - // nym_kkt::key_utils::produce_key_digests(self.ed25519.public_key().as_bytes()), - // ); - // - // let mut expected_kem_key_digests = HashMap::new(); - // for (kem, kem_key) in &self.kem_keypairs { - // expected_kem_key_digests.insert( - // *kem, - // nym_kkt::key_utils::produce_key_digests(&kem_key.encoded_encapsulation_key()), - // ); - // } - // - // LpRemotePeer { - // ed25519_public: *self.ed25519.public_key(), - // x25519_public: self.x25519.pk, - // expected_kem_key_digests, - // expected_signing_key_digests, - // } + let mut expected_signing_key_digests = HashMap::new(); + expected_signing_key_digests.insert( + SignatureScheme::Ed25519, + nym_kkt::key_utils::produce_key_digests(self.ed25519.public_key().as_bytes()), + ); + + let mut expected_kem_key_digests = HashMap::new(); + if let Some(keys) = &self.kem_keypairs { + for kem in [KEM::MlKem768, KEM::McEliece] { + expected_kem_key_digests.insert( + kem, + nym_kkt::key_utils::produce_key_digests( + keys.encoded_encapsulation_key(kem).unwrap(), + ), + ); + } + } + + LpRemotePeer { + ed25519_public: *self.ed25519.public_key(), + x25519_public: self.x25519.pk, + expected_kem_key_digests, + expected_signing_key_digests, + } } } @@ -192,63 +187,38 @@ impl LpRemotePeer { } #[cfg(any(feature = "mock", test))] -pub fn mock_peer(kem: KEM) -> LpLocalPeer { +pub fn mock_peer() -> LpLocalPeer { // use deterministic rng let mut rng = nym_test_utils::helpers::deterministic_rng(); - - let ciphersuite = Ciphersuite::new( - kem, - HashFunction::Blake3, - SignatureScheme::Ed25519, - HashLength::Default, - ); - - random_peer(&mut rng, ciphersuite) + random_peer(&mut rng) } #[cfg(any(feature = "mock", test))] -pub fn random_peer<'a, R: rand::CryptoRng + rand::RngCore>( - rng: &mut R, - ciphersuite: Ciphersuite, -) -> LpLocalPeer { - use nym_kkt::key_utils::{generate_keypair_mceliece, generate_keypair_mlkem}; +pub fn random_peer<'a, R: rand::CryptoRng + rand::RngCore>(rng: &mut R) -> LpLocalPeer { let ed25519 = Arc::new(ed25519::KeyPair::new(rng)); - let mut sk = [0u8; 32]; - rng.fill_bytes(&mut sk); + // disgusting conversion between rng08 and rng09 + let mut seed = [0u8; 32]; + rng.fill_bytes(&mut seed); - let TODO = ""; - // clamp - sk[0] &= 248u8; - sk[31] &= 127u8; - sk[31] |= 64u8; + let mut rng09 = seeded_rng_09(seed); - let x25519 = Arc::new(DHKeyPair::from(DHPrivateKey::from_bytes(&sk).unwrap())); + let x25519 = Arc::new(generate_keypair_x25519(&mut rng09)); - todo!() - // let default_peer = LpLocalPeer { - // ciphersuite: Arc::new(ciphersuite), - // ed25519, - // x25519, - // mlkem: None, - // mceliece: None, - // }; - // - // match ciphersuite.kem() { - // KEM::MlKem768 => { - // let mlkem_keypair = generate_keypair_mlkem(&mut rand09::rng()); - // default_peer.with_mlkem_keypair(&mlkem_keypair.0, &mlkem_keypair.1) - // } - // KEM::McEliece => { - // let mceliece_keypair = generate_keypair_mceliece(&mut rand09::rng()); - // default_peer.with_mceliece_keypair(mceliece_keypair.0, mceliece_keypair.1) - // } - // _ => unreachable!(), - // } + LpLocalPeer { + ciphersuite: Ciphersuite::default(), + ed25519, + x25519, + kem_keypairs: Some(Arc::new(KEMKeys::new( + generate_keypair_mceliece(&mut rng09), + generate_keypair_mlkem(&mut rng09), + ))), + } } #[cfg(any(feature = "mock", test))] -pub fn mock_peers(kem: KEM) -> (LpLocalPeer, LpLocalPeer) { - println!("KEM: {:?}", kem); - (mock_peer(kem), mock_peer(kem)) +pub fn mock_peers() -> (LpLocalPeer, LpLocalPeer) { + let mut rng = deterministic_rng(); + + (random_peer(&mut rng), random_peer(&mut rng)) } diff --git a/common/nym-lp/src/psq/mod.rs b/common/nym-lp/src/psq/mod.rs index 3e8506f1dc..2a28d8c0da 100644 --- a/common/nym-lp/src/psq/mod.rs +++ b/common/nym-lp/src/psq/mod.rs @@ -29,10 +29,10 @@ use rand09::rngs::ThreadRng; use std::fmt::Debug; -pub(crate) const AAD_INITIATOR_OUTER: &[u8] = b"NYM-PSQ-AAD-INIT-OUTER-V1"; -pub(crate) const AAD_INITIATOR_INNER: &[u8] = b"NYM-PSQ-AAD-INIT-INNER-V1"; -pub(crate) const AAD_RESPONDER: &[u8] = b"NYM-PSQ-AAD-RESP-V1"; -pub(crate) const SESSION_CONTEXT: &[u8] = b"NYM-PSQ-SESSION-CONTEXT-V1"; +pub(crate) const AAD_INITIATOR_OUTER: &[u8] = b"NYM-PQ-AAD-INIT-OUTER-V1"; +pub(crate) const AAD_INITIATOR_INNER: &[u8] = b"NYM-PQ-AAD-INIT-INNER-V1"; +pub(crate) const AAD_RESPONDER: &[u8] = b"NYM-PQ-AAD-RESP-V1"; +pub(crate) const SESSION_CONTEXT: &[u8] = b"NYM-PQ-SESSION-CONTEXT-V1"; pub enum PSQState<'a> { Initiator(RegistrationInitiator<'a, ThreadRng>), @@ -324,6 +324,7 @@ mod tests { }; use nym_kkt::responder::KKTResponder; use nym_kkt_ciphersuite::{HashFunction, HashLength, SignatureScheme}; + use nym_test_utils::helpers::deterministic_rng_09; use nym_test_utils::mocks::async_read_write::MockIOStream; use nym_test_utils::traits::{Leak, TimeboxedSpawnable}; use std::time::Duration; @@ -490,7 +491,15 @@ mod tests { // plain test without any wrappers #[test] fn e2e_test_plain() { - let mut rng = rand09::rng(); + let mut rng = deterministic_rng_09(); + + let (mut init, resp) = mock_peers(); + + init.ciphersuite = Ciphersuite::default().with_kem(KEM::MlKem768); + let resp_remote = resp.as_remote(); + let dir_hash = resp_remote.expected_kem_key_hash(init.ciphersuite).unwrap(); + + let resp_keys = resp.kem_keypairs.as_ref().unwrap(); // we should add these as consts let aad_initiator_outer = b"Test Data I Outer"; @@ -503,29 +512,10 @@ mod tests { let hash_function = HashFunction::Blake3; // generate kem public keys - let responder_mlkem_keypair = generate_keypair_mlkem(&mut rng); - let responder_mceliece_keypair = generate_keypair_mceliece(&mut rng); - - let r_dir_hash_mlkem = hash_encapsulation_key( - // &ciphersuite.hash_function(), - hash_function, - // ciphersuite.hash_len(), - HashLength::Default.value(), - responder_mlkem_keypair.1.as_slice().as_slice(), - ); - - let _r_dir_hash_mceliece = hash_encapsulation_key( - // &ciphersuite.hash_function(), - hash_function, - // ciphersuite.hash_len(), - HashLength::Default.value(), - responder_mceliece_keypair.1.as_ref(), - ); - let kkt_responder = KKTResponder::new( &responder_x25519_keypair, - Some(&responder_mlkem_keypair.1), - Some(&responder_mceliece_keypair.1), + Some(resp_keys.ml_kem768_encapsulation_key()), + Some(resp_keys.mc_eliece_encapsulation_key()), &[ HashFunction::Blake3, HashFunction::SHA256, @@ -542,8 +532,8 @@ mod tests { let responder_ciphersuite = CiphersuiteBuilder::new(psq_ciphersuite) .longterm_x25519_keys(&responder_x25519_keypair) - .longterm_mlkem_encapsulation_key(&responder_mlkem_keypair.1) - .longterm_mlkem_decapsulation_key(&responder_mlkem_keypair.0) + .longterm_mlkem_encapsulation_key(resp_keys.ml_kem768_encapsulation_key()) + .longterm_mlkem_decapsulation_key(resp_keys.ml_kem768_decapsulation_key()) .build_responder_ciphersuite() .unwrap(); @@ -566,7 +556,7 @@ mod tests { &mut rng, &ciphersuite, &responder_x25519_keypair.pk, - &r_dir_hash_mlkem, + &dir_hash, 1u8, ) .unwrap(); @@ -577,7 +567,10 @@ mod tests { assert_eq!( i_obtained_key, - responder_mlkem_keypair.1.as_slice().as_slice(), + resp_keys + .ml_kem768_encapsulation_key() + .as_slice() + .as_slice(), ); let mlkem_key = diff --git a/common/test-utils/Cargo.toml b/common/test-utils/Cargo.toml index ab509eb8f9..ead0cff923 100644 --- a/common/test-utils/Cargo.toml +++ b/common/test-utils/Cargo.toml @@ -15,6 +15,7 @@ description = "Helpers, traits, and mock definitions for tests" anyhow = { workspace = true } futures = { workspace = true } rand_chacha = { workspace = true } +rand_chacha09 = { workspace = true } tokio = { workspace = true, features = ["sync", "time", "rt"] } tracing = { workspace = true } diff --git a/common/test-utils/src/helpers.rs b/common/test-utils/src/helpers.rs index e96494dfc0..9ec5f676b4 100644 --- a/common/test-utils/src/helpers.rs +++ b/common/test-utils/src/helpers.rs @@ -7,13 +7,19 @@ use nym_bin_common::logging::tracing_subscriber::layer::SubscriberExt; use nym_bin_common::logging::tracing_subscriber::util::SubscriberInitExt; use nym_bin_common::logging::{default_tracing_fmt_layer, tracing_subscriber}; use rand_chacha::rand_core::SeedableRng; +use rand_chacha09::rand_core::SeedableRng as SeedableRng09; use std::future::Future; use tokio::task::JoinHandle; use tokio::time::error::Elapsed; +// 'current' rand crate pub use rand_chacha::ChaCha20Rng as DeterministicRng; pub use rand_chacha::rand_core::{CryptoRng, RngCore}; +// rand09 compat +pub use rand_chacha09::ChaChaRng as DeterministicRng09; +pub use rand_chacha09::rand_core::{CryptoRng as CryptoRng09, RngCore as RngCore09}; + pub fn leak(val: T) -> &'static mut T { Box::leak(Box::new(val)) } @@ -26,6 +32,10 @@ where tokio::spawn(async move { fut.timeboxed().await }) } +pub fn deterministic_rng_09() -> DeterministicRng09 { + seeded_rng_09([42u8; 32]) +} + pub fn deterministic_rng() -> DeterministicRng { seeded_rng([42u8; 32]) } @@ -34,10 +44,18 @@ pub fn seeded_rng(seed: [u8; 32]) -> DeterministicRng { DeterministicRng::from_seed(seed) } +pub fn seeded_rng_09(seed: [u8; 32]) -> DeterministicRng09 { + DeterministicRng09::from_seed(seed) +} + pub fn u64_seeded_rng(seed: u64) -> DeterministicRng { DeterministicRng::seed_from_u64(seed) } +pub fn u64_seeded_rng_09(seed: u64) -> DeterministicRng09 { + DeterministicRng09::seed_from_u64(seed) +} + // test logger to use during debugging #[allow(clippy::unwrap_used)] pub fn setup_test_logger() {