canic_core/ops/
random.rs

1//! Randomness seeding helpers.
2
3use crate::{
4    config::schema::{RandomnessConfig, RandomnessSource},
5    log,
6    log::Topic,
7    ops::{
8        config::ConfigOps,
9        ic::{mgmt, timer::TimerOps},
10    },
11};
12use canic_utils::rand as rand_utils;
13use sha2::{Digest, Sha256};
14use std::time::Duration;
15
16// -----------------------------------------------------------------------------
17// RandomOps
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        Self::schedule_seeding(Duration::ZERO, interval, cfg.source);
48    }
49
50    async fn seed_once(source: RandomnessSource) {
51        match source {
52            RandomnessSource::Ic => match mgmt::raw_rand().await {
53                Ok(seed) => {
54                    log!(Topic::Init, Warn, "seed_from({seed:?})");
55                    rand_utils::seed_from(seed);
56                }
57                Err(err) => {
58                    crate::log!(Topic::Init, Warn, "raw_rand reseed failed: {err}");
59                }
60            },
61            RandomnessSource::Time => Self::seed_from_time(),
62        }
63    }
64
65    fn schedule_seeding(delay: Duration, interval: Duration, source: RandomnessSource) {
66        let _ = TimerOps::set(delay, "random:seed", async move {
67            Self::seed_once(source).await;
68            Self::schedule_seeding(interval, interval, source);
69        });
70    }
71
72    fn randomness_config() -> RandomnessConfig {
73        match ConfigOps::current_canister() {
74            Ok(cfg) => cfg.randomness,
75            Err(err) => {
76                crate::log!(
77                    Topic::Init,
78                    Warn,
79                    "randomness config unavailable, using defaults: {err}"
80                );
81                RandomnessConfig::default()
82            }
83        }
84    }
85
86    fn seed_from_time() {
87        let now = crate::cdk::api::time();
88        let canister_id = crate::cdk::api::canister_self();
89
90        let mut hasher = Sha256::new();
91        hasher.update(now.to_be_bytes());
92        hasher.update(canister_id.as_slice());
93        let seed: [u8; 32] = hasher.finalize().into();
94
95        rand_utils::seed_from(seed);
96    }
97}