1pub mod gen;
16
17use std::time::{Duration, Instant};
18
19use crate::internal::zeroize::Zeroize;
20use crate::primitives::sha3::sha3_256;
21use crate::primitives::MemoryTier;
22
23#[derive(Clone)]
28pub struct SeedHandle {
29 seed: Vec<u8>,
30 created: Instant,
31 ttl: Option<Duration>,
32 tier: MemoryTier,
33}
34
35impl SeedHandle {
36 pub fn new(seed: &[u8], ttl: Option<Duration>) -> Self {
42 let seed_vec = seed.to_vec();
43 #[cfg(unix)]
45 if !seed_vec.is_empty() {
46 let _ = unsafe {
47 extern "C" {
48 fn mlock(addr: *const u8, len: usize) -> i32;
49 }
50 mlock(seed_vec.as_ptr(), seed_vec.len());
51 };
52 }
53
54 Self {
55 seed: seed_vec,
56 created: Instant::now(),
57 ttl,
58 tier: MemoryTier::default(),
59 }
60 }
61
62 pub fn with_tier(seed: &[u8], ttl: Option<Duration>, tier: MemoryTier) -> Self {
64 let mut handle = Self::new(seed, ttl);
65 handle.tier = tier;
66 handle
67 }
68
69 pub fn is_expired(&self) -> bool {
71 match self.ttl {
72 Some(ttl) => self.created.elapsed() > ttl,
73 None => false,
74 }
75 }
76
77 pub fn as_bytes(&self) -> Option<&[u8]> {
79 if self.is_expired() {
80 None
81 } else {
82 Some(&self.seed)
83 }
84 }
85
86 pub fn as_bytes_unchecked(&self) -> &[u8] {
88 &self.seed
89 }
90
91 pub fn fingerprint(&self) -> [u8; 32] {
95 sha3_256(&self.seed)
96 }
97
98 pub fn derive_key(&self, domain: &str, info: &str, len: usize) -> Option<Vec<u8>> {
105 if self.is_expired() {
106 return None;
107 }
108 let mut output = vec![0u8; len];
109 crate::kdf::hkdf::hkdf_sha3_256(
110 &self.seed,
111 Some(domain.as_bytes()),
112 info.as_bytes(),
113 &mut output,
114 )
115 .ok()
116 .map(|_| output)
117 }
118
119 pub fn tier(&self) -> MemoryTier {
121 self.tier
122 }
123
124 pub fn remaining(&self) -> Option<Duration> {
126 self.ttl.map(|ttl| {
127 let elapsed = self.created.elapsed();
128 if elapsed >= ttl {
129 Duration::ZERO
130 } else {
131 ttl - elapsed
132 }
133 })
134 }
135}
136
137impl Drop for SeedHandle {
138 fn drop(&mut self) {
139 self.seed.zeroize();
141
142 #[cfg(unix)]
144 if !self.seed.is_empty() {
145 unsafe {
147 extern "C" {
148 fn munlock(addr: *const u8, len: usize) -> i32;
149 }
150 munlock(self.seed.as_ptr(), self.seed.capacity());
151 }
152 }
153 }
154}
155
156impl std::fmt::Debug for SeedHandle {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 f.debug_struct("SeedHandle")
159 .field("seed_len", &self.seed.len())
160 .field("expired", &self.is_expired())
161 .field("tier", &self.tier)
162 .field("ttl", &self.ttl)
163 .finish()
164 }
165}
166
167pub fn derive_child_seed(parent: &[u8], domain: &str) -> Result<Vec<u8>, String> {
172 if parent.is_empty() {
173 return Err("Parent seed is empty".to_string());
174 }
175 if domain.is_empty() {
176 return Err("Domain is empty".to_string());
177 }
178 let mut output = [0u8; 32];
179 crate::kdf::hkdf::hkdf_sha3_256(
180 parent,
181 Some(domain.as_bytes()),
182 b"origin-child-seed",
183 &mut output,
184 )
185 .map_err(|e| format!("HKDF failed: {}", e))?;
186 Ok(output.to_vec())
187}
188
189pub fn derive_signing_keys(seed: &[u8], domain: &str) -> Result<(Vec<u8>, Vec<u8>), String> {
194 let mut ed_seed = [0u8; 32];
196 crate::kdf::hkdf::hkdf_sha3_256(
197 seed,
198 Some(b"signing"),
199 &[domain.as_bytes(), b"ed25519"].concat(),
200 &mut ed_seed,
201 )
202 .map_err(|e| format!("Ed25519 key derivation failed: {}", e))?;
203
204 let mut falcon_seed = [0u8; 32];
206 crate::kdf::hkdf::hkdf_sha3_256(
207 seed,
208 Some(b"signing"),
209 &[domain.as_bytes(), b"falcon1024"].concat(),
210 &mut falcon_seed,
211 )
212 .map_err(|e| format!("Falcon key derivation failed: {}", e))?;
213
214 let ed25519_sk = ed_seed.to_vec();
216
217 let (falcon_pk, falcon_sk) = crate::pqc::falcon1024::generate_keypair_from_seed(&falcon_seed)
219 .map_err(|e| format!("Falcon keypair generation failed: {}", e))?;
220
221 let mut falcon_packed = falcon_sk.as_bytes().to_vec();
223 falcon_packed.extend_from_slice(falcon_pk.as_bytes());
224
225 Ok((ed25519_sk, falcon_packed))
226}
227
228pub fn derive_verifying_keys(
233 ed25519_sk: &[u8],
234 falcon_packed: &[u8],
235) -> Result<(Vec<u8>, Vec<u8>), String> {
236 let ed_secret = ed25519_dalek::SigningKey::from_bytes(
238 ed25519_sk
239 .try_into()
240 .map_err(|_| "Invalid Ed25519 secret key length")?,
241 );
242 let ed_public = ed_secret.verifying_key();
243 let ed25519_pk = ed_public.to_bytes().to_vec();
244
245 const FALCON_SK_SIZE: usize = 2305;
248 if falcon_packed.len() <= FALCON_SK_SIZE {
249 return Err("Packed Falcon bytes too short -- missing public key".to_string());
250 }
251 let falcon_pk = falcon_packed[FALCON_SK_SIZE..].to_vec();
252
253 Ok((ed25519_pk, falcon_pk))
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_seed_handle_basic() {
262 let seed = [42u8; 32];
263 let handle = SeedHandle::new(&seed, None);
264 assert!(!handle.is_expired());
265 assert_eq!(handle.as_bytes().unwrap(), &seed);
266 assert_eq!(handle.fingerprint(), sha3_256(&seed));
267 }
268
269 #[test]
270 fn test_seed_handle_ttl() {
271 let seed = [42u8; 32];
272 let handle = SeedHandle::new(&seed, Some(Duration::from_secs(1)));
273 assert!(!handle.is_expired());
274 assert!(handle.remaining().unwrap() > Duration::ZERO);
275 }
276
277 #[test]
278 fn test_seed_handle_derive_key() {
279 let seed = [42u8; 32];
280 let handle = SeedHandle::new(&seed, None);
281 let key1 = handle.derive_key("signing", "ed25519", 32).unwrap();
282 let key2 = handle.derive_key("signing", "ed25519", 32).unwrap();
283 assert_eq!(key1, key2, "Same domain+info should produce same key");
284
285 let key3 = handle.derive_key("encryption", "xchacha20", 32).unwrap();
286 assert_ne!(key1, key3, "Different domain should produce different key");
287 }
288
289 #[test]
290 fn test_derive_child_seed() {
291 let parent = [1u8; 32];
292 let child1 = derive_child_seed(&parent, "domain1").unwrap();
293 let child2 = derive_child_seed(&parent, "domain2").unwrap();
294 assert_ne!(
295 child1, child2,
296 "Different domains should produce different children"
297 );
298 assert_eq!(child1.len(), 32, "Child seed should be 32 bytes");
299 }
300
301 #[test]
302 fn test_derive_signing_keys() {
303 let seed = [42u8; 32];
304 let (ed_sk, falcon_sk) = derive_signing_keys(&seed, "test-domain").unwrap();
305 assert_eq!(ed_sk.len(), 32, "Ed25519 SK should be 32 bytes");
306 assert!(!falcon_sk.is_empty(), "Falcon SK should not be empty");
307 }
308
309 #[test]
310 fn test_seed_handle_zeroize() {
311 let seed = [42u8; 32];
312 let handle = SeedHandle::new(&seed, None);
313 let _ = handle; }
316}