use crate::hybrid::{self, HybridKeypair};
use crate::symmetric::XChaCha20;
use sha3::{Digest, Sha3_256};
pub fn holo_hash(data: &[u8]) -> [u8; 32] {
let mut h = Sha3_256::new();
h.update(b"ling-geo-holo-hash-v1");
h.update(data);
h.finalize().into()
}
fn gcd(mut a: u32, mut b: u32) -> u32 {
while b != 0 { let t = b; b = a % b; a = t; }
a
}
#[derive(Clone, Debug, PartialEq)]
pub struct KnotShape {
pub p: u32,
pub q: u32,
pub major_r: f32,
pub minor_r: f32,
pub volume: f32,
pub points: Vec<[f32; 3]>,
}
impl KnotShape {
pub const SAMPLES: usize = 256;
pub fn from_bytes(data: &[u8]) -> Self {
Self::from_digest(&holo_hash(data))
}
pub fn from_digest(d: &[u8; 32]) -> Self {
let mut p = 2 + (d[0] as u32 % 16);
let mut q = 2 + (d[1] as u32 % 16);
if p == q { q = 2 + ((q) % 16) + 1; }
while gcd(p, q) != 1 { q += 1; if q > 18 { q = 2; p += 1; if p > 18 { p = 2; } } }
let major_r = 2.0 + (d[2] as f32 / 255.0) * 1.5;
let minor_r = 0.4 + (d[3] as f32 / 255.0) * 0.8;
let phase = (u16::from_le_bytes([d[4], d[5]]) as f32 / 65535.0) * std::f32::consts::TAU;
let volume = 1.0 + (u32::from_le_bytes([d[6], d[7], d[8], d[9]]) as f32 / u32::MAX as f32) * 11.0;
let mut points = Vec::with_capacity(Self::SAMPLES);
for i in 0..Self::SAMPLES {
let t = (i as f32 / Self::SAMPLES as f32) * std::f32::consts::TAU + phase;
let qc = (q as f32 * t).cos();
let r = major_r + minor_r * qc;
let x = r * (p as f32 * t).cos();
let y = r * (p as f32 * t).sin();
let z = minor_r * (q as f32 * t).sin();
points.push([x, y, z]);
}
Self { p, q, major_r, minor_r, volume, points }
}
pub fn label(&self) -> String {
format!("knot-{}_{}-v{:.1}", self.p, self.q, self.volume)
}
}
pub struct KnotIdentity {
inner: HybridKeypair,
public: Vec<u8>,
}
impl KnotIdentity {
pub fn generate() -> Self {
let inner = HybridKeypair::generate();
let public = inner.public_key();
Self { inner, public }
}
pub fn public_key(&self) -> &[u8] {
&self.public
}
pub fn public_knot(&self) -> KnotShape {
KnotShape::from_bytes(&self.public)
}
pub fn decapsulate(&self, ciphertext: &[u8]) -> Result<[u8; 32], &'static str> {
self.inner.decapsulate(ciphertext)
}
}
pub fn knot_encapsulate(public_key: &[u8]) -> Result<(Vec<u8>, [u8; 32]), &'static str> {
hybrid::encapsulate(public_key)
}
pub fn knot_for_public_key(public_key: &[u8]) -> KnotShape {
KnotShape::from_bytes(public_key)
}
pub fn holo_seal(key: [u8; 32], plaintext: &[u8]) -> Result<Vec<u8>, &'static str> {
XChaCha20::new(key).encrypt(plaintext)
}
pub fn holo_open(key: [u8; 32], ciphertext: &[u8]) -> Result<Vec<u8>, &'static str> {
XChaCha20::new(key).decrypt(ciphertext)
}
#[derive(Clone, Debug, PartialEq)]
pub struct HoloFragment {
pub index: u32,
pub coord: [f32; 4],
pub block: [u8; 32],
}
const HOLO_BLOCK: usize = 32;
fn ks_block(k: &[u8; 32], i: u32) -> [u8; 32] {
let mut h = blake3::Hasher::new_keyed(k);
h.update(b"ling-holo-aont-v1");
h.update(&i.to_le_bytes());
*h.finalize().as_bytes()
}
pub fn scatter(data: &[u8]) -> Vec<HoloFragment> {
use rand::RngCore;
let mut k = [0u8; 32];
rand::rngs::OsRng.fill_bytes(&mut k);
let mut msg = (data.len() as u64).to_le_bytes().to_vec();
msg.extend_from_slice(data);
while msg.len() % HOLO_BLOCK != 0 { msg.push(0); }
let n = (msg.len() / HOLO_BLOCK) as u32;
let mut blocks: Vec<[u8; 32]> = Vec::with_capacity(n as usize + 1);
for i in 0..n {
let ks = ks_block(&k, i);
let mut c = [0u8; 32];
for j in 0..HOLO_BLOCK { c[j] = msg[i as usize * HOLO_BLOCK + j] ^ ks[j]; }
blocks.push(c);
}
let mut h = blake3::Hasher::new();
h.update(b"ling-holo-anchor-v1");
for c in &blocks { h.update(c); }
let digest = *h.finalize().as_bytes();
let mut anchor = [0u8; 32];
for j in 0..HOLO_BLOCK { anchor[j] = k[j] ^ digest[j]; }
blocks.push(anchor);
blocks.into_iter().enumerate().map(|(i, block)| HoloFragment {
index: i as u32,
coord: sphere4_point(i as u32, &block),
block,
}).collect()
}
pub fn gather(fragments: &[HoloFragment]) -> Option<Vec<u8>> {
if fragments.len() < 2 { return None; }
let mut frags = fragments.to_vec();
frags.sort_by_key(|f| f.index);
for (i, f) in frags.iter().enumerate() {
if f.index as usize != i { return None; }
}
let n = frags.len() - 1;
let mut h = blake3::Hasher::new();
h.update(b"ling-holo-anchor-v1");
for f in &frags[..n] { h.update(&f.block); }
let digest = *h.finalize().as_bytes();
let mut k = [0u8; 32];
for j in 0..HOLO_BLOCK { k[j] = frags[n].block[j] ^ digest[j]; }
let mut msg = Vec::with_capacity(n * HOLO_BLOCK);
for i in 0..n {
let ks = ks_block(&k, i as u32);
for j in 0..HOLO_BLOCK { msg.push(frags[i].block[j] ^ ks[j]); }
}
if msg.len() < 8 { return None; }
let len = u64::from_le_bytes(msg[..8].try_into().ok()?) as usize;
if 8 + len > msg.len() { return None; }
Some(msg[8..8 + len].to_vec())
}
fn sphere4_point(index: u32, block: &[u8; 32]) -> [f32; 4] {
let a = (u16::from_le_bytes([block[0], block[1]]) as f32 / 65535.0) * std::f32::consts::PI;
let b = (u16::from_le_bytes([block[2], block[3]]) as f32 / 65535.0) * std::f32::consts::TAU;
let c = ((index as f32) * 0.61803398875).fract() * std::f32::consts::TAU;
[a.sin() * b.cos(), a.sin() * b.sin(), a.cos() * c.cos(), a.cos() * c.sin()]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn knot_is_deterministic_and_avalanches() {
let k1 = KnotShape::from_bytes(b"alice-public-key");
let k2 = KnotShape::from_bytes(b"alice-public-key");
let k3 = KnotShape::from_bytes(b"alice-public-keyX");
assert_eq!(k1, k2, "same input → same knot");
assert_ne!(k1.points, k3.points, "one byte change reshapes the knot");
assert_eq!(k1.points.len(), KnotShape::SAMPLES);
assert_eq!(gcd(k1.p, k1.q), 1, "p,q coprime → genuine torus knot");
}
#[test]
fn knot_identity_kem_round_trip() {
let id = KnotIdentity::generate();
let pk = id.public_key().to_vec();
assert_eq!(knot_for_public_key(&pk), id.public_knot());
let (ct, ss_send) = knot_encapsulate(&pk).expect("encapsulate");
let ss_recv = id.decapsulate(&ct).expect("decapsulate");
assert_eq!(ss_send, ss_recv);
}
#[test]
fn seal_open_round_trip() {
let id = KnotIdentity::generate();
let (ct, key) = knot_encapsulate(id.public_key()).unwrap();
let sealed = holo_seal(key, b"meet at the temple at dusk").unwrap();
let key2 = id.decapsulate(&ct).unwrap();
let opened = holo_open(key2, &sealed).unwrap();
assert_eq!(opened, b"meet at the temple at dusk");
}
#[test]
fn holographic_aont_needs_every_fragment() {
let secret = b"all-or-nothing holographic payload \x00\xff";
let frags = scatter(secret);
assert!(frags.len() >= 2);
assert_eq!(gather(&frags).as_deref(), Some(&secret[..]));
for drop in 0..frags.len() {
let partial: Vec<_> = frags.iter().cloned().filter(|f| f.index as usize != drop).collect();
assert!(gather(&partial).is_none(), "missing fragment {drop} must break recovery");
}
}
#[test]
fn holographic_fragments_leak_nothing_individually() {
let secret = [0x41u8; 64]; let frags = scatter(&secret);
for f in &frags {
assert_ne!(f.block, [0x41u8; 32], "a lone hologram fragment reveals plaintext");
}
}
}