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 rs/consensus/src/consensus/batch_delivery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ use ic_consensus_idkg::utils::{
generate_responses_to_signature_request_contexts,
get_idkg_subnet_public_keys_and_pre_signatures,
};
use ic_consensus_utils::{
crypto_hashable_to_seed, membership::Membership, pool_reader::PoolReader,
};
use ic_consensus_utils::{membership::Membership, pool_reader::PoolReader};
use ic_consensus_vetkd::VetKdPayloadBuilderImpl;
use ic_error_types::RejectCode;
use ic_https_outcalls_consensus::payload_builder::CanisterHttpPayloadBuilderImpl;
Expand All @@ -28,6 +26,7 @@ use ic_protobuf::{
log::consensus_log_entry::v1::ConsensusLogEntry,
registry::{crypto::v1::PublicKey as PublicKeyProto, subnet::v1::InitialNiDkgTranscriptRecord},
};
use ic_types::crypto::crypto_hashable_to_randomness;
use ic_types::{
Height, PrincipalId, Randomness, SubnetId,
batch::{
Expand Down Expand Up @@ -167,7 +166,7 @@ pub(crate) fn deliver_batches_with_result_processor(
}
}

let randomness = Randomness::from(crypto_hashable_to_seed(&tape));
let randomness = crypto_hashable_to_randomness(&tape);

// Retrieve the dkg summary block
let Some(summary_block) = pool.dkg_summary_block_for_finalized_height(height) else {
Expand Down
12 changes: 1 addition & 11 deletions rs/consensus/utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use ic_types::{
Height, NodeId, RegistryVersion, ReplicaVersion, SubnetId,
consensus::{Block, BlockProposal, HasCommittee, HasHeight, HasRank, Threshold},
crypto::{
CryptoHash, CryptoHashable, Signed,
Signed,
threshold_sig::ni_dkg::{NiDkgId, NiDkgReceivers, NiDkgTag, NiDkgTranscript},
},
};
Expand Down Expand Up @@ -56,16 +56,6 @@ impl RoundRobin {
}
}

/// Convert a CryptoHashable into a 32 bytes which can be used to seed a RNG
pub fn crypto_hashable_to_seed<T: CryptoHashable>(hashable: &T) -> [u8; 32] {
let hash = ic_types::crypto::crypto_hash(hashable);
let CryptoHash(hash_bytes) = hash.get();
let mut seed = [0; 32]; // zero padded if digest is less than 32 bytes
let n = hash_bytes.len().min(32);
seed[0..n].copy_from_slice(&hash_bytes[0..n]);
seed
}

/// Return the validated block proposals with the lowest rank at height `h` that
/// have not been disqualified, if there are any. Else, return an empty Vec.
pub fn find_lowest_ranked_non_disqualified_proposals(
Expand Down
3 changes: 2 additions & 1 deletion rs/consensus/utils/src/membership.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ impl Membership {
// `sort_unstable` is effectively the same as `sort` but slightly more
// efficient.
node_ids.sort_unstable();
let mut rng = Csprng::from_random_beacon_and_purpose(previous_beacon, purpose);
let seed = Csprng::seed_from_random_beacon(previous_beacon);
let mut rng = Csprng::from_seed_and_purpose(seed, purpose);
node_ids.shuffle(&mut rng);
Ok(node_ids)
}
Expand Down
37 changes: 13 additions & 24 deletions rs/crypto/prng/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! Offers cryptographically secure pseudorandom number generation (CSPRNG).
use RandomnessPurpose::*;
use ic_crypto_internal_seed::Seed;
use ic_types::Randomness;
use ic_types::consensus::RandomBeacon;
use ic_types::crypto::CryptoHashable;
use ic_types::{Randomness, crypto::crypto_hash};
use ic_types::crypto::crypto_hashable_to_randomness;
use rand::{CryptoRng, Error, RngCore};
use rand_chacha::ChaCha20Rng;
use std::fmt;
Expand All @@ -30,34 +30,23 @@ pub struct Csprng {
}

impl Csprng {
/// Creates a CSPRNG from the given random beacon for the given purpose.
pub fn from_random_beacon_and_purpose(
random_beacon: &RandomBeacon,
purpose: &RandomnessPurpose,
) -> Self {
let seed = Self::seed_from_crypto_hashable(random_beacon);
let seed_for_purpose = seed.derive(&purpose.domain_separator());
Csprng::from_seed(seed_for_purpose)
}

/// Creates a CSPRNG from the given seed for the given purpose.
pub fn from_seed_and_purpose(seed: &Randomness, purpose: &RandomnessPurpose) -> Self {
let seed = Seed::from_bytes(&seed.get());
pub fn from_seed_and_purpose(seed: Seed, purpose: &RandomnessPurpose) -> Self {
let seed_for_purpose = seed.derive(&purpose.domain_separator());
Csprng::from_seed(seed_for_purpose)
}

/// Creates a CSPRNG from the given seed.
fn from_seed(seed: ic_crypto_internal_seed::Seed) -> Self {
Csprng {
rng: seed.into_rng(),
rng: seed_for_purpose.into_rng(),
}
}

/// Creates a CSPRNG seed from the given crypto hashable.
fn seed_from_crypto_hashable<T: CryptoHashable>(crypto_hashable: &T) -> Seed {
let hash = crypto_hash(crypto_hashable);
Seed::from_bytes(&hash.get().0)
/// Creates a seed from the given randomness.
pub fn seed_from_randomness(randomness: &Randomness) -> Seed {
Seed::from_bytes(&randomness.get())
}

/// Creates a seed from the given random beacon.
pub fn seed_from_random_beacon(random_beacon: &RandomBeacon) -> Seed {
let randomness = crypto_hashable_to_randomness(random_beacon);
Csprng::seed_from_randomness(&randomness)
}
}

Expand Down
60 changes: 42 additions & 18 deletions rs/crypto/prng/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,21 @@ fn should_use_unique_domain_separator_per_randomness_purpose() {

#[test]
fn should_incorporate_crypto_hash_domain_when_generating_randomness_for_random_beacon() {
// Because the crypto hash domain of random beacons is hardcoded and cannot be
// controlled from within a test, the only way to ensure that the crypto
// hash domain of the random beacon is incorporated when generating the
// randomness is to test the actual expected implementation.
// Verify that the seed derivation from a random beacon incorporates the crypto
// hash domain by checking that different random beacons produce different seeds.

let rb1 = fake_random_beacon(1);
let rb2 = fake_random_beacon(2);

let rb = fake_random_beacon(1);
for purpose in RandomnessPurpose::iter() {
// Replicate the implementation: hash the random beacon, create a seed, derive for purpose
let hash = ic_types::crypto::crypto_hash(&rb);
let seed = ic_crypto_internal_seed::Seed::from_bytes(&hash.get().0);
let seed_for_purpose = seed.derive(&purpose.domain_separator());
let mut csprng = Csprng::from_seed(seed_for_purpose);
let seed1 = Csprng::seed_from_random_beacon(&rb1);
let seed2 = Csprng::seed_from_random_beacon(&rb2);

assert_eq!(
Csprng::from_random_beacon_and_purpose(&rb, &purpose).next_u32(),
csprng.next_u32()
)
let mut csprng1 = Csprng::from_seed_and_purpose(seed1, &purpose);
let mut csprng2 = Csprng::from_seed_and_purpose(seed2, &purpose);

// Different random beacons should produce different randomness
assert_ne!(csprng1.next_u32(), csprng2.next_u32());
}
}

Expand All @@ -61,11 +59,12 @@ fn fake_dkg_id(h: u64) -> NiDkgId {

#[test]
fn should_produce_different_randomness_for_execution_thread_edge_cases() {
let seed = ic_types::Randomness::new([99; 32]);
let randomness = ic_types::Randomness::new([99; 32]);
let seed = Csprng::seed_from_randomness(&randomness);

let mut rng_0 = Csprng::from_seed_and_purpose(&seed, &ExecutionThread(0));
let mut rng_max = Csprng::from_seed_and_purpose(&seed, &ExecutionThread(u32::MAX));
let mut rng_1 = Csprng::from_seed_and_purpose(&seed, &ExecutionThread(1));
let mut rng_0 = Csprng::from_seed_and_purpose(seed.clone(), &ExecutionThread(0));
let mut rng_max = Csprng::from_seed_and_purpose(seed.clone(), &ExecutionThread(u32::MAX));
let mut rng_1 = Csprng::from_seed_and_purpose(seed, &ExecutionThread(1));

let val_0 = rng_0.next_u32();
let val_max = rng_max.next_u32();
Expand All @@ -77,6 +76,31 @@ fn should_produce_different_randomness_for_execution_thread_edge_cases() {
assert_ne!(val_max, val_1);
}

#[test]
fn seed_from_random_beacon_should_match_manual_extraction_via_crypto_hashable_to_randomness() {
let rb = fake_random_beacon(42);

// Direct path: seed_from_random_beacon
let seed_direct = Csprng::seed_from_random_beacon(&rb);

// Manual path: crypto_hashable_to_randomness -> seed_from_randomness
let randomness = crypto_hashable_to_randomness(&rb);
let seed_manual = Csprng::seed_from_randomness(&randomness);

// Both seeds should produce the same CSPRNG output for all purposes
for purpose in RandomnessPurpose::iter() {
let mut rng_direct = Csprng::from_seed_and_purpose(seed_direct.clone(), &purpose);
let mut rng_manual = Csprng::from_seed_and_purpose(seed_manual.clone(), &purpose);

assert_eq!(
rng_direct.next_u64(),
rng_manual.next_u64(),
"Mismatch for purpose {:?}",
purpose
);
}
}

fn fake_random_beacon(height: u64) -> RandomBeacon {
Signed {
content: RandomBeaconContent::new(
Expand Down
63 changes: 39 additions & 24 deletions rs/crypto/prng/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,29 +43,32 @@ fn should_produce_deterministic_randomness_from_random_beacon_and_purpose() {
fix_replica_version();

let random_beacon = fake_random_beacon(1);
let seed = Csprng::seed_from_random_beacon(&random_beacon);

let mut rng = Csprng::from_random_beacon_and_purpose(&random_beacon, &BlockmakerRanking);
let mut rng = Csprng::from_seed_and_purpose(seed, &BlockmakerRanking);

assert_eq!(rng.next_u32(), 1_242_121_839);
}

#[test]
fn should_produce_deterministic_randomness_from_seed_and_purpose() {
fn should_produce_deterministic_randomness_from_randomness_and_purpose() {
fix_replica_version();

let seed = seed();
let randomness = seed();
let crypto_seed = Csprng::seed_from_randomness(&randomness);

let mut rng = Csprng::from_seed_and_purpose(&seed, &CommitteeSampling);
let mut rng = Csprng::from_seed_and_purpose(crypto_seed, &CommitteeSampling);

assert_eq!(rng.next_u32(), 2_206_231_697);
}

#[test]
fn should_offer_methods_of_rng_trait() {
use rand::Rng;
let seed = seed();
let randomness = seed();
let crypto_seed = Csprng::seed_from_randomness(&randomness);

let mut rng = Csprng::from_seed_and_purpose(&seed, &CommitteeSampling);
let mut rng = Csprng::from_seed_and_purpose(crypto_seed, &CommitteeSampling);

assert_eq!(rng.r#gen::<u32>(), 2_206_231_697);
}
Expand All @@ -75,10 +78,11 @@ fn should_generate_purpose_specific_randomness_for_random_beacon() {
fix_replica_version();

let rb = random_beacon();
let crypto_seed = Csprng::seed_from_random_beacon(&rb);

let mut rng_cs = Csprng::from_random_beacon_and_purpose(&rb, &CommitteeSampling);
let mut rng_br = Csprng::from_random_beacon_and_purpose(&rb, &BlockmakerRanking);
let mut rng_et = Csprng::from_random_beacon_and_purpose(&rb, &ExecutionThread(0));
let mut rng_cs = Csprng::from_seed_and_purpose(crypto_seed.clone(), &CommitteeSampling);
let mut rng_br = Csprng::from_seed_and_purpose(crypto_seed.clone(), &BlockmakerRanking);
let mut rng_et = Csprng::from_seed_and_purpose(crypto_seed, &ExecutionThread(0));

let mut set = BTreeSet::new();
assert!(set.insert(rng_cs.next_u32()));
Expand All @@ -91,11 +95,12 @@ fn should_generate_purpose_specific_randomness_for_random_beacon() {

#[test]
fn should_generate_purpose_specific_randomness_for_randomness_seed() {
let seed = seed();
let randomness = seed();
let crypto_seed = Csprng::seed_from_randomness(&randomness);

let mut rng_cs = Csprng::from_seed_and_purpose(&seed, &CommitteeSampling);
let mut rng_br = Csprng::from_seed_and_purpose(&seed, &BlockmakerRanking);
let mut rng_et = Csprng::from_seed_and_purpose(&seed, &ExecutionThread(0));
let mut rng_cs = Csprng::from_seed_and_purpose(crypto_seed.clone(), &CommitteeSampling);
let mut rng_br = Csprng::from_seed_and_purpose(crypto_seed.clone(), &BlockmakerRanking);
let mut rng_et = Csprng::from_seed_and_purpose(crypto_seed, &ExecutionThread(0));

let mut set = BTreeSet::new();
assert!(set.insert(rng_cs.next_u32()));
Expand All @@ -114,20 +119,26 @@ fn should_produce_different_randomness_for_same_purpose_for_different_random_bea
assert_ne!(rb1, rb2);
let purpose = CommitteeSampling;

let mut csprng1 = Csprng::from_random_beacon_and_purpose(&rb1, &purpose);
let mut csprng2 = Csprng::from_random_beacon_and_purpose(&rb2, &purpose);
let seed1 = Csprng::seed_from_random_beacon(&rb1);
let seed2 = Csprng::seed_from_random_beacon(&rb2);

let mut csprng1 = Csprng::from_seed_and_purpose(seed1, &purpose);
let mut csprng2 = Csprng::from_seed_and_purpose(seed2, &purpose);

assert_ne!(csprng1.next_u32(), csprng2.next_u32());
}

#[test]
fn should_produce_different_randomness_for_same_purpose_for_different_randomness_seeds() {
let (s1, s2) = (seed(), seed_2());
assert_ne!(s1, s2);
let (r1, r2) = (seed(), seed_2());
assert_ne!(r1, r2);
let purpose = CommitteeSampling;

let mut csprng1 = Csprng::from_seed_and_purpose(&s1, &purpose);
let mut csprng2 = Csprng::from_seed_and_purpose(&s2, &purpose);
let seed1 = Csprng::seed_from_randomness(&r1);
let seed2 = Csprng::seed_from_randomness(&r2);

let mut csprng1 = Csprng::from_seed_and_purpose(seed1, &purpose);
let mut csprng2 = Csprng::from_seed_and_purpose(seed2, &purpose);

assert_ne!(csprng1.next_u32(), csprng2.next_u32());
}
Expand All @@ -137,23 +148,27 @@ fn should_produce_different_randomness_for_different_execution_threads_for_rando
fix_replica_version();

let rb = random_beacon();
let crypto_seed = Csprng::seed_from_random_beacon(&rb);
let (thread_1, thread_2) = (1, 2);
assert_ne!(thread_1, thread_2);

let mut csprng1 = Csprng::from_random_beacon_and_purpose(&rb, &ExecutionThread(thread_1));
let mut csprng2 = Csprng::from_random_beacon_and_purpose(&rb, &ExecutionThread(thread_2));
let mut csprng1 =
Csprng::from_seed_and_purpose(crypto_seed.clone(), &ExecutionThread(thread_1));
let mut csprng2 = Csprng::from_seed_and_purpose(crypto_seed, &ExecutionThread(thread_2));

assert_ne!(csprng1.next_u32(), csprng2.next_u32());
}

#[test]
fn should_produce_different_randomness_for_different_execution_threads_for_randomness_seed() {
let seed = seed();
let randomness = seed();
let crypto_seed = Csprng::seed_from_randomness(&randomness);
let (thread_1, thread_2) = (1, 2);
assert_ne!(thread_1, thread_2);

let mut csprng1 = Csprng::from_seed_and_purpose(&seed, &ExecutionThread(thread_1));
let mut csprng2 = Csprng::from_seed_and_purpose(&seed, &ExecutionThread(thread_2));
let mut csprng1 =
Csprng::from_seed_and_purpose(crypto_seed.clone(), &ExecutionThread(thread_1));
let mut csprng2 = Csprng::from_seed_and_purpose(crypto_seed, &ExecutionThread(thread_2));

assert_ne!(csprng1.next_u32(), csprng2.next_u32());
}
Expand Down
3 changes: 2 additions & 1 deletion rs/execution_environment/src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1300,8 +1300,9 @@ impl Scheduler for SchedulerImpl {
// passing the number of scheduler cores is ok. It would need to be
// updated in case the execution of subnet messages is running across
// many threads to ensure a unique execution thread id.
let seed = Csprng::seed_from_randomness(&randomness);
csprng = Csprng::from_seed_and_purpose(
&randomness,
seed,
&ExecutionThread(self.config.scheduler_cores as u32),
);

Expand Down
3 changes: 2 additions & 1 deletion rs/execution_environment/src/scheduler/test_utilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,9 @@ impl SchedulerTest {
pub fn drain_subnet_messages(&mut self) -> ReplicatedState {
let state = self.state.take().unwrap();
let compute_allocation_used = state.total_compute_allocation();
let seed = Csprng::seed_from_randomness(&Randomness::from([0; 32]));
let mut csprng = Csprng::from_seed_and_purpose(
&Randomness::from([0; 32]),
seed,
&ExecutionThread(self.scheduler.config.scheduler_cores as u32),
);
let mut round_limits = RoundLimits {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ mod tests {
vec![],
pre_signature_stashes,
&mut Csprng::from_seed_and_purpose(
&Randomness::new([1; 32]),
Csprng::seed_from_randomness(&Randomness::new([1; 32])),
&ic_crypto_prng::RandomnessPurpose::ExecutionThread(1),
),
&RegistryExecutionSettings {
Expand Down
Loading
Loading