clock_rand/
seed.rs

1//! Seed types and blockchain state seeding
2
3#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6use crate::error::{Result, SeedError};
7
8/// Seed type for RNG initialization
9///
10/// Seeds can be created from various blockchain state sources
11/// including block hashes, timestamps, and VRF outputs.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Seed {
14    bytes: Vec<u8>,
15}
16
17impl Seed {
18    /// Create a seed from raw bytes
19    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
20        if bytes.is_empty() {
21            return Err(SeedError::InvalidSize {
22                expected: 1,
23                got: 0,
24            }
25            .into());
26        }
27
28        // Check for all zeros (weak seed)
29        if bytes.iter().all(|&b| b == 0) {
30            return Err(SeedError::AllZeros.into());
31        }
32
33        Ok(Self { bytes })
34    }
35
36    /// Create a seed from a block hash (32 bytes)
37    pub fn from_block_hash(hash: &[u8; 32]) -> Result<Self> {
38        Self::from_bytes(hash.to_vec())
39    }
40
41    /// Create a seed from a block hash slice
42    pub fn from_block_hash_slice(hash: &[u8]) -> Result<Self> {
43        if hash.len() < 16 {
44            return Err(SeedError::InvalidSize {
45                expected: 16,
46                got: hash.len(),
47            }
48            .into());
49        }
50        Self::from_bytes(hash.to_vec())
51    }
52
53    /// Create a seed from a timestamp
54    pub fn from_timestamp(timestamp: u64) -> Result<Self> {
55        let bytes = timestamp.to_le_bytes().to_vec();
56        Self::from_bytes(bytes)
57    }
58
59    /// Create a seed from a VRF output
60    pub fn from_vrf(vrf_output: &[u8]) -> Result<Self> {
61        if vrf_output.is_empty() {
62            return Err(SeedError::InvalidSize {
63                expected: 1,
64                got: 0,
65            }
66            .into());
67        }
68        Self::from_bytes(vrf_output.to_vec())
69    }
70
71    /// Combine multiple seed sources into one seed
72    ///
73    /// Uses Blake3 to hash all inputs together for uniform distribution
74    pub fn from_combined(sources: &[&[u8]]) -> Result<Self> {
75        if sources.is_empty() {
76            return Err(SeedError::ValidationFailed("No seed sources provided").into());
77        }
78
79        #[cfg(feature = "crypto_rng")]
80        {
81            use blake3;
82            let mut hasher = blake3::Hasher::new();
83            for source in sources {
84                hasher.update(source);
85            }
86            let hash = hasher.finalize();
87            Self::from_bytes(hash.as_bytes().to_vec())
88        }
89
90        #[cfg(not(feature = "crypto_rng"))]
91        {
92            // Fallback: simple XOR mixing when crypto_rng feature is disabled
93            let mut combined = Vec::new();
94            let max_len = sources.iter().map(|s| s.len()).max().unwrap_or(0);
95
96            if max_len == 0 {
97                return Err(SeedError::ValidationFailed("All seed sources are empty").into());
98            }
99
100            combined.resize(max_len, 0u8);
101            for source in sources {
102                for (i, &byte) in source.iter().enumerate() {
103                    if i < combined.len() {
104                        combined[i] ^= byte;
105                    }
106                }
107            }
108            Self::from_bytes(combined)
109        }
110    }
111
112    /// Create a seed from block hash, timestamp, and optional VRF
113    pub fn from_blockchain_state(
114        block_hash: &[u8; 32],
115        timestamp: u64,
116        vrf_output: Option<&[u8]>,
117    ) -> Result<Self> {
118        let timestamp_bytes = timestamp.to_le_bytes();
119        let mut sources = Vec::new();
120        sources.push(block_hash.as_slice());
121        sources.push(&timestamp_bytes);
122        if let Some(vrf) = vrf_output {
123            sources.push(vrf);
124        }
125        Self::from_combined(&sources)
126    }
127
128    /// Get the seed as a byte slice
129    pub fn as_bytes(&self) -> &[u8] {
130        &self.bytes
131    }
132
133    /// Get the seed length in bytes
134    pub fn len(&self) -> usize {
135        self.bytes.len()
136    }
137
138    /// Check if the seed is empty
139    pub fn is_empty(&self) -> bool {
140        self.bytes.is_empty()
141    }
142
143    /// Expand or truncate seed to a specific size
144    pub fn resize(&mut self, size: usize) -> Result<()> {
145        if size == 0 {
146            return Err(SeedError::InvalidSize {
147                expected: 1,
148                got: 0,
149            }
150            .into());
151        }
152
153        if self.bytes.len() < size {
154            // Expand by hashing the current seed
155            #[cfg(feature = "crypto_rng")]
156            {
157                use blake3;
158                let mut hasher = blake3::Hasher::new();
159                hasher.update(&self.bytes);
160                let mut counter = 0u64;
161                while self.bytes.len() < size {
162                    let mut hasher2 = hasher.clone();
163                    hasher2.update(&counter.to_le_bytes());
164                    let hash = hasher2.finalize();
165                    self.bytes.extend_from_slice(hash.as_bytes());
166                    counter += 1;
167                }
168            }
169
170            #[cfg(not(feature = "crypto_rng"))]
171            {
172                // Simple expansion: repeat with XOR
173                let original = self.bytes.clone();
174                while self.bytes.len() < size {
175                    let needed = size - self.bytes.len();
176                    let to_copy = needed.min(original.len());
177                    for &byte in original.iter().take(to_copy) {
178                        self.bytes.push(byte ^ (self.bytes.len() as u8));
179                    }
180                }
181            }
182        }
183
184        self.bytes.truncate(size);
185        Ok(())
186    }
187}
188
189impl AsRef<[u8]> for Seed {
190    fn as_ref(&self) -> &[u8] {
191        &self.bytes
192    }
193}
194
195impl From<[u8; 32]> for Seed {
196    fn from(bytes: [u8; 32]) -> Self {
197        Self::from_bytes(bytes.to_vec()).expect("32-byte array is always valid")
198    }
199}
200
201impl From<Vec<u8>> for Seed {
202    fn from(bytes: Vec<u8>) -> Self {
203        Self::from_bytes(bytes).expect("Seed validation should be done explicitly")
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn test_seed_from_block_hash() {
213        let hash = [0x42u8; 32];
214        let seed = Seed::from_block_hash(&hash).unwrap();
215        assert_eq!(seed.as_bytes(), &hash);
216    }
217
218    #[test]
219    fn test_seed_from_timestamp() {
220        let timestamp = 1234567890u64;
221        let seed = Seed::from_timestamp(timestamp).unwrap();
222        assert_eq!(seed.as_bytes(), &timestamp.to_le_bytes());
223    }
224
225    #[test]
226    fn test_seed_rejects_all_zeros() {
227        let zeros = Vec::from([0u8; 32]);
228        assert!(Seed::from_bytes(zeros).is_err());
229    }
230
231    #[test]
232    fn test_seed_from_combined() {
233        let source1 = b"source1";
234        let source2 = b"source2";
235        let seed = Seed::from_combined(&[source1, source2]).unwrap();
236        assert!(!seed.is_empty());
237    }
238}