use crate::crypto::blake3_drbg::Blake3Drbg;
use crate::error::Result;
use crate::fast::pcg64::Pcg64;
use crate::seed::Seed;
use crate::traits::{CryptoRng, DeterministicRng, Rng, SeedableRng};
#[cfg(feature = "security")]
use zeroize::Zeroize;
pub struct ChainSeedXBuilder {
block_hashes: Vec<[u8; 32]>,
timestamp: Option<u64>,
vrf_output: Option<Vec<u8>>,
fork_detection_enabled: bool,
fork_history_size: usize,
}
impl ChainSeedXBuilder {
pub fn new() -> Self {
Self {
block_hashes: Vec::new(),
timestamp: None,
vrf_output: None,
fork_detection_enabled: true,
fork_history_size: 3,
}
}
pub fn with_block_hash(mut self, hash: [u8; 32]) -> Self {
if self.block_hashes.len() < 3 {
self.block_hashes.push(hash);
}
self
}
pub fn with_block_hashes(mut self, hashes: &[[u8; 32]]) -> Self {
for hash in hashes.iter().take(3) {
if self.block_hashes.len() < 3 {
self.block_hashes.push(*hash);
}
}
self
}
pub fn with_timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = Some(timestamp);
self
}
pub fn with_vrf(mut self, vrf: &[u8]) -> Self {
self.vrf_output = Some(vrf.to_vec());
self
}
pub fn with_fork_detection(mut self, enabled: bool) -> Self {
self.fork_detection_enabled = enabled;
self
}
pub fn with_fork_history_size(mut self, size: usize) -> Self {
self.fork_history_size = size;
self
}
pub fn build(self) -> Result<ChainSeedX> {
let mut sources = Vec::new();
for hash in &self.block_hashes {
sources.push(hash.as_slice());
}
let ts_bytes = self.timestamp.map(|ts| ts.to_le_bytes());
if let Some(ref bytes) = ts_bytes {
sources.push(bytes);
}
if let Some(vrf) = &self.vrf_output {
sources.push(vrf.as_slice());
}
if sources.is_empty() {
return Err(
crate::error::SeedError::ValidationFailed("No seed sources provided").into(),
);
}
let seed = Seed::from_combined(&sources)?;
Ok(ChainSeedX {
blake3_drbg: Blake3Drbg::new(&seed)?,
pcg: Pcg64::from_seed_obj(&seed)?,
block_hashes: self.block_hashes,
fork_detection_enabled: self.fork_detection_enabled,
fork_history_size: self.fork_history_size,
})
}
}
impl Default for ChainSeedXBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct ChainSeedX {
blake3_drbg: Blake3Drbg,
pcg: Pcg64,
block_hashes: Vec<[u8; 32]>,
fork_detection_enabled: bool,
fork_history_size: usize,
}
#[cfg(feature = "security")]
impl Zeroize for ChainSeedX {
fn zeroize(&mut self) {
self.blake3_drbg.zeroize();
self.block_hashes.clear();
}
}
impl ChainSeedX {
pub fn builder() -> ChainSeedXBuilder {
ChainSeedXBuilder::new()
}
pub fn check_fork(&mut self, current_hash: &[u8; 32]) -> Result<bool> {
if !self.fork_detection_enabled {
return Ok(false);
}
if let Some(last_hash) = self.block_hashes.last()
&& last_hash == current_hash
{
return Ok(false); }
self.block_hashes.push(*current_hash);
if self.block_hashes.len() > self.fork_history_size {
self.block_hashes.remove(0);
}
let seed = Seed::from_block_hash(current_hash)?;
self.blake3_drbg.reseed(seed.clone())?;
self.pcg.reseed(seed)?;
Ok(true) }
pub fn update_block(&mut self, block_hash: &[u8; 32], timestamp: u64) -> Result<()> {
let fork_detected = self.check_fork(block_hash)?;
if !fork_detected {
let timestamp_bytes = timestamp.to_le_bytes();
let mut sources = vec![block_hash.as_slice(), ×tamp_bytes];
if let Some(vrf) = self.block_hashes.last() {
sources.push(vrf.as_slice());
}
let seed = Seed::from_combined(&sources)?;
self.pcg.reseed(seed)?;
}
Ok(())
}
}
impl Rng for ChainSeedX {
fn next_u32(&mut self) -> u32 {
self.blake3_drbg.next_u32() ^ self.pcg.next_u32()
}
fn next_u64(&mut self) -> u64 {
self.blake3_drbg.next_u64() ^ self.pcg.next_u64()
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
let mut pcg_bytes = vec![0u8; dest.len()];
self.blake3_drbg.fill_bytes(dest);
self.pcg.fill_bytes(&mut pcg_bytes);
for (d, p) in dest.iter_mut().zip(pcg_bytes.iter()) {
*d ^= *p;
}
}
}
impl CryptoRng for ChainSeedX {}
impl DeterministicRng for ChainSeedX {
fn is_deterministic(&self) -> bool {
true
}
}
impl SeedableRng for ChainSeedX {
type Seed = Seed;
fn from_seed(seed: Self::Seed) -> Self {
let seed_bytes = seed.as_ref();
let mut hash = [0u8; 32];
if seed_bytes.len() >= 32 {
hash.copy_from_slice(&seed_bytes[..32]);
} else {
let mut expanded = seed_bytes.to_vec();
while expanded.len() < 32 {
expanded.extend_from_slice(seed_bytes);
}
hash.copy_from_slice(&expanded[..32]);
}
Self::builder()
.with_block_hash(hash)
.build()
.expect("Seed should be valid")
}
fn reseed(&mut self, seed: Self::Seed) -> Result<()> {
let hash = if seed.len() >= 32 {
let mut hash = [0u8; 32];
hash.copy_from_slice(&seed.as_ref()[..32]);
hash
} else {
let mut expanded = seed.as_ref().to_vec();
while expanded.len() < 32 {
expanded.extend_from_slice(seed.as_ref());
}
let mut hash = [0u8; 32];
hash.copy_from_slice(&expanded[..32]);
hash
};
self.blake3_drbg.reseed(seed.clone())?;
self.pcg.reseed(seed)?;
self.block_hashes = vec![hash];
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(feature = "custom_rng")]
fn test_chainseed_x_builder() {
let hash = [0x42u8; 32];
let rng = ChainSeedX::builder()
.with_block_hash(hash)
.with_timestamp(12345)
.build()
.unwrap();
assert!(!rng.block_hashes.is_empty());
}
#[test]
#[cfg(feature = "custom_rng")]
fn test_chainseed_x_fork_detection() {
let hash1 = [0x01u8; 32];
let hash2 = [0x02u8; 32];
let mut rng = ChainSeedX::builder()
.with_block_hash(hash1)
.with_fork_detection(true)
.build()
.unwrap();
let fork_detected = rng.check_fork(&hash2).unwrap();
assert!(fork_detected);
}
}