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::time::Duration;
14
15// -----------------------------------------------------------------------------
16// RandomOps
17// -----------------------------------------------------------------------------
18
19///
20/// RandomOps
21/// Schedules PRNG seeding from the configured source (IC `raw_rand` or time).
22///
23
24pub struct RandomOps;
25
26impl RandomOps {
27    /// Start the periodic seeding timers.
28    pub fn start() {
29        let cfg = Self::randomness_config();
30        if !cfg.enabled {
31            crate::log!(Topic::Init, Info, "randomness seeding disabled by config");
32            return;
33        }
34
35        let interval_secs = cfg.reseed_interval_secs;
36        if interval_secs == 0 {
37            crate::log!(
38                Topic::Init,
39                Warn,
40                "randomness reseed_interval_secs is 0; seeding disabled"
41            );
42            return;
43        }
44
45        let interval = Duration::from_secs(interval_secs);
46        Self::schedule_seeding(Duration::ZERO, interval, cfg.source);
47    }
48
49    async fn seed_once(source: RandomnessSource) {
50        match source {
51            RandomnessSource::Ic => match mgmt::raw_rand().await {
52                Ok(seed) => rand_utils::seed_from(seed),
53                Err(err) => {
54                    crate::log!(Topic::Init, Warn, "raw_rand reseed failed: {err}");
55                }
56            },
57            RandomnessSource::Time => Self::seed_from_time(),
58        }
59    }
60
61    fn schedule_seeding(delay: Duration, interval: Duration, source: RandomnessSource) {
62        let _ = TimerOps::set(delay, "random:seed", async move {
63            Self::seed_once(source).await;
64            Self::schedule_seeding(interval, interval, source);
65        });
66    }
67
68    fn randomness_config() -> RandomnessConfig {
69        match ConfigOps::current_canister() {
70            Ok(cfg) => cfg.randomness,
71            Err(err) => {
72                crate::log!(
73                    Topic::Init,
74                    Warn,
75                    "randomness config unavailable, using defaults: {err}"
76                );
77                RandomnessConfig::default()
78            }
79        }
80    }
81
82    fn seed_from_time() {
83        let now = crate::cdk::api::time();
84        let canister_id = crate::cdk::api::canister_self();
85
86        let mut hasher = Sha256::new();
87        hasher.update(now.to_be_bytes());
88        hasher.update(canister_id.as_slice());
89        let seed: [u8; 32] = hasher.finalize().into();
90
91        rand_utils::seed_from(seed);
92    }
93}