From ccabc096881343e454ab62642894e6c7547aa78e Mon Sep 17 00:00:00 2001 From: jeshli Date: Thu, 30 Oct 2025 13:55:27 -0400 Subject: [PATCH 1/7] ic backend --- src/backends.rs | 3 ++ src/backends/internet_computer.rs | 70 +++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src/backends/internet_computer.rs diff --git a/src/backends.rs b/src/backends.rs index 991d413b5..8d27a4beb 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -11,6 +11,9 @@ cfg_if! { if #[cfg(getrandom_backend = "custom")] { mod custom; pub use custom::*; + } else if #[cfg(getrandom_backend = "ic")] { + mod internet_computer; + pub use internet_computer::*; } else if #[cfg(getrandom_backend = "linux_getrandom")] { mod getrandom; mod sanitizer; diff --git a/src/backends/internet_computer.rs b/src/backends/internet_computer.rs new file mode 100644 index 000000000..5a3f9f533 --- /dev/null +++ b/src/backends/internet_computer.rs @@ -0,0 +1,70 @@ +//! Backend for Internet Computer (IC) WASM canisters +//! +//! This backend uses the IC system API's raw_rand function to generate +//! cryptographically secure random bytes. + +use crate::Error; +use core::mem::MaybeUninit; + +// IC system API for randomness +// See: https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-random +#[link(wasm_import_module = "ic0")] +extern "C" { + fn raw_rand(dst: *mut u8, len: u32); +} + +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let len = dest.len(); + if len == 0 { + return Ok(()); + } + + // SAFETY: + // - IC's raw_rand will write exactly `len` bytes to `dst` + // - The pointer is valid for writes of `len` bytes + // - raw_rand initializes all bytes in the buffer + unsafe { + let ptr = dest.as_mut_ptr().cast::(); + raw_rand(ptr, len as u32); + } + + Ok(()) +} + +pub fn inner_u32() -> Result { + let mut buf = [MaybeUninit::::uninit(); 4]; + fill_inner(&mut buf)?; + + // SAFETY: fill_inner succeeded, so buf is fully initialized + let buf = unsafe { + [ + buf[0].assume_init(), + buf[1].assume_init(), + buf[2].assume_init(), + buf[3].assume_init(), + ] + }; + + Ok(u32::from_ne_bytes(buf)) +} + +pub fn inner_u64() -> Result { + let mut buf = [MaybeUninit::::uninit(); 8]; + fill_inner(&mut buf)?; + + // SAFETY: fill_inner succeeded, so buf is fully initialized + let buf = unsafe { + [ + buf[0].assume_init(), + buf[1].assume_init(), + buf[2].assume_init(), + buf[3].assume_init(), + buf[4].assume_init(), + buf[5].assume_init(), + buf[6].assume_init(), + buf[7].assume_init(), + ] + }; + + Ok(u64::from_ne_bytes(buf)) +} \ No newline at end of file From 3e643d1822c7e5ec387954b0002e5ce4501594c9 Mon Sep 17 00:00:00 2001 From: jeshli Date: Thu, 30 Oct 2025 14:28:58 -0400 Subject: [PATCH 2/7] determinisitc stub --- src/backends/internet_computer.rs | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/backends/internet_computer.rs b/src/backends/internet_computer.rs index 5a3f9f533..fb7bccb0e 100644 --- a/src/backends/internet_computer.rs +++ b/src/backends/internet_computer.rs @@ -1,31 +1,27 @@ //! Backend for Internet Computer (IC) WASM canisters //! -//! This backend uses the IC system API's raw_rand function to generate -//! cryptographically secure random bytes. +//! Uses deterministic values since IC's raw_rand is async-only +//! and cannot be called synchronously from getrandom. +//! +//! This is safe for inference workloads that don't require cryptographic randomness. use crate::Error; use core::mem::MaybeUninit; -// IC system API for randomness -// See: https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-random -#[link(wasm_import_module = "ic0")] -extern "C" { - fn raw_rand(dst: *mut u8, len: u32); -} - pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { let len = dest.len(); if len == 0 { return Ok(()); } - // SAFETY: - // - IC's raw_rand will write exactly `len` bytes to `dst` - // - The pointer is valid for writes of `len` bytes - // - raw_rand initializes all bytes in the buffer - unsafe { - let ptr = dest.as_mut_ptr().cast::(); - raw_rand(ptr, len as u32); + // Deterministic fill pattern - repeatable but sufficient for non-crypto use + // Uses a simple PRNG-like pattern for better distribution + for (i, byte) in dest.iter_mut().enumerate() { + unsafe { + // Simple mixing function for better value distribution + let val = (i.wrapping_mul(73).wrapping_add(197)) as u8; + byte.write(val); + } } Ok(()) @@ -35,7 +31,6 @@ pub fn inner_u32() -> Result { let mut buf = [MaybeUninit::::uninit(); 4]; fill_inner(&mut buf)?; - // SAFETY: fill_inner succeeded, so buf is fully initialized let buf = unsafe { [ buf[0].assume_init(), @@ -52,7 +47,6 @@ pub fn inner_u64() -> Result { let mut buf = [MaybeUninit::::uninit(); 8]; fill_inner(&mut buf)?; - // SAFETY: fill_inner succeeded, so buf is fully initialized let buf = unsafe { [ buf[0].assume_init(), From d124e30ae63150d696edfbb53acccd85d28092a7 Mon Sep 17 00:00:00 2001 From: jeshli Date: Mon, 3 Nov 2025 21:06:45 -0500 Subject: [PATCH 3/7] Ignore .idea directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 51778e4a6..0f082cdee 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ **/*.rs.bk nopanic_check/Cargo.lock nopanic_check/target/ +.idea/ From 78208c9f3cd8e50c9e994633c615b9e1387f70c7 Mon Sep 17 00:00:00 2001 From: jeshli Date: Wed, 12 Nov 2025 22:20:05 -0500 Subject: [PATCH 4/7] use subnet time to seed PRNG --- Cargo.toml | 3 +++ src/backends/internet_computer.rs | 36 ++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 933b9d9cb..4cb4f351c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,9 @@ libc = { version = "0.2.154", default-features = false } [target.'cfg(all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"))'.dependencies] wasip2 = { version = "1", default-features = false } +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +ic-cdk = { version = "0.18.7", default-features = false } + # wasm_js [target.'cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))'.dependencies] wasm-bindgen = { version = "0.2.98", default-features = false, optional = true } diff --git a/src/backends/internet_computer.rs b/src/backends/internet_computer.rs index fb7bccb0e..2028b74c3 100644 --- a/src/backends/internet_computer.rs +++ b/src/backends/internet_computer.rs @@ -7,6 +7,19 @@ use crate::Error; use core::mem::MaybeUninit; +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] +use ic_cdk::api::time; + +// SplitMix64: tiny, fast, good distribution for non-crypto use +#[inline] +fn splitmix64_next(x: &mut u64) -> u64 { + *x = x.wrapping_add(0x9E3779B97F4A7C15); + let mut z = *x; + z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9); + z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB); + z ^ (z >> 31) +} + pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { let len = dest.len(); @@ -14,16 +27,23 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { return Ok(()); } - // Deterministic fill pattern - repeatable but sufficient for non-crypto use - // Uses a simple PRNG-like pattern for better distribution - for (i, byte) in dest.iter_mut().enumerate() { - unsafe { - // Simple mixing function for better value distribution - let val = (i.wrapping_mul(73).wrapping_add(197)) as u8; - byte.write(val); + // Consensus timestamp: deterministic across replicas in an UPDATE call. + // (In a QUERY it is *not* consensus-certified.) + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + let mut state = (time() as u64) ^ ((len as u64).wrapping_mul(0x9E3779B97F4A7C15)); + + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] + let mut state = (len as u64).wrapping_mul(0x9E3779B97F4A7C15); // fallback seed + + let mut i = 0; + while i < len { + let word = splitmix64_next(&mut state).to_le_bytes(); + let n = core::cmp::min(8, len - i); + for j in 0..n { + unsafe { dest[i + j].write(word[j]); } } + i += n; } - Ok(()) } From e43e786522907d618175889f92f9fd10ae238ade Mon Sep 17 00:00:00 2001 From: jeshli Date: Thu, 4 Dec 2025 11:20:24 -0500 Subject: [PATCH 5/7] ic-cdk only on ic, not all wasm32; improve randomness entropy --- Cargo.toml | 4 +-- src/backends/internet_computer.rs | 59 ++++++++++++------------------- 2 files changed, 24 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bf9b1a68f..43abc7de7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ libc = { version = "0.2.154", default-features = false } [target.'cfg(all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"))'.dependencies] wasip2 = { version = "1", default-features = false } -[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown", getrandom_backend = "ic"))'.dependencies] ic-cdk = { version = "0.18.7", default-features = false } # wasm_js @@ -80,7 +80,7 @@ wasm-bindgen-test = "0.3" [lints.rust.unexpected_cfgs] level = "warn" check-cfg = [ - 'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "wasm_js", "windows_legacy", "unsupported"))', + 'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "wasm_js", "windows_legacy", "unsupported", "ic"))', 'cfg(getrandom_msan)', 'cfg(getrandom_test_linux_fallback)', 'cfg(getrandom_test_linux_without_fallback)', diff --git a/src/backends/internet_computer.rs b/src/backends/internet_computer.rs index 2028b74c3..5e4f5389a 100644 --- a/src/backends/internet_computer.rs +++ b/src/backends/internet_computer.rs @@ -1,16 +1,22 @@ //! Backend for Internet Computer (IC) WASM canisters //! -//! Uses deterministic values since IC's raw_rand is async-only -//! and cannot be called synchronously from getrandom. +//! # ⚠️ WARNING: NOT CRYPTOGRAPHICALLY SECURE //! -//! This is safe for inference workloads that don't require cryptographic randomness. +//! This backend provides **deterministic pseudo-random bytes** seeded from +//! canister execution state. It exists solely to satisfy dependencies that +//! require `getrandom` for non-security purposes (e.g., ML inference, +//! hash table initialization). +//! +//! **DO NOT USE** for key generation, nonces, tokens, or any security-sensitive purpose. +//! +//! IC's `raw_rand()` is async-only and cannot be called from synchronous `getrandom`. use crate::Error; use core::mem::MaybeUninit; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] -use ic_cdk::api::time; +use ic_cdk::api::{instruction_counter, time}; -// SplitMix64: tiny, fast, good distribution for non-crypto use #[inline] fn splitmix64_next(x: &mut u64) -> u64 { *x = x.wrapping_add(0x9E3779B97F4A7C15); @@ -20,27 +26,26 @@ fn splitmix64_next(x: &mut u64) -> u64 { z ^ (z >> 31) } - pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { let len = dest.len(); if len == 0 { return Ok(()); } - // Consensus timestamp: deterministic across replicas in an UPDATE call. - // (In a QUERY it is *not* consensus-certified.) #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] - let mut state = (time() as u64) ^ ((len as u64).wrapping_mul(0x9E3779B97F4A7C15)); + let mut state = time() + ^ instruction_counter() + ^ (len as u64).wrapping_mul(0x9E3779B97F4A7C15); #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] - let mut state = (len as u64).wrapping_mul(0x9E3779B97F4A7C15); // fallback seed + let mut state = (len as u64).wrapping_mul(0x9E3779B97F4A7C15); let mut i = 0; while i < len { let word = splitmix64_next(&mut state).to_le_bytes(); let n = core::cmp::min(8, len - i); for j in 0..n { - unsafe { dest[i + j].write(word[j]); } + dest[i + j].write(word[j]); } i += n; } @@ -50,35 +55,15 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { pub fn inner_u32() -> Result { let mut buf = [MaybeUninit::::uninit(); 4]; fill_inner(&mut buf)?; - - let buf = unsafe { - [ - buf[0].assume_init(), - buf[1].assume_init(), - buf[2].assume_init(), - buf[3].assume_init(), - ] - }; - - Ok(u32::from_ne_bytes(buf)) + Ok(u32::from_ne_bytes(core::array::from_fn(|i| unsafe { + buf[i].assume_init() + }))) } pub fn inner_u64() -> Result { let mut buf = [MaybeUninit::::uninit(); 8]; fill_inner(&mut buf)?; - - let buf = unsafe { - [ - buf[0].assume_init(), - buf[1].assume_init(), - buf[2].assume_init(), - buf[3].assume_init(), - buf[4].assume_init(), - buf[5].assume_init(), - buf[6].assume_init(), - buf[7].assume_init(), - ] - }; - - Ok(u64::from_ne_bytes(buf)) + Ok(u64::from_ne_bytes(core::array::from_fn(|i| unsafe { + buf[i].assume_init() + }))) } \ No newline at end of file From 64cf362ff488f3745f16b676ecb5a687f468a87f Mon Sep 17 00:00:00 2001 From: jeshli Date: Thu, 4 Dec 2025 11:31:04 -0500 Subject: [PATCH 6/7] cargo fmt --- src/backends/internet_computer.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/backends/internet_computer.rs b/src/backends/internet_computer.rs index 5e4f5389a..55ba2a4f7 100644 --- a/src/backends/internet_computer.rs +++ b/src/backends/internet_computer.rs @@ -33,9 +33,7 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { } #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] - let mut state = time() - ^ instruction_counter() - ^ (len as u64).wrapping_mul(0x9E3779B97F4A7C15); + let mut state = time() ^ instruction_counter() ^ (len as u64).wrapping_mul(0x9E3779B97F4A7C15); #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] let mut state = (len as u64).wrapping_mul(0x9E3779B97F4A7C15); @@ -66,4 +64,4 @@ pub fn inner_u64() -> Result { Ok(u64::from_ne_bytes(core::array::from_fn(|i| unsafe { buf[i].assume_init() }))) -} \ No newline at end of file +} From 3eb69cb46e195a515e61320fa255f18ec3b38909 Mon Sep 17 00:00:00 2001 From: jeshli Date: Thu, 4 Dec 2025 11:56:07 -0500 Subject: [PATCH 7/7] ic-cdk version 19 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index df10f9916..31398d5d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ libc = { version = "0.2.154", default-features = false } wasip2 = { version = "1", default-features = false } [target.'cfg(all(target_arch = "wasm32", target_os = "unknown", getrandom_backend = "ic"))'.dependencies] -ic-cdk = { version = "0.18.7", default-features = false } +ic-cdk = { version = "0.19.0", default-features = false } # wasm_js [target.'cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))'.dependencies]