gioser

tawasuyu/card

Sovereign content-addressed identity primitive (card-core + WIT-typed flows) in Rust.

Markdown 9Rust 7TOML 4HTML 1165 Definiciones

⎇ e6d21c60273e028ce20801c3287895cc7777c43a shared/card/card-net/src/key.rs Rust · 167 líneas · 5.4 KB Historia Quién tocó qué crudo

feat: card standalone — primitiva de identidad soberana (hoja, autocontenida) e6d21c602 · Sergio ·

1//! Claves namespaced del DHT compartido — la **primitiva unificadora** de
2//! Brahman (Capa 1).
3//!
4//! El ecosistema brahman corre UN solo Kademlia (en este crate, `card-net`).
5//! Para que distintos dominios — código indexado (minga), Cards
6//! (card-discovery), Personas (ágora), servicios — coexistan sin colisión,
7//! cada clave lleva un byte de `kind` como prefijo. La representación en wire
8//! es de longitud fija: `[kind_tag] ++ blake3(id)` = 33 bytes.
9//!
10//! Vive en `card-net` (y no en un dominio concreto) precisamente porque es el
11//! namespace COMÚN: minga, agora y card-discovery la comparten. `minga-dht`
12//! la re-exporta por compatibilidad histórica.
13
14use serde::{Deserialize, Serialize};
15
16/// Tipo de registro — el namespace de una clave en el DHT.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum RecordKind {
19    /// Bloque de código indexado (minga).
20    Code,
21    /// `Card` brahman (módulo, ente, etc.).
22    Card,
23    /// `Persona` de ágora (identidad humana federada).
24    Persona,
25    /// Endpoint de servicio.
26    Service,
27    /// Dominio definido por el consumidor.
28    Custom(u8),
29}
30
31impl RecordKind {
32    /// Byte de etiqueta. `Custom(n)` ocupa `0x80 | n` (top bit) para no
33    /// chocar nunca con los kinds estándar (`0x00..`).
34    pub fn tag(&self) -> u8 {
35        match self {
36            RecordKind::Code => 0x01,
37            RecordKind::Card => 0x02,
38            RecordKind::Persona => 0x03,
39            RecordKind::Service => 0x04,
40            RecordKind::Custom(n) => 0x80 | (n & 0x7f),
41        }
42    }
43}
44
45/// Longitud fija de la clave en wire: 1 byte de kind + 32 de hash.
46pub const DHT_KEY_LEN: usize = 33;
47
48/// Clave de DHT namespaced. Dos formas de construcción:
49///
50/// - Con un `id` legible (`new`, `code`, `card`, `persona`) — el wire
51///   hashea el id con blake3. Útil cuando el consumidor publica
52///   identidades simbólicas (nombres de módulos, slugs de personas).
53/// - Con `for_hash` — el wire usa los 32 bytes del hash directamente.
54///   Útil cuando el id YA es un blake3 (como en minga, que indexa
55///   contenido por su α-hash, o ágora, cuyo `IdentityId` ya es
56///   `blake3(pubkey)`) — evita una segunda pasada de blake3.
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct DhtKey {
59    kind: RecordKind,
60    /// Representación canónica de los 32 bytes que forman la "id" en wire.
61    /// Si se construyó por nombre simbólico, son `blake3(id_string)`.
62    /// Si se construyó por hash directo, son el hash tal cual.
63    body: [u8; 32],
64    /// `id` legible — para `Display`. `None` si se construyó por hash.
65    label: Option<String>,
66}
67
68impl DhtKey {
69    pub fn new(kind: RecordKind, id: impl Into<String>) -> Self {
70        let id = id.into();
71        let body = *blake3::hash(id.as_bytes()).as_bytes();
72        Self {
73            kind,
74            body,
75            label: Some(id),
76        }
77    }
78
79    /// Clave para un bloque de código (id legible).
80    pub fn code(id: impl Into<String>) -> Self {
81        Self::new(RecordKind::Code, id)
82    }
83
84    /// Clave para una Card.
85    pub fn card(id: impl Into<String>) -> Self {
86        Self::new(RecordKind::Card, id)
87    }
88
89    /// Clave para una Persona.
90    pub fn persona(id: impl Into<String>) -> Self {
91        Self::new(RecordKind::Persona, id)
92    }
93
94    /// Clave a partir de un hash ya computado (32 bytes). El wire usa
95    /// esos bytes directamente, **sin re-hashear**.
96    pub fn for_hash(kind: RecordKind, hash: [u8; 32]) -> Self {
97        Self {
98            kind,
99            body: hash,
100            label: None,
101        }
102    }
103
104    pub fn kind(&self) -> RecordKind {
105        self.kind
106    }
107
108    pub fn id(&self) -> Option<&str> {
109        self.label.as_deref()
110    }
111
112    /// Representación en wire: `[kind_tag] ++ body`, 33 bytes.
113    pub fn to_bytes(&self) -> Vec<u8> {
114        let mut out = Vec::with_capacity(DHT_KEY_LEN);
115        out.push(self.kind.tag());
116        out.extend_from_slice(&self.body);
117        out
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn wire_key_has_fixed_length() {
127        assert_eq!(DhtKey::card("modulo-x").to_bytes().len(), DHT_KEY_LEN);
128        assert_eq!(DhtKey::code("fn-hash").to_bytes().len(), DHT_KEY_LEN);
129    }
130
131    #[test]
132    fn same_id_different_kind_does_not_collide() {
133        let a = DhtKey::card("foo").to_bytes();
134        let b = DhtKey::code("foo").to_bytes();
135        let c = DhtKey::persona("foo").to_bytes();
136        assert_ne!(a, b);
137        assert_ne!(b, c);
138        assert_ne!(a, c);
139        // El hash del id es el mismo; sólo difiere el byte de kind.
140        assert_eq!(a[1..], b[1..]);
141        assert_ne!(a[0], b[0]);
142    }
143
144    #[test]
145    fn same_kind_and_id_is_stable() {
146        assert_eq!(DhtKey::card("x").to_bytes(), DhtKey::card("x").to_bytes());
147    }
148
149    #[test]
150    fn for_hash_no_rehashea() {
151        // for_hash debe usar los bytes tal cual: body = hash, no blake3(hash).
152        let h = [7u8; 32];
153        let k = DhtKey::for_hash(RecordKind::Persona, h);
154        let wire = k.to_bytes();
155        assert_eq!(wire[0], RecordKind::Persona.tag());
156        assert_eq!(&wire[1..], &h);
157    }
158
159    #[test]
160    fn custom_kind_never_collides_with_standard() {
161        for std in [RecordKind::Code, RecordKind::Card, RecordKind::Persona, RecordKind::Service] {
162            for n in 0..=127u8 {
163                assert_ne!(std.tag(), RecordKind::Custom(n).tag());
164            }
165        }
166    }
167}