-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
### What Expose `prng_reseed`, `u64_in_inclusive_range` and `vec_shuffle` in the SDK. ### Why Exposing the PRNG functions in the SDK allows users to utilize randomness provided by the host in their smart contracts. ### Known limitations N/A Close #969 ----- I added tests for `sha256` and `verify_sig_ed25519` while I was in here. Let me know if those changes should be moved to a separate PR. I considered adding a PRNG module separate from the `crypto` module. I decided to keep them under `crypto` because there are only three functions. Also this is also similar `getRandomValues` is a function implemented within the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) in browsers. --------- Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com>
- Loading branch information
1 parent
94f29fc
commit 4efef11
Showing
8 changed files
with
306 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
//! Prng contains a pseudo-random generator. | ||
//! | ||
//! # Warning | ||
//! | ||
//! **The pseudo-random generator contained in this module is not suitable for | ||
//! security-sensitive work.** | ||
//! | ||
//! The entropy used to seed the generator is not strong. Every node in the | ||
//! network executing a contract get exactly the same output. The value is hard | ||
//! to predict, but trivial to derive once the network has determined the inputs | ||
//! into the ledger the invocation occurs in. The value is also controllable by | ||
//! the node nominating. Therefore, the results of the pseudo-random number | ||
//! generator are determinable once the inputs to a ledger are known. | ||
//! | ||
//! Every contract invocation gets its own, independent seed. If a contract | ||
//! invocation fails, the seed from the failed invocation is not reused for the | ||
//! next invocation of the contract. | ||
//! | ||
//! In tests, the contract invocation seed is consistently zero, and tests will | ||
//! receive consistent results from the PRNG. | ||
use core::ops::{Bound, RangeBounds}; | ||
|
||
use crate::{env::internal, unwrap::UnwrapInfallible, Bytes, Env, IntoVal, TryIntoVal, Val, Vec}; | ||
|
||
/// Prng is a pseudo-random generator. | ||
/// | ||
/// # Warning | ||
/// | ||
/// **The pseudo-random generator contained in this module is not suitable for | ||
/// security-sensitive work.** | ||
pub struct Prng { | ||
env: Env, | ||
} | ||
|
||
impl Prng { | ||
pub(crate) fn new(env: &Env) -> Prng { | ||
Prng { env: env.clone() } | ||
} | ||
|
||
pub fn env(&self) -> &Env { | ||
&self.env | ||
} | ||
|
||
/// Reseeds the PRNG with the provided value. | ||
/// | ||
/// The seed is combined with the seed assigned to the contract invocation. | ||
/// | ||
/// # Warning | ||
/// | ||
/// **The pseudo-random generator contained in this module is not suitable for | ||
/// security-sensitive work.** | ||
pub fn seed(&self, seed: Bytes) { | ||
let env = self.env(); | ||
internal::Env::prng_reseed(env, seed.into()).unwrap_infallible(); | ||
} | ||
|
||
/// Returns a random u64 in the range specified. | ||
/// | ||
/// # Panics | ||
/// | ||
/// If the range is empty. | ||
/// | ||
/// # Warning | ||
/// | ||
/// **The pseudo-random generator contained in this module is not suitable for | ||
/// security-sensitive work.** | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use soroban_sdk::{Env}; | ||
/// | ||
/// # use soroban_sdk::{contract, contractimpl, symbol_short, Bytes}; | ||
/// # | ||
/// # #[contract] | ||
/// # pub struct Contract; | ||
/// # | ||
/// # #[cfg(feature = "testutils")] | ||
/// # fn main() { | ||
/// # let env = Env::default(); | ||
/// # let contract_id = env.register_contract(None, Contract); | ||
/// # env.as_contract(&contract_id, || { | ||
/// # env.prng().seed(Bytes::from_array(&env, &[1; 32])); | ||
/// // Get values in the range of 1 to 100, inclusive. | ||
/// let value = env.prng().u64_in_range(1..=100); | ||
/// assert_eq!(value, 77); | ||
/// let value = env.prng().u64_in_range(1..=100); | ||
/// assert_eq!(value, 66); | ||
/// let value = env.prng().u64_in_range(1..=100); | ||
/// assert_eq!(value, 72); | ||
/// # }) | ||
/// # } | ||
/// # #[cfg(not(feature = "testutils"))] | ||
/// # fn main() { } | ||
/// ``` | ||
pub fn u64_in_range(&self, r: impl RangeBounds<u64>) -> u64 { | ||
let start_bound = match r.start_bound() { | ||
Bound::Included(b) => *b, | ||
Bound::Excluded(b) => *b + 1, | ||
Bound::Unbounded => 0, | ||
}; | ||
let end_bound = match r.end_bound() { | ||
Bound::Included(b) => *b, | ||
Bound::Excluded(b) => *b - 1, | ||
Bound::Unbounded => u64::MAX, | ||
}; | ||
let env = self.env(); | ||
internal::Env::prng_u64_in_inclusive_range(env, start_bound.into(), end_bound.into()) | ||
.unwrap_infallible() | ||
.into() | ||
} | ||
|
||
/// Shuffles a given vector v using the Fisher-Yates algorithm. | ||
/// | ||
/// # Warning | ||
/// | ||
/// **The pseudo-random generator contained in this module is not suitable for | ||
/// security-sensitive work.** | ||
pub fn shuffle<V>(&self, v: V) -> Vec<Val> | ||
where | ||
V: IntoVal<Env, Vec<Val>>, | ||
{ | ||
let env = self.env(); | ||
let v_val = v.into_val(env); | ||
|
||
internal::Env::prng_vec_shuffle(env, v_val.to_object()) | ||
.unwrap_infallible() | ||
.try_into_val(env) | ||
.unwrap_infallible() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
use crate::{bytes, bytesn, Env}; | ||
|
||
#[test] | ||
fn test_verify_sig_ed25519() { | ||
let env = Env::default(); | ||
|
||
// From https://datatracker.ietf.org/doc/html/rfc8032#section-7.1 TEST 2 | ||
let public_key = bytesn!( | ||
&env, | ||
0x3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c | ||
); | ||
let signature = bytesn!( | ||
&env, | ||
0x92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00 | ||
); | ||
let message = bytes!(&env, 0x72); | ||
|
||
env.crypto() | ||
.ed25519_verify(&public_key, &message, &signature); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected = "HostError: Error(Crypto, InvalidInput)")] | ||
fn test_verify_sig_ed25519_invalid_sig() { | ||
let env = Env::default(); | ||
|
||
// From https://datatracker.ietf.org/doc/html/rfc8032#section-7.1 TEST 2, message modified from 0x72 to 0x73 | ||
let public_key = bytesn!( | ||
&env, | ||
0x3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c | ||
) | ||
.try_into() | ||
.unwrap(); | ||
let signature = bytesn!( | ||
&env, | ||
0x92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00 | ||
); | ||
let message = bytes!(&env, 0x73); | ||
|
||
env.crypto() | ||
.ed25519_verify(&public_key, &message, &signature); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
use crate::{bytes, bytesn, Env}; | ||
|
||
#[test] | ||
fn test_sha256() { | ||
let env = Env::default(); | ||
|
||
let input = bytes!(&env, 0x01); | ||
let expect = bytesn!( | ||
&env, | ||
0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | ||
); | ||
assert_eq!(env.crypto().sha256(&input), expect); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
use crate::{self as soroban_sdk}; | ||
use crate::{bytes, vec, Env, Val, Vec}; | ||
use soroban_sdk::contract; | ||
|
||
#[contract] | ||
pub struct TestPrngContract; | ||
|
||
#[test] | ||
fn test_prng_seed() { | ||
let e = Env::default(); | ||
let id = e.register_contract(None, TestPrngContract); | ||
|
||
e.as_contract(&id, || { | ||
assert_eq!(e.prng().u64_in_range(0..=9), 6); | ||
e.prng().seed(bytes!( | ||
&e, | ||
0x0000000000000000000000000000000000000000000000000000000000000001 | ||
)); | ||
assert_eq!(e.prng().u64_in_range(0..=9), 5); | ||
}); | ||
} | ||
|
||
#[test] | ||
fn test_prng_shuffle() { | ||
let e = Env::default(); | ||
let id = e.register_contract(None, TestPrngContract); | ||
|
||
e.as_contract(&id, || { | ||
let v = vec![&e, 1, 2, 3]; | ||
assert_eq!(e.prng().shuffle(v), vec![&e, 2, 3, 1].to_vals()); | ||
}); | ||
|
||
e.as_contract(&id, || { | ||
let v = Vec::<i64>::new(&e); | ||
assert_eq!(e.prng().shuffle(v), Vec::<Val>::new(&e).to_vals()); | ||
}); | ||
} | ||
|
||
#[test] | ||
fn test_vec_shuffle() { | ||
let e = Env::default(); | ||
let id = e.register_contract(None, TestPrngContract); | ||
|
||
e.as_contract(&id, || { | ||
let v = vec![&e, 1, 2, 3]; | ||
let s = v.shuffle(); | ||
assert_eq!(s, vec![&e, 2, 3, 1]); | ||
assert_eq!(v, vec![&e, 1, 2, 3]); | ||
}); | ||
|
||
e.as_contract(&id, || { | ||
let v = Vec::<i64>::new(&e); | ||
let s = v.shuffle(); | ||
assert_eq!(s, vec![&e]); | ||
assert_eq!(v, vec![&e]); | ||
}); | ||
} | ||
|
||
#[test] | ||
fn test_prng_u64_in_range() { | ||
let e = Env::default(); | ||
let id = e.register_contract(None, TestPrngContract); | ||
|
||
e.as_contract(&id, || { | ||
assert_eq!(e.prng().u64_in_range(..), 11654647981089815984); | ||
assert_eq!(e.prng().u64_in_range(u64::MAX..), u64::MAX); | ||
assert_eq!( | ||
e.prng().u64_in_range(u64::MAX - 1..u64::MAX), | ||
18446744073709551614 | ||
); | ||
assert_eq!(e.prng().u64_in_range(u64::MAX..=u64::MAX), u64::MAX); | ||
assert_eq!(e.prng().u64_in_range(0..1), 0); | ||
assert_eq!(e.prng().u64_in_range(0..=0), 0); | ||
assert_eq!(e.prng().u64_in_range(..=0), 0); | ||
}); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected = "low > high")] | ||
fn test_prng_u64_in_range_panic_on_empty_range() { | ||
let e = Env::default(); | ||
let id = e.register_contract(None, TestPrngContract); | ||
|
||
e.as_contract(&id, || { | ||
e.prng().u64_in_range(u64::MAX..u64::MAX); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters