tawasuyu

tawasuyu/brahman

Rust 492TOML 194Markdown 53JSON 176939 Definiciones

⎇ 53dbdf0f1deea10d6fc567ea2b43e1e75d6d60cd crates/modules/semantic_dht/minga-core/src/identity.rs Rust · 223 líneas · 7.8 KB Historia Quién tocó qué crudo

chore: monorepo inicial con arje + minga + yahweh absorbidos 53dbdf0f1 · Sergio ·

1//! Identidad self-sovereign basada en Ed25519.
2//!
3//! Cada peer (y cada autor humano o agente IA) se identifica por un
4//! `Did` — el bytestring de su clave pública Ed25519. La clave privada
5//! vive en su `Keypair` y nunca sale del nodo. Firmar un mensaje con la
6//! `Keypair` produce una `Signature` que cualquiera con el `Did` puede
7//! verificar — la atribución es irrefutable bajo el modelo
8//! criptográfico estándar (asumiendo que la clave privada no fugó).
9//!
10//! El esquema es deliberadamente minimalista: no hay rotación de
11//! claves, ni revocación, ni metadatos en el DID. Esas capas (DID
12//! Documents, métodos `did:web`/`did:ion`, claves de firma versus de
13//! cifrado, etc.) se construyen encima cuando la complejidad del
14//! producto lo justifique. Por ahora, el `Did` ES la clave pública.
15
16use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit, Nonce};
17use argon2::Argon2;
18use ed25519_dalek::{
19    Signature as Ed25519Sig, Signer, SigningKey, Verifier, VerifyingKey, SECRET_KEY_LENGTH,
20    SIGNATURE_LENGTH,
21};
22use rand::rngs::OsRng;
23use rand::RngCore;
24
25/// Cabecera del formato de keypair cifrado en disco.
26const KEYPAIR_MAGIC: &[u8; 8] = b"MINGAKEY";
27const KEYPAIR_VERSION: u8 = 1;
28const ARGON2_SALT_LEN: usize = 16;
29const AES_NONCE_LEN: usize = 12;
30const KEYPAIR_HEADER_LEN: usize = 8 + 1 + ARGON2_SALT_LEN + AES_NONCE_LEN;
31
32#[derive(Debug, thiserror::Error)]
33pub enum KeypairCryptoError {
34    #[error("formato inválido: faltan magic / versión / longitud")]
35    InvalidFormat,
36
37    #[error("passphrase incorrecta o cifrado manipulado")]
38    DecryptFailed,
39
40    #[error("argon2: {0}")]
41    Argon2(String),
42}
43
44/// Decentralized Identifier: 32 bytes de la clave pública Ed25519.
45#[derive(
46    Debug,
47    Clone,
48    Copy,
49    PartialEq,
50    Eq,
51    Hash,
52    PartialOrd,
53    Ord,
54    serde::Serialize,
55    serde::Deserialize,
56)]
57#[serde(transparent)]
58pub struct Did(pub [u8; SECRET_KEY_LENGTH]);
59
60impl Did {
61    pub fn as_bytes(&self) -> &[u8; SECRET_KEY_LENGTH] {
62        &self.0
63    }
64
65    /// Verifica que `sig` sea una firma válida sobre `msg` producida
66    /// con la llave privada correspondiente a este DID. Devuelve
67    /// `false` ante cualquier irregularidad: bytes de DID que no son
68    /// un punto válido en la curva, firma malformada, mensaje que no
69    /// coincide.
70    pub fn verify(&self, msg: &[u8], sig: &Signature) -> bool {
71        let Ok(vk) = VerifyingKey::from_bytes(&self.0) else {
72            return false;
73        };
74        let ed_sig = Ed25519Sig::from_bytes(&sig.0);
75        vk.verify(msg, &ed_sig).is_ok()
76    }
77}
78
79impl std::fmt::Display for Did {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        write!(f, "did:key:")?;
82        for b in &self.0 {
83            write!(f, "{:02x}", b)?;
84        }
85        Ok(())
86    }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
90#[serde(transparent)]
91pub struct Signature(
92    #[serde(with = "serde_big_array::BigArray")] pub [u8; SIGNATURE_LENGTH],
93);
94
95impl Signature {
96    pub fn as_bytes(&self) -> &[u8; SIGNATURE_LENGTH] {
97        &self.0
98    }
99}
100
101/// Llave criptográfica completa: priva (para firmar) + pública (para
102/// que otros verifiquen). Por convención llamamos `Did` al lado público
103/// expuesto al mundo, pero el `Keypair` mantiene ambos lados juntos.
104#[derive(Clone)]
105pub struct Keypair {
106    signing: SigningKey,
107}
108
109impl Keypair {
110    /// Genera un nuevo `Keypair` usando aleatoriedad del sistema
111    /// operativo (`/dev/urandom` en Unix, `BCryptGenRandom` en
112    /// Windows). Para producción.
113    pub fn generate() -> Self {
114        let mut seed = [0u8; SECRET_KEY_LENGTH];
115        OsRng.fill_bytes(&mut seed);
116        Self::from_seed(&seed)
117    }
118
119    /// Reconstruye un `Keypair` desde una semilla de 32 bytes. Misma
120    /// semilla → mismo `Keypair` (mismo `Did`, mismas firmas). Útil
121    /// para tests reproducibles y para escenarios donde la semilla
122    /// proviene de otra fuente determinista (HKDF, BIP39, etc.).
123    pub fn from_seed(seed: &[u8; SECRET_KEY_LENGTH]) -> Self {
124        Self {
125            signing: SigningKey::from_bytes(seed),
126        }
127    }
128
129    pub fn did(&self) -> Did {
130        Did(self.signing.verifying_key().to_bytes())
131    }
132
133    pub fn sign(&self, msg: &[u8]) -> Signature {
134        Signature(self.signing.sign(msg).to_bytes())
135    }
136
137    /// Cifra la parte privada del keypair con una passphrase humana.
138    /// Esquema:
139    ///
140    /// 1. Genera un salt aleatorio de 16 bytes y un nonce de 12 bytes.
141    /// 2. Deriva una clave AES-256 desde la passphrase vía Argon2id
142    ///    (parámetros por defecto OWASP).
143    /// 3. Cifra los 32 bytes de la clave secreta con AES-256-GCM
144    ///    (autenticado: integrity built-in).
145    /// 4. Compone el blob:
146    ///    `MAGIC(8) || VERSION(1) || SALT(16) || NONCE(12) || CIPHERTEXT+TAG(48)`.
147    ///
148    /// Total: 85 bytes. La passphrase nunca se almacena; quien no la
149    /// conozca no puede recuperar la identidad.
150    pub fn encrypt(&self, passphrase: &str) -> Result<Vec<u8>, KeypairCryptoError> {
151        let mut salt = [0u8; ARGON2_SALT_LEN];
152        let mut nonce_bytes = [0u8; AES_NONCE_LEN];
153        OsRng.fill_bytes(&mut salt);
154        OsRng.fill_bytes(&mut nonce_bytes);
155
156        let aes_key = derive_aes_key(passphrase, &salt)?;
157
158        let cipher = Aes256Gcm::new_from_slice(&aes_key)
159            .map_err(|_| KeypairCryptoError::DecryptFailed)?;
160        let nonce = Nonce::from_slice(&nonce_bytes);
161        let secret_bytes = self.signing.to_bytes();
162        let ciphertext = cipher
163            .encrypt(nonce, secret_bytes.as_ref())
164            .map_err(|_| KeypairCryptoError::DecryptFailed)?;
165
166        let mut out = Vec::with_capacity(KEYPAIR_HEADER_LEN + ciphertext.len());
167        out.extend_from_slice(KEYPAIR_MAGIC);
168        out.push(KEYPAIR_VERSION);
169        out.extend_from_slice(&salt);
170        out.extend_from_slice(&nonce_bytes);
171        out.extend_from_slice(&ciphertext);
172        Ok(out)
173    }
174
175    /// Descifra un keypair cifrado con `encrypt`. Falla con
176    /// `DecryptFailed` si la passphrase es incorrecta **o** si los
177    /// bytes han sido manipulados (AES-GCM detecta ambas vías).
178    pub fn decrypt(bytes: &[u8], passphrase: &str) -> Result<Self, KeypairCryptoError> {
179        if bytes.len() < KEYPAIR_HEADER_LEN {
180            return Err(KeypairCryptoError::InvalidFormat);
181        }
182        if &bytes[..8] != KEYPAIR_MAGIC {
183            return Err(KeypairCryptoError::InvalidFormat);
184        }
185        if bytes[8] != KEYPAIR_VERSION {
186            return Err(KeypairCryptoError::InvalidFormat);
187        }
188
189        let salt = &bytes[9..9 + ARGON2_SALT_LEN];
190        let nonce_bytes = &bytes[9 + ARGON2_SALT_LEN..KEYPAIR_HEADER_LEN];
191        let ciphertext = &bytes[KEYPAIR_HEADER_LEN..];
192
193        let aes_key = derive_aes_key(passphrase, salt)?;
194        let cipher = Aes256Gcm::new_from_slice(&aes_key)
195            .map_err(|_| KeypairCryptoError::DecryptFailed)?;
196        let nonce = Nonce::from_slice(nonce_bytes);
197        let plaintext = cipher
198            .decrypt(nonce, ciphertext)
199            .map_err(|_| KeypairCryptoError::DecryptFailed)?;
200
201        if plaintext.len() != SECRET_KEY_LENGTH {
202            return Err(KeypairCryptoError::InvalidFormat);
203        }
204        let mut seed = [0u8; SECRET_KEY_LENGTH];
205        seed.copy_from_slice(&plaintext);
206        Ok(Self::from_seed(&seed))
207    }
208}
209
210fn derive_aes_key(passphrase: &str, salt: &[u8]) -> Result<[u8; 32], KeypairCryptoError> {
211    let mut key = [0u8; 32];
212    Argon2::default()
213        .hash_password_into(passphrase.as_bytes(), salt, &mut key)
214        .map_err(|e| KeypairCryptoError::Argon2(e.to_string()))?;
215    Ok(key)
216}
217
218impl std::fmt::Debug for Keypair {
219    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220        // Nunca exponemos la parte privada en debug. Solo el DID.
221        write!(f, "Keypair {{ did: {} }}", self.did())
222    }
223}