diff --git a/Cargo.lock b/Cargo.lock index bb1bab11..0efc9ff3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2385,8 +2385,7 @@ dependencies = [ [[package]] name = "miden-client" version = "0.16.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60536acdc7d062845a8adb2cc57052c0629ce54b10e3eec180ac1f8a54ab25f5" +source = "git+https://github.com/0xMiden/rust-sdk?branch=replace-random-coin#cd55c64f26623b43c9384578f2432e1eb822433e" dependencies = [ "anyhow", "async-trait", @@ -2407,6 +2406,7 @@ dependencies = [ "prost", "prost-types", "rand 0.10.2", + "rand_chacha 0.10.0", "serde", "serde_json", "tempfile", @@ -2424,8 +2424,7 @@ dependencies = [ [[package]] name = "miden-client-sqlite-store" version = "0.16.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517d47a4444ef546eafb9d8e6ae163de673b9b79827625a9f49a62396aea2551" +source = "git+https://github.com/0xMiden/rust-sdk?branch=replace-random-coin#cd55c64f26623b43c9384578f2432e1eb822433e" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 29a2482c..b32cab2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,8 +48,8 @@ js-export-macro = { path = "crates/js-export-macro", version = "0.16.0-rc.3" } # Cargo.lock intentionally follows the rust-sdk v0.16.0-rc.2 release graph. Broad # `cargo update` runs may select newer transitive versions; validate those against # this release before accepting lockfile changes. -miden-client = { default-features = false, version = "0.16.0-rc.2" } -miden-client-sqlite-store = { default-features = false, version = "0.16.0-rc.2" } +miden-client = { branch = "replace-random-coin", default-features = false, git = "https://github.com/0xMiden/rust-sdk" } +miden-client-sqlite-store = { branch = "replace-random-coin", default-features = false, git = "https://github.com/0xMiden/rust-sdk" } # External dependencies async-trait = { version = "0.1" } diff --git a/crates/web-client/src/lib.rs b/crates/web-client/src/lib.rs index 5fe4f674..f671bd02 100644 --- a/crates/web-client/src/lib.rs +++ b/crates/web-client/src/lib.rs @@ -21,7 +21,6 @@ use js_export_macro::js_export; #[cfg(feature = "browser")] use js_sys::{Function, Reflect}; use miden_client::builder::{ClientBuilder, DEFAULT_GRPC_TIMEOUT_MS}; -use miden_client::crypto::RandomCoin; #[cfg(feature = "nodejs")] use miden_client::keystore::FilesystemKeyStore; use miden_client::note_transport::NoteTransportClient; @@ -30,15 +29,15 @@ use miden_client::rpc::{Endpoint, GrpcClient, NodeRpcClient, VerifyingRpcClient} use miden_client::store::Store; use miden_client::testing::mock::MockRpcApi; use miden_client::testing::note_transport::MockNoteTransportApi; -use miden_client::{Client, ClientError, ErrorHint, Felt}; +use miden_client::{Client, ClientError, ClientRng, ErrorHint}; use models::code_builder::CodeBuilder; #[cfg(feature = "nodejs")] use napi_derive::napi; #[cfg(feature = "nodejs")] use platform::maybe_wrap_send; use platform::{AsyncCell, ClientAuth, JsErr, from_str_err}; +use rand::SeedableRng; use rand::rngs::StdRng; -use rand::{RngExt, SeedableRng}; #[cfg(feature = "browser")] use tracing::Level; #[cfg(feature = "browser")] @@ -389,13 +388,21 @@ impl WebClient { let store_name = store_name.unwrap_or(format!("{}_{}", BASE_STORE_NAME, endpoint.to_network_id())); - let rng = create_rng(seed)?; + let mut rng = create_rng(seed)?; let store: Arc = Arc::new( IdxdbStore::new(store_name.clone()) .await .map_err(|_| JsValue::from_str("Failed to initialize IdxdbStore"))?, ); - let keystore = WebKeyStore::new_with_callbacks(rng, store_name.clone(), None, None, None); + // The keystore gets its own stream so signature nonces don't share state with the + // client's RNG. + let keystore = WebKeyStore::new_with_callbacks( + StdRng::from_rng(&mut rng), + store_name.clone(), + None, + None, + None, + ); self.setup_client(web_rpc_client, store, keystore, rng, note_transport_client) .await?; @@ -442,14 +449,19 @@ impl WebClient { let store_name = store_name.unwrap_or(format!("{}_{}", BASE_STORE_NAME, endpoint.to_network_id())); - let rng = create_rng(seed)?; + let mut rng = create_rng(seed)?; let store: Arc = Arc::new( IdxdbStore::new(store_name.clone()) .await .map_err(|_| JsValue::from_str("Failed to initialize IdxdbStore"))?, ); - let keystore = - WebKeyStore::new_with_callbacks(rng, store_name, get_key_cb, insert_key_cb, sign_cb); + let keystore = WebKeyStore::new_with_callbacks( + StdRng::from_rng(&mut rng), + store_name, + get_key_cb, + insert_key_cb, + sign_cb, + ); self.setup_client(web_rpc_client, store, keystore, rng, note_transport_client) .await?; @@ -461,8 +473,8 @@ impl WebClient { &self, rpc_client: Arc, store: Arc, - keystore: WebKeyStore, - rng: RandomCoin, + keystore: WebKeyStore, + rng: StdRng, note_transport_client: Option>, ) -> Result<(), JsValue> { let mut builder = ClientBuilder::new() @@ -548,7 +560,7 @@ impl WebClient { rpc_client: Arc, store: Arc, keystore: FilesystemKeyStore, - rng: RandomCoin, + rng: StdRng, note_transport_client: Option>, ) -> Result<(), JsErr> { let client = maybe_wrap_send(async move { @@ -582,23 +594,22 @@ impl WebClient { } } -pub(crate) fn create_rng(seed: Option>) -> Result { - let mut rng = match seed { +pub(crate) fn create_rng(seed: Option>) -> Result { + match seed { Some(seed_bytes) => { - if seed_bytes.len() == 32 { - let mut seed_array = [0u8; 32]; - seed_array.copy_from_slice(&seed_bytes); - StdRng::from_seed(seed_array) - } else { - return Err(from_str_err("Seed must be exactly 32 bytes")); - } + let seed_array: [u8; 32] = seed_bytes + .try_into() + .map_err(|_| from_str_err("Seed must be exactly 32 bytes"))?; + Ok(StdRng::from_seed(seed_array)) }, - None => StdRng::from_rng(&mut rand::rng()), - }; - let coin_seed: [u64; 4] = rng.random(); - // `coin_seed` is freshly drawn `u64`s; the probability of hitting the modulus is - // vanishing and `new_unchecked` matches the upstream Rust client's usage. - Ok(RandomCoin::new(coin_seed.map(Felt::new_unchecked).into())) + None => Ok(StdRng::from_rng(&mut rand::rng())), + } +} + +/// Builds a standalone [`ClientRng`] for the note constructors that need a `FeltRng` without +/// going through a client. +pub(crate) fn create_felt_rng() -> ClientRng { + ClientRng::new(Box::new(StdRng::from_rng(&mut rand::rng()))) } // ERROR HANDLING HELPERS diff --git a/crates/web-client/src/mock.rs b/crates/web-client/src/mock.rs index 8e704ae3..989463f8 100644 --- a/crates/web-client/src/mock.rs +++ b/crates/web-client/src/mock.rs @@ -51,13 +51,19 @@ impl WebClient { }; let store_name = "mock_client_db".to_owned(); - let rng = create_rng(seed)?; + let mut rng = create_rng(seed)?; let store: Arc = Arc::new( IdxdbStore::new(store_name.clone()) .await .map_err(|_| from_str_err("Failed to initialize IdxdbStore"))?, ); - let keystore = WebKeyStore::new_with_callbacks(rng, store_name, None, None, None); + let keystore = WebKeyStore::new_with_callbacks( + StdRng::from_rng(&mut rng), + store_name, + None, + None, + None, + ); self.setup_client( mock_rpc_api.clone(), diff --git a/crates/web-client/src/models/note.rs b/crates/web-client/src/models/note.rs index 86909179..d3ab5cbf 100644 --- a/crates/web-client/src/models/note.rs +++ b/crates/web-client/src/models/note.rs @@ -1,10 +1,9 @@ use js_export_macro::js_export; +use miden_client::Word as NativeWord; use miden_client::agglayer::B2AggNote; use miden_client::asset::Asset as NativeAsset; use miden_client::block::BlockNumber as NativeBlockNumber; -use miden_client::crypto::RandomCoin; use miden_client::note::{Note as NativeNote, NoteAssets as NativeNoteAssets, P2idNote, P2ideNote}; -use miden_client::{Felt as NativeFelt, Word as NativeWord}; use super::NoteType; use super::account_id::AccountId; @@ -114,11 +113,7 @@ impl Note { note_type: NoteType, attachment: &NoteAttachment, ) -> Result { - let coin_seed: [u64; 4] = rand::random(); - // `coin_seed` is freshly random `u64`s; values at or beyond the modulus would only - // happen with vanishing probability and `new_unchecked` is what the upstream Rust - // client uses in the same spot. - let mut rng = RandomCoin::new(coin_seed.map(NativeFelt::new_unchecked).into()); + let mut rng = crate::create_felt_rng(); let native_note_assets: NativeNoteAssets = assets.into(); let native_assets: Vec = native_note_assets.iter().copied().collect(); @@ -150,9 +145,7 @@ impl Note { note_type: NoteType, attachment: &NoteAttachment, ) -> Result { - let coin_seed: [u64; 4] = rand::random(); - // See `create_p2id_note` for why `new_unchecked` is fine here. - let mut rng = RandomCoin::new(coin_seed.map(NativeFelt::new_unchecked).into()); + let mut rng = crate::create_felt_rng(); let native_note_assets: NativeNoteAssets = assets.into(); let native_assets: Vec = native_note_assets.iter().copied().collect(); @@ -190,9 +183,7 @@ impl Note { destination_network: u32, destination_address: &EthAddress, ) -> Result { - let coin_seed: [u64; 4] = rand::random(); - // See `create_p2id_note` for why `new_unchecked` is fine here. - let mut rng = RandomCoin::new(coin_seed.map(NativeFelt::new_unchecked).into()); + let mut rng = crate::create_felt_rng(); let native_assets: NativeNoteAssets = assets.into(); diff --git a/crates/web-client/src/models/note_recipient.rs b/crates/web-client/src/models/note_recipient.rs index 2b04e095..6c8a03f6 100644 --- a/crates/web-client/src/models/note_recipient.rs +++ b/crates/web-client/src/models/note_recipient.rs @@ -1,11 +1,11 @@ use js_export_macro::js_export; -use miden_client::crypto::RandomCoin; +use miden_client::Word as NativeWord; +use miden_client::crypto::FeltRng; use miden_client::note::{ NoteRecipient as NativeNoteRecipient, NoteScript as NativeNoteScript, NoteStorage as NativeNoteStorage, }; -use miden_client::{Felt as NativeFelt, Word as NativeWord}; use super::note_script::NoteScript; use super::note_storage::NoteStorage; @@ -47,10 +47,7 @@ impl NoteRecipient { /// serial number (the secret that prevents double-spends). #[js_export(js_name = "fromScript")] pub fn from_script(note_script: &NoteScript, storage: &NoteStorage) -> NoteRecipient { - let coin_seed: [u64; 4] = rand::random(); - // See `Note::create_p2id_note` for why `new_unchecked` is fine here. - let mut rng = RandomCoin::new(coin_seed.map(NativeFelt::new_unchecked).into()); - let serial_num: NativeWord = [rng.draw(), rng.draw(), rng.draw(), rng.draw()].into(); + let serial_num: NativeWord = crate::create_felt_rng().draw_word(); let native = NativeNoteRecipient::new(serial_num, note_script.into(), storage.into()); NoteRecipient(native) diff --git a/crates/web-client/src/platform.rs b/crates/web-client/src/platform.rs index 4ba849f0..805e9037 100644 --- a/crates/web-client/src/platform.rs +++ b/crates/web-client/src/platform.rs @@ -206,7 +206,7 @@ pub(crate) fn maybe_wrap_send( /// Platform-specific client authenticator type. #[cfg(feature = "browser")] -pub(crate) type ClientAuth = crate::web_keystore::WebKeyStore; +pub(crate) type ClientAuth = crate::web_keystore::WebKeyStore; /// Platform-specific client authenticator type. #[cfg(feature = "nodejs")]