| use hyperkitty_core::{Glyph, MAX_ENTROPY}; |
| use hyperkitty_qlg::{K_QLG, vec3_from_glyph}; |
|
|
| |
|
|
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub struct Ledger { |
| pub s: u64, |
| pub delta: i64, |
| pub iota: i64, |
| pub omega: i64, |
| } |
|
|
| impl Ledger { |
| |
| pub const fn new(s: u64, delta: i64, omega: i64) -> Self { |
| Self { s, delta, iota: -delta, omega } |
| } |
|
|
| |
| pub const fn identity(omega: i64) -> Self { |
| Self::new(0, 0, omega) |
| } |
|
|
| |
| pub fn reconciliation(&self) -> i64 { |
| self.delta + self.iota |
| } |
|
|
| pub fn is_balanced(&self) -> bool { |
| self.reconciliation() == 0 |
| } |
|
|
| |
| |
| pub fn compose(&self, other: &Self) -> Option<Self> { |
| if self.omega != other.omega { |
| return None; |
| } |
| Some(Self { |
| s: self.s.wrapping_add(other.s), |
| delta: self.delta + other.delta, |
| iota: self.iota + other.iota, |
| omega: self.omega, |
| }) |
| } |
|
|
| |
| |
| pub fn evolve(&self, increment: Ledger) -> Option<Self> { |
| if increment.delta + increment.iota != 0 { |
| return None; |
| } |
| if increment.omega != 0 { |
| return None; |
| } |
| let new_s = self.s.wrapping_add(increment.s); |
| let new_delta = self.delta + increment.delta; |
| Some(Ledger::new(new_s, new_delta, self.omega)) |
| } |
|
|
| |
| pub fn encode(&self) -> [u8; 24] { |
| let mut out = [0u8; 24]; |
| out[0..8].copy_from_slice(&self.s.to_le_bytes()); |
| out[8..16].copy_from_slice(&self.delta.to_le_bytes()); |
| out[16..24].copy_from_slice(&self.omega.to_le_bytes()); |
| out |
| } |
|
|
| pub fn decode(bytes: &[u8; 24]) -> Self { |
| let s = u64::from_le_bytes(bytes[0..8].try_into().unwrap()); |
| let delta = i64::from_le_bytes(bytes[8..16].try_into().unwrap()); |
| let omega = i64::from_le_bytes(bytes[16..24].try_into().unwrap()); |
| Self::new(s, delta, omega) |
| } |
| } |
|
|
| |
|
|
| #[derive(Debug, Clone, PartialEq, Eq, Default)] |
| pub struct LedgerSum { |
| pub s_acc: u64, |
| pub delta_acc: i64, |
| pub iota_acc: i64, |
| pub omega: i64, |
| pub count: usize, |
| } |
|
|
| impl LedgerSum { |
| pub fn new() -> Self { |
| Self::default() |
| } |
|
|
| |
| |
| pub fn push(&mut self, p: Ledger) -> Option<()> { |
| if !p.is_balanced() { |
| return None; |
| } |
| if self.count == 0 { |
| self.omega = p.omega; |
| } else if p.omega != self.omega { |
| return None; |
| } |
| self.s_acc = self.s_acc.wrapping_add(p.s); |
| self.delta_acc += p.delta; |
| self.iota_acc = -self.delta_acc; |
| self.count += 1; |
| Some(()) |
| } |
|
|
| |
| pub fn is_balanced(&self) -> bool { |
| self.delta_acc + self.iota_acc == 0 |
| } |
|
|
| |
| |
| pub fn encode_wire_frame(&self) -> [u8; 4] { |
| let inv_flag: u8 = ((self.omega & 0xFF) as u8) << 4; |
| let terminal = 0x0A | inv_flag; |
| let primitives = (self.delta_acc as u8 & 0x0F) |
| | ((self.delta_acc as u8).wrapping_neg() & 0xF0); |
| [primitives, 0x0F, 0xFF, terminal] |
| } |
| } |
|
|
| |
|
|
| pub fn hash_symbol(s: &str) -> u64 { |
| let mut h: u64 = 0xcbf29ce484222325; |
| for b in s.as_bytes() { |
| h ^= *b as u64; |
| h = h.wrapping_mul(0x100000001b3); |
| } |
| h |
| } |
|
|
| |
|
|
| pub fn ledger_from_glyph(g: Glyph) -> Ledger { |
| let v = vec3_from_glyph(g); |
| |
| Ledger::new(g.index() as u64, v.x, K_QLG) |
| } |
|
|
| |
|
|
| #[derive(Debug, Clone, PartialEq, Eq)] |
| pub struct SLACertificate { |
| pub ledger: Ledger, |
| pub is_balanced: bool, |
| pub balance_value: i64, |
| pub omega_preserved: bool, |
| pub omega: i64, |
| } |
|
|
| impl SLACertificate { |
| pub fn new(l: Ledger) -> Self { |
| Self { |
| is_balanced: l.is_balanced(), |
| balance_value: l.reconciliation(), |
| omega_preserved: true, |
| omega: l.omega, |
| ledger: l, |
| } |
| } |
| pub fn validate(&self) -> bool { |
| self.is_balanced && self.omega_preserved |
| } |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| #[test] |
| fn balance_axiom_enforced_at_construction() { |
| let l = Ledger::new(1, 5, 42); |
| assert_eq!(l.iota, -5); |
| assert!(l.is_balanced()); |
| assert_eq!(l.reconciliation(), 0); |
| } |
|
|
| #[test] |
| fn composition_preserves_balance() { |
| let a = Ledger::new(1, 3, 42); |
| let b = Ledger::new(2, -2, 42); |
| let c = a.compose(&b).unwrap(); |
| assert!(c.is_balanced()); |
| assert_eq!(c.delta, 1); |
| assert_eq!(c.omega, 42); |
| } |
|
|
| #[test] |
| fn composition_rejects_omega_mismatch() { |
| let a = Ledger::new(1, 3, 42); |
| let b = Ledger::new(2, -2, 99); |
| assert!(a.compose(&b).is_none()); |
| } |
|
|
| #[test] |
| fn evolve_preserves_omega() { |
| let base = Ledger::new(1, 5, 42); |
| let inc = Ledger::new(2, 3, 0); |
| let next = base.evolve(inc).unwrap(); |
| assert_eq!(next.omega, 42); |
| assert!(next.is_balanced()); |
| } |
|
|
| #[test] |
| fn evolve_rejects_bad_increment() { |
| let base = Ledger::new(1, 5, 42); |
| |
| let bad = Ledger { s: 1, delta: 3, iota: 0, omega: 0 }; |
| assert!(base.evolve(bad).is_none()); |
| } |
|
|
| #[test] |
| fn ledger_sum_geometric_accumulation() { |
| const INV: i64 = 0x42; |
| let p1 = Ledger::new(1, 5, INV); |
| let p2 = Ledger::new(2, -3, INV); |
| let p3 = Ledger::new(3, 2, INV); |
| let mut sum = LedgerSum::new(); |
| assert!(sum.push(p1).is_some()); |
| assert!(sum.push(p2).is_some()); |
| assert!(sum.push(p3).is_some()); |
| assert_eq!(sum.delta_acc + sum.iota_acc, 0); |
| assert_eq!(sum.omega, INV); |
| assert_eq!(sum.count, 3); |
| let frame = sum.encode_wire_frame(); |
| assert_eq!(frame[2], 0xFF); |
| assert_eq!(frame[3] & 0x0F, 0x0A); |
| } |
|
|
| #[test] |
| fn wire_roundtrip() { |
| let l = Ledger::new(0xdeadbeef, -7, 99); |
| let enc = l.encode(); |
| let dec = Ledger::decode(&enc); |
| assert_eq!(dec, l); |
| } |
|
|
| #[test] |
| fn all_glyphs_produce_balanced_ledgers() { |
| for g in Glyph::all() { |
| let l = ledger_from_glyph(g); |
| assert!(l.is_balanced(), "{g} ledger not balanced"); |
| assert_eq!(l.omega, K_QLG); |
| } |
| } |
| } |
|
|