Seeded RNG generates same random number on every call

I’ve been trying to configure a random number generator for generating nonce strings. I seed it using raw_rand and store it in a thread local static.

thread_local! {
  static RNG: RefCell<Option<ChaCha20Rng>> = RefCell::new(None);

// from init fn
ic_cdk_timers::set_timer(Duration::ZERO, || {
    ic_cdk::spawn(async {
        let (seed,): ([u8; 32],) =
            ic_cdk::call(Principal::management_canister(), "raw_rand", ())
        RNG.with(|rng| *rng.borrow_mut() = Some(ChaCha20Rng::from_seed(seed)));

In canister calls I then use this generate_nonce function.

fn generate_nonce() -> Result<[u8; 10], String> {
    let mut buf = [0u8; 10];
    RNG.with(|rng| rng.borrow_mut().as_mut().unwrap().fill_bytes(&mut buf));


  • Calling generate_nonce in two separate canister calls returns the same nonce
  • Calling generate_nonce two times within the same canister call returns different nonces (as expected)

The RNG state does not seem to be persisted in between canister calls. @AdamS I tried to follow one of your examples when setting this up. Any ideas?

  • Calling generate_nonce in two separate canister calls returns the same nonce

@kristofer : thanks for the report! Just to clarify, are observing this issue in dfx or on the mainnet?

In dfx, haven’t pushed to mainnet yet.

Are those query or update calls?

A minimal example reproducing the issue would help here…


Will do, stay tuned.

Here is a minimal repo with a query call: GitHub - kristoferlund/rng

Thanks for preparing the repo. The issue is in the query call. All RNG modifications done by a query are discarded at the end of a query. That’s because queries are read-only by design.

If you change this line from query to update, then it should work as expected.


Ah, that makes sense of course. Thanks!