Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).
Expand Down
3 changes: 3 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 }
serde = { workspace = true }
serde_json = { features = ["arbitrary_precision"], workspace = true }
Expand Down
10 changes: 1 addition & 9 deletions bin/integration-tests/src/tests/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 @@ -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";
Expand Down Expand Up @@ -134,11 +132,6 @@ impl ClientConfig {
) -> Result<(ClientBuilder<FilesystemKeyStore>, 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())
})?;
Expand All @@ -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);
Expand Down
12 changes: 7 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, draw_word};
use miden_client::note::{
Note,
NoteAssets,
Expand All @@ -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;

Expand Down Expand Up @@ -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![])?;
Expand Down Expand Up @@ -320,7 +322,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 @@ -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<Note> {
let mem_addr: u32 = 1000;

Expand All @@ -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![
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 @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion bin/miden-cli/src/commands/new_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ async fn create_client_account<AUTH: Keystore + Sync + 'static>(
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,
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 @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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()
Expand Down
6 changes: 5 additions & 1 deletion crates/rust-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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
46 changes: 35 additions & 11 deletions crates/rust-client/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<dyn TransactionProver + Send + Sync> =
self.tx_prover.unwrap_or_else(|| Arc::new(LocalTransactionProver::default()));
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -593,3 +599,21 @@ impl ClientBuilder<FilesystemKeyStore> {
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");
}
}
Loading
Loading