Skip to content
Draft
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
7 changes: 3 additions & 4 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
63 changes: 37 additions & 26 deletions crates/web-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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")]
Expand Down Expand Up @@ -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<dyn Store> = 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?;
Expand Down Expand Up @@ -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<dyn Store> = 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?;
Expand All @@ -461,8 +473,8 @@ impl WebClient {
&self,
rpc_client: Arc<dyn NodeRpcClient>,
store: Arc<dyn Store>,
keystore: WebKeyStore<RandomCoin>,
rng: RandomCoin,
keystore: WebKeyStore<StdRng>,
rng: StdRng,
note_transport_client: Option<Arc<dyn NoteTransportClient>>,
) -> Result<(), JsValue> {
let mut builder = ClientBuilder::new()
Expand Down Expand Up @@ -548,7 +560,7 @@ impl WebClient {
rpc_client: Arc<dyn NodeRpcClient>,
store: Arc<dyn Store>,
keystore: FilesystemKeyStore,
rng: RandomCoin,
rng: StdRng,
note_transport_client: Option<Arc<dyn NoteTransportClient>>,
) -> Result<(), JsErr> {
let client = maybe_wrap_send(async move {
Expand Down Expand Up @@ -582,23 +594,22 @@ impl WebClient {
}
}

pub(crate) fn create_rng(seed: Option<Vec<u8>>) -> Result<RandomCoin, JsErr> {
let mut rng = match seed {
pub(crate) fn create_rng(seed: Option<Vec<u8>>) -> Result<StdRng, JsErr> {
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
Expand Down
10 changes: 8 additions & 2 deletions crates/web-client/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Store> = 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(),
Expand Down
17 changes: 4 additions & 13 deletions crates/web-client/src/models/note.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -114,11 +113,7 @@ impl Note {
note_type: NoteType,
attachment: &NoteAttachment,
) -> Result<Self, JsErr> {
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<NativeAsset> = native_note_assets.iter().copied().collect();
Expand Down Expand Up @@ -150,9 +145,7 @@ impl Note {
note_type: NoteType,
attachment: &NoteAttachment,
) -> Result<Self, JsErr> {
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<NativeAsset> = native_note_assets.iter().copied().collect();
Expand Down Expand Up @@ -190,9 +183,7 @@ impl Note {
destination_network: u32,
destination_address: &EthAddress,
) -> Result<Self, JsErr> {
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();

Expand Down
9 changes: 3 additions & 6 deletions crates/web-client/src/models/note_recipient.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion crates/web-client/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ pub(crate) fn maybe_wrap_send<F: std::future::Future>(

/// Platform-specific client authenticator type.
#[cfg(feature = "browser")]
pub(crate) type ClientAuth = crate::web_keystore::WebKeyStore<miden_client::crypto::RandomCoin>;
pub(crate) type ClientAuth = crate::web_keystore::WebKeyStore<rand::rngs::StdRng>;

/// Platform-specific client authenticator type.
#[cfg(feature = "nodejs")]
Expand Down
Loading