const DOMAIN: &str = "ling-mandala-v1";
#[derive(Debug, Clone, PartialEq)]
pub struct MandalaParams {
pub n_rings: u8, pub n_spokes: u8, pub n_petals: u8, pub n_spiral: u8, pub inner_r: f32, pub outer_r: f32, pub twist: f32, pub hue_offset: f32, pub seed: u64, }
impl MandalaParams {
pub fn new(n_rings: u8, n_spokes: u8, n_petals: u8) -> Self {
Self {
n_rings,
n_spokes,
n_petals,
n_spiral: 4,
inner_r: 0.1,
outer_r: 0.9,
twist: 0.0,
hue_offset: 0.0,
seed: 0,
}
}
fn serialize(&self) -> Vec<u8> {
let mut v = DOMAIN.as_bytes().to_vec();
v.push(self.n_rings);
v.push(self.n_spokes);
v.push(self.n_petals);
v.push(self.n_spiral);
v.extend_from_slice(&self.inner_r.to_bits().to_le_bytes());
v.extend_from_slice(&self.outer_r.to_bits().to_le_bytes());
v.extend_from_slice(&self.twist.to_bits().to_le_bytes());
v.extend_from_slice(&self.hue_offset.to_bits().to_le_bytes());
v.extend_from_slice(&self.seed.to_le_bytes());
v
}
}
pub struct MandalaHash {
params: MandalaParams,
base: [u8; 32],
}
impl MandalaHash {
pub fn new(params: MandalaParams) -> Self {
let serialized = params.serialize();
let base = *blake3::hash(&serialized).as_bytes();
Self { params, base }
}
pub fn key(&self) -> [u8; 32] {
self.base
}
pub fn expand(&self, len: usize) -> Vec<u8> {
let mut hasher = blake3::Hasher::new_keyed(&self.base);
hasher.update(b"expand");
let mut out = vec![0u8; len];
hasher.finalize_xof().fill(&mut out);
out
}
pub fn subkey(&self, context: &str) -> [u8; 32] {
*blake3::keyed_hash(&self.base, context.as_bytes()).as_bytes()
}
pub fn encryption_key(&self) -> [u8; 32] {
self.subkey("ling:encryption:v1")
}
pub fn signing_seed(&self) -> [u8; 32] {
self.subkey("ling:signing:v1")
}
pub fn kem_seed(&self) -> [u8; 32] {
self.subkey("ling:kem:v1")
}
pub fn params(&self) -> &MandalaParams {
&self.params
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deterministic() {
let p = MandalaParams::new(8, 12, 8);
let h1 = MandalaHash::new(p.clone());
let h2 = MandalaHash::new(p);
assert_eq!(h1.key(), h2.key());
}
#[test]
fn different_params_different_keys() {
let h1 = MandalaHash::new(MandalaParams::new(8, 12, 8));
let h2 = MandalaHash::new(MandalaParams::new(9, 12, 8));
assert_ne!(h1.key(), h2.key());
}
#[test]
fn subkeys_independent() {
let h = MandalaHash::new(MandalaParams::new(8, 12, 8));
assert_ne!(h.encryption_key(), h.signing_seed());
assert_ne!(h.signing_seed(), h.kem_seed());
}
}