tawasuyu

tawasuyu/brahman

Rust 326TOML 123JSON 17Markdown 144606 Sutichaykuna

⎇ 72758e75ce515f355f27615671c95ecaf8c2a8bc crates/core/brahman-handshake/src/signature.rs Rust · 155 siq'ikuna · 5.7 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat(brahman-handshake): Fase 3 — trust remoto via firma Ed25519 c164e9f42 · Sergio ·

1//! Firma y verificación del payload del `Hello` para trust remoto.
2//!
3//! Usa la identidad Ed25519 de libp2p (la misma keypair que el peer
4//! presenta al swarm vía Noise). Esto ancla la identidad criptográfica
5//! del Ente a la identidad de transporte: si Noise autenticó al
6//! `peer_id` X, sólo X puede firmar Cards válidas para esa conexión.
7//!
8//! ## Payload firmado
9//!
10//! Bytes postcard de la tupla `(WireCard, Option<WitInterface>)`. Se
11//! eligió postcard porque ya es el wire format del resto del protocolo:
12//! mismo determinismo, sin convertir a otro formato sólo para firmar.
13//!
14//! Cualquier campo que entre al payload firmado en el futuro debe
15//! añadirse al final de la tupla (postcard es position-dependent), o
16//! bumpearse el [`SIGNATURE_VERSION`] para distinguir esquemas.
17
18use brahman_card::{WireCard, WitInterface};
19use brahman_net::{Keypair, PeerId, PublicKey};
20
21use crate::messages::HelloSignature;
22
23/// Versión del esquema de payload firmado. Si cambia el shape de
24/// `(WireCard, Option<WitInterface>)` o cómo se serializa, bump este
25/// número y el verificador rechaza firmas antiguas.
26pub const SIGNATURE_VERSION: u8 = 1;
27
28/// Errores de verificación de firma.
29#[derive(Debug, thiserror::Error)]
30pub enum SignatureError {
31    #[error("public_key inválida (libp2p decode protobuf): {0}")]
32    DecodeKey(String),
33    #[error("encode del payload falló: {0}")]
34    EncodePayload(String),
35    #[error("firma rechazada: bytes inválidos para la public_key")]
36    Invalid,
37    #[error("peer_id de la firma ({signer}) no coincide con el peer libp2p autenticado ({expected})")]
38    PeerMismatch { signer: PeerId, expected: PeerId },
39    #[error("firma del Hello faltante (requerida para conexión remota libp2p)")]
40    Missing,
41    #[error("firma del Hello inesperada en path local sin trust remoto")]
42    Unexpected,
43}
44
45/// Construye los bytes canónicos a firmar/verificar para un Hello.
46/// Postcard determinístico de `(version, WireCard, Option<WitInterface>)`.
47fn payload_bytes(card: &WireCard, wit: &Option<WitInterface>) -> Result<Vec<u8>, SignatureError> {
48    let tup = (SIGNATURE_VERSION, card, wit);
49    postcard::to_allocvec(&tup).map_err(|e| SignatureError::EncodePayload(e.to_string()))
50}
51
52/// Firma `(card, wit)` con la `keypair`. La public key derivada de
53/// `keypair` debe coincidir con la identidad libp2p del peer cuando
54/// el verificador la chequee.
55pub fn sign_hello(
56    keypair: &Keypair,
57    card: &WireCard,
58    wit: &Option<WitInterface>,
59) -> Result<HelloSignature, SignatureError> {
60    let bytes = payload_bytes(card, wit)?;
61    let signature_bytes = keypair
62        .sign(&bytes)
63        .map_err(|e| SignatureError::EncodePayload(e.to_string()))?;
64    Ok(HelloSignature {
65        public_key: keypair.public().encode_protobuf(),
66        signature: signature_bytes,
67    })
68}
69
70/// Verifica que `sig` es una firma válida sobre `(card, wit)` y que
71/// la public key declarada coincide con `expected_peer` (la identidad
72/// libp2p autenticada por Noise).
73///
74/// Devuelve `Ok(())` si todo cuadra; si no, el error concreto.
75pub fn verify_hello(
76    sig: &HelloSignature,
77    card: &WireCard,
78    wit: &Option<WitInterface>,
79    expected_peer: PeerId,
80) -> Result<(), SignatureError> {
81    let public_key = PublicKey::try_decode_protobuf(&sig.public_key)
82        .map_err(|e| SignatureError::DecodeKey(e.to_string()))?;
83    let signer_peer = public_key.to_peer_id();
84    if signer_peer != expected_peer {
85        return Err(SignatureError::PeerMismatch {
86            signer: signer_peer,
87            expected: expected_peer,
88        });
89    }
90    let bytes = payload_bytes(card, wit)?;
91    if !public_key.verify(&bytes, &sig.signature) {
92        return Err(SignatureError::Invalid);
93    }
94    Ok(())
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use brahman_card::Card;
101
102    fn sample_card() -> WireCard {
103        Card::new("test.signed").into()
104    }
105
106    #[test]
107    fn sign_then_verify_roundtrip() {
108        let kp = Keypair::generate_ed25519();
109        let peer = kp.public().to_peer_id();
110        let card = sample_card();
111        let wit = None;
112        let sig = sign_hello(&kp, &card, &wit).unwrap();
113        verify_hello(&sig, &card, &wit, peer).expect("firma propia debe verificar");
114    }
115
116    #[test]
117    fn verify_rejects_wrong_peer() {
118        let kp = Keypair::generate_ed25519();
119        let other = Keypair::generate_ed25519().public().to_peer_id();
120        let card = sample_card();
121        let wit = None;
122        let sig = sign_hello(&kp, &card, &wit).unwrap();
123        let err = verify_hello(&sig, &card, &wit, other).unwrap_err();
124        assert!(matches!(err, SignatureError::PeerMismatch { .. }), "got {err:?}");
125    }
126
127    #[test]
128    fn verify_rejects_tampered_card() {
129        let kp = Keypair::generate_ed25519();
130        let peer = kp.public().to_peer_id();
131        let original = sample_card();
132        let wit = None;
133        let sig = sign_hello(&kp, &original, &wit).unwrap();
134
135        // Verificamos contra una Card distinta (mismo shape, distinto label).
136        let tampered: WireCard = Card::new("test.tampered").into();
137        let err = verify_hello(&sig, &tampered, &wit, peer).unwrap_err();
138        assert!(matches!(err, SignatureError::Invalid), "got {err:?}");
139    }
140
141    #[test]
142    fn verify_rejects_corrupted_signature() {
143        let kp = Keypair::generate_ed25519();
144        let peer = kp.public().to_peer_id();
145        let card = sample_card();
146        let wit = None;
147        let mut sig = sign_hello(&kp, &card, &wit).unwrap();
148        // Flip un bit de la firma.
149        if let Some(b) = sig.signature.last_mut() {
150            *b ^= 0x01;
151        }
152        let err = verify_hello(&sig, &card, &wit, peer).unwrap_err();
153        assert!(matches!(err, SignatureError::Invalid), "got {err:?}");
154    }
155}