From 9c382585b0fc371c34eef1dbed2e87dc1c0ddf5d Mon Sep 17 00:00:00 2001 From: Toufic Date: Mon, 16 Feb 2026 10:40:44 +0400 Subject: [PATCH 01/16] update ipfs link --- README.md | 12 ++++++------ src/ctrng/ipfs.rs | 2 +- tests/ipfs_live.rs | 2 +- tests/mixed_tests.rs | 2 +- tests/testu01_tests.rs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index dc9549d..73a826b 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,8 @@ crypto-ctrng = { git = "https://github.com/spacecomputer-io/crypto-ctrng.git", t ```rust use crypto_ctrng::{IpfsCtrng, RandomBlockSource}; -let gateway = "https://ipfs.io"; -let beacon_key = "k2k4r8pigrw8i34z63om8f015tt5igdq0c46xupq8spp1bogt35k5vhe"; +let gateway = "https://ipfs.filebase.io/ipns/"; +let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; let mut ctrng = IpfsCtrng::new(gateway, beacon_key); let block = ctrng.next_block().expect("failed to fetch IPFS block"); @@ -58,8 +58,8 @@ let block = ctrng.next_block().expect("failed to fetch IPFS block"); use crypto_ctrng::{IpfsCtrng, MixedCtrng, ReseedingRng}; use rand_core::RngCore; -let gateway = "https://ipfs.io"; -let beacon_key = "k2k4r8pigrw8i34z63om8f015tt5igdq0c46xupq8spp1bogt35k5vhe"; +let gateway = "https://ipfs.filebase.io/ipns/"; +let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; let ipfs = IpfsCtrng::new(gateway, beacon_key); let mixed = MixedCtrng::new(ipfs).unwrap(); @@ -75,8 +75,8 @@ rng.fill_bytes(&mut key); use std::time::Duration; use crypto_ctrng::{IpfsCtrng, MixedCtrng, ReseedConfig, ReseedingRng}; -let gateway = "https://ipfs.io"; -let beacon_key = "k2k4r8pigrw8i34z63om8f015tt5igdq0c46xupq8spp1bogt35k5vhe"; +let gateway = "https://ipfs.filebase.io/ipns/"; +let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; let ipfs = IpfsCtrng::new(gateway, beacon_key); let mixed = MixedCtrng::new(ipfs).unwrap(); diff --git a/src/ctrng/ipfs.rs b/src/ctrng/ipfs.rs index b2e381d..14d9cb9 100644 --- a/src/ctrng/ipfs.rs +++ b/src/ctrng/ipfs.rs @@ -30,7 +30,7 @@ impl IpfsCtrng { fn refill_cache(&mut self) -> Result<(), SourceError> { let url = format!( - "{}/ipns/{}", + "{}/{}", self.gateway_url.trim_end_matches('/'), self.beacon_key ); diff --git a/tests/ipfs_live.rs b/tests/ipfs_live.rs index 2554693..6c2e861 100644 --- a/tests/ipfs_live.rs +++ b/tests/ipfs_live.rs @@ -1,7 +1,7 @@ use crypto_ctrng::RandomBlockSource; const BEACON_KEY: &str = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -const GATEWAY: &str = "https://ipfs.io"; +const GATEWAY: &str = "https://ipfs.filebase.io/ipns/"; #[test] #[ignore] diff --git a/tests/mixed_tests.rs b/tests/mixed_tests.rs index 37def64..c3ef66a 100644 --- a/tests/mixed_tests.rs +++ b/tests/mixed_tests.rs @@ -1,7 +1,7 @@ use crypto_ctrng::RandomBlockSource; const BEACON_KEY: &str = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -const GATEWAY: &str = "https://ipfs.io"; +const GATEWAY: &str = "https://ipfs.filebase.io/ipns/"; #[test] fn local_is_32_bytes() { diff --git a/tests/testu01_tests.rs b/tests/testu01_tests.rs index c0fd4a6..76f5aba 100644 --- a/tests/testu01_tests.rs +++ b/tests/testu01_tests.rs @@ -11,7 +11,7 @@ use testu01_runner::{delete_unif01_gen, make_unif01_gen, register_rng}; #[cfg(feature = "testu01")] fn build_ipfs_seeded_rng() -> ChaCha20Rng { - let gateway = "https://ipfs.io"; + let gateway = "https://ipfs.filebase.io/ipns/"; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; let mut ctrng = IpfsCtrng::new(gateway, beacon_key); let seed = ctrng.next_block().expect("failed to fetch IPFS block"); From db3e213353f5347cc0e6d594b372b89ee227dd8a Mon Sep 17 00:00:00 2001 From: Toufic Date: Mon, 16 Feb 2026 10:53:18 +0400 Subject: [PATCH 02/16] consistency --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 73a826b..2f8b4e3 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ This crate's responsibility is to **verify** that the cTRNG backend provides uni | Mode | Code | DRBG | Reseed | |------|------|------|--------| -| Raw entropy | `BlockRng::new(source)` | ❌ | N/A | -| Seeded DRBG (testing) | `ChaCha20Rng::from_seed(seed)` | ChaCha20 | ❌ | +| Raw entropy | `BlockRng::new(source)` | N/A | N/A | +| Seeded DRBG (testing) | `ChaCha20Rng::from_seed(seed)` | ChaCha20 | N/A | | DRBG + auto reseed | `ReseedingRng::new(source)` | ChaCha20 | 3200 bytes / 1 week | | DRBG + prediction resistance | `ReseedingRng::with_prediction_resistance(source)` | ChaCha20 | every call | From fac38b92729e1c17259005a0c91a745f5fa6fa47 Mon Sep 17 00:00:00 2001 From: Toufic Date: Mon, 16 Feb 2026 14:41:24 +0400 Subject: [PATCH 03/16] fallback --- README.md | 34 ++++++++++++++++++- src/ctrng/ipfs.rs | 85 +++++++++++++++++++++++++++++++++++++++------- src/lib.rs | 2 +- tests/ipfs_live.rs | 52 ++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 2f8b4e3..fc47ca4 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ To use this crate, add it to your `Cargo.toml`: crypto-ctrng = { git = "https://github.com/spacecomputer-io/crypto-ctrng.git", tag = "v0.1.0" } ``` -### Fetch raw entropy +### Fetch raw entropy (single gateway) ```rust use crypto_ctrng::{IpfsCtrng, RandomBlockSource}; @@ -52,6 +52,38 @@ let mut ctrng = IpfsCtrng::new(gateway, beacon_key); let block = ctrng.next_block().expect("failed to fetch IPFS block"); ``` +### Multi-gateway with automatic fallback + +If a gateway is unresponsive or down, the next one in the list is tried automatically. +The default timeout is 10 seconds per gateway; pass a custom `Duration` to override. + +```rust +use std::time::Duration; +use crypto_ctrng::{IpfsCtrng, RandomBlockSource, DEFAULT_GATEWAYS}; + +let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; + +// Use the built-in default gateways (ipfs.io, ipfs.filebase.io, dweb.link) +let mut ctrng = IpfsCtrng::new_with_gateways( + DEFAULT_GATEWAYS.iter().copied(), + beacon_key, + None, // default 10s timeout +); +let block = ctrng.next_block().expect("at least one gateway should respond"); + +// Or provide your own list with a custom timeout +let gateways = vec![ + "https://ipfs.filebase.io/ipns/", + "https://ipfs.io/ipns/", + "https://dweb.link/ipns/", +]; +let mut ctrng = IpfsCtrng::new_with_gateways( + gateways, + beacon_key, + Some(Duration::from_secs(5)), +); +``` + ### DRBG with automatic reseeding (recommended) ```rust diff --git a/src/ctrng/ipfs.rs b/src/ctrng/ipfs.rs index 14d9cb9..eebe0c5 100644 --- a/src/ctrng/ipfs.rs +++ b/src/ctrng/ipfs.rs @@ -1,13 +1,24 @@ +use std::time::Duration; + use crate::error::SourceError; use crate::traits::RandomBlockSource; use super::types::{BeaconResponse, CtrngBlock}; +pub const DEFAULT_GATEWAYS: &[&str] = &[ + "https://ipfs.io/ipns/", + "https://ipfs.filebase.io/ipns/", + "https://dweb.link/ipns/", +]; + +pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10); + /// cTRNG backed by an IPFS beacon. #[derive(Debug)] pub struct IpfsCtrng { - gateway_url: String, + gateways: Vec, beacon_key: String, + timeout: Duration, cache: Vec, cursor: usize, last_served: Option, @@ -15,9 +26,27 @@ pub struct IpfsCtrng { impl IpfsCtrng { pub fn new(gateway_url: impl Into, beacon_key: impl Into) -> Self { + let mut gateways = vec![gateway_url.into()]; + gateways.extend(DEFAULT_GATEWAYS.iter().map(|s| s.to_string())); + Self { + gateways, + beacon_key: beacon_key.into(), + timeout: DEFAULT_TIMEOUT, + cache: Vec::new(), + cursor: 0, + last_served: None, + } + } + + pub fn new_with_gateways( + gateways: impl IntoIterator>, + beacon_key: impl Into, + timeout: Option, + ) -> Self { Self { - gateway_url: gateway_url.into(), + gateways: gateways.into_iter().map(Into::into).collect(), beacon_key: beacon_key.into(), + timeout: timeout.unwrap_or(DEFAULT_TIMEOUT), cache: Vec::new(), cursor: 0, last_served: None, @@ -28,24 +57,56 @@ impl IpfsCtrng { self.last_served } - fn refill_cache(&mut self) -> Result<(), SourceError> { + fn try_gateway(&self, gateway: &str) -> Result, String> { let url = format!( "{}/{}", - self.gateway_url.trim_end_matches('/'), + gateway.trim_end_matches('/'), self.beacon_key ); - let resp = reqwest::blocking::get(&url) - .map_err(|e| SourceError::ctrng(format!("ipfs fetch failed: {e}")))?; + + let client = reqwest::blocking::Client::builder() + .timeout(self.timeout) + .build() + .map_err(|e| format!("{gateway}: client build failed: {e}"))?; + + let resp = client + .get(&url) + .send() + .map_err(|e| format!("{gateway}: fetch failed: {e}"))?; + if !resp.status().is_success() { - return Err(SourceError::ctrng(format!( - "ipfs returned HTTP {}", - resp.status() - ))); + return Err(format!("{gateway}: HTTP {}", resp.status())); } + let bytes = resp .bytes() - .map_err(|e| SourceError::ctrng(format!("ipfs body read failed: {e}")))?; - let parsed: BeaconResponse = serde_json::from_slice(&bytes) + .map_err(|e| format!("{gateway}: body read failed: {e}"))?; + + Ok(bytes.to_vec()) + } + + fn refill_cache(&mut self) -> Result<(), SourceError> { + let mut errors = Vec::new(); + + for gateway in &self.gateways { + match self.try_gateway(gateway) { + Ok(bytes) => { + return self.parse_and_store(&bytes); + } + Err(e) => { + errors.push(e); + } + } + } + + Err(SourceError::ctrng(format!( + "all gateways failed: [{}]", + errors.join(", ") + ))) + } + + fn parse_and_store(&mut self, bytes: &[u8]) -> Result<(), SourceError> { + let parsed: BeaconResponse = serde_json::from_slice(bytes) .map_err(|e| SourceError::ctrng(format!("ipfs json parse failed: {e}")))?; let mut new_cache = Vec::with_capacity(parsed.data.ctrng.len()); diff --git a/src/lib.rs b/src/lib.rs index d3c4a43..98f5734 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ pub mod reseed; pub mod rng; pub mod traits; -pub use ctrng::ipfs::IpfsCtrng; +pub use ctrng::ipfs::{DEFAULT_GATEWAYS, DEFAULT_TIMEOUT, IpfsCtrng}; pub use derive::derive_seed; pub use error::SourceError; pub use local::LocalRng; diff --git a/tests/ipfs_live.rs b/tests/ipfs_live.rs index 6c2e861..7851262 100644 --- a/tests/ipfs_live.rs +++ b/tests/ipfs_live.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use crypto_ctrng::RandomBlockSource; const BEACON_KEY: &str = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; @@ -35,3 +37,53 @@ fn ipfs_unique_blocks() { } } } + +#[test] +#[ignore] +fn ipfs_multi_gateway_fallback() { + // First gateway is invalid, second is valid — should fall back successfully + let gateways = vec![ + "https://invalid-gateway.example.com/ipns/", + GATEWAY, + ]; + let mut client = crypto_ctrng::IpfsCtrng::new_with_gateways( + gateways, + BEACON_KEY, + Some(Duration::from_secs(5)), + ); + let block = client.next_block().expect("fallback should succeed"); + assert_eq!(block.len(), 32); +} + +#[test] +#[ignore] +fn ipfs_all_gateways_fail() { + let gateways = vec![ + "https://invalid-gateway1.example.com/ipns/", + "https://invalid-gateway2.example.com/ipns/", + ]; + let mut client = crypto_ctrng::IpfsCtrng::new_with_gateways( + gateways, + BEACON_KEY, + Some(Duration::from_secs(3)), + ); + let result = client.next_block(); + assert!(result.is_err(), "all gateways should fail"); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("all gateways failed"), + "error should mention all gateways failed: {err}" + ); +} + +#[test] +#[ignore] +fn ipfs_default_gateways_list() { + let mut client = crypto_ctrng::IpfsCtrng::new_with_gateways( + crypto_ctrng::DEFAULT_GATEWAYS.iter().copied(), + BEACON_KEY, + None, + ); + let block = client.next_block().expect("default gateways should work"); + assert_eq!(block.len(), 32); +} From 087279356594e870ae9eb36f1c6371e238a8793d Mon Sep 17 00:00:00 2001 From: Toufic Date: Thu, 5 Mar 2026 13:16:07 +0100 Subject: [PATCH 04/16] update gateway --- README.md | 2 +- src/ctrng/ipfs.rs | 45 ++++++++++++++++++++++++++++++++------------- src/lib.rs | 2 +- tests/ipfs_live.rs | 8 ++++---- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index fc47ca4..03f3eb3 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ This crate's responsibility is to **verify** that the cTRNG backend provides uni - `MixedCtrng` combines entropy sources (XOR of IPFS beacon + local OS randomness) for enhanced security, but still relies on the gateway for uniqueness guarantees. **Uniqueness guarantees:** -- This crate ensures that each `next_block()` call returns a block with a timestamp strictly greater than the previous one, preventing global block reuse (I thought so, but according to today's bug, we can have same ctrng for two different timestamp, so we need to find another way). +- This crate ensures that each `next_block()` call returns a block with a timestamp strictly greater than the previous one, preventing global block reuse. - The calling library must use `derive_seed()` to personalize blocks per user/execution to prevent collisions between different users reading the same raw block. ## Usage modes diff --git a/src/ctrng/ipfs.rs b/src/ctrng/ipfs.rs index eebe0c5..140372e 100644 --- a/src/ctrng/ipfs.rs +++ b/src/ctrng/ipfs.rs @@ -6,17 +6,40 @@ use crate::traits::RandomBlockSource; use super::types::{BeaconResponse, CtrngBlock}; pub const DEFAULT_GATEWAYS: &[&str] = &[ - "https://ipfs.io/ipns/", - "https://ipfs.filebase.io/ipns/", - "https://dweb.link/ipns/", + "https://ipfs.io", + "https://ipfs.filebase.io", + "https://dweb.link", ]; pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10); +#[derive(Debug, Clone)] +pub struct IpfsGateway(String); + +impl IpfsGateway { + pub fn new(url: impl Into) -> Self { + Self(url.into().trim_end_matches('/').to_string()) + } + + pub fn ipfs(&self, key: &str) -> String { + format!("{}/ipfs/{}", self.0, key) + } + + pub fn ipns(&self, key: &str) -> String { + format!("{}/ipns/{}", self.0, key) + } +} + +impl std::fmt::Display for IpfsGateway { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + /// cTRNG backed by an IPFS beacon. #[derive(Debug)] pub struct IpfsCtrng { - gateways: Vec, + gateways: Vec, beacon_key: String, timeout: Duration, cache: Vec, @@ -26,8 +49,8 @@ pub struct IpfsCtrng { impl IpfsCtrng { pub fn new(gateway_url: impl Into, beacon_key: impl Into) -> Self { - let mut gateways = vec![gateway_url.into()]; - gateways.extend(DEFAULT_GATEWAYS.iter().map(|s| s.to_string())); + let mut gateways = vec![IpfsGateway::new(gateway_url)]; + gateways.extend(DEFAULT_GATEWAYS.iter().map(|s| IpfsGateway::new(*s))); Self { gateways, beacon_key: beacon_key.into(), @@ -44,7 +67,7 @@ impl IpfsCtrng { timeout: Option, ) -> Self { Self { - gateways: gateways.into_iter().map(Into::into).collect(), + gateways: gateways.into_iter().map(IpfsGateway::new).collect(), beacon_key: beacon_key.into(), timeout: timeout.unwrap_or(DEFAULT_TIMEOUT), cache: Vec::new(), @@ -57,12 +80,8 @@ impl IpfsCtrng { self.last_served } - fn try_gateway(&self, gateway: &str) -> Result, String> { - let url = format!( - "{}/{}", - gateway.trim_end_matches('/'), - self.beacon_key - ); + fn try_gateway(&self, gateway: &IpfsGateway) -> Result, String> { + let url = gateway.ipns(&self.beacon_key); let client = reqwest::blocking::Client::builder() .timeout(self.timeout) diff --git a/src/lib.rs b/src/lib.rs index 98f5734..6511d57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ pub mod reseed; pub mod rng; pub mod traits; -pub use ctrng::ipfs::{DEFAULT_GATEWAYS, DEFAULT_TIMEOUT, IpfsCtrng}; +pub use ctrng::ipfs::{DEFAULT_GATEWAYS, DEFAULT_TIMEOUT, IpfsCtrng, IpfsGateway}; pub use derive::derive_seed; pub use error::SourceError; pub use local::LocalRng; diff --git a/tests/ipfs_live.rs b/tests/ipfs_live.rs index 7851262..90c48c1 100644 --- a/tests/ipfs_live.rs +++ b/tests/ipfs_live.rs @@ -3,7 +3,7 @@ use std::time::Duration; use crypto_ctrng::RandomBlockSource; const BEACON_KEY: &str = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -const GATEWAY: &str = "https://ipfs.filebase.io/ipns/"; +const GATEWAY: &str = "https://ipfs.filebase.io"; #[test] #[ignore] @@ -43,7 +43,7 @@ fn ipfs_unique_blocks() { fn ipfs_multi_gateway_fallback() { // First gateway is invalid, second is valid — should fall back successfully let gateways = vec![ - "https://invalid-gateway.example.com/ipns/", + "https://invalid-gateway.example.com", GATEWAY, ]; let mut client = crypto_ctrng::IpfsCtrng::new_with_gateways( @@ -59,8 +59,8 @@ fn ipfs_multi_gateway_fallback() { #[ignore] fn ipfs_all_gateways_fail() { let gateways = vec![ - "https://invalid-gateway1.example.com/ipns/", - "https://invalid-gateway2.example.com/ipns/", + "https://invalid-gateway1.example.com", + "https://invalid-gateway2.example.com", ]; let mut client = crypto_ctrng::IpfsCtrng::new_with_gateways( gateways, From 29ceea1c4b5d11a7ce5ec0882b94dcb774870319 Mon Sep 17 00:00:00 2001 From: Toufic Date: Thu, 5 Mar 2026 17:13:47 +0100 Subject: [PATCH 05/16] ctrng abstraction + gateway unification --- README.md | 48 +++++++++++++++++-------------------- src/ctrng/ipfs.rs | 54 +++++++++++++++++++++++++++++------------- src/ctrng/mod.rs | 42 +++++++++++++++++++++++++++++++- src/lib.rs | 3 ++- tests/ipfs_live.rs | 38 +++++++++++++++-------------- tests/mixed_tests.rs | 10 ++++---- tests/testu01_tests.rs | 6 ++--- 7 files changed, 130 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 03f3eb3..1c045d0 100644 --- a/README.md +++ b/README.md @@ -40,60 +40,57 @@ To use this crate, add it to your `Cargo.toml`: crypto-ctrng = { git = "https://github.com/spacecomputer-io/crypto-ctrng.git", tag = "v0.1.0" } ``` -### Fetch raw entropy (single gateway) +### Fetch raw entropy (default gateways) ```rust -use crypto_ctrng::{IpfsCtrng, RandomBlockSource}; +use crypto_ctrng::{Ctrng, RandomBlockSource}; -let gateway = "https://ipfs.filebase.io/ipns/"; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -let mut ctrng = IpfsCtrng::new(gateway, beacon_key); +let mut ctrng = Ctrng::ipfs([], beacon_key, None); let block = ctrng.next_block().expect("failed to fetch IPFS block"); ``` -### Multi-gateway with automatic fallback +### Custom gateways with automatic fallback If a gateway is unresponsive or down, the next one in the list is tried automatically. -The default timeout is 10 seconds per gateway; pass a custom `Duration` to override. +The default timeout is 10 seconds per gateway. ```rust +use crypto_ctrng::{Ctrng, IpfsConfig, RandomBlockSource}; use std::time::Duration; -use crypto_ctrng::{IpfsCtrng, RandomBlockSource, DEFAULT_GATEWAYS}; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -// Use the built-in default gateways (ipfs.io, ipfs.filebase.io, dweb.link) -let mut ctrng = IpfsCtrng::new_with_gateways( - DEFAULT_GATEWAYS.iter().copied(), +// Custom gateways + default gateways appended automatically +let mut ctrng = Ctrng::ipfs( + ["https://my-gateway.example.com"], beacon_key, - None, // default 10s timeout + None, ); let block = ctrng.next_block().expect("at least one gateway should respond"); -// Or provide your own list with a custom timeout -let gateways = vec![ - "https://ipfs.filebase.io/ipns/", - "https://ipfs.io/ipns/", - "https://dweb.link/ipns/", -]; -let mut ctrng = IpfsCtrng::new_with_gateways( - gateways, +// Custom gateways only (no defaults), with a custom timeout +let config = IpfsConfig { + use_defaults: false, + timeout: Duration::from_secs(5), +}; +let mut ctrng = Ctrng::ipfs( + ["https://ipfs.filebase.io", "https://ipfs.io"], beacon_key, - Some(Duration::from_secs(5)), + Some(config), ); ``` ### DRBG with automatic reseeding (recommended) ```rust -use crypto_ctrng::{IpfsCtrng, MixedCtrng, ReseedingRng}; +use crypto_ctrng::{Ctrng, MixedCtrng, ReseedingRng}; use rand_core::RngCore; -let gateway = "https://ipfs.filebase.io/ipns/"; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -let ipfs = IpfsCtrng::new(gateway, beacon_key); +let ipfs = Ctrng::ipfs([], beacon_key, None); let mixed = MixedCtrng::new(ipfs).unwrap(); let mut rng = ReseedingRng::new(mixed).unwrap(); @@ -105,12 +102,11 @@ rng.fill_bytes(&mut key); ```rust use std::time::Duration; -use crypto_ctrng::{IpfsCtrng, MixedCtrng, ReseedConfig, ReseedingRng}; +use crypto_ctrng::{Ctrng, MixedCtrng, ReseedConfig, ReseedingRng}; -let gateway = "https://ipfs.filebase.io/ipns/"; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -let ipfs = IpfsCtrng::new(gateway, beacon_key); +let ipfs = Ctrng::ipfs([], beacon_key, None); let mixed = MixedCtrng::new(ipfs).unwrap(); let config = ReseedConfig::new(1600, Duration::from_secs(3600)); // 50 blocks, 1 hour diff --git a/src/ctrng/ipfs.rs b/src/ctrng/ipfs.rs index 140372e..34955b1 100644 --- a/src/ctrng/ipfs.rs +++ b/src/ctrng/ipfs.rs @@ -13,6 +13,22 @@ pub const DEFAULT_GATEWAYS: &[&str] = &[ pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10); +/// Configuration for IPFS-backed cTRNG. +#[derive(Debug, Clone)] +pub struct IpfsConfig { + pub use_defaults: bool, + pub timeout: Duration, +} + +impl Default for IpfsConfig { + fn default() -> Self { + Self { + use_defaults: true, + timeout: DEFAULT_TIMEOUT, + } + } +} + #[derive(Debug, Clone)] pub struct IpfsGateway(String); @@ -36,6 +52,12 @@ impl std::fmt::Display for IpfsGateway { } } +impl PartialEq for IpfsGateway { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + /// cTRNG backed by an IPFS beacon. #[derive(Debug)] pub struct IpfsCtrng { @@ -48,28 +70,26 @@ pub struct IpfsCtrng { } impl IpfsCtrng { - pub fn new(gateway_url: impl Into, beacon_key: impl Into) -> Self { - let mut gateways = vec![IpfsGateway::new(gateway_url)]; - gateways.extend(DEFAULT_GATEWAYS.iter().map(|s| IpfsGateway::new(*s))); - Self { - gateways, - beacon_key: beacon_key.into(), - timeout: DEFAULT_TIMEOUT, - cache: Vec::new(), - cursor: 0, - last_served: None, - } - } - - pub fn new_with_gateways( + pub fn new( gateways: impl IntoIterator>, beacon_key: impl Into, - timeout: Option, + config: Option, ) -> Self { + let config = config.unwrap_or_default(); + let mut gw_list: Vec = + gateways.into_iter().map(IpfsGateway::new).collect(); + if config.use_defaults { + for default in DEFAULT_GATEWAYS { + let gw = IpfsGateway::new(*default); + if !gw_list.contains(&gw) { + gw_list.push(gw); + } + } + } Self { - gateways: gateways.into_iter().map(IpfsGateway::new).collect(), + gateways: gw_list, beacon_key: beacon_key.into(), - timeout: timeout.unwrap_or(DEFAULT_TIMEOUT), + timeout: config.timeout, cache: Vec::new(), cursor: 0, last_served: None, diff --git a/src/ctrng/mod.rs b/src/ctrng/mod.rs index 2196ed7..3b84ad7 100644 --- a/src/ctrng/mod.rs +++ b/src/ctrng/mod.rs @@ -1,2 +1,42 @@ -pub mod ipfs; +pub(crate) mod ipfs; pub mod types; + +use ipfs::{IpfsConfig, IpfsCtrng}; +use types::CtrngBlock; +use crate::error::SourceError; +use crate::traits::RandomBlockSource; + +/// Unified cTRNG abstraction supporting multiple backends. +#[derive(Debug)] +pub enum Ctrng { + Ipfs(IpfsCtrng), + // Future: DataHeaven(DataHeavenCtrng), +} + +impl Ctrng { + /// Create an IPFS cTRNG source. + /// + /// Pass `None` for default config (default gateways appended, 10s timeout). + pub fn ipfs( + gateways: impl IntoIterator>, + beacon_key: impl Into, + config: Option, + ) -> Self { + Self::Ipfs(IpfsCtrng::new(gateways, beacon_key, config)) + } + + /// Returns the last block served, if any. + pub fn last_served_block(&self) -> Option { + match self { + Self::Ipfs(ipfs) => ipfs.last_served_block(), + } + } +} + +impl RandomBlockSource for Ctrng { + fn next_block(&mut self) -> Result<[u8; 32], SourceError> { + match self { + Self::Ipfs(ipfs) => ipfs.next_block(), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 6511d57..9dae946 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,8 @@ pub mod reseed; pub mod rng; pub mod traits; -pub use ctrng::ipfs::{DEFAULT_GATEWAYS, DEFAULT_TIMEOUT, IpfsCtrng, IpfsGateway}; +pub use ctrng::ipfs::{DEFAULT_GATEWAYS, DEFAULT_TIMEOUT, IpfsConfig, IpfsGateway}; +pub use ctrng::Ctrng; pub use derive::derive_seed; pub use error::SourceError; pub use local::LocalRng; diff --git a/tests/ipfs_live.rs b/tests/ipfs_live.rs index 90c48c1..d43c2cd 100644 --- a/tests/ipfs_live.rs +++ b/tests/ipfs_live.rs @@ -8,7 +8,7 @@ const GATEWAY: &str = "https://ipfs.filebase.io"; #[test] #[ignore] fn ipfs_client_can_fetch_live_block() { - let mut client = crypto_ctrng::IpfsCtrng::new(GATEWAY, BEACON_KEY); + let mut client = crypto_ctrng::Ctrng::ipfs([GATEWAY], BEACON_KEY, None); let block = client.next_block().expect("fetch 32-byte block"); assert_eq!(block.len(), 32); } @@ -16,7 +16,7 @@ fn ipfs_client_can_fetch_live_block() { #[test] #[ignore] fn ipfs_unique_blocks() { - let mut client = crypto_ctrng::IpfsCtrng::new(GATEWAY, BEACON_KEY); + let mut client = crypto_ctrng::Ctrng::ipfs([GATEWAY], BEACON_KEY, None); let a = client.next_block().expect("first block"); let b = client.next_block().expect("second block"); @@ -41,15 +41,14 @@ fn ipfs_unique_blocks() { #[test] #[ignore] fn ipfs_multi_gateway_fallback() { - // First gateway is invalid, second is valid — should fall back successfully - let gateways = vec![ - "https://invalid-gateway.example.com", - GATEWAY, - ]; - let mut client = crypto_ctrng::IpfsCtrng::new_with_gateways( - gateways, + let config = crypto_ctrng::IpfsConfig { + use_defaults: false, + timeout: Duration::from_secs(5), + }; + let mut client = crypto_ctrng::Ctrng::ipfs( + ["https://invalid-gateway.example.com", GATEWAY], BEACON_KEY, - Some(Duration::from_secs(5)), + Some(config), ); let block = client.next_block().expect("fallback should succeed"); assert_eq!(block.len(), 32); @@ -58,14 +57,17 @@ fn ipfs_multi_gateway_fallback() { #[test] #[ignore] fn ipfs_all_gateways_fail() { - let gateways = vec![ - "https://invalid-gateway1.example.com", - "https://invalid-gateway2.example.com", - ]; - let mut client = crypto_ctrng::IpfsCtrng::new_with_gateways( - gateways, + let config = crypto_ctrng::IpfsConfig { + use_defaults: false, + timeout: Duration::from_secs(3), + }; + let mut client = crypto_ctrng::Ctrng::ipfs( + [ + "https://invalid-gateway1.example.com", + "https://invalid-gateway2.example.com", + ], BEACON_KEY, - Some(Duration::from_secs(3)), + Some(config), ); let result = client.next_block(); assert!(result.is_err(), "all gateways should fail"); @@ -79,7 +81,7 @@ fn ipfs_all_gateways_fail() { #[test] #[ignore] fn ipfs_default_gateways_list() { - let mut client = crypto_ctrng::IpfsCtrng::new_with_gateways( + let mut client = crypto_ctrng::Ctrng::ipfs( crypto_ctrng::DEFAULT_GATEWAYS.iter().copied(), BEACON_KEY, None, diff --git a/tests/mixed_tests.rs b/tests/mixed_tests.rs index c3ef66a..da76f45 100644 --- a/tests/mixed_tests.rs +++ b/tests/mixed_tests.rs @@ -1,7 +1,7 @@ use crypto_ctrng::RandomBlockSource; const BEACON_KEY: &str = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -const GATEWAY: &str = "https://ipfs.filebase.io/ipns/"; +const GATEWAY: &str = "https://ipfs.filebase.io"; #[test] fn local_is_32_bytes() { @@ -21,7 +21,7 @@ fn local_unique_blocks() { #[test] #[ignore] fn mixed_is_32_bytes() { - let ipfs = crypto_ctrng::IpfsCtrng::new(GATEWAY, BEACON_KEY); + let ipfs = crypto_ctrng::Ctrng::ipfs([GATEWAY], BEACON_KEY, None); let mut client = crypto_ctrng::MixedCtrng::new(ipfs).unwrap(); let block = client.next_block().unwrap(); assert_eq!(block.len(), 32); @@ -30,8 +30,8 @@ fn mixed_is_32_bytes() { #[test] #[ignore] fn each_source_is_different() { - let mut ipfs = crypto_ctrng::IpfsCtrng::new(GATEWAY, BEACON_KEY); - let ipfs2 = crypto_ctrng::IpfsCtrng::new(GATEWAY, BEACON_KEY); + let mut ipfs = crypto_ctrng::Ctrng::ipfs([GATEWAY], BEACON_KEY, None); + let ipfs2 = crypto_ctrng::Ctrng::ipfs([GATEWAY], BEACON_KEY, None); let mut mixed = crypto_ctrng::MixedCtrng::new(ipfs2).unwrap(); let ipfs_block = ipfs.next_block().unwrap(); @@ -43,7 +43,7 @@ fn each_source_is_different() { #[test] #[ignore] fn mixed_unique_blocks() { - let ipfs = crypto_ctrng::IpfsCtrng::new(GATEWAY, BEACON_KEY); + let ipfs = crypto_ctrng::Ctrng::ipfs([GATEWAY], BEACON_KEY, None); let mut client = crypto_ctrng::MixedCtrng::new(ipfs).unwrap(); let a = client.next_block().unwrap(); let b = client.next_block().unwrap(); diff --git a/tests/testu01_tests.rs b/tests/testu01_tests.rs index 76f5aba..a7a7eeb 100644 --- a/tests/testu01_tests.rs +++ b/tests/testu01_tests.rs @@ -1,5 +1,5 @@ #[cfg(feature = "testu01")] -use crypto_ctrng::{IpfsCtrng, RandomBlockSource}; +use crypto_ctrng::{Ctrng, RandomBlockSource}; #[cfg(feature = "testu01")] use rand_chacha::ChaCha20Rng; #[cfg(feature = "testu01")] @@ -11,9 +11,9 @@ use testu01_runner::{delete_unif01_gen, make_unif01_gen, register_rng}; #[cfg(feature = "testu01")] fn build_ipfs_seeded_rng() -> ChaCha20Rng { - let gateway = "https://ipfs.filebase.io/ipns/"; + let gateway = "https://ipfs.filebase.io"; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; - let mut ctrng = IpfsCtrng::new(gateway, beacon_key); + let mut ctrng = Ctrng::ipfs([gateway], beacon_key, None); let seed = ctrng.next_block().expect("failed to fetch IPFS block"); ChaCha20Rng::from_seed(seed) } From e7add55b6534c6d6cced253d26d7d74af4926fff Mon Sep 17 00:00:00 2001 From: Toufic Date: Thu, 5 Mar 2026 17:48:00 +0100 Subject: [PATCH 06/16] default gateway --- README.md | 26 +++++++++++++------------- src/ctrng/ipfs.rs | 10 ++++------ src/ctrng/mod.rs | 10 +++------- tests/ipfs_live.rs | 34 +++++++++++++--------------------- tests/mixed_tests.rs | 9 ++++----- tests/testu01_tests.rs | 2 +- 6 files changed, 38 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 1c045d0..235cd8b 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ use crypto_ctrng::{Ctrng, RandomBlockSource}; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -let mut ctrng = Ctrng::ipfs([], beacon_key, None); +let mut ctrng = Ctrng::ipfs(beacon_key, None); let block = ctrng.next_block().expect("failed to fetch IPFS block"); ``` @@ -63,23 +63,23 @@ use std::time::Duration; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; // Custom gateways + default gateways appended automatically -let mut ctrng = Ctrng::ipfs( - ["https://my-gateway.example.com"], - beacon_key, - None, -); +let config = IpfsConfig { + gateways: vec!["https://my-gateway.example.com".into()], + ..Default::default() +}; +let mut ctrng = Ctrng::ipfs(beacon_key, Some(config)); let block = ctrng.next_block().expect("at least one gateway should respond"); // Custom gateways only (no defaults), with a custom timeout let config = IpfsConfig { + gateways: vec![ + "https://ipfs.filebase.io".into(), + "https://ipfs.io".into(), + ], use_defaults: false, timeout: Duration::from_secs(5), }; -let mut ctrng = Ctrng::ipfs( - ["https://ipfs.filebase.io", "https://ipfs.io"], - beacon_key, - Some(config), -); +let mut ctrng = Ctrng::ipfs(beacon_key, Some(config)); ``` ### DRBG with automatic reseeding (recommended) @@ -90,7 +90,7 @@ use rand_core::RngCore; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -let ipfs = Ctrng::ipfs([], beacon_key, None); +let ipfs = Ctrng::ipfs(beacon_key, None); let mixed = MixedCtrng::new(ipfs).unwrap(); let mut rng = ReseedingRng::new(mixed).unwrap(); @@ -106,7 +106,7 @@ use crypto_ctrng::{Ctrng, MixedCtrng, ReseedConfig, ReseedingRng}; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -let ipfs = Ctrng::ipfs([], beacon_key, None); +let ipfs = Ctrng::ipfs(beacon_key, None); let mixed = MixedCtrng::new(ipfs).unwrap(); let config = ReseedConfig::new(1600, Duration::from_secs(3600)); // 50 blocks, 1 hour diff --git a/src/ctrng/ipfs.rs b/src/ctrng/ipfs.rs index 34955b1..bb53805 100644 --- a/src/ctrng/ipfs.rs +++ b/src/ctrng/ipfs.rs @@ -16,6 +16,7 @@ pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10); /// Configuration for IPFS-backed cTRNG. #[derive(Debug, Clone)] pub struct IpfsConfig { + pub gateways: Vec, pub use_defaults: bool, pub timeout: Duration, } @@ -23,6 +24,7 @@ pub struct IpfsConfig { impl Default for IpfsConfig { fn default() -> Self { Self { + gateways: Vec::new(), use_defaults: true, timeout: DEFAULT_TIMEOUT, } @@ -70,14 +72,10 @@ pub struct IpfsCtrng { } impl IpfsCtrng { - pub fn new( - gateways: impl IntoIterator>, - beacon_key: impl Into, - config: Option, - ) -> Self { + pub fn new(beacon_key: impl Into, config: Option) -> Self { let config = config.unwrap_or_default(); let mut gw_list: Vec = - gateways.into_iter().map(IpfsGateway::new).collect(); + config.gateways.into_iter().map(IpfsGateway::new).collect(); if config.use_defaults { for default in DEFAULT_GATEWAYS { let gw = IpfsGateway::new(*default); diff --git a/src/ctrng/mod.rs b/src/ctrng/mod.rs index 3b84ad7..59cf094 100644 --- a/src/ctrng/mod.rs +++ b/src/ctrng/mod.rs @@ -16,13 +16,9 @@ pub enum Ctrng { impl Ctrng { /// Create an IPFS cTRNG source. /// - /// Pass `None` for default config (default gateways appended, 10s timeout). - pub fn ipfs( - gateways: impl IntoIterator>, - beacon_key: impl Into, - config: Option, - ) -> Self { - Self::Ipfs(IpfsCtrng::new(gateways, beacon_key, config)) + /// Pass `None` for default config (default gateways, 10s timeout). + pub fn ipfs(beacon_key: impl Into, config: Option) -> Self { + Self::Ipfs(IpfsCtrng::new(beacon_key, config)) } /// Returns the last block served, if any. diff --git a/tests/ipfs_live.rs b/tests/ipfs_live.rs index d43c2cd..cad2fa0 100644 --- a/tests/ipfs_live.rs +++ b/tests/ipfs_live.rs @@ -8,7 +8,7 @@ const GATEWAY: &str = "https://ipfs.filebase.io"; #[test] #[ignore] fn ipfs_client_can_fetch_live_block() { - let mut client = crypto_ctrng::Ctrng::ipfs([GATEWAY], BEACON_KEY, None); + let mut client = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); let block = client.next_block().expect("fetch 32-byte block"); assert_eq!(block.len(), 32); } @@ -16,13 +16,12 @@ fn ipfs_client_can_fetch_live_block() { #[test] #[ignore] fn ipfs_unique_blocks() { - let mut client = crypto_ctrng::Ctrng::ipfs([GATEWAY], BEACON_KEY, None); + let mut client = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); let a = client.next_block().expect("first block"); let b = client.next_block().expect("second block"); let c = client.next_block().expect("third block"); - // 4th call triggers refill_cache() - may fail if new batch has timestamp <= last served let result = client.next_block(); assert_ne!(a, b, "first and second blocks should be different"); @@ -42,14 +41,14 @@ fn ipfs_unique_blocks() { #[ignore] fn ipfs_multi_gateway_fallback() { let config = crypto_ctrng::IpfsConfig { + gateways: vec![ + "https://invalid-gateway.example.com".into(), + GATEWAY.into(), + ], use_defaults: false, timeout: Duration::from_secs(5), }; - let mut client = crypto_ctrng::Ctrng::ipfs( - ["https://invalid-gateway.example.com", GATEWAY], - BEACON_KEY, - Some(config), - ); + let mut client = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, Some(config)); let block = client.next_block().expect("fallback should succeed"); assert_eq!(block.len(), 32); } @@ -58,17 +57,14 @@ fn ipfs_multi_gateway_fallback() { #[ignore] fn ipfs_all_gateways_fail() { let config = crypto_ctrng::IpfsConfig { + gateways: vec![ + "https://invalid-gateway1.example.com".into(), + "https://invalid-gateway2.example.com".into(), + ], use_defaults: false, timeout: Duration::from_secs(3), }; - let mut client = crypto_ctrng::Ctrng::ipfs( - [ - "https://invalid-gateway1.example.com", - "https://invalid-gateway2.example.com", - ], - BEACON_KEY, - Some(config), - ); + let mut client = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, Some(config)); let result = client.next_block(); assert!(result.is_err(), "all gateways should fail"); let err = result.unwrap_err().to_string(); @@ -81,11 +77,7 @@ fn ipfs_all_gateways_fail() { #[test] #[ignore] fn ipfs_default_gateways_list() { - let mut client = crypto_ctrng::Ctrng::ipfs( - crypto_ctrng::DEFAULT_GATEWAYS.iter().copied(), - BEACON_KEY, - None, - ); + let mut client = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); let block = client.next_block().expect("default gateways should work"); assert_eq!(block.len(), 32); } diff --git a/tests/mixed_tests.rs b/tests/mixed_tests.rs index da76f45..7184593 100644 --- a/tests/mixed_tests.rs +++ b/tests/mixed_tests.rs @@ -1,7 +1,6 @@ use crypto_ctrng::RandomBlockSource; const BEACON_KEY: &str = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -const GATEWAY: &str = "https://ipfs.filebase.io"; #[test] fn local_is_32_bytes() { @@ -21,7 +20,7 @@ fn local_unique_blocks() { #[test] #[ignore] fn mixed_is_32_bytes() { - let ipfs = crypto_ctrng::Ctrng::ipfs([GATEWAY], BEACON_KEY, None); + let ipfs = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); let mut client = crypto_ctrng::MixedCtrng::new(ipfs).unwrap(); let block = client.next_block().unwrap(); assert_eq!(block.len(), 32); @@ -30,8 +29,8 @@ fn mixed_is_32_bytes() { #[test] #[ignore] fn each_source_is_different() { - let mut ipfs = crypto_ctrng::Ctrng::ipfs([GATEWAY], BEACON_KEY, None); - let ipfs2 = crypto_ctrng::Ctrng::ipfs([GATEWAY], BEACON_KEY, None); + let mut ipfs = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); + let ipfs2 = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); let mut mixed = crypto_ctrng::MixedCtrng::new(ipfs2).unwrap(); let ipfs_block = ipfs.next_block().unwrap(); @@ -43,7 +42,7 @@ fn each_source_is_different() { #[test] #[ignore] fn mixed_unique_blocks() { - let ipfs = crypto_ctrng::Ctrng::ipfs([GATEWAY], BEACON_KEY, None); + let ipfs = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); let mut client = crypto_ctrng::MixedCtrng::new(ipfs).unwrap(); let a = client.next_block().unwrap(); let b = client.next_block().unwrap(); diff --git a/tests/testu01_tests.rs b/tests/testu01_tests.rs index a7a7eeb..62f900d 100644 --- a/tests/testu01_tests.rs +++ b/tests/testu01_tests.rs @@ -13,7 +13,7 @@ use testu01_runner::{delete_unif01_gen, make_unif01_gen, register_rng}; fn build_ipfs_seeded_rng() -> ChaCha20Rng { let gateway = "https://ipfs.filebase.io"; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; - let mut ctrng = Ctrng::ipfs([gateway], beacon_key, None); + let mut ctrng = Ctrng::ipfs(beacon_key, None); let seed = ctrng.next_block().expect("failed to fetch IPFS block"); ChaCha20Rng::from_seed(seed) } From 01ee292d4bbe3994fff6cdebf8aee501a52b1d85 Mon Sep 17 00:00:00 2001 From: Toufic Date: Thu, 5 Mar 2026 17:48:20 +0100 Subject: [PATCH 07/16] fmt --- src/ctrng/mod.rs | 4 ++-- src/lib.rs | 2 +- tests/ipfs_live.rs | 5 +---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/ctrng/mod.rs b/src/ctrng/mod.rs index 59cf094..3ce34bb 100644 --- a/src/ctrng/mod.rs +++ b/src/ctrng/mod.rs @@ -1,10 +1,10 @@ pub(crate) mod ipfs; pub mod types; -use ipfs::{IpfsConfig, IpfsCtrng}; -use types::CtrngBlock; use crate::error::SourceError; use crate::traits::RandomBlockSource; +use ipfs::{IpfsConfig, IpfsCtrng}; +use types::CtrngBlock; /// Unified cTRNG abstraction supporting multiple backends. #[derive(Debug)] diff --git a/src/lib.rs b/src/lib.rs index 9dae946..ff16063 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,8 @@ pub mod reseed; pub mod rng; pub mod traits; -pub use ctrng::ipfs::{DEFAULT_GATEWAYS, DEFAULT_TIMEOUT, IpfsConfig, IpfsGateway}; pub use ctrng::Ctrng; +pub use ctrng::ipfs::{DEFAULT_GATEWAYS, DEFAULT_TIMEOUT, IpfsConfig, IpfsGateway}; pub use derive::derive_seed; pub use error::SourceError; pub use local::LocalRng; diff --git a/tests/ipfs_live.rs b/tests/ipfs_live.rs index cad2fa0..54c4caa 100644 --- a/tests/ipfs_live.rs +++ b/tests/ipfs_live.rs @@ -41,10 +41,7 @@ fn ipfs_unique_blocks() { #[ignore] fn ipfs_multi_gateway_fallback() { let config = crypto_ctrng::IpfsConfig { - gateways: vec![ - "https://invalid-gateway.example.com".into(), - GATEWAY.into(), - ], + gateways: vec!["https://invalid-gateway.example.com".into(), GATEWAY.into()], use_defaults: false, timeout: Duration::from_secs(5), }; From 9566746c412086dd345eea50a8ff657f6e4ca876 Mon Sep 17 00:00:00 2001 From: Toufic Date: Thu, 5 Mar 2026 17:50:41 +0100 Subject: [PATCH 08/16] remove derive_seed --- README.md | 4 ++-- src/derive.rs | 16 ---------------- src/lib.rs | 4 ++-- tests/rng_tests.rs | 14 +------------- 4 files changed, 5 insertions(+), 33 deletions(-) delete mode 100644 src/derive.rs diff --git a/README.md b/README.md index 235cd8b..9c9f950 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,14 @@ This crate's responsibility is to **verify** that the cTRNG backend provides uni - **Gateway/back-end** (e.g. IPFS beacon, hardware TRNG) is responsible for generating unique 32-byte blocks with monotonic timestamps. This responsibility sits outside of this crate. - **This crate** (`crypto-ctrng`) verifies uniqueness by enforcing timestamp monotonicity and rejecting duplicate blocks. -- **Application/client code** (e.g. TECDSA library) is responsible for personalizing the provided blocks using `derive_seed(execution_id, party_id, counter, block)` to domain-separate pulls so a single user never receives the same derived value twice, even if they consume the same cTRNG block as other users (but this is something that we are looking to prevent in the future). +- **Application/client code** (e.g. TECDSA library) is responsible for personalizing the provided blocks to domain-separate pulls so a single user never receives the same derived value twice, even if they consume the same cTRNG block as other users (but this is something that we are looking to prevent in the future). **Note on entropy mixing:** - `MixedCtrng` combines entropy sources (XOR of IPFS beacon + local OS randomness) for enhanced security, but still relies on the gateway for uniqueness guarantees. **Uniqueness guarantees:** - This crate ensures that each `next_block()` call returns a block with a timestamp strictly greater than the previous one, preventing global block reuse. -- The calling library must use `derive_seed()` to personalize blocks per user/execution to prevent collisions between different users reading the same raw block. +- The calling library must personalize blocks per user/execution to prevent collisions between different users reading the same raw block. ## Usage modes diff --git a/src/derive.rs b/src/derive.rs deleted file mode 100644 index a8ce15f..0000000 --- a/src/derive.rs +++ /dev/null @@ -1,16 +0,0 @@ -use sha2::{Digest, Sha256}; - -/// Derives a personalized seed using SHA-256 domain separation. -pub fn derive_seed( - execution_id: &[u8], - party_id: u16, - counter: u64, - ctrng_block: &[u8; 32], -) -> [u8; 32] { - let mut hasher = Sha256::new(); - hasher.update(execution_id); - hasher.update(party_id.to_be_bytes()); - hasher.update(counter.to_be_bytes()); - hasher.update(ctrng_block); - hasher.finalize().into() -} diff --git a/src/lib.rs b/src/lib.rs index ff16063..ee19ae0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ pub mod ctrng; -pub mod derive; + pub mod error; pub mod local; pub mod mixed; @@ -9,7 +9,7 @@ pub mod traits; pub use ctrng::Ctrng; pub use ctrng::ipfs::{DEFAULT_GATEWAYS, DEFAULT_TIMEOUT, IpfsConfig, IpfsGateway}; -pub use derive::derive_seed; + pub use error::SourceError; pub use local::LocalRng; pub use mixed::MixedCtrng; diff --git a/tests/rng_tests.rs b/tests/rng_tests.rs index 03d77fe..a4aa41b 100644 --- a/tests/rng_tests.rs +++ b/tests/rng_tests.rs @@ -1,6 +1,5 @@ -use crypto_ctrng::{BlockRng, RandomBlockSource, SourceError, derive_seed}; +use crypto_ctrng::{BlockRng, RandomBlockSource, SourceError}; use rand_core::RngCore; -use std::collections::HashSet; #[test] fn flaky_source_propagates_error() { @@ -53,14 +52,3 @@ fn flaky_source_propagates_error() { let mut buf3 = [0u8; 64]; assert!(rng_trait.try_fill_bytes(&mut buf3).is_err()); } - -#[test] -fn derive_seed_yields_unique_material() { - let exec = b"derive-seed-exec-id"; - let ctrng = [0xAA; 32]; - let mut seen = HashSet::new(); - for ctr in 0..256u64 { - let seed = derive_seed(exec, 1, ctr, &ctrng); - assert!(seen.insert(seed), "duplicate seed for counter {ctr}"); - } -} From b62f7548973ccbda5b5786b6a9ab67728166a233 Mon Sep 17 00:00:00 2001 From: Toufic Date: Fri, 6 Mar 2026 13:42:34 +0100 Subject: [PATCH 09/16] add practrand --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 3 ++- README.md | 37 ++++++------------------------------- tests/testu01_tests.rs | 36 ++++++++++++++++++++++++++++-------- 4 files changed, 48 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c49da8..926a003 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,10 +143,10 @@ dependencies = [ "rand_chacha 0.3.1", "rand_core 0.6.4", "reqwest", + "rng-statistical-tests", "serde", "serde_json", "sha2", - "testu01-runner", "thiserror 1.0.69", "zeroize", ] @@ -185,7 +185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -909,6 +909,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rng-statistical-tests" +version = "0.1.0" +source = "git+https://github.com/spacecomputer-io/statistical-verification.git#fd16ef0da4c74ed5e704e98a47d581e61a06ef71" +dependencies = [ + "bindgen", + "once_cell", + "rand_core 0.6.4", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1118,16 +1128,6 @@ dependencies = [ "syn", ] -[[package]] -name = "testu01-runner" -version = "0.1.0" -source = "git+https://github.com/spacecomputer-io/statistical-verification.git#2588081a7115db21fa595abf79395be76c8eb418" -dependencies = [ - "bindgen", - "once_cell", - "rand_core 0.6.4", -] - [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index af4a4a0..b2877de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ hex = "0.4" [features] default = [] testu01 = [] +practrand = [] [dev-dependencies] -testu01-runner = { git = "https://github.com/spacecomputer-io/statistical-verification.git" } +rng-statistical-tests = { git = "https://github.com/spacecomputer-io/statistical-verification.git" } diff --git a/README.md b/README.md index 9c9f950..2517e63 100644 --- a/README.md +++ b/README.md @@ -123,42 +123,17 @@ To run offline and online tests : ```cargo test -- --include-ignored``` -### TestU01 statistical runner +### Statistical verification -Statistical testing is provided by the separate [`testu01-runner`](https://github.com/spacecomputer-io/statistical-verification) library. +Statistical testing is provided by the separate [`rng-statistical-tests`](https://github.com/spacecomputer-io/statistical-verification) library, which supports both **TestU01** and **PractRand**. -To use it, add to your `Cargo.toml`: +TestU01 and PractRand are automatically downloaded and built during compilation - no manual setup required. -```toml -[dependencies] -testu01-runner = { git = "https://github.com/spacecomputer-io/statistical-verification.git" } -crypto-ctrng = { git = "https://github.com/spacecomputer-io/crypto-ctrng.git" } -``` - -TestU01 is automatically downloaded and built during compilation - no manual setup required. - -To run TestU01 statistical tests: +To run statistical tests: ```bash -cargo test --features testu01 -``` - -Example usage: - -```rust -use rand_chacha::ChaCha20Rng; -use rand_core::SeedableRng; -use testu01_runner::{bbattery_BigCrush, register_rng, make_unif01_gen, delete_unif01_gen}; - -let seed = [0u8; 32]; -let rng = ChaCha20Rng::from_seed(seed); -register_rng(rng); - -unsafe { - let gen = make_unif01_gen("crypto-ctrng"); - bbattery_BigCrush(gen); - delete_unif01_gen(gen); -} +cargo test --features testu01 # TestU01 SmallCrush +cargo test --features practrand # PractRand (1 MiB) ``` diff --git a/tests/testu01_tests.rs b/tests/testu01_tests.rs index 62f900d..3c52003 100644 --- a/tests/testu01_tests.rs +++ b/tests/testu01_tests.rs @@ -5,13 +5,12 @@ use rand_chacha::ChaCha20Rng; #[cfg(feature = "testu01")] use rand_core::SeedableRng; #[cfg(feature = "testu01")] -use testu01_runner::bbattery_SmallCrush; -#[cfg(feature = "testu01")] -use testu01_runner::{delete_unif01_gen, make_unif01_gen, register_rng}; +use rng_statistical_tests::testu01::{ + bbattery_SmallCrush, delete_unif01_gen, make_unif01_gen, register_rng, +}; #[cfg(feature = "testu01")] fn build_ipfs_seeded_rng() -> ChaCha20Rng { - let gateway = "https://ipfs.filebase.io"; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; let mut ctrng = Ctrng::ipfs(beacon_key, None); let seed = ctrng.next_block().expect("failed to fetch IPFS block"); @@ -20,18 +19,39 @@ fn build_ipfs_seeded_rng() -> ChaCha20Rng { #[test] #[cfg(feature = "testu01")] -fn test_ctrng_rng_smallcrush() { +fn testu01_smallcrush() { let rng = build_ipfs_seeded_rng(); register_rng(rng); unsafe { let generator = make_unif01_gen("crypto-ctrng-deterministic"); - println!("=== SmallCrush ==="); bbattery_SmallCrush(generator); - delete_unif01_gen(generator); } } -// Crush and BigCrush tests removed - TestU01 only allows one external generator at a time +#[test] +#[cfg(feature = "practrand")] +fn practrand_ctrng_seeded() { + use crypto_ctrng::{Ctrng, RandomBlockSource}; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + use rng_statistical_tests::practrand; + + let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; + let mut ctrng = Ctrng::ipfs(beacon_key, None); + let seed = ctrng.next_block().expect("failed to fetch IPFS block"); + let rng = ChaCha20Rng::from_seed(seed); + + let result = practrand::run_test( + rng, + practrand::Config { + test_size_kb: Some(1024), + ..Default::default() + }, + ) + .expect("failed to run PractRand"); + + assert!(result.passed, "PractRand reported failure"); +} From 7ce87cf698322fff8f4c0fbc784d71daecfe2e16 Mon Sep 17 00:00:00 2001 From: Toufic Date: Fri, 6 Mar 2026 17:43:55 +0100 Subject: [PATCH 10/16] update docs --- CONTEXT.md | 88 +++++++++++++++++++++++++++++++ README.md | 148 ++++------------------------------------------------ overview.md | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 244 insertions(+), 138 deletions(-) create mode 100644 CONTEXT.md create mode 100644 overview.md diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..ecc560b --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,88 @@ +# Project context + +> For AI assistants. + +## Purpose + +**crypto-ctrng** is a Rust crate that provides secure randomness sourced from a cosmic True Random Number Generator (cTRNG) backend. It wraps this entropy into the standard `rand_core::RngCore` / `CryptoRng` traits so any Rust cryptographic library can consume it directly. + +The crate does **not** generate randomness itself; it **fetches, validates, and delivers** 32-byte blocks from one or more remote backends, enforcing timestamp monotonicity and duplicate detection. + +## Architecture + +``` +User code + │ + ▼ +Ctrng ← public enum; one variant per backend (currently Ipfs) + │ + ├─► IpfsCtrng ← fetches JSON beacon via IPFS gateways, caches blocks + │ + ├─► (future: DataHeavenCtrng, hardware TRNG, …) + │ + ▼ +RandomBlockSource trait ← fn next_block() -> Result<[u8; 32], SourceError> + │ + ▼ +MixedCtrng ← XORs remote block with OS entropy (defense in depth) + │ + ▼ +ReseedingRng ← ChaCha20-based DRBG, auto-reseeds from source + │ + ▼ +RngCore / CryptoRng ← standard rand_core traits +``` + +## Key types + +| Type | File | Role | +|------|------|------| +| `RandomBlockSource` | `src/traits.rs` | Trait every entropy backend must implement | +| `Ctrng` | `src/ctrng/mod.rs` | Public enum dispatching to concrete backends | +| `IpfsCtrng` | `src/ctrng/ipfs.rs` | IPFS beacon client (internal, behind `Ctrng`) | +| `IpfsConfig` | `src/ctrng/ipfs.rs` | Gateway list, `use_defaults`, timeout | +| `IpfsGateway` | `src/ctrng/ipfs.rs` | Newtype for gateway URL; builds `/ipfs/` and `/ipns/` paths | +| `CtrngBlock` | `src/ctrng/types.rs` | Block with sequence number, timestamp, 32-byte data | +| `MixedCtrng` | `src/mixed.rs` | XOR of remote + local OS entropy | +| `ReseedingRng` | `src/reseed.rs` | ChaCha20 DRBG with configurable reseed intervals | +| `BlockRng` | `src/rng.rs` | Thin `RngCore` adapter over any `RandomBlockSource` | +| `LocalRng` | `src/local.rs` | OS-level randomness (`getrandom`) | +| `SourceError` | `src/error.rs` | Error enum: `Ctrng(String)` / `Os(String)` | + +## File layout + +``` +src/ +├── lib.rs public re-exports +├── traits.rs RandomBlockSource trait +├── error.rs SourceError +├── ctrng/ +│ ├── mod.rs Ctrng enum + RandomBlockSource impl +│ ├── ipfs.rs IpfsCtrng, IpfsGateway, IpfsConfig, DEFAULT_GATEWAYS +│ └── types.rs BeaconResponse, CtrngBlock +├── mixed.rs MixedCtrng (XOR mixer) +├── reseed.rs ReseedingRng, ReseedConfig +├── rng.rs BlockRng (RngCore adapter) +└── local.rs LocalRng (OS entropy) + +tests/ +├── ipfs_live.rs live IPFS gateway tests (#[ignore]) +├── mixed_tests.rs MixedCtrng tests (some #[ignore]) +├── reseed_tests.rs ReseedingRng offline tests +├── rng_tests.rs BlockRng error propagation +└── statistical_tests.rs TestU01 / PractRand (behind features) +``` + +## Conventions + +- **PRs**: follow [.github/pull_request_template.md](.github/pull_request_template.md) — compiles without warnings, `cargo test` passes, clear commits, link issue if applicable. +- **Tests**: `cargo test` runs offline tests only. Online (IPFS) tests are `#[ignore]` — run with `cargo test -- --include-ignored`. + +## Useful commands + +```bash +cargo test # offline unit + integration +cargo test -- --include-ignored # includes live IPFS tests +cargo test --features testu01 # TestU01 SmallCrush +cargo test --features practrand # PractRand (1 MiB) +``` diff --git a/README.md b/README.md index 2517e63..08d5da4 100644 --- a/README.md +++ b/README.md @@ -1,146 +1,18 @@ -**WARNING: This project is experimental and work-in-progress, use at your own risk!** +# SpaceComputer | crypto-ctrng -## Overview - -This crate provides a lightweight and secure abstraction layer over a cosmic True Random Number Generator (cTRNG) backend. -It allows deterministic or hardware-backed randomness to be consumed through the standard `RngCore` and `CryptoRng` traits from `rand_core`. - -The main goals is to expose a clean, composable API for hardware or remote entropy backends. It can serve as the randomness foundation for distributed protocols such as threshold ECDSA, or MPC systems that require reproducible yet secure entropy sources. - -### Entropy ownership contract - -This crate's responsibility is to **verify** that the cTRNG backend provides unique blocks. The randomness flow is split between three layers: - -- **Gateway/back-end** (e.g. IPFS beacon, hardware TRNG) is responsible for generating unique 32-byte blocks with monotonic timestamps. This responsibility sits outside of this crate. -- **This crate** (`crypto-ctrng`) verifies uniqueness by enforcing timestamp monotonicity and rejecting duplicate blocks. -- **Application/client code** (e.g. TECDSA library) is responsible for personalizing the provided blocks to domain-separate pulls so a single user never receives the same derived value twice, even if they consume the same cTRNG block as other users (but this is something that we are looking to prevent in the future). - -**Note on entropy mixing:** -- `MixedCtrng` combines entropy sources (XOR of IPFS beacon + local OS randomness) for enhanced security, but still relies on the gateway for uniqueness guarantees. - -**Uniqueness guarantees:** -- This crate ensures that each `next_block()` call returns a block with a timestamp strictly greater than the previous one, preventing global block reuse. -- The calling library must personalize blocks per user/execution to prevent collisions between different users reading the same raw block. - -## Usage modes - -| Mode | Code | DRBG | Reseed | -|------|------|------|--------| -| Raw entropy | `BlockRng::new(source)` | N/A | N/A | -| Seeded DRBG (testing) | `ChaCha20Rng::from_seed(seed)` | ChaCha20 | N/A | -| DRBG + auto reseed | `ReseedingRng::new(source)` | ChaCha20 | 3200 bytes / 1 week | -| DRBG + prediction resistance | `ReseedingRng::with_prediction_resistance(source)` | ChaCha20 | every call | - -## Usage - -To use this crate, add it to your `Cargo.toml`: - -```toml -[dependencies] -crypto-ctrng = { git = "https://github.com/spacecomputer-io/crypto-ctrng.git", tag = "v0.1.0" } -``` - -### Fetch raw entropy (default gateways) - -```rust -use crypto_ctrng::{Ctrng, RandomBlockSource}; - -let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; - -let mut ctrng = Ctrng::ipfs(beacon_key, None); -let block = ctrng.next_block().expect("failed to fetch IPFS block"); -``` - -### Custom gateways with automatic fallback - -If a gateway is unresponsive or down, the next one in the list is tried automatically. -The default timeout is 10 seconds per gateway. - -```rust -use crypto_ctrng::{Ctrng, IpfsConfig, RandomBlockSource}; -use std::time::Duration; - -let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; +![spacecomputer logo](https://raw.githubusercontent.com/spacecomputer-io/media-kit/refs/heads/main/SpaceComputer/logo/SpaceComputer_banner.png) -// Custom gateways + default gateways appended automatically -let config = IpfsConfig { - gateways: vec!["https://my-gateway.example.com".into()], - ..Default::default() -}; -let mut ctrng = Ctrng::ipfs(beacon_key, Some(config)); -let block = ctrng.next_block().expect("at least one gateway should respond"); +![Tests](https://github.com/spacecomputer-io/crypto-ctrng/actions/workflows/rust.yml/badge.svg?branch=main) -// Custom gateways only (no defaults), with a custom timeout -let config = IpfsConfig { - gateways: vec![ - "https://ipfs.filebase.io".into(), - "https://ipfs.io".into(), - ], - use_defaults: false, - timeout: Duration::from_secs(5), -}; -let mut ctrng = Ctrng::ipfs(beacon_key, Some(config)); -``` - -### DRBG with automatic reseeding (recommended) - -```rust -use crypto_ctrng::{Ctrng, MixedCtrng, ReseedingRng}; -use rand_core::RngCore; - -let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; - -let ipfs = Ctrng::ipfs(beacon_key, None); -let mixed = MixedCtrng::new(ipfs).unwrap(); -let mut rng = ReseedingRng::new(mixed).unwrap(); - -let mut key = [0u8; 32]; -rng.fill_bytes(&mut key); -``` - -### Custom reseed intervals - -```rust -use std::time::Duration; -use crypto_ctrng::{Ctrng, MixedCtrng, ReseedConfig, ReseedingRng}; - -let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; - -let ipfs = Ctrng::ipfs(beacon_key, None); -let mixed = MixedCtrng::new(ipfs).unwrap(); - -let config = ReseedConfig::new(1600, Duration::from_secs(3600)); // 50 blocks, 1 hour -let mut rng = ReseedingRng::with_config(mixed, config).unwrap(); -``` - -## Tests - -To run offline tests : - -```cargo test``` - -To run offline and online tests : - -```cargo test -- --include-ignored``` - -### Statistical verification - -Statistical testing is provided by the separate [`rng-statistical-tests`](https://github.com/spacecomputer-io/statistical-verification) library, which supports both **TestU01** and **PractRand**. - -TestU01 and PractRand are automatically downloaded and built during compilation - no manual setup required. - -To run statistical tests: - -```bash -cargo test --features testu01 # TestU01 SmallCrush -cargo test --features practrand # PractRand (1 MiB) -``` +This repository contains the crypto-ctrng project by SpaceComputer. +## Overview -## License +This crate provides a lightweight and secure abstraction layer over a cosmic True Random Number Generator (cTRNG) backend. +It allows deterministic or hardware-backed randomness to be consumed through the standard `RngCore` and `CryptoRng` traits from `rand_core`. -This project is licensed under the terms of the MIT License. See the [LICENSE](LICENSE) file for details. +The main goals is to expose a clean, composable API for hardware or remote entropy backends. -## Contributing +## Links -We welcome contributions to this project! If you have suggestions for improvements or new features, please open an issue or submit a pull request. +* [SpaceCopmuter docs](https://docs.spacecomputer.io) diff --git a/overview.md b/overview.md new file mode 100644 index 0000000..0412666 --- /dev/null +++ b/overview.md @@ -0,0 +1,146 @@ +**WARNING: This project is experimental and work-in-progress, use at your own risk!** + +## Overview + +This crate provides a lightweight and secure abstraction layer over a cosmic True Random Number Generator (cTRNG) backend. +It allows deterministic or hardware-backed randomness to be consumed through the standard `RngCore` and `CryptoRng` traits from `rand_core`. + +The main goals is to expose a clean, composable API for hardware or remote entropy backends. + +### Entropy ownership contract + +This crate's responsibility is to **verify** that the cTRNG backend provides unique blocks. The randomness flow is split between three layers: + +- **Gateway/back-end** (e.g. IPFS beacon, hardware TRNG) is responsible for generating unique 32-byte blocks with monotonic timestamps. This responsibility sits outside of this crate. +- **This crate** (`crypto-ctrng`) verifies uniqueness by enforcing timestamp monotonicity and rejecting duplicate blocks. +- **Application/client code** (e.g. TECDSA library) is responsible for personalizing the provided blocks to domain-separate pulls so a single user never receives the same derived value twice, even if they consume the same cTRNG block as other users (but this is something that we are looking to prevent in the future). + +**Note on entropy mixing:** +- `MixedCtrng` combines entropy sources (XOR of IPFS beacon + local OS randomness) for enhanced security, but still relies on the gateway for uniqueness guarantees. + +**Uniqueness guarantees:** +- This crate ensures that each `next_block()` call returns a block with a timestamp strictly greater than the previous one, preventing global block reuse. +- The calling library must personalize blocks per user/execution to prevent collisions between different users reading the same raw block. + +## Usage modes + +| Mode | Code | DRBG | Reseed | +|------|------|------|--------| +| Raw entropy | `BlockRng::new(source)` | N/A | N/A | +| Seeded DRBG (testing) | `ChaCha20Rng::from_seed(seed)` | ChaCha20 | N/A | +| DRBG + auto reseed | `ReseedingRng::new(source)` | ChaCha20 | 3200 bytes / 1 week | +| DRBG + prediction resistance | `ReseedingRng::with_prediction_resistance(source)` | ChaCha20 | every call | + +## Usage + +To use this crate, add it to your `Cargo.toml`: + +```toml +[dependencies] +crypto-ctrng = { git = "https://github.com/spacecomputer-io/crypto-ctrng.git", tag = "v0.1.0" } +``` + +### Fetch raw entropy (default gateways) + +```rust +use crypto_ctrng::{Ctrng, RandomBlockSource}; + +let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; + +let mut ctrng = Ctrng::ipfs(beacon_key, None); +let block = ctrng.next_block().expect("failed to fetch IPFS block"); +``` + +### Custom gateways with automatic fallback + +If a gateway is unresponsive or down, the next one in the list is tried automatically. +The default timeout is 10 seconds per gateway. + +```rust +use crypto_ctrng::{Ctrng, IpfsConfig, RandomBlockSource}; +use std::time::Duration; + +let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; + +// Custom gateways + default gateways appended automatically +let config = IpfsConfig { + gateways: vec!["https://my-gateway.example.com".into()], + ..Default::default() +}; +let mut ctrng = Ctrng::ipfs(beacon_key, Some(config)); +let block = ctrng.next_block().expect("at least one gateway should respond"); + +// Custom gateways only (no defaults), with a custom timeout +let config = IpfsConfig { + gateways: vec![ + "https://ipfs.filebase.io".into(), + "https://ipfs.io".into(), + ], + use_defaults: false, + timeout: Duration::from_secs(5), +}; +let mut ctrng = Ctrng::ipfs(beacon_key, Some(config)); +``` + +### DRBG with automatic reseeding (recommended) + +```rust +use crypto_ctrng::{Ctrng, MixedCtrng, ReseedingRng}; +use rand_core::RngCore; + +let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; + +let ipfs = Ctrng::ipfs(beacon_key, None); +let mixed = MixedCtrng::new(ipfs).unwrap(); +let mut rng = ReseedingRng::new(mixed).unwrap(); + +let mut key = [0u8; 32]; +rng.fill_bytes(&mut key); +``` + +### Custom reseed intervals + +```rust +use std::time::Duration; +use crypto_ctrng::{Ctrng, MixedCtrng, ReseedConfig, ReseedingRng}; + +let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; + +let ipfs = Ctrng::ipfs(beacon_key, None); +let mixed = MixedCtrng::new(ipfs).unwrap(); + +let config = ReseedConfig::new(1600, Duration::from_secs(3600)); // 50 blocks, 1 hour +let mut rng = ReseedingRng::with_config(mixed, config).unwrap(); +``` + +## Tests + +To run offline tests : + +```cargo test``` + +To run offline and online tests : + +```cargo test -- --include-ignored``` + +### Statistical verification + +Statistical testing is provided by the separate [`rng-statistical-tests`](https://github.com/spacecomputer-io/statistical-verification) library, which supports both **TestU01** and **PractRand**. + +TestU01 and PractRand are automatically downloaded and built during compilation - no manual setup required. + +To run statistical tests: + +```bash +cargo test --features testu01 # TestU01 SmallCrush +cargo test --features practrand # PractRand (1 MiB) +``` + + +## License + +This project is licensed under the terms of the MIT License. See the [LICENSE](LICENSE) file for details. + +## Contributing + +We welcome contributions to this project! If you have suggestions for improvements or new features, please open an issue or submit a pull request. From 8e686b004ea309938ad58bf9bc1b14675b7b9c5a Mon Sep 17 00:00:00 2001 From: Toufic Date: Tue, 10 Mar 2026 14:46:20 +0100 Subject: [PATCH 11/16] rename + e2e --- .github/workflows/rust.yml | 2 +- README.md | 174 ++++++++++++++++++++++++++ src/ctrng/{ipfs.rs => ipfs_beacon.rs} | 6 +- src/ctrng/mod.rs | 8 +- src/lib.rs | 2 +- tests/{ipfs_live.rs => e2e_ipfs.rs} | 17 +-- tests/e2e_mixed.rs | 32 +++++ tests/mixed_tests.rs | 34 ----- 8 files changed, 221 insertions(+), 54 deletions(-) rename src/ctrng/{ipfs.rs => ipfs_beacon.rs} (98%) rename tests/{ipfs_live.rs => e2e_ipfs.rs} (91%) create mode 100644 tests/e2e_mixed.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9fd45e0..b9dd740 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,4 +19,4 @@ jobs: - name: Build run: cargo build --verbose - name: Run tests - run: cargo test --verbose + run: cargo test --verbose -- --skip test_e2e_ diff --git a/README.md b/README.md index 08d5da4..0aa67b4 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,177 @@ The main goals is to expose a clean, composable API for hardware or remote entro ## Links * [SpaceCopmuter docs](https://docs.spacecomputer.io) + +### Entropy ownership contract + +This crate's responsibility is to **verify** that the cTRNG backend provides unique blocks. The randomness flow is split between three layers: + +- **Gateway/back-end** (e.g. IPFS beacon, hardware TRNG) is responsible for generating unique 32-byte blocks with monotonic timestamps. This responsibility sits outside of this crate. +- **This crate** (`crypto-ctrng`) verifies uniqueness by enforcing timestamp monotonicity and rejecting duplicate blocks. +- **Application/client code** (e.g. TECDSA library) is responsible for personalizing the provided blocks to domain-separate pulls so a single user never receives the same derived value twice, even if they consume the same cTRNG block as other users (but this is something that we are looking to prevent in the future). + +**Note on entropy mixing:** +- `MixedCtrng` combines entropy sources (XOR of IPFS beacon + local OS randomness) for enhanced security, but still relies on the gateway for uniqueness guarantees. + +**Uniqueness guarantees:** +- This crate ensures that each `next_block()` call returns a block with a timestamp strictly greater than the previous one, preventing global block reuse. +- The calling library must personalize blocks per user/execution to prevent collisions between different users reading the same raw block. + +## Usage modes + +| Mode | Code | DRBG | Reseed | +|------|------|------|--------| +| Raw entropy | `BlockRng::new(source)` | N/A | N/A | +| Seeded DRBG (testing) | `ChaCha20Rng::from_seed(seed)` | ChaCha20 | N/A | +| DRBG + auto reseed | `ReseedingRng::new(source)` | ChaCha20 | 3200 bytes / 1 week | +| DRBG + prediction resistance | `ReseedingRng::with_prediction_resistance(source)` | ChaCha20 | every call | + +## Usage + +To use this crate, add it to your `Cargo.toml`: + +```toml +[dependencies] +crypto-ctrng = { git = "https://github.com/spacecomputer-io/crypto-ctrng.git", tag = "v0.1.0" } +``` + +### Fetch raw entropy (default gateways) + +```rust +use crypto_ctrng::{Ctrng, RandomBlockSource}; + +let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; + +let mut ctrng = Ctrng::ipfs(beacon_key, None); +let block = ctrng.next_block().expect("failed to fetch IPFS block"); +``` + +### Custom gateways with automatic fallback + +If a gateway is unresponsive or down, the next one in the list is tried automatically. +The default timeout is 10 seconds per gateway. + +```rust +use crypto_ctrng::{Ctrng, IpfsConfig, RandomBlockSource}; +use std::time::Duration; + +let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; + +// Custom gateways + default gateways appended automatically +let config = IpfsConfig { + gateways: vec!["https://my-gateway.example.com".into()], + ..Default::default() +}; +let mut ctrng = Ctrng::ipfs(beacon_key, Some(config)); +let block = ctrng.next_block().expect("at least one gateway should respond"); + +// Custom gateways only (no defaults), with a custom timeout +let config = IpfsConfig { + gateways: vec![ + "https://ipfs.filebase.io".into(), + "https://ipfs.io".into(), + ], + use_defaults: false, + timeout: Duration::from_secs(5), +}; +let mut ctrng = Ctrng::ipfs(beacon_key, Some(config)); +``` + +### DRBG with automatic reseeding (recommended) + +```rust +use crypto_ctrng::{Ctrng, MixedCtrng, ReseedingRng}; +use rand_core::RngCore; + +let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; + +let ipfs = Ctrng::ipfs(beacon_key, None); +let mixed = MixedCtrng::new(ipfs).unwrap(); +let mut rng = ReseedingRng::new(mixed).unwrap(); + +let mut key = [0u8; 32]; +rng.fill_bytes(&mut key); +``` + +### Custom reseed intervals + +```rust +use std::time::Duration; +use crypto_ctrng::{Ctrng, MixedCtrng, ReseedConfig, ReseedingRng}; + +let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; + +let ipfs = Ctrng::ipfs(beacon_key, None); +let mixed = MixedCtrng::new(ipfs).unwrap(); + +let config = ReseedConfig::new(1600, Duration::from_secs(3600)); // 50 blocks, 1 hour +let mut rng = ReseedingRng::with_config(mixed, config).unwrap(); +``` + +## Tests + +To run offline tests : + +```bash +cargo test -- --skip test_e2e_ +``` + +To run the IPFS-backed end-to-end tests : + +```bash +cargo test test_e2e_ +``` + +You can also run the dedicated e2e integration targets individually: + +```bash +cargo test --test e2e_ipfs +cargo test --test e2e_mixed +``` + +### TestU01 statistical runner + +Statistical testing is provided by the separate [`testu01-runner`](https://github.com/spacecomputer-io/statistical-verification) library. + +To use it, add to your `Cargo.toml`: + +```toml +[dependencies] +testu01-runner = { git = "https://github.com/spacecomputer-io/statistical-verification.git" } +crypto-ctrng = { git = "https://github.com/spacecomputer-io/crypto-ctrng.git" } +``` + +TestU01 is automatically downloaded and built during compilation - no manual setup required. + +To run TestU01 statistical tests: + +```bash +cargo test --features testu01 +``` + +Example usage: + +```rust +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; +use testu01_runner::{bbattery_BigCrush, register_rng, make_unif01_gen, delete_unif01_gen}; + +let seed = [0u8; 32]; +let rng = ChaCha20Rng::from_seed(seed); +register_rng(rng); + +unsafe { + let gen = make_unif01_gen("crypto-ctrng"); + bbattery_BigCrush(gen); + delete_unif01_gen(gen); +} +``` + + +## License + +This project is licensed under the terms of the MIT License. See the [LICENSE](LICENSE) file for details. + +## Contributing + +We welcome contributions to this project! If you have suggestions for improvements or new features, please open an issue or submit a pull request. diff --git a/src/ctrng/ipfs.rs b/src/ctrng/ipfs_beacon.rs similarity index 98% rename from src/ctrng/ipfs.rs rename to src/ctrng/ipfs_beacon.rs index bb53805..ebb34b9 100644 --- a/src/ctrng/ipfs.rs +++ b/src/ctrng/ipfs_beacon.rs @@ -62,7 +62,7 @@ impl PartialEq for IpfsGateway { /// cTRNG backed by an IPFS beacon. #[derive(Debug)] -pub struct IpfsCtrng { +pub struct IpfsBeacon { gateways: Vec, beacon_key: String, timeout: Duration, @@ -71,7 +71,7 @@ pub struct IpfsCtrng { last_served: Option, } -impl IpfsCtrng { +impl IpfsBeacon { pub fn new(beacon_key: impl Into, config: Option) -> Self { let config = config.unwrap_or_default(); let mut gw_list: Vec = @@ -183,7 +183,7 @@ impl IpfsCtrng { } } -impl RandomBlockSource for IpfsCtrng { +impl RandomBlockSource for IpfsBeacon { fn next_block(&mut self) -> Result<[u8; 32], SourceError> { if self.cursor < self.cache.len() { let block = self.cache[self.cursor]; diff --git a/src/ctrng/mod.rs b/src/ctrng/mod.rs index 3ce34bb..1d18b6f 100644 --- a/src/ctrng/mod.rs +++ b/src/ctrng/mod.rs @@ -1,15 +1,15 @@ -pub(crate) mod ipfs; +pub(crate) mod ipfs_beacon; pub mod types; use crate::error::SourceError; use crate::traits::RandomBlockSource; -use ipfs::{IpfsConfig, IpfsCtrng}; +use ipfs_beacon::{IpfsConfig, IpfsBeacon}; use types::CtrngBlock; /// Unified cTRNG abstraction supporting multiple backends. #[derive(Debug)] pub enum Ctrng { - Ipfs(IpfsCtrng), + Ipfs(IpfsBeacon), // Future: DataHeaven(DataHeavenCtrng), } @@ -18,7 +18,7 @@ impl Ctrng { /// /// Pass `None` for default config (default gateways, 10s timeout). pub fn ipfs(beacon_key: impl Into, config: Option) -> Self { - Self::Ipfs(IpfsCtrng::new(beacon_key, config)) + Self::Ipfs(IpfsBeacon::new(beacon_key, config)) } /// Returns the last block served, if any. diff --git a/src/lib.rs b/src/lib.rs index ee19ae0..8b88bf3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ pub mod rng; pub mod traits; pub use ctrng::Ctrng; -pub use ctrng::ipfs::{DEFAULT_GATEWAYS, DEFAULT_TIMEOUT, IpfsConfig, IpfsGateway}; +pub use ctrng::ipfs_beacon::{DEFAULT_GATEWAYS, DEFAULT_TIMEOUT, IpfsConfig, IpfsGateway}; pub use error::SourceError; pub use local::LocalRng; diff --git a/tests/ipfs_live.rs b/tests/e2e_ipfs.rs similarity index 91% rename from tests/ipfs_live.rs rename to tests/e2e_ipfs.rs index 54c4caa..d288697 100644 --- a/tests/ipfs_live.rs +++ b/tests/e2e_ipfs.rs @@ -6,16 +6,14 @@ const BEACON_KEY: &str = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn0 const GATEWAY: &str = "https://ipfs.filebase.io"; #[test] -#[ignore] -fn ipfs_client_can_fetch_live_block() { +fn test_e2e_ipfs_client_can_fetch_live_block() { let mut client = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); let block = client.next_block().expect("fetch 32-byte block"); assert_eq!(block.len(), 32); } #[test] -#[ignore] -fn ipfs_unique_blocks() { +fn test_e2e_ipfs_unique_blocks() { let mut client = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); let a = client.next_block().expect("first block"); @@ -32,14 +30,13 @@ fn ipfs_unique_blocks() { assert_ne!(c, d, "third and fourth blocks should be different"); } Err(_) => { - // Expected failure confirms timestamp validation works + // Expected failure confirms timestamp validation works. } } } #[test] -#[ignore] -fn ipfs_multi_gateway_fallback() { +fn test_e2e_ipfs_multi_gateway_fallback() { let config = crypto_ctrng::IpfsConfig { gateways: vec!["https://invalid-gateway.example.com".into(), GATEWAY.into()], use_defaults: false, @@ -51,8 +48,7 @@ fn ipfs_multi_gateway_fallback() { } #[test] -#[ignore] -fn ipfs_all_gateways_fail() { +fn test_e2e_ipfs_all_gateways_fail() { let config = crypto_ctrng::IpfsConfig { gateways: vec![ "https://invalid-gateway1.example.com".into(), @@ -72,8 +68,7 @@ fn ipfs_all_gateways_fail() { } #[test] -#[ignore] -fn ipfs_default_gateways_list() { +fn test_e2e_ipfs_default_gateways_list() { let mut client = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); let block = client.next_block().expect("default gateways should work"); assert_eq!(block.len(), 32); diff --git a/tests/e2e_mixed.rs b/tests/e2e_mixed.rs new file mode 100644 index 0000000..afd9449 --- /dev/null +++ b/tests/e2e_mixed.rs @@ -0,0 +1,32 @@ +use crypto_ctrng::RandomBlockSource; + +const BEACON_KEY: &str = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; + +#[test] +fn test_e2e_mixed_is_32_bytes() { + let ipfs = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); + let mut client = crypto_ctrng::MixedCtrng::new(ipfs).unwrap(); + let block = client.next_block().unwrap(); + assert_eq!(block.len(), 32); +} + +#[test] +fn test_e2e_each_source_is_different() { + let mut ipfs = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); + let ipfs2 = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); + let mut mixed = crypto_ctrng::MixedCtrng::new(ipfs2).unwrap(); + + let ipfs_block = ipfs.next_block().unwrap(); + let mixed_block = mixed.next_block().unwrap(); + + assert_ne!(ipfs_block, mixed_block); +} + +#[test] +fn test_e2e_mixed_unique_blocks() { + let ipfs = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); + let mut client = crypto_ctrng::MixedCtrng::new(ipfs).unwrap(); + let a = client.next_block().unwrap(); + let b = client.next_block().unwrap(); + assert_ne!(a, b); +} diff --git a/tests/mixed_tests.rs b/tests/mixed_tests.rs index 7184593..cd3dad9 100644 --- a/tests/mixed_tests.rs +++ b/tests/mixed_tests.rs @@ -1,7 +1,5 @@ use crypto_ctrng::RandomBlockSource; -const BEACON_KEY: &str = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; - #[test] fn local_is_32_bytes() { let mut client = crypto_ctrng::LocalRng::new(); @@ -16,35 +14,3 @@ fn local_unique_blocks() { let b = client.next_block().unwrap(); assert_ne!(a, b); } - -#[test] -#[ignore] -fn mixed_is_32_bytes() { - let ipfs = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); - let mut client = crypto_ctrng::MixedCtrng::new(ipfs).unwrap(); - let block = client.next_block().unwrap(); - assert_eq!(block.len(), 32); -} - -#[test] -#[ignore] -fn each_source_is_different() { - let mut ipfs = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); - let ipfs2 = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); - let mut mixed = crypto_ctrng::MixedCtrng::new(ipfs2).unwrap(); - - let ipfs_block = ipfs.next_block().unwrap(); - let mixed_block = mixed.next_block().unwrap(); - - assert_ne!(ipfs_block, mixed_block); -} - -#[test] -#[ignore] -fn mixed_unique_blocks() { - let ipfs = crypto_ctrng::Ctrng::ipfs(BEACON_KEY, None); - let mut client = crypto_ctrng::MixedCtrng::new(ipfs).unwrap(); - let a = client.next_block().unwrap(); - let b = client.next_block().unwrap(); - assert_ne!(a, b); -} From c218b6d8e063a11fea5f6de74fe2a2029f56a459 Mon Sep 17 00:00:00 2001 From: Toufic Date: Wed, 11 Mar 2026 13:30:58 +0100 Subject: [PATCH 12/16] add vuln-scan --- .github/workflows/rust-vuln-scan.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/rust-vuln-scan.yml diff --git a/.github/workflows/rust-vuln-scan.yml b/.github/workflows/rust-vuln-scan.yml new file mode 100644 index 0000000..3a643a5 --- /dev/null +++ b/.github/workflows/rust-vuln-scan.yml @@ -0,0 +1,26 @@ +name: Rust Vulnerability Scan + +on: + push: + branches: [ "main" ] + pull_request: + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + # schedule: + # # Run daily at midnight to catch newly disclosed vulnerabilities + # - cron: '0 0 * * *' + +jobs: + audit: + name: Run cargo-audit + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install cargo-audit + uses: taiki-e/install-action@cargo-audit + + - name: Run cargo-audit + run: cargo audit \ No newline at end of file From 22e0ab85329770b45ec451bae7edfc51f8cb3e41 Mon Sep 17 00:00:00 2001 From: Toufic Date: Wed, 11 Mar 2026 13:43:23 +0100 Subject: [PATCH 13/16] fix cargo audit --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 926a003..0f0f820 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,9 +69,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" @@ -730,9 +730,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "getrandom 0.3.4", From 71f993a078f3cc94410bb974079e06501b0deba3 Mon Sep 17 00:00:00 2001 From: Toufic Date: Wed, 11 Mar 2026 15:19:47 +0100 Subject: [PATCH 14/16] fix e2e_ --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0aa67b4..339e458 100644 --- a/README.md +++ b/README.md @@ -128,13 +128,13 @@ let mut rng = ReseedingRng::with_config(mixed, config).unwrap(); To run offline tests : ```bash -cargo test -- --skip test_e2e_ +cargo test -- --skip test_e2e ``` To run the IPFS-backed end-to-end tests : ```bash -cargo test test_e2e_ +cargo test test_e2e ``` You can also run the dedicated e2e integration targets individually: From 3b87e96b7d7fa92d8a2591c974db8f7d9ebe71bc Mon Sep 17 00:00:00 2001 From: Toufic Date: Thu, 26 Mar 2026 20:17:00 +0100 Subject: [PATCH 15/16] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dcd4183..e0738bc 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The main goals is to expose a clean, composable API for hardware or remote entro ## Links -* [SpaceCopmuter docs](https://docs.spacecomputer.io) +* [SpaceComputer docs](https://docs.spacecomputer.io) ### Entropy ownership contract From 5a85198694c402f7059c4575da9fb65f93005e24 Mon Sep 17 00:00:00 2001 From: Toufic Date: Thu, 26 Mar 2026 20:17:52 +0100 Subject: [PATCH 16/16] fix ci --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f0f820..1beb529 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -970,9 +970,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "ring", "rustls-pki-types",