1#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6use crate::error::{Result, SeedError};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Seed {
14 bytes: Vec<u8>,
15}
16
17impl Seed {
18 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 if bytes.iter().all(|&b| b == 0) {
30 return Err(SeedError::AllZeros.into());
31 }
32
33 Ok(Self { bytes })
34 }
35
36 pub fn from_block_hash(hash: &[u8; 32]) -> Result<Self> {
38 Self::from_bytes(hash.to_vec())
39 }
40
41 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 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 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 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 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 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(×tamp_bytes);
122 if let Some(vrf) = vrf_output {
123 sources.push(vrf);
124 }
125 Self::from_combined(&sources)
126 }
127
128 pub fn as_bytes(&self) -> &[u8] {
130 &self.bytes
131 }
132
133 pub fn len(&self) -> usize {
135 self.bytes.len()
136 }
137
138 pub fn is_empty(&self) -> bool {
140 self.bytes.is_empty()
141 }
142
143 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 #[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 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(), ×tamp.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}