canic_core/ops/
random.rs

1//! Randomness seeding helpers.
2
3use crate::{
4    config::schema::{RandomnessConfig, RandomnessSource},
5    log::Topic,
6    ops::{
7        config::ConfigOps,
8        ic::{mgmt, timer::TimerOps},
9    },
10};
11use canic_utils::rand as rand_utils;
12use sha2::{Digest, Sha256};
13use std::{cell::RefCell, time::Duration};
14
15thread_local! {
16    static SEED_TIMER: RefCell<Option<crate::ops::ic::timer::TimerId>> =
17        const { RefCell::new(None) };
18}
19
20///
21/// RandomOps
22/// Schedules PRNG seeding from the configured source (IC `raw_rand` or time).
23///
24
25pub struct RandomOps;
26
27impl RandomOps {
28    /// Start the periodic seeding timers.
29    pub fn start() {
30        let cfg = Self::randomness_config();
31        if !cfg.enabled {
32            crate::log!(Topic::Init, Info, "randomness seeding disabled by config");
33            return;
34        }
35
36        let interval_secs = cfg.reseed_interval_secs;
37        if interval_secs == 0 {
38            crate::log!(
39                Topic::Init,
40                Warn,
41                "randomness reseed_interval_secs is 0; seeding disabled"
42            );
43            return;
44        }
45
46        let interval = Duration::from_secs(interval_secs);
47        let source = cfg.source;
48        let _ = TimerOps::set_guarded_interval(
49            &SEED_TIMER,
50            Duration::ZERO,
51            "random:seed:init",
52            move || async move {
53                Self::seed_once(source).await;
54            },
55            interval,
56            "random:seed:interval",
57            move || async move {
58                Self::seed_once(source).await;
59            },
60        );
61    }
62
63    async fn seed_once(source: RandomnessSource) {
64        match source {
65            RandomnessSource::Ic => match mgmt::raw_rand().await {
66                Ok(seed) => rand_utils::seed_from(seed),
67                Err(err) => {
68                    crate::log!(Topic::Init, Warn, "raw_rand reseed failed: {err}");
69                }
70            },
71            RandomnessSource::Time => Self::seed_from_time(),
72        }
73    }
74
75    fn randomness_config() -> RandomnessConfig {
76        ConfigOps::current_canister().randomness
77    }
78
79    fn seed_from_time() {
80        let now = crate::cdk::api::time();
81        let canister_id = crate::cdk::api::canister_self();
82
83        let mut hasher = Sha256::new();
84        hasher.update(now.to_be_bytes());
85        hasher.update(canister_id.as_slice());
86        let seed: [u8; 32] = hasher.finalize().into();
87
88        rand_utils::seed_from(seed);
89    }
90}