diff --git a/Cargo.lock b/Cargo.lock index 330da37..65c5d5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,6 +487,7 @@ dependencies = [ "lalrpop-util", "lazy_static", "miniscript", + "rand_chacha", "serde", "serde-wasm-bindgen", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 01c7f65..0c21657 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ base64 = "0.22" thiserror = "1.0.20" chrono = "0.4.13" lazy_static = "1.4.0" +rand_chacha = "0.3" # Optional serde = { version = "1", features=["derive"], optional = true } diff --git a/src/stdlib/crypto.rs b/src/stdlib/crypto.rs index bf5a225..d3f339d 100644 --- a/src/stdlib/crypto.rs +++ b/src/stdlib/crypto.rs @@ -1,8 +1,9 @@ use std::convert::TryFrom; -use bitcoin::hashes::{self, Hash}; +use bitcoin::hashes::{self, sha256, Hash}; use bitcoin::secp256k1::{self, rand}; -use rand::{random, thread_rng, RngCore}; +use rand::{thread_rng, Rng, RngCore, SeedableRng}; +use rand_chacha::ChaCha12Rng; use crate::runtime::scope::{Mutable, ScopeRef}; use crate::runtime::{Array, Error, Result, Value}; @@ -101,26 +102,38 @@ pub mod fns { } // Generate a random Bytes sequence - /// rand::bytes(Int size) -> Bytes + /// rand::bytes(Int size[, Bytes seed]) -> Bytes pub fn rand_bytes(args: Array, _: &ScopeRef) -> Result { - let size = args.arg_into()?; + let (size, seed) = args.args_into()?; let mut bytes = vec![0u8; size]; - thread_rng().fill_bytes(&mut bytes); + make_rng(seed).fill_bytes(&mut bytes); Ok(bytes.into()) } /// Generate a random signed 64-bit integer - /// rand::i64() -> Int + /// rand::i64([Bytes seed]) -> Int pub fn rand_i64(args: Array, _: &ScopeRef) -> Result { - args.no_args()?; - Ok(random::().into()) + let seed = args.arg_into()?; + Ok(make_rng(seed).gen::().into()) } /// Generate a random 64-bit float in the [0, 1) range - /// rand::f64() -> Float + /// rand::f64([Bytes seed]) -> Float pub fn rand_f64(args: Array, _: &ScopeRef) -> Result { - args.no_args()?; - Ok(random::().into()) + let seed = args.arg_into()?; + Ok(make_rng(seed).gen::().into()) + } + + fn make_rng(seed: Option>) -> ChaCha12Rng { + // Uses ChaCha12 (rand's current default StdRng) directly for reproducibility + // From https://docs.rs/rand/latest/rand/rngs/struct.StdRng.html: "The algorithm is deterministic but should not be + // considered reproducible due to dependence on configuration and possible replacement in future library versions. + // For a secure reproducible generator, we recommend use of the rand_chacha crate directly." + if let Some(seed) = seed { + ChaCha12Rng::from_seed(sha256::Hash::hash(&seed).to_byte_array()) + } else { + ChaCha12Rng::from_rng(thread_rng()).unwrap() + } } }