From 000e08c0418c0a748d83258f46374aedcc0bbe52 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Sat, 13 May 2023 09:17:01 -0700 Subject: [PATCH 1/5] Use crypto.getRandomValues on WASM/JS --- Cargo.toml | 11 +++++++---- src/global_rng.rs | 41 +++++++++++++++++++++++++---------------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8547e43..a1487ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,13 +17,16 @@ exclude = ["/.*"] [features] default = ["std"] alloc = [] -std = ["alloc", "instant"] +std = ["alloc"] -[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] -instant = { version = "0.1", optional = true } +[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies.web-sys] +version = "0.3.62" +features = [ + "Window", + "Crypto" +] [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dev-dependencies] -instant = { version = "0.1", features = ["wasm-bindgen"] } wasm-bindgen-test = "0.3" getrandom = { version = "0.2", features = ["js"] } diff --git a/src/global_rng.rs b/src/global_rng.rs index 6594cc1..88b5e1e 100644 --- a/src/global_rng.rs +++ b/src/global_rng.rs @@ -5,11 +5,6 @@ use crate::Rng; use std::cell::Cell; use std::ops::RangeBounds; -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] -use instant::Instant; -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] -use std::time::Instant; - impl Default for Rng { /// Initialize the `Rng` from the system's random number generator. /// @@ -29,17 +24,7 @@ impl Rng { } thread_local! { - static RNG: Cell = Cell::new(Rng({ - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - use std::thread; - - let mut hasher = DefaultHasher::new(); - Instant::now().hash(&mut hasher); - thread::current().id().hash(&mut hasher); - let hash = hasher.finish(); - (hash << 1) | 1 - })); + static RNG: Cell = Cell::new(Rng(random_seed().unwrap_or(0x4d595df4d0f33173))); } /// Run an operation with the current thread-local generator. @@ -190,3 +175,27 @@ pub fn f64() -> f64 { pub fn choose_multiple(source: T, amount: usize) -> Vec { with_rng(|rng| rng.choose_multiple(source, amount)) } + +#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] +fn random_seed() -> Option { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + use std::thread; + use std::time::Instant; + + let mut hasher = DefaultHasher::new(); + Instant::now().hash(&mut hasher); + thread::current().id().hash(&mut hasher); + let hash = hasher.finish(); + Some((hash << 1) | 1) +} + +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] +fn random_seed() -> Option { + // TODO(notgull): Failures should be logged somewhere. + let mut seed = [0u8; 8]; + web_sys::window() + .and_then(|window| window.crypto().ok()) + .and_then(|crypto| crypto.get_random_values_with_u8_array(&mut seed).ok()) + .map(|_| u64::from_ne_bytes(seed)) +} From e9121270b85c02817aeddf748744ed6f3636ddd1 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Sat, 13 May 2023 09:26:17 -0700 Subject: [PATCH 2/5] Use getrandom to seed the global RNG on WASM --- Cargo.toml | 9 +++------ src/global_rng.rs | 18 ++++++++++++------ src/lib.rs | 13 +++++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1487ca..850f311 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,13 +18,10 @@ exclude = ["/.*"] default = ["std"] alloc = [] std = ["alloc"] +js = ["std", "getrandom"] -[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies.web-sys] -version = "0.3.62" -features = [ - "Window", - "Crypto" -] +[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] +getrandom = { version = "0.2", features = ["js"], optional = true } [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/src/global_rng.rs b/src/global_rng.rs index 88b5e1e..a93d9fa 100644 --- a/src/global_rng.rs +++ b/src/global_rng.rs @@ -5,6 +5,9 @@ use crate::Rng; use std::cell::Cell; use std::ops::RangeBounds; +// Chosen by fair roll of the dice. +const DEFAULT_RNG_SEED: u64 = 0xef6f79ed30ba75a; + impl Default for Rng { /// Initialize the `Rng` from the system's random number generator. /// @@ -24,7 +27,7 @@ impl Rng { } thread_local! { - static RNG: Cell = Cell::new(Rng(random_seed().unwrap_or(0x4d595df4d0f33173))); + static RNG: Cell = Cell::new(Rng(random_seed().unwrap_or(DEFAULT_RNG_SEED))); } /// Run an operation with the current thread-local generator. @@ -190,12 +193,15 @@ fn random_seed() -> Option { Some((hash << 1) | 1) } -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "js"))] fn random_seed() -> Option { // TODO(notgull): Failures should be logged somewhere. let mut seed = [0u8; 8]; - web_sys::window() - .and_then(|window| window.crypto().ok()) - .and_then(|crypto| crypto.get_random_values_with_u8_array(&mut seed).ok()) - .map(|_| u64::from_ne_bytes(seed)) + getrandom::getrandom(&mut seed).ok()?; + Some(u64::from_ne_bytes(seed)) +} + +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(feature = "js")))] +fn random_seed() -> Option { + None } diff --git a/src/lib.rs b/src/lib.rs index ec4b36d..ae97368 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,19 @@ //! - `std` (enabled by default): Enables the `std` library. This is required for the global //! generator and global entropy. Without this feature, [`Rng`] can only be instantiated using //! the [`with_seed`](Rng::with_seed) method. +//! - `js`: Assumes that WebAssembly targets are being run in a JavaScript environment. See the +//! [WebAssembly Notes](#webassembly-notes) section for more information. +//! +//! # WebAssembly Notes +//! +//! For non-WASI WASM targets, there is additional sublety to consider when utilizing the global RNG. +//! By default, `std` targets will use entropy sources in the standard library to seed the global RNG. +//! However, these sources are not available by default on WASM targets outside of WASI. +//! +//! If the `js` feature is enabled, this crate will assume that it is running in a JavaScript +//! environment. At this point, the [`getrandom`] crate will be used in order to access the available +//! entropy sources and seed the global RNG. If the `js` feature is not enabled, the global RNG will +//! use a predefined seed. #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] From bdd269292d06889be52a5048ec2de7cdf9bd24d8 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Sat, 13 May 2023 09:27:23 -0700 Subject: [PATCH 3/5] Broken doclink --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ae97368..3e352b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,6 +91,8 @@ //! environment. At this point, the [`getrandom`] crate will be used in order to access the available //! entropy sources and seed the global RNG. If the `js` feature is not enabled, the global RNG will //! use a predefined seed. +//! +//! [`getrandom`]: https://crates.io/crates/getrandom #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] From 08ba8b103b656de5e78ebf52c02792ab752bc243 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Sat, 13 May 2023 09:29:55 -0700 Subject: [PATCH 4/5] Formatting error --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3e352b9..415aae9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,7 +91,7 @@ //! environment. At this point, the [`getrandom`] crate will be used in order to access the available //! entropy sources and seed the global RNG. If the `js` feature is not enabled, the global RNG will //! use a predefined seed. -//! +//! //! [`getrandom`]: https://crates.io/crates/getrandom #![cfg_attr(not(feature = "std"), no_std)] From ee02482eb2f5fa0ef2f960b2e6ad9ac643968008 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 3 Jun 2023 08:29:00 -0700 Subject: [PATCH 5/5] Make sure getrandom isn't pulled in for emscripten --- Cargo.toml | 4 ++-- src/global_rng.rs | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 850f311..dfbbfa5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,10 +20,10 @@ alloc = [] std = ["alloc"] js = ["std", "getrandom"] -[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] getrandom = { version = "0.2", features = ["js"], optional = true } -[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dev-dependencies] +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies] wasm-bindgen-test = "0.3" getrandom = { version = "0.2", features = ["js"] } diff --git a/src/global_rng.rs b/src/global_rng.rs index a93d9fa..f994902 100644 --- a/src/global_rng.rs +++ b/src/global_rng.rs @@ -179,7 +179,10 @@ pub fn choose_multiple(source: T, amount: usize) -> Vec { with_rng(|rng| rng.choose_multiple(source, amount)) } -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] +#[cfg(not(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown" +)))] fn random_seed() -> Option { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -193,7 +196,11 @@ fn random_seed() -> Option { Some((hash << 1) | 1) } -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "js"))] +#[cfg(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown", + feature = "js" +))] fn random_seed() -> Option { // TODO(notgull): Failures should be logged somewhere. let mut seed = [0u8; 8]; @@ -201,7 +208,11 @@ fn random_seed() -> Option { Some(u64::from_ne_bytes(seed)) } -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(feature = "js")))] +#[cfg(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown", + not(feature = "js") +))] fn random_seed() -> Option { None }