Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fb3cc74
fix(rust-client): require a CryptoRng in the client and default to Ch…
ricomateo Aug 18, 2026
e2f2ad1
fix(rust-client): draw secret keys and input sealing from a separate …
ricomateo Aug 18, 2026
42aea8b
refactor(rust-client): remove RandomCoin in favor of ChaCha20Rng
ricomateo Aug 18, 2026
98e77fd
refactor(rust-client): use explicit branching for the default RNG
ricomateo Aug 18, 2026
a69138b
docs(rust-client): correct which RNG covers account seeds
ricomateo Aug 18, 2026
f2c72b7
docs(rust-client): fix broken intra-doc link in draw_felt
ricomateo Aug 18, 2026
4efc3f3
chore: improve docs
ricomateo Aug 18, 2026
2e5ebc7
chore: improve changelog
ricomateo Aug 18, 2026
9e408fe
chore: shorten doc comment
ricomateo Aug 18, 2026
a440cdb
test(rust-client): add trybuild test asserting ClientBuilder::rng rej…
ricomateo Aug 19, 2026
18cf8d9
chore: remove doc test
ricomateo Aug 19, 2026
47b7d75
chore(rust-client): silence the cargo-shear warning for trybuild fixt…
ricomateo Aug 19, 2026
35e647e
Merge branch 'next' into replace-random-coin
ricomateo Aug 20, 2026
e3b4748
chore: move draw_felt and draw_word functions to rng.rs
ricomateo Aug 20, 2026
b36b001
chore: fix intra-doc link
ricomateo Aug 20, 2026
cd55c64
Merge branch 'next' into replace-random-coin
ricomateo Aug 24, 2026
8c636ca
chore: remove Client::secure_rng
ricomateo Sep 9, 2026
8fbc523
Merge branch 'next' into replace-random-coin
ricomateo Sep 9, 2026
ceaad0a
chore: change ClientBuilder::rng so that is only available under the …
ricomateo Sep 9, 2026
9ab1d07
chore: fix import path in changelog entry
ricomateo Sep 9, 2026
64a2ae2
chore: remove remaining RandomCoin occurrences
ricomateo Sep 9, 2026
a42ad90
chore: change ClientRng so that is only available under the testing f…
ricomateo Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### 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::rng::{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][type][rust] `ClientBuilder::rng` is now only available under the `testing` feature and requires `CryptoRng + Send + Sync`; the marker trait `ClientFeltRng` is renamed to `ClientCryptoRng`. Outside of tests the client's RNG is always an OS-seeded `ChaCha20Rng`; drop the `rng()` call ([#2414](https://github.com/0xMiden/rust-sdk/pull/2414)).
* [BREAKING][type][rust] Added the `TransactionRequestError::SwapNoteWithZeroAsset` variant, so exhaustive matches on `TransactionRequestError` must handle it ([#2459](https://github.com/0xMiden/rust-sdk/pull/2459)).

### Fixes
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bin/integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
rustix = { workspace = true }
serde = { workspace = true }
Expand Down
10 changes: 1 addition & 9 deletions bin/integration-tests/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -16,9 +16,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::testing::fee::FeeFunder;
use miden_client::{Felt, RemoteTransactionProver};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
use rand::RngExt;
use uuid::Uuid;

use crate::fee_funding;
Expand Down Expand Up @@ -136,11 +134,6 @@ impl ClientConfig {
let store_config = create_test_store_path();
let auth_path = create_test_auth_path();

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())
})?;
Expand All @@ -152,7 +145,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);
Expand Down
13 changes: 8 additions & 5 deletions bin/integration-tests/src/tests/custom_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
use miden_client::note::{
Note,
NoteAssets,
Expand All @@ -12,6 +12,7 @@ use miden_client::note::{
NoteType,
PartialNoteMetadata,
};
use miden_client::rng::draw_word;
use miden_client::store::{NoteFilter, TransactionFilter};
use miden_client::testing::common::*;
use miden_client::transaction::{
Expand All @@ -22,6 +23,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::ClientConfig;

Expand Down Expand Up @@ -273,7 +276,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![])?;
Expand Down Expand Up @@ -317,7 +320,7 @@ async fn mint_custom_note(
target_account_id: AccountId,
) -> Result<Note> {
// 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 =
Expand All @@ -334,7 +337,7 @@ fn create_custom_note(
client: &TestClient,
faucet_account_id: AccountId,
target_account_id: AccountId,
rng: &mut RandomCoin,
rng: &mut impl rand::Rng,
) -> Result<Note> {
let mem_addr: u32 = 1000;

Expand All @@ -353,7 +356,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![
Expand Down
9 changes: 1 addition & 8 deletions bin/miden-bench/src/config.rs
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -46,13 +44,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, RPC_TIMEOUT_MS))))
.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)
Expand Down
9 changes: 1 addition & 8 deletions bin/miden-cli/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::sync::Arc;
use anyhow::Result;
use assert_cmd::Command;
use assert_cmd::cargo::cargo_bin_cmd;
use miden_client::Deserializable;
use miden_client::account::component::{
AccountComponentMetadata,
FeltSchema,
Expand All @@ -21,7 +22,6 @@ use miden_client::address::{Address, NetworkId};
use miden_client::assembly::CodeBuilder;
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;
Expand All @@ -43,7 +43,6 @@ use miden_client::vm::{
SectionId,
TargetType,
};
use miden_client::{self, Deserializable, Felt};
use miden_client_cli::MIDEN_DIR;
use miden_client_cli::config::{KEYSTORE_DIRECTORY, Network};
use miden_client_integration_tests::{ClientConfig, fee_funding};
Expand Down Expand Up @@ -1608,16 +1607,10 @@ async fn create_rust_client(
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(keystore_path.to_path_buf())?;

let client = ClientBuilder::new()
.grpc_client(&endpoint, Some(10_000))
.rng(rng)
.store(store)
.authenticator(Arc::new(keystore.clone()))
.build()
Expand Down
2 changes: 1 addition & 1 deletion crates/rust-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,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 }
Expand Down Expand Up @@ -124,7 +125,6 @@ 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 }

[lints]
Expand Down
25 changes: 14 additions & 11 deletions crates/rust-client/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ 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::auth::TransactionAuthenticator;
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;
Expand Down Expand Up @@ -85,9 +85,10 @@ 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 generating
/// keys, serial numbers, and other cryptographic operations. It is always created from a random
/// seed, so that a caller cannot make the keys it generates predictable. Under the `testing`
/// feature it can be overridden with `rng()`.
///
/// - **Authenticator** ([`TransactionAuthenticator`]): Handles transaction signing when signatures
/// are requested from within the VM. Configure via [`authenticator()`](Self::authenticator).
Expand All @@ -114,7 +115,7 @@ pub struct ClientBuilder<AUTH> {
rpc_api: Option<Arc<dyn NodeRpcClient>>,
/// An optional store provided by the user.
pub store: Option<StoreBuilder>,
/// An optional RNG provided by the user.
/// An optional RNG provided by the user. Only settable under the `testing` feature.
rng: Option<ClientRngBox>,
/// The authenticator provided by the user.
authenticator: Option<Arc<AUTH>>,
Expand Down Expand Up @@ -337,6 +338,10 @@ where
}

/// Optionally provide a custom RNG.
///
/// Restricted to the `testing` feature: the client's RNG generates secret keys and seals
/// transaction inputs, so outside of tests its output must not be predictable to a caller.
#[cfg(feature = "testing")]
#[must_use]
pub fn rng(mut self, rng: ClientRngBox) -> Self {
self.rng = Some(rng);
Expand Down Expand Up @@ -471,12 +476,10 @@ 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()))
};

let tx_prover: Arc<dyn TransactionProver + Send + Sync> =
Expand Down
24 changes: 12 additions & 12 deletions crates/rust-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ pub mod transaction;
pub mod utils;

pub mod builder;
pub mod rng;

#[cfg(feature = "testing")]
mod test_utils;
Expand Down Expand Up @@ -292,7 +293,7 @@ pub mod crypto {
NodeIndex,
SparseMerklePath,
};
pub use miden_protocol::crypto::rand::{FeltRng, RandomCoin};
pub use miden_protocol::crypto::rand::FeltRng;
}

/// Provides types for working with addresses within the Miden network.
Expand Down Expand Up @@ -374,7 +375,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;

Expand Down Expand Up @@ -558,21 +559,22 @@ impl<AUTH> Client<AUTH> {
// 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<T> ClientFeltRng for T where T: FeltRng + Send + Sync {}
pub trait ClientCryptoRng: CryptoRng + Send + Sync {}
impl<T> ClientCryptoRng for T where T: CryptoRng + Send + Sync {}

/// Boxed RNG trait object used by the client.
pub type ClientRngBox = Box<dyn ClientFeltRng>;
pub type ClientRngBox = Box<dyn ClientCryptoRng>;

/// A wrapper around a [`FeltRng`] that implements the [`TryRng`] trait. This allows the user to
/// pass their own generic RNG so that it's used by the client.
/// 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);

impl ClientRng {
pub fn new(rng: ClientRngBox) -> Self {
Self(rng)
}

#[cfg(feature = "testing")]
pub fn inner_mut(&mut self) -> &mut ClientRngBox {
&mut self.0
}
Expand All @@ -595,18 +597,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()
rng::draw_felt(&mut self.0)
}

fn draw_word(&mut self) -> Word {
self.0.draw_word()
rng::draw_word(&mut self.0)
}
}

Expand Down
22 changes: 22 additions & 0 deletions crates/rust-client/src/rng.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use rand::RngExt;

/// 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 {
loop {
if let Ok(felt) = crate::Felt::new(rng.random::<u64>()) {
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`](crate::Felt).
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)])
}
Loading
Loading