pub mod gen;
use std::time::{Duration, Instant};
use crate::internal::zeroize::Zeroize;
use crate::primitives::sha3::sha3_256;
use crate::primitives::MemoryTier;
#[derive(Clone)]
pub struct SeedHandle {
seed: Vec<u8>,
created: Instant,
ttl: Option<Duration>,
tier: MemoryTier,
}
impl SeedHandle {
pub fn new(seed: &[u8], ttl: Option<Duration>) -> Self {
let seed_vec = seed.to_vec();
#[cfg(unix)]
if !seed_vec.is_empty() {
let _ = unsafe {
extern "C" {
fn mlock(addr: *const u8, len: usize) -> i32;
}
mlock(seed_vec.as_ptr(), seed_vec.len());
};
}
Self {
seed: seed_vec,
created: Instant::now(),
ttl,
tier: MemoryTier::default(),
}
}
pub fn with_tier(seed: &[u8], ttl: Option<Duration>, tier: MemoryTier) -> Self {
let mut handle = Self::new(seed, ttl);
handle.tier = tier;
handle
}
pub fn is_expired(&self) -> bool {
match self.ttl {
Some(ttl) => self.created.elapsed() > ttl,
None => false,
}
}
pub fn as_bytes(&self) -> Option<&[u8]> {
if self.is_expired() {
None
} else {
Some(&self.seed)
}
}
pub fn as_bytes_unchecked(&self) -> &[u8] {
&self.seed
}
pub fn fingerprint(&self) -> [u8; 32] {
sha3_256(&self.seed)
}
pub fn derive_key(&self, domain: &str, info: &str, len: usize) -> Option<Vec<u8>> {
if self.is_expired() {
return None;
}
let mut output = vec![0u8; len];
crate::kdf::hkdf::hkdf_sha3_256(
&self.seed,
Some(domain.as_bytes()),
info.as_bytes(),
&mut output,
)
.ok()
.map(|_| output)
}
pub fn tier(&self) -> MemoryTier {
self.tier
}
pub fn remaining(&self) -> Option<Duration> {
self.ttl.map(|ttl| {
let elapsed = self.created.elapsed();
if elapsed >= ttl {
Duration::ZERO
} else {
ttl - elapsed
}
})
}
}
impl Drop for SeedHandle {
fn drop(&mut self) {
self.seed.zeroize();
#[cfg(unix)]
if !self.seed.is_empty() {
unsafe {
extern "C" {
fn munlock(addr: *const u8, len: usize) -> i32;
}
munlock(self.seed.as_ptr(), self.seed.capacity());
}
}
}
}
impl std::fmt::Debug for SeedHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SeedHandle")
.field("seed_len", &self.seed.len())
.field("expired", &self.is_expired())
.field("tier", &self.tier)
.field("ttl", &self.ttl)
.finish()
}
}
pub fn derive_child_seed(parent: &[u8], domain: &str) -> Result<Vec<u8>, String> {
if parent.is_empty() {
return Err("Parent seed is empty".to_string());
}
if domain.is_empty() {
return Err("Domain is empty".to_string());
}
let mut output = [0u8; 32];
crate::kdf::hkdf::hkdf_sha3_256(
parent,
Some(domain.as_bytes()),
b"origin-child-seed",
&mut output,
)
.map_err(|e| format!("HKDF failed: {}", e))?;
Ok(output.to_vec())
}
pub fn derive_signing_keys(seed: &[u8], domain: &str) -> Result<(Vec<u8>, Vec<u8>), String> {
let mut ed_seed = [0u8; 32];
crate::kdf::hkdf::hkdf_sha3_256(
seed,
Some(b"signing"),
&[domain.as_bytes(), b"ed25519"].concat(),
&mut ed_seed,
)
.map_err(|e| format!("Ed25519 key derivation failed: {}", e))?;
let mut falcon_seed = [0u8; 32];
crate::kdf::hkdf::hkdf_sha3_256(
seed,
Some(b"signing"),
&[domain.as_bytes(), b"falcon1024"].concat(),
&mut falcon_seed,
)
.map_err(|e| format!("Falcon key derivation failed: {}", e))?;
let ed25519_sk = ed_seed.to_vec();
let (falcon_pk, falcon_sk) = crate::pqc::falcon1024::generate_keypair_from_seed(&falcon_seed)
.map_err(|e| format!("Falcon keypair generation failed: {}", e))?;
let mut falcon_packed = falcon_sk.as_bytes().to_vec();
falcon_packed.extend_from_slice(falcon_pk.as_bytes());
Ok((ed25519_sk, falcon_packed))
}
pub fn derive_verifying_keys(
ed25519_sk: &[u8],
falcon_packed: &[u8],
) -> Result<(Vec<u8>, Vec<u8>), String> {
let ed_secret = ed25519_dalek::SigningKey::from_bytes(
ed25519_sk
.try_into()
.map_err(|_| "Invalid Ed25519 secret key length")?,
);
let ed_public = ed_secret.verifying_key();
let ed25519_pk = ed_public.to_bytes().to_vec();
const FALCON_SK_SIZE: usize = 2305;
if falcon_packed.len() <= FALCON_SK_SIZE {
return Err("Packed Falcon bytes too short -- missing public key".to_string());
}
let falcon_pk = falcon_packed[FALCON_SK_SIZE..].to_vec();
Ok((ed25519_pk, falcon_pk))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_seed_handle_basic() {
let seed = [42u8; 32];
let handle = SeedHandle::new(&seed, None);
assert!(!handle.is_expired());
assert_eq!(handle.as_bytes().unwrap(), &seed);
assert_eq!(handle.fingerprint(), sha3_256(&seed));
}
#[test]
fn test_seed_handle_ttl() {
let seed = [42u8; 32];
let handle = SeedHandle::new(&seed, Some(Duration::from_secs(1)));
assert!(!handle.is_expired());
assert!(handle.remaining().unwrap() > Duration::ZERO);
}
#[test]
fn test_seed_handle_derive_key() {
let seed = [42u8; 32];
let handle = SeedHandle::new(&seed, None);
let key1 = handle.derive_key("signing", "ed25519", 32).unwrap();
let key2 = handle.derive_key("signing", "ed25519", 32).unwrap();
assert_eq!(key1, key2, "Same domain+info should produce same key");
let key3 = handle.derive_key("encryption", "xchacha20", 32).unwrap();
assert_ne!(key1, key3, "Different domain should produce different key");
}
#[test]
fn test_derive_child_seed() {
let parent = [1u8; 32];
let child1 = derive_child_seed(&parent, "domain1").unwrap();
let child2 = derive_child_seed(&parent, "domain2").unwrap();
assert_ne!(
child1, child2,
"Different domains should produce different children"
);
assert_eq!(child1.len(), 32, "Child seed should be 32 bytes");
}
#[test]
fn test_derive_signing_keys() {
let seed = [42u8; 32];
let (ed_sk, falcon_sk) = derive_signing_keys(&seed, "test-domain").unwrap();
assert_eq!(ed_sk.len(), 32, "Ed25519 SK should be 32 bytes");
assert!(!falcon_sk.is_empty(), "Falcon SK should not be empty");
}
#[test]
fn test_seed_handle_zeroize() {
let seed = [42u8; 32];
let handle = SeedHandle::new(&seed, None);
let _ = handle; }
}