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 dc9549d..7807308 100644 --- a/README.md +++ b/README.md @@ -13,21 +13,21 @@ 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 (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). -- The calling library must use `derive_seed()` to personalize blocks per user/execution to prevent collisions between different users reading the same raw block. +- 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 | -| 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 | @@ -40,28 +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 +### Fetch raw entropy (default gateways) ```rust -use crypto_ctrng::{IpfsCtrng, RandomBlockSource}; +use crypto_ctrng::{Ctrng, RandomBlockSource}; -let gateway = "https://ipfs.io"; -let beacon_key = "k2k4r8pigrw8i34z63om8f015tt5igdq0c46xupq8spp1bogt35k5vhe"; +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"); ``` +### 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::{IpfsCtrng, MixedCtrng, ReseedingRng}; +use crypto_ctrng::{Ctrng, MixedCtrng, ReseedingRng}; use rand_core::RngCore; -let gateway = "https://ipfs.io"; -let beacon_key = "k2k4r8pigrw8i34z63om8f015tt5igdq0c46xupq8spp1bogt35k5vhe"; +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(); @@ -73,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.io"; -let beacon_key = "k2k4r8pigrw8i34z63om8f015tt5igdq0c46xupq8spp1bogt35k5vhe"; +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 @@ -89,11 +117,22 @@ let mut rng = ReseedingRng::with_config(mixed, config).unwrap(); To run offline tests : -```cargo test``` +```bash +cargo test -- --skip test_e2e +``` + +To run the IPFS-backed end-to-end tests : -To run offline and online tests : +```bash +cargo test test_e2e +``` -```cargo test -- --include-ignored``` +You can also run the dedicated e2e integration targets individually: + +```bash +cargo test --test e2e_ipfs +cargo test --test e2e_mixed +``` ### TestU01 statistical runner diff --git a/src/ctrng/ipfs.rs b/src/ctrng/ipfs.rs deleted file mode 100644 index b2e381d..0000000 --- a/src/ctrng/ipfs.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::error::SourceError; -use crate::traits::RandomBlockSource; - -use super::types::{BeaconResponse, CtrngBlock}; - -/// cTRNG backed by an IPFS beacon. -#[derive(Debug)] -pub struct IpfsCtrng { - gateway_url: String, - beacon_key: String, - cache: Vec, - cursor: usize, - last_served: Option, -} - -impl IpfsCtrng { - pub fn new(gateway_url: impl Into, beacon_key: impl Into) -> Self { - Self { - gateway_url: gateway_url.into(), - beacon_key: beacon_key.into(), - cache: Vec::new(), - cursor: 0, - last_served: None, - } - } - - pub fn last_served_block(&self) -> Option { - self.last_served - } - - fn refill_cache(&mut self) -> Result<(), SourceError> { - let url = format!( - "{}/ipns/{}", - self.gateway_url.trim_end_matches('/'), - self.beacon_key - ); - let resp = reqwest::blocking::get(&url) - .map_err(|e| SourceError::ctrng(format!("ipfs fetch failed: {e}")))?; - if !resp.status().is_success() { - return Err(SourceError::ctrng(format!( - "ipfs returned 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| SourceError::ctrng(format!("ipfs json parse failed: {e}")))?; - - let mut new_cache = Vec::with_capacity(parsed.data.ctrng.len()); - let sequence = parsed.data.sequence; - let timestamp = parsed.data.timestamp; - for hex_str in parsed.data.ctrng.iter() { - let raw = hex::decode(hex_str) - .map_err(|e| SourceError::ctrng(format!("invalid hex in ctrng entry: {e}")))?; - if raw.len() != 32 { - return Err(SourceError::ctrng(format!( - "ctrng entry length is {}, expected 32", - raw.len() - ))); - } - let mut block = [0u8; 32]; - block.copy_from_slice(&raw); - new_cache.push(CtrngBlock { - sequence, - timestamp, - data: block, - }); - } - if new_cache.is_empty() { - return Err(SourceError::ctrng("ipfs beacon returned empty ctrng list")); - } - if let (Some(last), Some(first)) = (self.last_served, new_cache.first().copied()) { - if first.timestamp <= last.timestamp { - return Err(SourceError::ctrng(format!( - "ipfs beacon timestamp {} is not greater than last served {}", - first.timestamp, last.timestamp - ))); - } - } - self.cache = new_cache; - self.cursor = 0; - Ok(()) - } -} - -impl RandomBlockSource for IpfsCtrng { - fn next_block(&mut self) -> Result<[u8; 32], SourceError> { - if self.cursor < self.cache.len() { - let block = self.cache[self.cursor]; - self.cursor += 1; - - if let Some(last) = self.last_served { - if block.data == last.data { - return Err(SourceError::ctrng(format!( - "ipfs beacon repeated randomness for timestamp {}", - block.timestamp - ))); - } - } - - self.last_served = Some(block); - Ok(block.data) - } else { - self.refill_cache()?; - self.next_block() - } - } -} diff --git a/src/ctrng/ipfs_beacon.rs b/src/ctrng/ipfs_beacon.rs new file mode 100644 index 0000000..ebb34b9 --- /dev/null +++ b/src/ctrng/ipfs_beacon.rs @@ -0,0 +1,208 @@ +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", + "https://ipfs.filebase.io", + "https://dweb.link", +]; + +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, +} + +impl Default for IpfsConfig { + fn default() -> Self { + Self { + gateways: Vec::new(), + use_defaults: true, + timeout: DEFAULT_TIMEOUT, + } + } +} + +#[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) + } +} + +impl PartialEq for IpfsGateway { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +/// cTRNG backed by an IPFS beacon. +#[derive(Debug)] +pub struct IpfsBeacon { + gateways: Vec, + beacon_key: String, + timeout: Duration, + cache: Vec, + cursor: usize, + last_served: Option, +} + +impl IpfsBeacon { + pub fn new(beacon_key: impl Into, config: Option) -> Self { + let config = config.unwrap_or_default(); + let mut gw_list: Vec = + config.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: gw_list, + beacon_key: beacon_key.into(), + timeout: config.timeout, + cache: Vec::new(), + cursor: 0, + last_served: None, + } + } + + pub fn last_served_block(&self) -> Option { + self.last_served + } + + fn try_gateway(&self, gateway: &IpfsGateway) -> Result, String> { + let url = gateway.ipns(&self.beacon_key); + + 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(format!("{gateway}: HTTP {}", resp.status())); + } + + let bytes = resp + .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()); + let sequence = parsed.data.sequence; + let timestamp = parsed.data.timestamp; + for hex_str in parsed.data.ctrng.iter() { + let raw = hex::decode(hex_str) + .map_err(|e| SourceError::ctrng(format!("invalid hex in ctrng entry: {e}")))?; + if raw.len() != 32 { + return Err(SourceError::ctrng(format!( + "ctrng entry length is {}, expected 32", + raw.len() + ))); + } + let mut block = [0u8; 32]; + block.copy_from_slice(&raw); + new_cache.push(CtrngBlock { + sequence, + timestamp, + data: block, + }); + } + if new_cache.is_empty() { + return Err(SourceError::ctrng("ipfs beacon returned empty ctrng list")); + } + if let (Some(last), Some(first)) = (self.last_served, new_cache.first().copied()) { + if first.timestamp <= last.timestamp { + return Err(SourceError::ctrng(format!( + "ipfs beacon timestamp {} is not greater than last served {}", + first.timestamp, last.timestamp + ))); + } + } + self.cache = new_cache; + self.cursor = 0; + Ok(()) + } +} + +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]; + self.cursor += 1; + + if let Some(last) = self.last_served { + if block.data == last.data { + return Err(SourceError::ctrng(format!( + "ipfs beacon repeated randomness for timestamp {}", + block.timestamp + ))); + } + } + + self.last_served = Some(block); + Ok(block.data) + } else { + self.refill_cache()?; + self.next_block() + } + } +} diff --git a/src/ctrng/mod.rs b/src/ctrng/mod.rs index 2196ed7..1d18b6f 100644 --- a/src/ctrng/mod.rs +++ b/src/ctrng/mod.rs @@ -1,2 +1,38 @@ -pub mod ipfs; +pub(crate) mod ipfs_beacon; pub mod types; + +use crate::error::SourceError; +use crate::traits::RandomBlockSource; +use ipfs_beacon::{IpfsConfig, IpfsBeacon}; +use types::CtrngBlock; + +/// Unified cTRNG abstraction supporting multiple backends. +#[derive(Debug)] +pub enum Ctrng { + Ipfs(IpfsBeacon), + // Future: DataHeaven(DataHeavenCtrng), +} + +impl Ctrng { + /// Create an IPFS cTRNG source. + /// + /// Pass `None` for default config (default gateways, 10s timeout). + pub fn ipfs(beacon_key: impl Into, config: Option) -> Self { + Self::Ipfs(IpfsBeacon::new(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/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 d3c4a43..8b88bf3 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; @@ -7,8 +7,9 @@ pub mod reseed; pub mod rng; pub mod traits; -pub use ctrng::ipfs::IpfsCtrng; -pub use derive::derive_seed; +pub use ctrng::Ctrng; +pub use ctrng::ipfs_beacon::{DEFAULT_GATEWAYS, DEFAULT_TIMEOUT, IpfsConfig, IpfsGateway}; + pub use error::SourceError; pub use local::LocalRng; pub use mixed::MixedCtrng; diff --git a/tests/e2e_ipfs.rs b/tests/e2e_ipfs.rs new file mode 100644 index 0000000..d288697 --- /dev/null +++ b/tests/e2e_ipfs.rs @@ -0,0 +1,75 @@ +use std::time::Duration; + +use crypto_ctrng::RandomBlockSource; + +const BEACON_KEY: &str = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; +const GATEWAY: &str = "https://ipfs.filebase.io"; + +#[test] +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] +fn test_e2e_ipfs_unique_blocks() { + 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"); + + let result = client.next_block(); + + assert_ne!(a, b, "first and second blocks should be different"); + assert_ne!(b, c, "second and third blocks should be different"); + + match result { + Ok(d) => { + assert_ne!(c, d, "third and fourth blocks should be different"); + } + Err(_) => { + // Expected failure confirms timestamp validation works. + } + } +} + +#[test] +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, + timeout: Duration::from_secs(5), + }; + 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); +} + +#[test] +fn test_e2e_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(BEACON_KEY, Some(config)); + 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] +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/ipfs_live.rs b/tests/ipfs_live.rs deleted file mode 100644 index 2554693..0000000 --- a/tests/ipfs_live.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crypto_ctrng::RandomBlockSource; - -const BEACON_KEY: &str = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -const GATEWAY: &str = "https://ipfs.io"; - -#[test] -#[ignore] -fn ipfs_client_can_fetch_live_block() { - let mut client = crypto_ctrng::IpfsCtrng::new(GATEWAY, BEACON_KEY); - let block = client.next_block().expect("fetch 32-byte block"); - assert_eq!(block.len(), 32); -} - -#[test] -#[ignore] -fn ipfs_unique_blocks() { - let mut client = crypto_ctrng::IpfsCtrng::new(GATEWAY, BEACON_KEY); - - 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"); - assert_ne!(b, c, "second and third blocks should be different"); - - match result { - Ok(d) => { - assert_ne!(c, d, "third and fourth blocks should be different"); - } - Err(_) => { - // Expected failure confirms timestamp validation works - } - } -} diff --git a/tests/mixed_tests.rs b/tests/mixed_tests.rs index 37def64..cd3dad9 100644 --- a/tests/mixed_tests.rs +++ b/tests/mixed_tests.rs @@ -1,8 +1,5 @@ use crypto_ctrng::RandomBlockSource; -const BEACON_KEY: &str = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; -const GATEWAY: &str = "https://ipfs.io"; - #[test] fn local_is_32_bytes() { let mut client = crypto_ctrng::LocalRng::new(); @@ -17,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::IpfsCtrng::new(GATEWAY, BEACON_KEY); - 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::IpfsCtrng::new(GATEWAY, BEACON_KEY); - let ipfs2 = crypto_ctrng::IpfsCtrng::new(GATEWAY, BEACON_KEY); - 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::IpfsCtrng::new(GATEWAY, BEACON_KEY); - 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/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}"); - } -} diff --git a/tests/testu01_tests.rs b/tests/testu01_tests.rs index c0fd4a6..62f900d 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.io"; + let gateway = "https://ipfs.filebase.io"; let beacon_key = "k2k4r8lvomw737sajfnpav0dpeernugnryng50uheyk1k39lursmn09f"; - let mut ctrng = IpfsCtrng::new(gateway, beacon_key); + let mut ctrng = Ctrng::ipfs(beacon_key, None); let seed = ctrng.next_block().expect("failed to fetch IPFS block"); ChaCha20Rng::from_seed(seed) }