Skip to main content

qualia_core_db/modalities/
identity_fabric.rs

1//! Resilient relational identity (§27, legal_logic.md) — fabric resolution.
2//!
3//! The strict axiom: an **identifier is not an identity**. A key, DID, or name is a pointer;
4//! identity is the dynamically-computed result of a *fabric* of anchors (identifiers,
5//! attestations, relations). So losing a primary key must NOT collapse identity — it is
6//! re-computed from the surviving fabric (the refugee / device-loss / theft resilience case),
7//! provided a quorum of anchors survives (k-of-n social/relational recovery).
8//!
9//! See [[principle-identifiers-not-identity]]. This complements the foundational
10//! `modal_kind`/`resolve` layer with the *resilience* primitive. Zero-heap.
11
12/// Identity survives the loss of anchors iff a **quorum** of the fabric remains. `quorum` must
13/// be ≥1 (an identity anchored by nothing is not an identity). k-of-n recovery.
14#[inline]
15pub fn identity_survives_loss(total_anchors: usize, lost_anchors: usize, quorum: usize) -> bool {
16    quorum > 0 && total_anchors.saturating_sub(lost_anchors) >= quorum
17}
18
19/// The surviving anchor count after a loss (saturating).
20#[inline]
21pub fn surviving_anchors(total_anchors: usize, lost_anchors: usize) -> usize {
22    total_anchors.saturating_sub(lost_anchors)
23}
24
25/// Re-compute the active anchor set from `all_anchors`, excluding any in `lost`, into `out`.
26/// Returns the count — the surviving fabric an identity is reconstructed from. Zero-heap.
27pub fn recompute_fabric(all_anchors: &[u64], lost: &[u64], out: &mut [u64]) -> usize {
28    let mut n = 0usize;
29    for &a in all_anchors {
30        if !lost.contains(&a) {
31            if n >= out.len() {
32                break;
33            }
34            out[n] = a;
35            n += 1;
36        }
37    }
38    n
39}
40
41/// The axiom, made explicit: an identifier is never, by itself, the identity.
42#[inline]
43pub const fn identifier_is_not_identity() -> bool {
44    true
45}
46
47/// Identity as an **enumerated state** ([[principle-identifiers-not-identity]]): its confidence is
48/// the share of its `total` cryptographic anchors (identifiers + related datasets) currently
49/// `present`. One identifier of many → low confidence; the full enumerated fabric → high. `0.0`
50/// if `total == 0` (an enumeration of nothing is not an identity).
51pub fn enumerated_identity_confidence(present: usize, total: usize) -> f32 {
52    if total == 0 {
53        0.0
54    } else {
55        present as f32 / total as f32
56    }
57}
58
59// ─── Shamir's Secret Sharing (k-of-n quorum key recovery) ─────────────────────────
60//
61// A real threshold scheme over the prime field GF(2^61−1): a secret is the constant term of a
62// random degree-(k−1) polynomial; shares are evaluations; any k shares reconstruct it by Lagrange
63// interpolation at x=0, fewer than k reveal nothing. Zero-heap (bounded arrays, u128 intermediates).
64
65/// The Mersenne prime field modulus `2^61 − 1`.
66pub const SHAMIR_PRIME: u64 = (1u64 << 61) - 1;
67
68#[inline]
69fn m_add(a: u64, b: u64) -> u64 {
70    ((a as u128 + b as u128) % SHAMIR_PRIME as u128) as u64
71}
72#[inline]
73fn m_sub(a: u64, b: u64) -> u64 {
74    ((a as u128 + SHAMIR_PRIME as u128 - (b % SHAMIR_PRIME) as u128) % SHAMIR_PRIME as u128) as u64
75}
76#[inline]
77fn m_mul(a: u64, b: u64) -> u64 {
78    ((a as u128 * b as u128) % SHAMIR_PRIME as u128) as u64
79}
80fn m_pow(mut base: u64, mut exp: u64) -> u64 {
81    base %= SHAMIR_PRIME;
82    let mut r = 1u64;
83    while exp > 0 {
84        if exp & 1 == 1 {
85            r = m_mul(r, base);
86        }
87        base = m_mul(base, base);
88        exp >>= 1;
89    }
90    r
91}
92#[inline]
93fn m_inv(a: u64) -> u64 {
94    m_pow(a, SHAMIR_PRIME - 2) // Fermat: a^(p-2) ≡ a⁻¹
95}
96
97/// Evaluate the sharing polynomial `secret + Σ coeffs[i]·xⁱ⁺¹` at `x`, mod the prime (Horner).
98fn poly_eval(secret: u64, coeffs: &[u64], x: u64) -> u64 {
99    let mut y = 0u64;
100    for &c in coeffs.iter().rev() {
101        y = m_add(m_mul(y, x), c);
102    }
103    m_add(m_mul(y, x), secret)
104}
105
106/// Split `secret` (reduced mod the prime) into `n` shares with threshold `k = coeffs.len()+1`:
107/// share i (x-coord `i+1`) y-value is written to `ys[i]`. `coeffs` are the `k−1` polynomial
108/// coefficients (from a CSPRNG in production; caller-supplied here for determinism). Returns `n`.
109pub fn shamir_split(secret: u64, coeffs: &[u64], n: usize, ys: &mut [u64]) -> usize {
110    let s = secret % SHAMIR_PRIME;
111    let mut written = 0usize;
112    for i in 0..n {
113        if i >= ys.len() {
114            break;
115        }
116        ys[i] = poly_eval(s, coeffs, (i + 1) as u64);
117        written += 1;
118    }
119    written
120}
121
122/// Reconstruct the secret from `k` shares `(xs[i], ys[i])` by Lagrange interpolation at `x = 0`.
123/// Any `k` of the `n` shares recover the secret; fewer reveal nothing. Zero-heap.
124pub fn shamir_reconstruct(xs: &[u64], ys: &[u64], k: usize) -> u64 {
125    let k = k.min(xs.len()).min(ys.len());
126    let mut secret = 0u64;
127    for i in 0..k {
128        let mut num = 1u64; // Π_{j≠i} (0 − x_j) = Π (−x_j)
129        let mut den = 1u64; // Π_{j≠i} (x_i − x_j)
130        for j in 0..k {
131            if j == i {
132                continue;
133            }
134            num = m_mul(num, m_sub(0, xs[j]));
135            den = m_mul(den, m_sub(xs[i], xs[j]));
136        }
137        let term = m_mul(ys[i], m_mul(num, m_inv(den)));
138        secret = m_add(secret, term);
139    }
140    secret
141}
142
143// ─── ZKP capability derivation & recursive web-of-trust ───────────────────────────
144
145/// **ZKP capability derivation**: a capability is granted by PROVING an identity trait (a zk proof)
146/// WITHOUT revealing the core identifier. Granted iff `trait_proven` AND the identifier was NOT
147/// revealed — the proof carries the trait, not the id (privacy-preserving derivation).
148#[inline]
149pub fn zkp_capability_granted(trait_proven: bool, identifier_revealed: bool) -> bool {
150    trait_proven && !identifier_revealed
151}
152
153/// **Recursive identity anchoring with web-of-trust decay**: an identity asserted through a chain
154/// of `depth` intermediary identities has confidence `base · decay^depth` — trust attenuates with
155/// each hop. `decay ∈ [0,1]`; `depth = 0` is a direct anchor (full `base`).
156pub fn web_of_trust_confidence(base: f32, depth: u32, decay: f32) -> f32 {
157    base * decay.powi(depth as i32)
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::q_hash;
164
165    #[test]
166    fn shamir_k_of_n_recovers_the_secret() {
167        let secret = 0x0BADC0DE_1234u64;
168        let coeffs = [98765u64, 4242u64]; // k = 3 (degree 2)
169        let mut ys = [0u64; 5];
170        let n = shamir_split(secret, &coeffs, 5, &mut ys); // 5 shares
171        assert_eq!(n, 5);
172        // Any 3 shares reconstruct the secret.
173        assert_eq!(
174            shamir_reconstruct(&[1, 2, 3], &[ys[0], ys[1], ys[2]], 3),
175            secret
176        );
177        assert_eq!(
178            shamir_reconstruct(&[2, 4, 5], &[ys[1], ys[3], ys[4]], 3),
179            secret
180        );
181        // Fewer than k shares do NOT yield the secret.
182        assert_ne!(shamir_reconstruct(&[1, 2], &[ys[0], ys[1]], 2), secret);
183    }
184
185    #[test]
186    fn enumerated_identity_zkp_and_web_of_trust() {
187        // Enumerated identity: 3 of 4 anchors present → 0.75 confidence.
188        assert!((enumerated_identity_confidence(3, 4) - 0.75).abs() < 1e-6);
189        assert_eq!(enumerated_identity_confidence(1, 0), 0.0);
190        // ZKP capability: granted only when the trait is proven AND the id stays hidden.
191        assert!(zkp_capability_granted(true, false));
192        assert!(
193            !zkp_capability_granted(true, true),
194            "revealing the identifier defeats the point"
195        );
196        assert!(!zkp_capability_granted(false, false));
197        // Web-of-trust decay: 0.9 base, decay 0.5 → depth 0 = 0.9, depth 2 = 0.225.
198        assert!((web_of_trust_confidence(0.9, 0, 0.5) - 0.9).abs() < 1e-6);
199        assert!((web_of_trust_confidence(0.9, 2, 0.5) - 0.225).abs() < 1e-6);
200    }
201
202    #[test]
203    fn identity_survives_key_loss_with_quorum() {
204        // 5 anchors, lose the primary key (1), quorum of 3 → survives.
205        assert!(identity_survives_loss(5, 1, 3));
206        // Lose 3 of 5, quorum 3 → exactly meets → survives.
207        assert!(identity_survives_loss(5, 2, 3));
208        // Lose too many → identity cannot be reconstructed.
209        assert!(!identity_survives_loss(5, 3, 3));
210        // Quorum 0 is invalid (nothing anchors nothing).
211        assert!(!identity_survives_loss(5, 0, 0));
212    }
213
214    #[test]
215    fn fabric_recomputes_from_survivors() {
216        let key = q_hash("anchor:primaryKey");
217        let social = q_hash("anchor:socialAttestation");
218        let bio = q_hash("anchor:biometric");
219        let device = q_hash("anchor:device");
220        let all = [key, social, bio, device];
221        let lost = [key, device]; // stolen phone + its key
222        let mut out = [0u64; 8];
223        let n = recompute_fabric(&all, &lost, &mut out);
224        assert_eq!(
225            n, 2,
226            "identity re-computes from the surviving relational fabric"
227        );
228        assert!(out[..n].contains(&social) && out[..n].contains(&bio));
229        assert!(!out[..n].contains(&key));
230        assert!(identifier_is_not_identity());
231    }
232}