use sha2::{Digest, Sha256};
pub fn derive_seed_canonical(master_seed: u64, tool: &str, iteration: u64) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(master_seed.to_le_bytes());
hasher.update(tool.as_bytes());
hasher.update(iteration.to_le_bytes());
hasher.finalize().into()
}
pub fn derive_seed(master_seed: u64, tool: &str, iteration: u64) -> u64 {
let canonical = derive_seed_canonical(master_seed, tool, iteration);
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&canonical[..8]);
u64::from_le_bytes(bytes)
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn canonical_seed_is_deterministic() {
let a = derive_seed_canonical(42, "echo", 7);
let b = derive_seed_canonical(42, "echo", 7);
assert_eq!(a, b);
}
#[test]
fn canonical_seed_is_input_sensitive() {
let base = derive_seed_canonical(42, "echo", 0);
assert_ne!(derive_seed_canonical(42, "echo", 1), base);
assert_ne!(derive_seed_canonical(42, "different", 0), base);
assert_ne!(derive_seed_canonical(43, "echo", 0), base);
}
#[test]
fn truncated_seed_matches_first_eight_bytes() {
let canonical = derive_seed_canonical(123, "tool", 4);
let truncated = derive_seed(123, "tool", 4);
let mut expected = [0u8; 8];
expected.copy_from_slice(&canonical[..8]);
assert_eq!(truncated, u64::from_le_bytes(expected));
}
}