diff --git a/CHANGELOG.md b/CHANGELOG.md index 94473f4945..89892cb98e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Breaking Changes +* [BREAKING][removal][rust] Removed the `miden_client::crypto::RandomCoin` re-export. Use a `rand` CSPRNG such as `ChaCha20Rng`, plus the new `miden_client::crypto::draw_felt` / `draw_word` helpers where a `Felt` or `Word` is needed from a generator that does not implement `FeltRng` ([#2414](https://github.com/0xMiden/rust-sdk/pull/2414)). +* [BREAKING][behavior][rust] Added `Client::secure_rng`, a second random number generator intended for secret values: secret keys, and the ephemeral key and nonce that seal transaction inputs. It is seeded from the operating system and cannot be overridden. The existing `Client::rng` is kept for non-secret values (note serial numbers, script arguments, account seeds); it stays the one `ClientBuilder::rng` overrides, so a caller may have seeded it and its output can be predictable. Callers generating secret keys must switch from `client.rng()` to `client.secure_rng()` ([#2414](https://github.com/0xMiden/rust-sdk/pull/2414)). +* [BREAKING][type][rust] `ClientBuilder::rng` now requires `CryptoRng + Send + Sync`, and the marker trait `ClientFeltRng` is renamed to `ClientCryptoRng`. A client with no RNG configured defaults to an OS-seeded `ChaCha20Rng` instead of a `RandomCoin`, which is no longer accepted since it is not a `CryptoRng`; pass a `rand` CSPRNG or drop the `rng()` call. `Client::rng()` still returns a `FeltRng`, so `draw_word` and `generate_serial_number` call sites are unaffected ([#2414](https://github.com/0xMiden/rust-sdk/pull/2414)). * [BREAKING][arch][store] The account SMT forest now persists in SQLite (new `forest_trees`, `forest_entries`, `forest_subtrees` and `forest_revision` tables) through a `LargeSmtForest` backend scoped to the store's own transaction, so forest mutations commit or roll back atomically with the account tables and opening the store no longer rebuilds the forest from account data. Tree inner nodes are persisted as packed subtree blobs, so witness reads load a single leaf plus eight blobs instead of rebuilding the account's tree, making their cost independent of the account's map size at the price of a larger store file. Tree updates are computed path-locally from the persisted leaves and subtree blobs, so committed update cost scales with the size of the change set rather than with the map size. Existing stores are not compatible and must be recreated ([#2333](https://github.com/0xMiden/rust-sdk/pull/2333)). * [BREAKING][removal][rust] `AccountSmtForest` is now generic over the forest storage `BackendReader`, with updates additionally requiring `Backend`, and is constructed per store operation. The in-memory root-staging API (`stage_roots`, `commit_roots`, `discard_roots`, `replace_roots`, `get_roots`) and the node-insertion helpers were removed. Trees are addressed by account ID and storage slot name rather than by root: `miden_client::store` now exports only `AccountSmtForest` and `AccountUpdate`, with lineage identifiers, update batches and their miden-crypto types kept internal ([#2333](https://github.com/0xMiden/rust-sdk/pull/2333)). * [BREAKING][type][rust] `rpc::domain::transaction::TransactionRecord` gained a non-public field, so it can no longer be constructed with a struct literal outside the crate ([#2300](https://github.com/0xMiden/rust-sdk/pull/2300)). diff --git a/Cargo.lock b/Cargo.lock index dc8cf825ad..56f0b4b621 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2463,6 +2463,7 @@ dependencies = [ "tonic-prost-build", "tonic-web-wasm-client", "tracing", + "trybuild", "uuid", ] @@ -2524,6 +2525,7 @@ dependencies = [ "miden-protocol", "num_cpus", "rand 0.10.1", + "rand_chacha 0.10.0", "regex", "serde", "serde_json", @@ -2562,6 +2564,7 @@ dependencies = [ "miden-standards", "miden-testing", "rand 0.10.1", + "rand_chacha 0.10.0", "rstest", "tempfile", "tokio", diff --git a/bin/integration-tests/Cargo.toml b/bin/integration-tests/Cargo.toml index 9c3850c98b..3ef93f7984 100644 --- a/bin/integration-tests/Cargo.toml +++ b/bin/integration-tests/Cargo.toml @@ -31,6 +31,7 @@ async-trait = { workspace = true } clap = { features = ["derive", "env"], workspace = true } num_cpus = { version = "1.0" } rand = { workspace = true } +rand_chacha = { workspace = true } regex = { workspace = true } serde = { workspace = true } serde_json = { features = ["arbitrary_precision"], workspace = true } diff --git a/bin/integration-tests/src/tests/config.rs b/bin/integration-tests/src/tests/config.rs index eb553b4e7b..c73b3e2ed7 100644 --- a/bin/integration-tests/src/tests/config.rs +++ b/bin/integration-tests/src/tests/config.rs @@ -5,8 +5,8 @@ use std::str::FromStr; use std::sync::Arc; use anyhow::{Context, Result}; +use miden_client::RemoteTransactionProver; use miden_client::builder::ClientBuilder; -use miden_client::crypto::RandomCoin; use miden_client::grpc_support::{DEVNET_PROVER_ENDPOINT, TESTNET_PROVER_ENDPOINT}; use miden_client::note_transport::grpc::GrpcNoteTransportClient; use miden_client::note_transport::{ @@ -15,9 +15,7 @@ use miden_client::note_transport::{ }; use miden_client::rpc::{Endpoint, GrpcClient, VerifyingRpcClient}; use miden_client::testing::common::{FilesystemKeyStore, TestClient, create_test_store_path}; -use miden_client::{Felt, RemoteTransactionProver}; use miden_client_sqlite_store::ClientBuilderSqliteExt; -use rand::RngExt; use uuid::Uuid; const NETWORK_DEVNET: &str = "devnet"; @@ -134,11 +132,6 @@ impl ClientConfig { ) -> Result<(ClientBuilder, FilesystemKeyStore)> { let (rpc_endpoint, rpc_timeout, store_config, auth_path) = self.as_parts(); - let mut rng = rand::rng(); - let coin_seed: [u64; 4] = rng.random(); - - let rng = RandomCoin::new(coin_seed.map(Felt::new_unchecked).into()); - let keystore = FilesystemKeyStore::new(auth_path.clone()).with_context(|| { format!("failed to create keystore at path: {}", auth_path.to_string_lossy()) })?; @@ -148,7 +141,6 @@ impl ClientConfig { let mut builder = ClientBuilder::new() .rpc(rpc_client) - .rng(Box::new(rng)) .sqlite_store(store_config) .authenticator(Arc::new(keystore.clone())) .tx_discard_delta(None); diff --git a/bin/integration-tests/src/tests/custom_transaction.rs b/bin/integration-tests/src/tests/custom_transaction.rs index 7e2b258f80..f79010c486 100644 --- a/bin/integration-tests/src/tests/custom_transaction.rs +++ b/bin/integration-tests/src/tests/custom_transaction.rs @@ -2,7 +2,7 @@ use anyhow::{Context, Result}; use miden_client::account::{AccountId, AccountType}; use miden_client::asset::FungibleAsset; use miden_client::auth::RPO_FALCON_SCHEME_ID; -use miden_client::crypto::{FeltRng, MerkleStore, MerkleTree, NodeIndex, Poseidon2, RandomCoin}; +use miden_client::crypto::{MerkleStore, MerkleTree, NodeIndex, Poseidon2, draw_word}; use miden_client::note::{ Note, NoteAssets, @@ -22,6 +22,8 @@ use miden_client::transaction::{ }; use miden_client::utils::{Deserializable, Serializable}; use miden_client::{Felt, Word, ZERO}; +use rand::SeedableRng; +use rand_chacha::ChaCha20Rng; use crate::tests::config::ClientConfig; @@ -276,7 +278,7 @@ pub async fn test_onchain_notes_sync_with_tag(client_config: ClientConfig) -> Re "; let note_script = client_1.code_builder().compile_note_script(note_script)?; let inputs = NoteStorage::new(vec![])?; - let serial_num = client_1.rng().draw_word(); + let serial_num = draw_word(client_1.rng()); let note_metadata = PartialNoteMetadata::new(basic_account_1.id(), NoteType::Public) .with_tag(NoteTag::with_account_target(basic_account_1.id())); let note_assets = NoteAssets::new(vec![])?; @@ -320,7 +322,7 @@ async fn mint_custom_note( target_account_id: AccountId, ) -> Result { // Prepare transaction - let mut random_coin = RandomCoin::new(Default::default()); + let mut random_coin = ChaCha20Rng::seed_from_u64(0); let note = create_custom_note(client, faucet_account_id, target_account_id, &mut random_coin)?; let transaction_request = @@ -337,7 +339,7 @@ fn create_custom_note( client: &TestClient, faucet_account_id: AccountId, target_account_id: AccountId, - rng: &mut RandomCoin, + rng: &mut impl rand::Rng, ) -> Result { let mem_addr: u32 = 1000; @@ -356,7 +358,7 @@ fn create_custom_note( let inputs = NoteStorage::new(vec![target_account_id.suffix(), target_account_id.prefix().as_felt()]) .context("failed to create note inputs")?; - let serial_num = rng.draw_word(); + let serial_num = draw_word(rng); let note_metadata = PartialNoteMetadata::new(faucet_account_id, NoteType::Private) .with_tag(NoteTag::with_account_target(target_account_id)); let note_assets = NoteAssets::new(vec![ diff --git a/bin/miden-bench/src/config.rs b/bin/miden-bench/src/config.rs index 06a165be00..e1630a449f 100644 --- a/bin/miden-bench/src/config.rs +++ b/bin/miden-bench/src/config.rs @@ -1,13 +1,11 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; +use miden_client::Client; use miden_client::builder::ClientBuilder; -use miden_client::crypto::RandomCoin; use miden_client::keystore::FilesystemKeyStore; use miden_client::rpc::{Endpoint, GrpcClient, VerifyingRpcClient}; -use miden_client::{Client, Felt}; use miden_client_sqlite_store::ClientBuilderSqliteExt; -use rand::RngExt; /// Default store directory name, created in the current working directory. pub const DEFAULT_STORE_DIR: &str = "miden-bench-store"; @@ -43,13 +41,8 @@ pub async fn create_client( let keystore_path = store_path.join("keystore"); std::fs::create_dir_all(&keystore_path)?; - let mut rng = rand::rng(); - let coin_seed: [u64; 4] = rng.random(); - let rng_coin = RandomCoin::new(coin_seed.map(Felt::new_unchecked).into()); - let client = ClientBuilder::new() .rpc(Arc::new(VerifyingRpcClient::new(GrpcClient::new(endpoint, 30_000)))) - .rng(Box::new(rng_coin)) .sqlite_store(sqlite_path) .filesystem_keystore(keystore_path.to_str().expect("keystore path should be valid UTF-8"))? .tx_discard_delta(None) diff --git a/bin/miden-cli/src/commands/new_account.rs b/bin/miden-cli/src/commands/new_account.rs index 236fd2ce73..4fd10816e1 100644 --- a/bin/miden-cli/src/commands/new_account.rs +++ b/bin/miden-cli/src/commands/new_account.rs @@ -541,7 +541,7 @@ async fn create_client_account( None } else { debug!("Adding default Falcon auth component"); - let kp = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.rng()); + let kp = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.secure_rng()); builder = builder.with_component(AuthSingleSig::new(Approver::new( kp.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2, diff --git a/bin/miden-cli/tests/cli.rs b/bin/miden-cli/tests/cli.rs index 87f645bb8a..09b825373b 100644 --- a/bin/miden-cli/tests/cli.rs +++ b/bin/miden-cli/tests/cli.rs @@ -12,7 +12,6 @@ use miden_client::account::{AccountId, AccountType, FaucetMetadata}; use miden_client::address::{Address, NetworkId}; use miden_client::auth::TransactionAuthenticator; use miden_client::builder::ClientBuilder; -use miden_client::crypto::RandomCoin; use miden_client::keystore::Keystore; use miden_client::note::NoteId; use miden_client::note_transport::NOTE_TRANSPORT_TESTNET_ENDPOINT; @@ -24,7 +23,7 @@ use miden_client::testing::common::{ create_test_store_path, }; use miden_client::utils::Serializable; -use miden_client::{self, Client, Felt}; +use miden_client::{self, Client}; use miden_client_cli::MIDEN_DIR; use miden_client_cli::config::Network; use miden_client_sqlite_store::SqliteStore; @@ -1598,16 +1597,10 @@ async fn create_rust_client_with_store_path( std::sync::Arc::new(sqlite_store) }; - let mut rng = rand::rng(); - let coin_seed: [u64; 4] = rng.random(); - - let rng = Box::new(RandomCoin::new(coin_seed.map(Felt::new_unchecked).into())); - let keystore = FilesystemKeyStore::new(temp_dir())?; let client = ClientBuilder::new() .grpc_client(&endpoint, Some(10_000)) - .rng(rng) .store(store) .authenticator(Arc::new(keystore.clone())) .build() diff --git a/crates/rust-client/Cargo.toml b/crates/rust-client/Cargo.toml index 540f2e3e7c..1f5eb5f9d2 100644 --- a/crates/rust-client/Cargo.toml +++ b/crates/rust-client/Cargo.toml @@ -24,6 +24,9 @@ ignored = ["getrandom", "prost-types", "tonic-prost"] # getrandom is used through rand's wasm backend selection. The other entries are used by # generated proto bindings and std-only keystore code that shear does not fully resolve. ignored = ["getrandom", "prost-types", "serde", "serde_json", "tempfile", "tonic-prost"] +# trybuild loads the UI fixtures as data rather than as cargo targets, so shear sees them +# as unreachable files and suggests deleting them. +ignored-paths = ["tests/ui/*.rs"] [lib] crate-type = ["lib"] @@ -91,6 +94,7 @@ hex = { workspace = true } prost = { features = ["derive"], workspace = true } prost-types = { version = "0.14" } rand = { workspace = true } +rand_chacha = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tempfile = { optional = true, workspace = true } @@ -121,8 +125,8 @@ tonic-prost-build = { version = "0.14" } miden-protocol = { default-features = false, features = ["testing"], workspace = true } miden-standards = { features = ["testing"], workspace = true } miden-testing = { default-features = false, workspace = true } -rand_chacha = { workspace = true } tokio = { workspace = true } +trybuild = { version = "1.0" } [lints] workspace = true diff --git a/crates/rust-client/src/builder.rs b/crates/rust-client/src/builder.rs index 8ec03f875c..a60eda72e5 100644 --- a/crates/rust-client/src/builder.rs +++ b/crates/rust-client/src/builder.rs @@ -5,10 +5,10 @@ use alloc::vec::Vec; use miden_protocol::assembly::{DefaultSourceManager, SourceManagerSync}; use miden_protocol::block::BlockNumber; -use miden_protocol::crypto::rand::RandomCoin; -use miden_protocol::{Felt, MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES}; +use miden_protocol::{MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES}; use miden_tx::{ExecutionOptions, LocalTransactionProver}; -use rand::RngExt; +use rand::SeedableRng; +use rand_chacha::ChaCha20Rng; #[cfg(any(feature = "tonic", feature = "std"))] use crate::alloc::string::ToString; @@ -85,9 +85,9 @@ pub trait StoreFactory { /// - **Store** ([`Store`]): Provides persistence for accounts, notes, and transaction history. /// Configure via [`store()`](Self::store). /// -/// - **RNG** ([`FeltRng`](miden_protocol::crypto::rand::FeltRng)): Provides randomness for -/// generating keys, serial numbers, and other cryptographic operations. If not provided, a random -/// seed-based RNG is created automatically. Configure via [`rng()`](Self::rng). +/// - **RNG** ([`ClientCryptoRng`](crate::ClientCryptoRng)): Provides randomness for note serial +/// numbers, script arguments and account seeds. If not provided, a random seed-based RNG is +/// created automatically. Configure via [`rng()`](Self::rng). /// /// - **Authenticator** ([`TransactionAuthenticator`](miden_tx::auth::TransactionAuthenticator)): /// Handles transaction signing when signatures are requested from within the VM. Configure via @@ -337,7 +337,10 @@ where self } - /// Optionally provide a custom RNG. + /// Optionally provide a custom RNG for note serial numbers, script arguments and account + /// seeds. Defaults to `ChaCha20Rng`. Secret keys and transaction input sealing + /// use a separate, non-overridable generator; see + /// [`Client::secure_rng`](crate::Client::secure_rng). #[must_use] pub fn rng(mut self, rng: ClientRngBox) -> Self { self.rng = Some(rng); @@ -474,14 +477,16 @@ where }; // Use the provided RNG, or create a default one. - let rng = if let Some(user_rng) = self.rng { + let rng: ClientRngBox = if let Some(user_rng) = self.rng { user_rng } else { - let mut seed_rng = rand::rng(); - let coin_seed: [u64; 4] = seed_rng.random(); - Box::new(RandomCoin::new(coin_seed.map(Felt::new_unchecked).into())) + Box::new(ChaCha20Rng::from_rng(&mut rand::rng())) }; + // Create a separate, secure RNG for the sealing of transaction inputs and secret key + // generation. + let secure_rng: ClientRngBox = Box::new(ChaCha20Rng::from_rng(&mut rand::rng())); + // Set default prover if not provided let tx_prover: Arc = self.tx_prover.unwrap_or_else(|| Arc::new(LocalTransactionProver::default())); @@ -524,6 +529,7 @@ where Ok(Client { store, rng: ClientRng::new(rng), + secure_rng: ClientRng::new(secure_rng), rpc_api, tx_prover, authenticator: self.authenticator, @@ -593,3 +599,21 @@ impl ClientBuilder { Ok(self.authenticator(Arc::new(keystore))) } } + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + /// Checks that [`ClientBuilder::rng`] rejects a generator that is not a `CryptoRng`. + /// + /// The driver lives here rather than in `tests/` because `make test` runs `--lib` only. + /// + /// The expected diagnostic is snapshotted in `tests/ui/*.stderr`. It quotes the compiler + /// verbatim, so a `rand` or `rustc` upgrade can reword it; regenerate with + /// `TRYBUILD=overwrite cargo test -p miden-client --features "testing std" --lib ui`. + #[test] + fn ui() { + trybuild::TestCases::new().compile_fail("tests/ui/*.rs"); + } +} diff --git a/crates/rust-client/src/lib.rs b/crates/rust-client/src/lib.rs index 566175d1bc..6711624257 100644 --- a/crates/rust-client/src/lib.rs +++ b/crates/rust-client/src/lib.rs @@ -287,7 +287,30 @@ pub mod crypto { NodeIndex, SparseMerklePath, }; - pub use miden_protocol::crypto::rand::{FeltRng, RandomCoin}; + pub use miden_protocol::crypto::rand::FeltRng; + + /// Draws a field element uniformly at random from `rng`. + /// + /// Uses rejection sampling: [`Felt::new`](crate::Felt::new) rejects any `u64` at or beyond + /// the field modulus, which keeps the result uniform over the field. The rejection + /// probability is about 2^-32. + pub fn draw_felt(rng: &mut impl rand::Rng) -> crate::Felt { + use rand::RngExt; + + loop { + if let Ok(felt) = crate::Felt::new(rng.random::()) { + return felt; + } + } + } + + /// Draws a [`Word`](crate::Word) uniformly at random from `rng`. + /// + /// Use this for note serial numbers when building notes from a plain [`rand`] generator, which + /// does not implement [`FeltRng`]. + pub fn draw_word(rng: &mut impl rand::Rng) -> crate::Word { + crate::Word::new([draw_felt(rng), draw_felt(rng), draw_felt(rng), draw_felt(rng)]) + } } /// Provides types for working with addresses within the Miden network. @@ -367,7 +390,7 @@ use miden_protocol::block::BlockNumber; use miden_protocol::crypto::merkle::mmr::PartialMmr; use miden_protocol::crypto::rand::FeltRng; use miden_tx::auth::TransactionAuthenticator; -use rand::{TryCryptoRng, TryRng}; +use rand::{CryptoRng, TryCryptoRng, TryRng}; use rpc::NodeRpcClient; use store::Store; @@ -387,9 +410,13 @@ use crate::transaction::TransactionProver; pub struct Client { /// The client's store, which provides a way to write and read entities to provide persistence. store: Arc, - /// An instance of [`FeltRng`] which provides randomness tools for generating new keys, - /// serial numbers, etc. + /// The client's random number generator for non-secret values: note serial + /// numbers, script arguments, account seeds, etc. The caller can override it, + /// so it must not be used for secret keys; see [`Client::secure_rng`] for those. rng: ClientRng, + /// The client's random number generator for secret values: secret keys and the + /// ephemeral key and nonce that seal transaction inputs. + secure_rng: ClientRng, /// An instance of [`NodeRpcClient`] which provides a way for the client to connect to the /// Miden node. rpc_api: Arc, @@ -482,11 +509,19 @@ where } /// Returns a reference to the client's random number generator. This can be used to generate - /// randomness for various purposes such as serial numbers, keys, etc. + /// randomness for non-secret values such as serial numbers, script arguments, etc. + /// Use [`Client::secure_rng`] for generating randomness for secret values. pub fn rng(&mut self) -> &mut ClientRng { &mut self.rng } + /// Returns a reference to the client's secure random number generator. This can be used to + /// generate randomness for secret values such as account keys, and the nonces that seal + /// transaction inputs. + pub fn secure_rng(&mut self) -> &mut ClientRng { + &mut self.secure_rng + } + pub fn prover(&self) -> Arc { self.tx_prover.clone() } @@ -544,7 +579,7 @@ impl Client { // CLIENT RNG // ================================================================================================ -// NOTE: The idea of having `ClientRng` is to enforce `Send` and `Sync` over `FeltRng`. +// NOTE: The idea of having `ClientRng` is to enforce `Send` and `Sync` over the supplied RNG. // This allows `Client`` to be `Send` and `Sync`. There may be users that would want to use clients // with !Send/!Sync RNGs. For this we have two options: // @@ -553,13 +588,13 @@ impl Client { // these bounds. (similar to TransactionAuthenticator) /// Marker trait for RNGs that can be shared across threads and used by the client. -pub trait ClientFeltRng: FeltRng + Send + Sync {} -impl ClientFeltRng for T where T: FeltRng + Send + Sync {} +pub trait ClientCryptoRng: CryptoRng + Send + Sync {} +impl ClientCryptoRng for T where T: CryptoRng + Send + Sync {} /// Boxed RNG trait object used by the client. -pub type ClientRngBox = Box; +pub type ClientRngBox = Box; -/// A wrapper around a [`FeltRng`] that implements the [`TryRng`] trait. +/// A wrapper around a [`CryptoRng`] that implements the [`TryRng`] and [`FeltRng`] traits. /// This allows the user to pass their own generic RNG so that it's used by the client. pub struct ClientRng(ClientRngBox); @@ -590,18 +625,16 @@ impl TryRng for ClientRng { } } -// The client's RNG already backs key and serial-number generation, so callers are required to -// supply cryptographically secure randomness. Asserting it here lets the RNG drive primitives that -// demand a `CryptoRng`, such as sealing transaction inputs. +// Holds because the inner generator is a `CryptoRng` and the delegation above is infallible. impl TryCryptoRng for ClientRng {} impl FeltRng for ClientRng { fn draw_element(&mut self) -> Felt { - self.0.draw_element() + crypto::draw_felt(&mut self.0) } fn draw_word(&mut self) -> Word { - self.0.draw_word() + crypto::draw_word(&mut self.0) } } diff --git a/crates/rust-client/src/test_utils/common.rs b/crates/rust-client/src/test_utils/common.rs index f166736588..e87a487819 100644 --- a/crates/rust-client/src/test_utils/common.rs +++ b/crates/rust-client/src/test_utils/common.rs @@ -629,7 +629,7 @@ pub async fn insert_account_with_custom_component( let mut init_seed = [0u8; 32]; client.rng().fill_bytes(&mut init_seed); - let key_pair = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.rng()); + let key_pair = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.secure_rng()); let pub_key = key_pair.public_key(); let account = AccountBuilder::new(init_seed) diff --git a/crates/rust-client/src/transaction/batch/mod.rs b/crates/rust-client/src/transaction/batch/mod.rs index 25b5190feb..be435b18b2 100644 --- a/crates/rust-client/src/transaction/batch/mod.rs +++ b/crates/rust-client/src/transaction/batch/mod.rs @@ -207,7 +207,7 @@ where .iter() .zip(transaction_inputs) .map(|(tx_id, inputs)| { - seal_transaction_inputs(&mut self.client.rng, &key, *tx_id, &inputs) + seal_transaction_inputs(&mut self.client.secure_rng, &key, *tx_id, &inputs) }) .collect::, _>>()?; diff --git a/crates/rust-client/src/transaction/mod.rs b/crates/rust-client/src/transaction/mod.rs index 7f77a197ce..7daefea97a 100644 --- a/crates/rust-client/src/transaction/mod.rs +++ b/crates/rust-client/src/transaction/mod.rs @@ -538,7 +538,7 @@ where let tx_id = proven_transaction.id(); let key = self.transaction_encryption_key().await?; let sealed_inputs = - seal_transaction_inputs(&mut self.rng, &key, tx_id, &transaction_inputs.into())?; + seal_transaction_inputs(&mut self.secure_rng, &key, tx_id, &transaction_inputs.into())?; let result = self.rpc_api.submit_proven_transaction(proven_transaction, sealed_inputs).await; if let Err(err) = &result { @@ -1430,7 +1430,6 @@ mod tests { use miden_protocol::account::auth::AuthSecretKey; use miden_protocol::account::{AccountBuilder, AccountComponent, AccountId, AccountType}; use miden_protocol::asset::FungibleAsset; - use miden_protocol::crypto::rand::RandomCoin; use miden_protocol::note::{Note, NoteType}; use miden_protocol::testing::account_id::{ ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, @@ -1441,6 +1440,8 @@ mod tests { use miden_standards::account::auth::{Approver, AuthSingleSig, FeeConversionInfo, NoAuth}; use miden_standards::account::wallets::BasicWallet; use miden_standards::note::P2idNote; + use rand::SeedableRng; + use rand_chacha::ChaCha20Rng; use super::{ Account, @@ -1452,20 +1453,21 @@ mod tests { }; use crate::ClientError; use crate::auth::AuthSchemeId; + use crate::crypto::draw_word; use crate::transaction::TransactionRequestError; fn own_note_with_sender(sender: AccountId) -> Note { let faucet_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(); let target_id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(); - let mut rng = RandomCoin::new(Word::default()); + let mut rng = ChaCha20Rng::seed_from_u64(0); P2idNote::builder() .sender(sender) .target(target_id) .asset(FungibleAsset::new(faucet_id, 100).unwrap()) .note_type(NoteType::Public) - .generate_serial_number(&mut rng) + .serial_number(draw_word(&mut rng)) .build() .expect("note creation failed") .into() diff --git a/crates/rust-client/src/transaction/request/mod.rs b/crates/rust-client/src/transaction/request/mod.rs index 6ef31db58c..005044a051 100644 --- a/crates/rust-client/src/transaction/request/mod.rs +++ b/crates/rust-client/src/transaction/request/mod.rs @@ -599,7 +599,6 @@ mod tests { StorageSlotName, }; use miden_protocol::asset::FungibleAsset; - use miden_protocol::crypto::rand::{FeltRng, RandomCoin}; use miden_protocol::note::{NoteTag, NoteType}; use miden_protocol::testing::account_id::{ ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, @@ -611,8 +610,11 @@ mod tests { use miden_standards::note::P2idNote; use miden_standards::testing::account_component::MockAccountComponent; use miden_tx::utils::serde::{Deserializable, Serializable}; + use rand::SeedableRng; + use rand_chacha::ChaCha20Rng; use super::{TransactionRequest, TransactionRequestBuilder}; + use crate::crypto::draw_word; use crate::rpc::domain::account::AccountStorageRequirements; use crate::transaction::ForeignAccount; @@ -646,7 +648,7 @@ mod tests { let target_id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(); let faucet_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(); - let mut rng = RandomCoin::new(Word::default()); + let mut rng = ChaCha20Rng::seed_from_u64(0); let mut notes = vec![]; for i in 0..6 { @@ -655,7 +657,7 @@ mod tests { .target(target_id) .assets(vec![FungibleAsset::new(faucet_id, 100 + i).unwrap()]) .note_type(NoteType::Private) - .generate_serial_number(&mut rng) + .serial_number(draw_word(&mut rng)) .build() .expect("note creation failed"); notes.push(note.into()); @@ -663,7 +665,7 @@ mod tests { let mut advice_vec: Vec<(Word, Vec)> = vec![]; for i in 0u32..10 { - advice_vec.push((rng.draw_word(), vec![Felt::from(i)])); + advice_vec.push((draw_word(&mut rng), vec![Felt::from(i)])); } let account = AccountBuilder::new(Default::default()) @@ -694,8 +696,8 @@ mod tests { ForeignAccount::private(&account).unwrap(), ]) .own_output_notes(vec![notes.pop().unwrap(), notes.pop().unwrap()]) - .script_arg(rng.draw_word()) - .auth_arg(rng.draw_word()) + .script_arg(draw_word(&mut rng)) + .auth_arg(draw_word(&mut rng)) .expected_ntx_scripts(vec![notes.first().unwrap().recipient().script().clone()]) .build() .unwrap(); diff --git a/crates/rust-client/tests/ui/rng_requires_cryptorng.rs b/crates/rust-client/tests/ui/rng_requires_cryptorng.rs new file mode 100644 index 0000000000..50e3fbd042 --- /dev/null +++ b/crates/rust-client/tests/ui/rng_requires_cryptorng.rs @@ -0,0 +1,44 @@ +//! Do not delete: this fixture is driven by the `builder::tests::ui` test. +//! +//! `ClientBuilder::rng` must reject a generator that is not a `CryptoRng`, even when it +//! implements `FeltRng`. See `Client::secure_rng` for why the bound is there. + +use miden_client::builder::ClientBuilder; +use miden_client::keystore::FilesystemKeyStore; +use miden_protocol::crypto::rand::FeltRng; +use miden_protocol::{Felt, Word}; + +/// A fully predictable generator. It implements `TryRng` (and so `Rng`) and `FeltRng`, but +/// deliberately not `TryCryptoRng`. +struct FixedRng(u64); + +impl rand::TryRng for FixedRng { + type Error = core::convert::Infallible; + + fn try_next_u32(&mut self) -> Result { + Ok(self.0 as u32) + } + + fn try_next_u64(&mut self) -> Result { + Ok(self.0) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> { + dest.fill(self.0 as u8); + Ok(()) + } +} + +impl FeltRng for FixedRng { + fn draw_element(&mut self) -> Felt { + Felt::new_unchecked(self.0) + } + + fn draw_word(&mut self) -> Word { + Word::new([self.draw_element(); 4]) + } +} + +fn main() { + let _ = ClientBuilder::::new().rng(Box::new(FixedRng(7))); +} diff --git a/crates/rust-client/tests/ui/rng_requires_cryptorng.stderr b/crates/rust-client/tests/ui/rng_requires_cryptorng.stderr new file mode 100644 index 0000000000..e6c5fd62ac --- /dev/null +++ b/crates/rust-client/tests/ui/rng_requires_cryptorng.stderr @@ -0,0 +1,15 @@ +error[E0277]: the trait bound `FixedRng: ClientCryptoRng` is not satisfied + --> tests/ui/rng_requires_cryptorng.rs:43:60 + | +43 | let _ = ClientBuilder::::new().rng(Box::new(FixedRng(7))); + | ^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `DerefMut` is not implemented for `FixedRng` + --> tests/ui/rng_requires_cryptorng.rs:13:1 + | +13 | struct FixedRng(u64); + | ^^^^^^^^^^^^^^^ + = note: required for `FixedRng` to implement `TryCryptoRng` + = note: required for `FixedRng` to implement `CryptoRng` + = note: required for `FixedRng` to implement `ClientCryptoRng` + = note: required for the cast from `Box` to `Box<(dyn ClientCryptoRng + 'static)>` diff --git a/crates/testing/miden-client-tests/Cargo.toml b/crates/testing/miden-client-tests/Cargo.toml index 2d33807245..a8799c2f36 100644 --- a/crates/testing/miden-client-tests/Cargo.toml +++ b/crates/testing/miden-client-tests/Cargo.toml @@ -13,6 +13,7 @@ miden-protocol = { default-features = false, features = ["testing"], miden-standards = { features = ["testing"], workspace = true } miden-testing = { default-features = false, workspace = true } rand = { workspace = true } +rand_chacha = { workspace = true } rstest = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true } diff --git a/crates/testing/miden-client-tests/src/tests.rs b/crates/testing/miden-client-tests/src/tests.rs index 26d389f1a3..27db4827b2 100644 --- a/crates/testing/miden-client-tests/src/tests.rs +++ b/crates/testing/miden-client-tests/src/tests.rs @@ -18,6 +18,7 @@ use miden_client::auth::{ RPO_FALCON_SCHEME_ID, }; use miden_client::builder::ClientBuilder; +use miden_client::crypto::draw_word; use miden_client::keystore::{FilesystemKeyStore, Keystore}; use miden_client::note::{BlockNumber, NetworkAccountTarget, NoteExecutionHint}; use miden_client::pswap::PswapLineageState; @@ -84,7 +85,7 @@ use miden_protocol::account::{ use miden_protocol::asset::{Asset, AssetAmount, AssetId, FungibleAsset, TokenSymbol}; use miden_protocol::crypto::dsa::eddsa_25519_sha512::KeyExchangeKey; use miden_protocol::crypto::merkle::MerklePath; -use miden_protocol::crypto::rand::{FeltRng, RandomCoin}; +use miden_protocol::crypto::rand::FeltRng; use miden_protocol::note::{ Note, NoteAssets, @@ -128,7 +129,8 @@ use miden_standards::testing::note::NoteBuilder; use miden_standards::tx_script::SendNotesTransactionScriptError; use miden_testing::{MockChain, MockChainBuilder, MockTransactionInput}; use rand::rngs::StdRng; -use rand::{Rng, RngExt, SeedableRng}; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha20Rng; use rstest::rstest; mod batch; @@ -1414,15 +1416,12 @@ async fn input_note_reader_finds_externally_consumed_notes() { chain.prove_next_block().unwrap(); // Build a client backed by this chain. - let rng = - RandomCoin::new(rand::random::<[u64; 4]>().map(|v| Felt::new_unchecked(v >> 1)).into()); let keystore_path = std::env::temp_dir(); let keystore = FilesystemKeyStore::new(keystore_path).unwrap(); let mock_rpc = MockRpcApi::new(chain); let mut client = ClientBuilder::new() .rpc(Arc::new(mock_rpc)) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore)) .tx_discard_delta(None) @@ -1521,14 +1520,11 @@ async fn import_by_id_already_consumed_note_is_findable_by_id() { chain.prove_next_block().unwrap(); // Build a client backed by this chain. This client never saw the note before. - let rng = - RandomCoin::new(rand::random::<[u64; 4]>().map(|v| Felt::new_unchecked(v >> 1)).into()); let keystore = FilesystemKeyStore::new(std::env::temp_dir()).unwrap(); let mock_rpc = MockRpcApi::new(chain); let mut client = ClientBuilder::new() .rpc(Arc::new(mock_rpc)) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore)) .tx_discard_delta(None) @@ -1565,22 +1561,16 @@ async fn setup_prunable_block_scenario( let mut builder = MockChainBuilder::new(); let mock_account = builder.add_existing_mock_account(miden_testing::Auth::IncrNonce).unwrap(); - let note_first = NoteBuilder::new( - mock_account.id(), - RandomCoin::new([0, 0, 0, 0].map(Felt::new_unchecked).into()), - ) - .note_type(NoteType::Public) - .tag(NoteTag::new(0).into()) - .build() - .unwrap(); - let note_second = NoteBuilder::new( - mock_account.id(), - RandomCoin::new([0, 0, 0, 1].map(Felt::new_unchecked).into()), - ) - .note_type(NoteType::Public) - .tag(NoteTag::new(0).into()) - .build() - .unwrap(); + let note_first = NoteBuilder::new(mock_account.id(), ChaCha20Rng::seed_from_u64(0)) + .note_type(NoteType::Public) + .tag(NoteTag::new(0).into()) + .build() + .unwrap(); + let note_second = NoteBuilder::new(mock_account.id(), ChaCha20Rng::seed_from_u64(1)) + .note_type(NoteType::Public) + .tag(NoteTag::new(0).into()) + .build() + .unwrap(); let spawn_note_1 = builder.add_spawn_note(std::slice::from_ref(¬e_first)).unwrap(); let spawn_note_2 = builder.add_spawn_note(std::slice::from_ref(¬e_second)).unwrap(); @@ -1622,14 +1612,11 @@ async fn setup_prunable_block_scenario( chain.add_pending_executed_transaction(&tx).unwrap(); chain.prove_next_block().unwrap(); - let rng = - RandomCoin::new(rand::random::<[u64; 4]>().map(|v| Felt::new_unchecked(v >> 1)).into()); let keystore = FilesystemKeyStore::new(std::env::temp_dir()).unwrap(); let mock_rpc = MockRpcApi::new(chain); let mut client = ClientBuilder::new() .rpc(Arc::new(mock_rpc.clone())) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore)) .tx_discard_delta(None) @@ -2291,15 +2278,12 @@ async fn note_screening_reports_only_the_account_bound_by_the_note() { let mut records = Vec::with_capacity(NOTE_COUNT); let mut expected_ids = BTreeSet::new(); for i in 0..NOTE_COUNT { - let note = NoteBuilder::new( - faucet_id, - RandomCoin::new([i as u64, 0, 0, 0].map(Felt::new_unchecked).into()), - ) - .script(script.clone()) - .note_storage([target.suffix(), target.prefix().as_felt()]) - .unwrap() - .build() - .unwrap(); + let note = NoteBuilder::new(faucet_id, ChaCha20Rng::seed_from_u64(i as u64)) + .script(script.clone()) + .note_storage([target.suffix(), target.prefix().as_felt()]) + .unwrap() + .build() + .unwrap(); expected_ids.insert(note.id()); let metadata = *note.metadata(); @@ -3308,15 +3292,10 @@ async fn pswap_cancel_test() { async fn create_pswap_test_client( mock_rpc_api: &MockRpcApi, ) -> (MockClient, FilesystemKeyStore) { - let mut seed_rng = rand::rng(); - let coin_seed: [u64; 4] = seed_rng.random(); - let rng = RandomCoin::new(coin_seed.map(|v| Felt::new_unchecked(v >> 1)).into()); - let keystore = FilesystemKeyStore::new(temp_dir()).unwrap(); let mut client = ClientBuilder::new() .rpc(Arc::new(mock_rpc_api.clone())) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore.clone())) .tx_discard_delta(None) @@ -4279,13 +4258,9 @@ async fn import_watched_account_by_id_rejects_already_tracked_native_account() { let account_id = account.id(); let rpc_api = MockRpcApi::new(mock_chain_builder.build().unwrap()); let arc_rpc_api = Arc::new(rpc_api); - let mut rng = rand::rng(); - let coin_seed: [u64; 4] = rng.random(); - let rng = RandomCoin::new(coin_seed.map(|v| Felt::new_unchecked(v >> 1)).into()); let keystore = FilesystemKeyStore::new(temp_dir()).unwrap(); let mut client = ClientBuilder::new() .rpc(arc_rpc_api) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore)) .build() @@ -4550,14 +4525,14 @@ async fn sync_stores_private_note_attachments() { // 2. Build a PRIVATE P2ID note carrying a NetworkAccountTarget attachment. let ntx_target = NetworkAccountTarget::new(target.id(), NoteExecutionHint::Always).unwrap(); let attachments = NoteAttachments::new(vec![ntx_target.into()]).unwrap(); - let mut note_rng = RandomCoin::new([1, 2, 3, 4].map(Felt::new_unchecked).into()); + let mut note_rng = ChaCha20Rng::seed_from_u64(1234); let private_note = P2idNote::builder() .sender(sender.id()) .target(target.id()) .asset(note_asset) .note_type(NoteType::Private) .attachments(attachments.clone().into_vec()) - .generate_serial_number(&mut note_rng) + .serial_number(draw_word(&mut note_rng)) .build() .unwrap() .into(); @@ -4591,12 +4566,9 @@ async fn sync_stores_private_note_attachments() { let rpc_api = Arc::new(MockRpcApi::new(mock_chain)); rpc_api.register_private_note_attachments(private_note.id(), attachments.clone()); - let rng = - RandomCoin::new(rand::random::<[u64; 4]>().map(|v| Felt::new_unchecked(v >> 1)).into()); let keystore = FilesystemKeyStore::new(std::env::temp_dir()).unwrap(); let mut client = ClientBuilder::new() .rpc(rpc_api) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore)) .tx_discard_delta(None) @@ -4734,16 +4706,11 @@ async fn sync_large_public_account() { // 4. Build a client and add the ORIGINAL (pre-tx) account. // The pre-tx commitment differs from on-chain, which triggers sync. - let mut rng = rand::rng(); - let coin_seed: [u64; 4] = rng.random(); - let rng = RandomCoin::new(coin_seed.map(|v| Felt::new_unchecked(v >> 1)).into()); - let keystore_path = temp_dir(); let keystore = FilesystemKeyStore::new(keystore_path).unwrap(); let mut client = ClientBuilder::new() .rpc(arc_rpc_api) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore)) .build() @@ -4799,10 +4766,6 @@ async fn prepare_offline_bootstrap_inserts_mock_chain_genesis() { use miden_protocol::crypto::merkle::smt::Smt; use miden_protocol::transaction::TransactionKernel; - let mut rng_seed = rand::rng(); - let coin_seed: [u64; 4] = rng_seed.random(); - let rng = RandomCoin::new(coin_seed.map(Felt::new_unchecked).into()); - let reference_rpc = MockRpcApi::default(); let (expected_genesis, _) = reference_rpc .get_block_header_by_number(Some(BlockNumber::GENESIS), false) @@ -4815,7 +4778,6 @@ async fn prepare_offline_bootstrap_inserts_mock_chain_genesis() { let mut client = ClientBuilder::new() .rpc(Arc::new(MockRpcApi::default())) .sqlite_store(create_test_store_path()) - .rng(Box::new(rng)) .authenticator(Arc::new(keystore)) .build() .await @@ -4871,11 +4833,6 @@ pub async fn seed_mock_transaction_encryption_key(client: &mut MockClient (ClientBuilder, MockRpcApi, FilesystemKeyStore) { - let mut rng = rand::rng(); - let coin_seed: [u64; 4] = rng.random(); - - let rng = RandomCoin::new(coin_seed.map(|v| Felt::new_unchecked(v >> 1)).into()); - let keystore_path = temp_dir(); let keystore = FilesystemKeyStore::new(keystore_path).unwrap(); @@ -4884,7 +4841,6 @@ pub async fn create_test_client_builder() let builder = ClientBuilder::new() .rpc(arc_rpc_api) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore.clone())) .tx_discard_delta(None); @@ -4898,23 +4854,17 @@ pub async fn create_prebuilt_mock_chain() -> MockChain { .add_existing_mock_account(miden_testing::Auth::IncrNonce) .unwrap(); - let note_first = NoteBuilder::new( - mock_account.id(), - RandomCoin::new([0, 0, 0, 0].map(Felt::new_unchecked).into()), - ) - .note_type(NoteType::Public) - .tag(NoteTag::new(0).into()) - .build() - .unwrap(); + let note_first = NoteBuilder::new(mock_account.id(), ChaCha20Rng::seed_from_u64(0)) + .note_type(NoteType::Public) + .tag(NoteTag::new(0).into()) + .build() + .unwrap(); - let note_second = NoteBuilder::new( - mock_account.id(), - RandomCoin::new([0, 0, 0, 1].map(Felt::new_unchecked).into()), - ) - .note_type(NoteType::Public) - .tag(NoteTag::new(0).into()) - .build() - .unwrap(); + let note_second = NoteBuilder::new(mock_account.id(), ChaCha20Rng::seed_from_u64(1)) + .note_type(NoteType::Public) + .tag(NoteTag::new(0).into()) + .build() + .unwrap(); let spawn_note_1 = mock_chain_builder.add_spawn_note(std::slice::from_ref(¬e_first)).unwrap(); let spawn_note_2 = @@ -4982,7 +4932,7 @@ async fn insert_new_wallet( visibility: AccountType, keystore: &FilesystemKeyStore, ) -> Result { - let key_pair = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.rng()); + let key_pair = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.secure_rng()); let pub_key = key_pair.public_key(); let mut init_seed = [0u8; 32]; @@ -5038,7 +4988,7 @@ async fn insert_new_fungible_faucet( visibility: AccountType, keystore: &FilesystemKeyStore, ) -> Result { - let key_pair = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.rng()); + let key_pair = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.secure_rng()); let pub_key = key_pair.public_key(); // we need to use an initial seed to create the wallet account diff --git a/crates/testing/miden-client-tests/src/tests/batch.rs b/crates/testing/miden-client-tests/src/tests/batch.rs index a9c2a011df..8813340004 100644 --- a/crates/testing/miden-client-tests/src/tests/batch.rs +++ b/crates/testing/miden-client-tests/src/tests/batch.rs @@ -26,8 +26,6 @@ use miden_client::transaction::{ TransactionStoreUpdate, }; use miden_client_sqlite_store::ClientBuilderSqliteExt; -use miden_protocol::Felt; -use miden_protocol::crypto::rand::RandomCoin; use miden_testing::{Auth, MockChainBuilder, MockTransactionInput}; use crate::tests::{create_test_client, seed_mock_transaction_encryption_key}; @@ -156,13 +154,10 @@ async fn apply_transaction_batch_rolls_back_on_mid_batch_failure() { let mock_chain = chain_builder.build().unwrap(); // Build a client backed by the mock chain. - let rng = - RandomCoin::new(rand::random::<[u64; 4]>().map(|v| Felt::new_unchecked(v >> 1)).into()); let keystore = FilesystemKeyStore::new(std::env::temp_dir()).unwrap(); let rpc_api = MockRpcApi::new(mock_chain); let mut client = ClientBuilder::new() .rpc(Arc::new(rpc_api.clone())) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore)) .tx_discard_delta(None) @@ -430,12 +425,10 @@ async fn batch_builder_submits_txs_across_multiple_accounts() { let account_id_b = account_b.id(); let mock_chain = chain_builder.build().unwrap(); - let rng = RandomCoin::new(rand::random::<[u64; 4]>().map(Felt::new_unchecked).into()); let keystore = FilesystemKeyStore::new(std::env::temp_dir()).unwrap(); let rpc_api = MockRpcApi::new(mock_chain); let mut client = ClientBuilder::new() .rpc(Arc::new(rpc_api.clone())) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore)) .tx_discard_delta(None) diff --git a/crates/testing/miden-client-tests/src/tests/transport.rs b/crates/testing/miden-client-tests/src/tests/transport.rs index 56b54d9e6a..9ca8070c17 100644 --- a/crates/testing/miden-client-tests/src/tests/transport.rs +++ b/crates/testing/miden-client-tests/src/tests/transport.rs @@ -24,7 +24,6 @@ use miden_client::testing::note_transport::{ }; use miden_client::utils::RwLock; use miden_client_sqlite_store::ClientBuilderSqliteExt; -use miden_protocol::Felt; use miden_protocol::account::{ AccountId, AccountIdVersion, @@ -33,14 +32,14 @@ use miden_protocol::account::{ }; use miden_protocol::asset::{Asset, FungibleAsset}; use miden_protocol::block::BlockNumber; -use miden_protocol::crypto::rand::RandomCoin; use miden_protocol::note::NoteType as ProtocolNoteType; use miden_protocol::transaction::RawOutputNote; use miden_protocol::utils::serde::Serializable; use miden_standards::note::P2idNote; use miden_standards::testing::note::NoteBuilder; use miden_testing::{Auth, MockChainBuilder, MockTransactionInput}; -use rand::RngExt; +use rand::SeedableRng; +use rand_chacha::ChaCha20Rng; use crate::tests::{ create_test_client_builder, @@ -106,15 +105,12 @@ async fn transport_recovers_attachments() { let target = mock_chain_builder.add_existing_wallet(Auth::IncrNonce).unwrap(); let ntx_target = NetworkAccountTarget::new(target.id(), NoteExecutionHint::Always).unwrap(); - let private_note = NoteBuilder::new( - sender.id(), - RandomCoin::new([1, 2, 3, 4].map(Felt::new_unchecked).into()), - ) - .note_type(ProtocolNoteType::Private) - .tag(NoteTag::new(0).into()) - .attachment(ntx_target) - .build() - .unwrap(); + let private_note = NoteBuilder::new(sender.id(), ChaCha20Rng::seed_from_u64(1234)) + .note_type(ProtocolNoteType::Private) + .tag(NoteTag::new(0).into()) + .attachment(ntx_target) + .build() + .unwrap(); let attachments = private_note.attachments().clone(); let spawn_note = @@ -139,11 +135,8 @@ async fn transport_recovers_attachments() { let mock_node = Arc::new(RwLock::new(MockNoteTransportNode::new())); let keystore = FilesystemKeyStore::new(temp_dir()).unwrap(); - let rng = - RandomCoin::new(rand::random::<[u64; 4]>().map(|v| Felt::new_unchecked(v >> 1)).into()); let mut client = ClientBuilder::new() .rpc(rpc_api.clone()) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore)) .note_transport(Arc::new(MockNoteTransportApi::new(mock_node.clone()))) @@ -498,14 +491,11 @@ async fn fetch_private_notes_finds_note_committed_at_sync_height() { .add_existing_mock_account(miden_testing::Auth::IncrNonce) .unwrap(); - let private_note = NoteBuilder::new( - mock_account.id(), - RandomCoin::new([1, 2, 3, 4].map(Felt::new_unchecked).into()), - ) - .note_type(ProtocolNoteType::Private) - .tag(NoteTag::new(0).into()) - .build() - .unwrap(); + let private_note = NoteBuilder::new(mock_account.id(), ChaCha20Rng::seed_from_u64(1234)) + .note_type(ProtocolNoteType::Private) + .tag(NoteTag::new(0).into()) + .build() + .unwrap(); let spawn_note = mock_chain_builder.add_spawn_note(std::slice::from_ref(&private_note)).unwrap(); @@ -538,16 +528,11 @@ async fn fetch_private_notes_finds_note_committed_at_sync_height() { let arc_rpc_api = Arc::new(rpc_api); let transport_client = MockNoteTransportApi::new(mock_transport_node.clone()); - let mut rng = rand::rng(); - let coin_seed: [u64; 4] = rng.random(); - let rng = RandomCoin::new(coin_seed.map(|v| Felt::new_unchecked(v >> 1)).into()); - let keystore_path = temp_dir(); let keystore = FilesystemKeyStore::new(keystore_path.clone()).unwrap(); let builder: ClientBuilder = ClientBuilder::new() .rpc(arc_rpc_api) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore)) .tx_discard_delta(None) @@ -927,14 +912,11 @@ pub async fn create_test_user_with_transport( /// distinct notes. Lets a test seed the mock transport with notes whose tag and relative ordering /// it controls, independent of any recipient's auto-registered account tag. fn private_note_with_tag(account: AccountId, tag: NoteTag, seed: u64) -> Note { - NoteBuilder::new( - account, - RandomCoin::new([seed, seed + 1, seed + 2, seed + 3].map(Felt::new_unchecked).into()), - ) - .note_type(ProtocolNoteType::Private) - .tag(tag.into()) - .build() - .unwrap() + NoteBuilder::new(account, ChaCha20Rng::seed_from_u64(seed)) + .note_type(ProtocolNoteType::Private) + .tag(tag.into()) + .build() + .unwrap() } /// Build a chain with a private note (tag 0) committed at block 1, advance @@ -953,12 +935,9 @@ async fn committed_private_note_recipient( .add_existing_mock_account(miden_testing::Auth::IncrNonce) .unwrap(); - let mut note_builder = NoteBuilder::new( - mock_account.id(), - RandomCoin::new([1, 2, 3, 4].map(Felt::new_unchecked).into()), - ) - .note_type(ProtocolNoteType::Private) - .tag(NoteTag::new(0).into()); + let mut note_builder = NoteBuilder::new(mock_account.id(), ChaCha20Rng::seed_from_u64(1234)) + .note_type(ProtocolNoteType::Private) + .tag(NoteTag::new(0).into()); if with_unserved_attachment { let ntx_target = NetworkAccountTarget::new(mock_account.id(), NoteExecutionHint::Always).unwrap(); @@ -995,16 +974,11 @@ async fn committed_private_note_recipient( let arc_rpc_api = Arc::new(rpc_api); let transport_client = MockNoteTransportApi::new(mock_transport_node.clone()); - let mut rng = rand::rng(); - let coin_seed: [u64; 4] = rng.random(); - let rng = RandomCoin::new(coin_seed.map(|v| Felt::new_unchecked(v >> 1)).into()); - let keystore_path = temp_dir(); let keystore = FilesystemKeyStore::new(keystore_path.clone()).unwrap(); let builder: ClientBuilder = ClientBuilder::new() .rpc(arc_rpc_api) - .rng(Box::new(rng)) .sqlite_store(create_test_store_path()) .authenticator(Arc::new(keystore)) .tx_discard_delta(None)