From ad940fe6b7e4c2c9cf487ed44e227a57da555a53 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 18 Mar 2022 18:33:47 +0100 Subject: [PATCH 1/5] Implement shuffle query --- Cargo.lock | 49 +++++++++++++- Cargo.toml | 4 ++ src/contract.rs | 148 ++++++++++++++++++++++++++++++++++++++++++- src/errors.rs | 4 ++ src/msg.rs | 17 ++++- tests/integration.rs | 56 +++++++++++++++- 6 files changed, 271 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a52659..dd18312 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,6 +72,16 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" +dependencies = [ + "either", + "radium", +] + [[package]] name = "blake2b_simd" version = "0.5.11" @@ -1148,6 +1158,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" + [[package]] name = "rand" version = "0.2.0" @@ -1159,8 +1175,11 @@ dependencies = [ "cw2", "drand-verify", "hex", + "rand_chacha 0.3.1", + "rand_core 0.6.1", "schemars", "serde", + "shuffle", "thiserror", ] @@ -1172,11 +1191,20 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha", + "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.1", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -1187,6 +1215,16 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.1", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -1456,6 +1494,15 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "shuffle" +version = "0.1.7" +source = "git+https://github.com/webmaster128/shuffle?branch=rm-getrandom#2c267f41a590c85a327c9ffd8796cf06fa5cb9e9" +dependencies = [ + "bitvec", + "rand 0.8.5", +] + [[package]] name = "signature" version = "1.3.2" diff --git a/Cargo.toml b/Cargo.toml index 396e7ad..8366ee8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.2.0" license = "AGPL-3.0" authors = ["Simon Warta "] edition = "2018" +resolver = "2" exclude = [ # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. @@ -38,6 +39,9 @@ cw2 = "0.13" schemars = "0.8.3" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = "1.0" +rand_chacha = { version = "0.3.1", default-features = false } +rand_core = { version = "0.6", default-features = false } +shuffle = { git = "https://github.com/webmaster128/shuffle", branch = "rm-getrandom" } [dev-dependencies] cosmwasm-schema = { version = "1.0.0-beta6" } diff --git a/src/contract.rs b/src/contract.rs index 8053c8f..5995230 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,14 +1,20 @@ +use std::convert::TryInto; + use cosmwasm_std::entry_point; use cosmwasm_std::{ - coins, to_binary, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, - Storage, SubMsg, + coins, to_binary, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdError, + StdResult, Storage, SubMsg, }; use drand_verify::{derive_randomness, g1_from_variable, verify}; +use rand_chacha::ChaCha8Rng; +use rand_core::SeedableRng; +use shuffle::irs::Irs; +use shuffle::shuffler::Shuffler; use crate::errors::ContractError; use crate::msg::{ BountiesResponse, Bounty, ConfigResponse, ExecuteMsg, GetResponse, InstantiateMsg, - LatestResponse, QueryMsg, + LatestResponse, QueryMsg, ShuffleResponse, }; use crate::state::{ beacons_storage, beacons_storage_read, bounties_storage, bounties_storage_read, config, @@ -129,6 +135,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result to_binary(&query_get(deps, round)?)?, QueryMsg::Latest {} => to_binary(&query_latest(deps)?)?, QueryMsg::Bounties {} => to_binary(&query_bounties(deps)?)?, + QueryMsg::Shuffle { round, from, to } => to_binary(&query_shuffle(deps, round, from, to)?)?, }; Ok(response) } @@ -182,6 +189,30 @@ fn query_bounties(deps: Deps) -> Result { }) } +fn query_shuffle( + deps: Deps, + round: u64, + from: u32, + to: u32, +) -> Result { + if from > to { + return Err(ContractError::InvalidRange {}); + } + let beacons = beacons_storage_read(deps.storage); + let randomness = beacons + .get(&round.to_be_bytes()) + .ok_or(ContractError::BeaconNotFound {})?; + let randomness: [u8; 32] = randomness.try_into().unwrap(); + + let mut rng = ChaCha8Rng::from_seed(randomness); + let mut list: Vec = (from..=to).collect(); + let mut irs = Irs::default(); + irs.shuffle(&mut list, &mut rng) + .map_err(|err| StdError::generic_err(err))?; + + Ok(ShuffleResponse { list }) +} + fn get_bounty(storage: &dyn Storage, round: u64) -> StdResult { let key = round.to_be_bytes(); let bounties = bounties_storage_read(storage); @@ -699,4 +730,115 @@ mod tests { } ); } + + #[test] + fn query_shuffle_works() { + let mut deps = mock_dependencies(); + + let info = mock_info("creator", &[]); + let msg = InstantiateMsg { + pubkey: pubkey_loe_mainnet(), + bounty_denom: BOUNTY_DENOM.into(), + }; + instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + // Beacon does not exist + + let response: GetResponse = + from_binary(&query(deps.as_ref(), mock_env(), QueryMsg::Get { round: 42 }).unwrap()) + .unwrap(); + assert_eq!(response.randomness, Binary::default()); + + // Beacon exists + + let msg = ExecuteMsg::Add { + // curl -sS https://drand.cloudflare.com/public/42 | jq + round: 42, + previous_signature: hex::decode("a418fccbfaa0c84aba8cbcd4e3c0555170eb2382dfed108ecfc6df249ad43efe00078bdcb5060fe2deed4731ca5b4c740069aaf77927ba59c5870ab3020352aca3853adfdb9162d40ec64f71b121285898e28cdf237e982ac5c4deb287b0d57b").unwrap().into(), + signature: hex::decode("9469186f38e5acdac451940b1b22f737eb0de060b213f0326166c7882f2f82b92ce119bdabe385941ef46f72736a4b4d02ce206e1eb46cac53019caf870080fede024edcd1bd0225eb1335b83002ae1743393e83180e47d9948ab8ba7568dd99").unwrap().into(), + }; + execute(deps.as_mut(), mock_env(), mock_info("anyone", &[]), msg).unwrap(); + + let response_data = query( + deps.as_ref(), + mock_env(), + QueryMsg::Shuffle { + round: 42, + from: 0, + to: 4, + }, + ) + .unwrap(); + let response: ShuffleResponse = from_binary(&response_data).unwrap(); + assert_eq!(response.list, [0, 1, 4, 2, 3]); + + let response_data = query( + deps.as_ref(), + mock_env(), + QueryMsg::Shuffle { + round: 42, + from: 0, + to: 5, + }, + ) + .unwrap(); + let response: ShuffleResponse = from_binary(&response_data).unwrap(); + assert_eq!(response.list, [2, 0, 5, 3, 4, 1]); + + let response_data = query( + deps.as_ref(), + mock_env(), + QueryMsg::Shuffle { + round: 42, + from: 3, + to: 5, + }, + ) + .unwrap(); + let response: ShuffleResponse = from_binary(&response_data).unwrap(); + assert_eq!(response.list, [3, 5, 4]); + + let response_data = query( + deps.as_ref(), + mock_env(), + QueryMsg::Shuffle { + round: 42, + from: 5, + to: 5, + }, + ) + .unwrap(); + let response: ShuffleResponse = from_binary(&response_data).unwrap(); + assert_eq!(response.list, [5]); + + let err = query( + deps.as_ref(), + mock_env(), + QueryMsg::Shuffle { + round: 42, + from: 6, + to: 5, + }, + ) + .unwrap_err(); + match err { + ContractError::InvalidRange {} => {} + err => panic!("Unexpected error: {}", err), + } + + let err = query( + deps.as_ref(), + mock_env(), + QueryMsg::Shuffle { + round: 33, + from: 4, + to: 10, + }, + ) + .unwrap_err(); + match err { + ContractError::BeaconNotFound {} => {} + err => panic!("Unexpected error: {}", err), + } + } } diff --git a/src/errors.rs b/src/errors.rs index aff7d20..05496f3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -13,4 +13,8 @@ pub enum ContractError { NoFundsSent { expected_denom: String }, #[error("No beacon exists in the database")] NoBeacon {}, + #[error("Beacon does not exist for this round")] + BeaconNotFound {}, + #[error("Range is not valid")] + InvalidRange {}, } diff --git a/src/msg.rs b/src/msg.rs index 82f59c6..4373697 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -25,9 +25,18 @@ pub enum ExecuteMsg { #[serde(rename_all = "snake_case")] pub enum QueryMsg { Config {}, - Get { round: u64 }, + Get { + round: u64, + }, Latest {}, Bounties {}, + /// Creates a list of integers [from, to] and shuffles it + /// given the randomess of the provided round. + Shuffle { + round: u64, + from: u32, + to: u32, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -58,3 +67,9 @@ pub struct Bounty { pub struct BountiesResponse { pub bounties: Vec, } + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct ShuffleResponse { + /// The shuffled list + pub list: Vec, +} diff --git a/tests/integration.rs b/tests/integration.rs index 0a6c44d..30f8cbf 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -8,8 +8,10 @@ use cosmwasm_vm::testing::{ }; use std::time::Instant; -use rand::msg::{ExecuteMsg, InstantiateMsg, LatestResponse, QueryMsg}; -static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/rand.wasm"); +use rand::msg::{ExecuteMsg, InstantiateMsg, LatestResponse, QueryMsg, ShuffleResponse}; + +// static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/rand.wasm"); +static WASM: &[u8] = include_bytes!("../artifacts/rand.wasm"); fn pubkey_loe_mainnet() -> Binary { vec![ @@ -98,3 +100,53 @@ fn verify_invalid() { let gas_used = gas_before - deps.get_gas_left(); println!("Gas used: {}", gas_used); } + +#[test] +fn query_shuffle() { + let mut deps = mock_instance_with_gas_limit(WASM, 1_000_000_000_000_000); + + let msg = InstantiateMsg { + pubkey: pubkey_loe_mainnet(), + bounty_denom: BOUNTY_DENOM.into(), + }; + let info = mock_info("creator", &[]); + let _res: Response = instantiate(&mut deps, mock_env(), info.clone(), msg).unwrap(); + + let msg = ExecuteMsg::Add { + round: 72785, + previous_signature: hex::decode("a609e19a03c2fcc559e8dae14900aaefe517cb55c840f6e69bc8e4f66c8d18e8a609685d9917efbfb0c37f058c2de88f13d297c7e19e0ab24813079efe57a182554ff054c7638153f9b26a60e7111f71a0ff63d9571704905d3ca6df0b031747").unwrap().into(), + signature: hex::decode("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42").unwrap().into(), + }; + let _res: Response = execute(&mut deps, mock_env(), info, msg).unwrap(); + + // Begin query test + + let time_before = Instant::now(); + let gas_before = deps.get_gas_left(); + let response: ShuffleResponse = from_binary( + &query( + &mut deps, + mock_env(), + QueryMsg::Shuffle { + round: 72785, + from: 1, + to: 100, + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!( + response.list, + [ + 47, 84, 53, 58, 10, 69, 21, 93, 24, 87, 65, 9, 82, 83, 54, 94, 90, 20, 22, 64, 61, 50, + 39, 38, 86, 51, 27, 88, 33, 4, 25, 89, 14, 11, 66, 96, 31, 15, 30, 36, 48, 98, 32, 49, + 1, 55, 46, 29, 70, 99, 12, 78, 68, 45, 2, 5, 71, 34, 63, 23, 59, 95, 81, 40, 72, 91, + 97, 28, 8, 76, 79, 73, 26, 56, 67, 43, 37, 80, 92, 77, 74, 19, 62, 57, 13, 16, 6, 35, + 60, 100, 42, 3, 41, 85, 44, 7, 52, 75, 18, 17 + ] + ); + let gas_used = gas_before - deps.get_gas_left(); + println!("Shuffle query gas used: {}", gas_used); + println!("Shuffle query time elapsed: {:.2?}", time_before.elapsed()); +} From a5a92c0b447e8e0909685164095de92a69905ee7 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Sat, 19 Mar 2022 11:01:16 +0100 Subject: [PATCH 2/5] Use Wasm from local build in integration tests --- tests/integration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 30f8cbf..af6ce31 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -10,8 +10,8 @@ use std::time::Instant; use rand::msg::{ExecuteMsg, InstantiateMsg, LatestResponse, QueryMsg, ShuffleResponse}; -// static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/rand.wasm"); -static WASM: &[u8] = include_bytes!("../artifacts/rand.wasm"); +static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/rand.wasm"); +// static WASM: &[u8] = include_bytes!("../artifacts/rand.wasm"); fn pubkey_loe_mainnet() -> Binary { vec![ From 2a9f0754bf209a8c846360b5ba9e8966a928a3fa Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Sat, 19 Mar 2022 11:01:26 +0100 Subject: [PATCH 3/5] Switch to FisherYates --- src/contract.rs | 15 +++++++------- tests/integration.rs | 48 ++++++++++++++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 5995230..63be30e 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -8,8 +8,7 @@ use cosmwasm_std::{ use drand_verify::{derive_randomness, g1_from_variable, verify}; use rand_chacha::ChaCha8Rng; use rand_core::SeedableRng; -use shuffle::irs::Irs; -use shuffle::shuffler::Shuffler; +use shuffle::{fy::FisherYates, shuffler::Shuffler}; use crate::errors::ContractError; use crate::msg::{ @@ -206,8 +205,10 @@ fn query_shuffle( let mut rng = ChaCha8Rng::from_seed(randomness); let mut list: Vec = (from..=to).collect(); - let mut irs = Irs::default(); - irs.shuffle(&mut list, &mut rng) + + let mut shuffler = FisherYates::default(); + shuffler + .shuffle(&mut list, &mut rng) .map_err(|err| StdError::generic_err(err))?; Ok(ShuffleResponse { list }) @@ -770,7 +771,7 @@ mod tests { ) .unwrap(); let response: ShuffleResponse = from_binary(&response_data).unwrap(); - assert_eq!(response.list, [0, 1, 4, 2, 3]); + assert_eq!(response.list, [2, 1, 0, 4, 3]); let response_data = query( deps.as_ref(), @@ -783,7 +784,7 @@ mod tests { ) .unwrap(); let response: ShuffleResponse = from_binary(&response_data).unwrap(); - assert_eq!(response.list, [2, 0, 5, 3, 4, 1]); + assert_eq!(response.list, [2, 5, 0, 1, 4, 3]); let response_data = query( deps.as_ref(), @@ -796,7 +797,7 @@ mod tests { ) .unwrap(); let response: ShuffleResponse = from_binary(&response_data).unwrap(); - assert_eq!(response.list, [3, 5, 4]); + assert_eq!(response.list, [5, 4, 3]); let response_data = query( deps.as_ref(), diff --git a/tests/integration.rs b/tests/integration.rs index af6ce31..d79b6d1 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -119,10 +119,6 @@ fn query_shuffle() { }; let _res: Response = execute(&mut deps, mock_env(), info, msg).unwrap(); - // Begin query test - - let time_before = Instant::now(); - let gas_before = deps.get_gas_left(); let response: ShuffleResponse = from_binary( &query( &mut deps, @@ -130,23 +126,49 @@ fn query_shuffle() { QueryMsg::Shuffle { round: 72785, from: 1, - to: 100, + to: 65, }, ) .unwrap(), ) .unwrap(); + assert_eq!(response.list.len(), 65); assert_eq!( response.list, [ - 47, 84, 53, 58, 10, 69, 21, 93, 24, 87, 65, 9, 82, 83, 54, 94, 90, 20, 22, 64, 61, 50, - 39, 38, 86, 51, 27, 88, 33, 4, 25, 89, 14, 11, 66, 96, 31, 15, 30, 36, 48, 98, 32, 49, - 1, 55, 46, 29, 70, 99, 12, 78, 68, 45, 2, 5, 71, 34, 63, 23, 59, 95, 81, 40, 72, 91, - 97, 28, 8, 76, 79, 73, 26, 56, 67, 43, 37, 80, 92, 77, 74, 19, 62, 57, 13, 16, 6, 35, - 60, 100, 42, 3, 41, 85, 44, 7, 52, 75, 18, 17 + 13, 25, 34, 56, 18, 11, 19, 49, 41, 15, 37, 53, 5, 58, 51, 2, 52, 21, 17, 33, 59, 28, + 47, 1, 43, 29, 48, 24, 27, 8, 36, 16, 30, 44, 46, 50, 60, 54, 64, 9, 26, 35, 39, 31, + 14, 55, 6, 3, 4, 65, 40, 10, 20, 32, 12, 38, 45, 23, 61, 7, 62, 22, 63, 42, 57 ] ); - let gas_used = gas_before - deps.get_gas_left(); - println!("Shuffle query gas used: {}", gas_used); - println!("Shuffle query time elapsed: {:.2?}", time_before.elapsed()); + + // Test various list sizes + + for count in [1, 5, 10, 25, 50, 100, 250, 500, 750, 900, 1000] { + let time_before = Instant::now(); + let gas_before = deps.get_gas_left(); + let response: ShuffleResponse = from_binary( + &query( + &mut deps, + mock_env(), + QueryMsg::Shuffle { + round: 72785, + from: 1, + to: count, + }, + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(response.list.len(), count as usize); + let gas_used = gas_before - deps.get_gas_left(); + let mega_gas_per_element = (gas_used as f32 / count as f32) / 1_000_000f32; + println!( + "{} | {} | {} | {:.2?}", + count, + gas_used, + mega_gas_per_element, + time_before.elapsed() + ); + } } From ed0511a380014bd35fd9e7fdd920a3193a37f188 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Sat, 19 Mar 2022 11:03:07 +0100 Subject: [PATCH 4/5] Fix clippy warning --- src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contract.rs b/src/contract.rs index 63be30e..91ba2f6 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -209,7 +209,7 @@ fn query_shuffle( let mut shuffler = FisherYates::default(); shuffler .shuffle(&mut list, &mut rng) - .map_err(|err| StdError::generic_err(err))?; + .map_err(StdError::generic_err)?; Ok(ShuffleResponse { list }) } From e70cb85aed73d64fd545e19a20531fb9d769d447 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Sat, 19 Mar 2022 11:43:00 +0100 Subject: [PATCH 5/5] Use Xoshiro128PlusPlus --- Cargo.lock | 23 +++++++++++------------ Cargo.toml | 2 +- src/contract.rs | 19 ++++++++++++------- tests/integration.rs | 6 +++--- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd18312..ba56cb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1175,8 +1175,8 @@ dependencies = [ "cw2", "drand-verify", "hex", - "rand_chacha 0.3.1", "rand_core 0.6.1", + "rand_xoshiro", "schemars", "serde", "shuffle", @@ -1191,7 +1191,7 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha 0.2.2", + "rand_chacha", "rand_core 0.5.1", "rand_hc", ] @@ -1215,16 +1215,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.1", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -1261,6 +1251,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.1", +] + [[package]] name = "rayon" version = "1.5.1" diff --git a/Cargo.toml b/Cargo.toml index 8366ee8..2e1d2b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ cw2 = "0.13" schemars = "0.8.3" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = "1.0" -rand_chacha = { version = "0.3.1", default-features = false } +rand_xoshiro = { version = "0.6.0", default-features = false } rand_core = { version = "0.6", default-features = false } shuffle = { git = "https://github.com/webmaster128/shuffle", branch = "rm-getrandom" } diff --git a/src/contract.rs b/src/contract.rs index 91ba2f6..487e16f 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -6,8 +6,8 @@ use cosmwasm_std::{ StdResult, Storage, SubMsg, }; use drand_verify::{derive_randomness, g1_from_variable, verify}; -use rand_chacha::ChaCha8Rng; use rand_core::SeedableRng; +use rand_xoshiro::Xoshiro128PlusPlus; use shuffle::{fy::FisherYates, shuffler::Shuffler}; use crate::errors::ContractError; @@ -201,9 +201,14 @@ fn query_shuffle( let randomness = beacons .get(&round.to_be_bytes()) .ok_or(ContractError::BeaconNotFound {})?; - let randomness: [u8; 32] = randomness.try_into().unwrap(); - - let mut rng = ChaCha8Rng::from_seed(randomness); + let randomness: [u8; 16] = randomness[0..16].try_into().unwrap(); // Cut first 16 bytes from 32 byte value + + // A PRNG that is not cryptographically secure. + // See https://docs.rs/rand/0.8.5/rand/rngs/struct.SmallRng.html + // where this is used for 32 bit systems. + // We don't use the SmallRng in order to get the same implementation + // in unit tests (64 bit dev machines) and the real contract (32 bit Wasm) + let mut rng = Xoshiro128PlusPlus::from_seed(randomness); let mut list: Vec = (from..=to).collect(); let mut shuffler = FisherYates::default(); @@ -771,7 +776,7 @@ mod tests { ) .unwrap(); let response: ShuffleResponse = from_binary(&response_data).unwrap(); - assert_eq!(response.list, [2, 1, 0, 4, 3]); + assert_eq!(response.list, [4, 3, 0, 2, 1]); let response_data = query( deps.as_ref(), @@ -784,7 +789,7 @@ mod tests { ) .unwrap(); let response: ShuffleResponse = from_binary(&response_data).unwrap(); - assert_eq!(response.list, [2, 5, 0, 1, 4, 3]); + assert_eq!(response.list, [5, 0, 4, 3, 2, 1]); let response_data = query( deps.as_ref(), @@ -797,7 +802,7 @@ mod tests { ) .unwrap(); let response: ShuffleResponse = from_binary(&response_data).unwrap(); - assert_eq!(response.list, [5, 4, 3]); + assert_eq!(response.list, [5, 3, 4]); let response_data = query( deps.as_ref(), diff --git a/tests/integration.rs b/tests/integration.rs index d79b6d1..16162e0 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -136,9 +136,9 @@ fn query_shuffle() { assert_eq!( response.list, [ - 13, 25, 34, 56, 18, 11, 19, 49, 41, 15, 37, 53, 5, 58, 51, 2, 52, 21, 17, 33, 59, 28, - 47, 1, 43, 29, 48, 24, 27, 8, 36, 16, 30, 44, 46, 50, 60, 54, 64, 9, 26, 35, 39, 31, - 14, 55, 6, 3, 4, 65, 40, 10, 20, 32, 12, 38, 45, 23, 61, 7, 62, 22, 63, 42, 57 + 41, 53, 24, 43, 18, 47, 45, 11, 51, 39, 5, 32, 7, 33, 56, 30, 27, 55, 15, 21, 31, 9, 2, + 61, 17, 28, 48, 19, 46, 37, 4, 34, 29, 8, 10, 13, 65, 38, 64, 16, 26, 50, 35, 6, 49, + 59, 58, 63, 12, 42, 62, 23, 52, 60, 44, 20, 57, 40, 22, 3, 25, 14, 54, 1, 36 ] );