Skip to main content

huddle_core/
identity.rs

1use ed25519_dalek::SigningKey;
2use libp2p::identity::{self, Keypair};
3use libp2p::PeerId;
4use sha2::{Digest, Sha256};
5
6use crate::error::{HuddleError, Result};
7
8pub struct Identity {
9    signing_key: SigningKey,
10    libp2p_keypair: Keypair,
11    peer_id: PeerId,
12    fingerprint: String,
13}
14
15impl Identity {
16    pub fn generate() -> Result<Self> {
17        let mut rng = rand::thread_rng();
18        let signing_key = SigningKey::generate(&mut rng);
19        Self::from_signing_key(signing_key)
20    }
21
22    pub fn from_secret_bytes(bytes: [u8; 32]) -> Result<Self> {
23        let signing_key = SigningKey::from_bytes(&bytes);
24        Self::from_signing_key(signing_key)
25    }
26
27    fn from_signing_key(signing_key: SigningKey) -> Result<Self> {
28        let secret = signing_key.to_bytes();
29        let public = signing_key.verifying_key().to_bytes();
30        let mut combined = [0u8; 64];
31        combined[..32].copy_from_slice(&secret);
32        combined[32..].copy_from_slice(&public);
33
34        let ed25519_kp = identity::ed25519::Keypair::try_from_bytes(&mut combined)
35            .map_err(|e| HuddleError::Identity(e.to_string()))?;
36        let libp2p_keypair = Keypair::from(ed25519_kp);
37        let peer_id = PeerId::from(libp2p_keypair.public());
38        let fingerprint = compute_fingerprint(&public);
39
40        Ok(Self {
41            signing_key,
42            libp2p_keypair,
43            peer_id,
44            fingerprint,
45        })
46    }
47
48    pub fn fingerprint(&self) -> &str {
49        &self.fingerprint
50    }
51
52    pub fn peer_id(&self) -> PeerId {
53        self.peer_id
54    }
55
56    pub fn keypair(&self) -> &Keypair {
57        &self.libp2p_keypair
58    }
59
60    pub fn secret_bytes(&self) -> [u8; 32] {
61        self.signing_key.to_bytes()
62    }
63
64    pub fn public_bytes(&self) -> [u8; 32] {
65        self.signing_key.verifying_key().to_bytes()
66    }
67}
68
69fn compute_fingerprint(public_key: &[u8; 32]) -> String {
70    let hash = Sha256::digest(public_key);
71    let hex_str = hex::encode(&hash[..12]);
72    hex_str
73        .as_bytes()
74        .chunks(4)
75        .map(|chunk| std::str::from_utf8(chunk).unwrap())
76        .collect::<Vec<&str>>()
77        .join("-")
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn fingerprint_is_deterministic() {
86        let key_bytes = [42u8; 32];
87        let id = Identity::from_secret_bytes(key_bytes).unwrap();
88        let id2 = Identity::from_secret_bytes(key_bytes).unwrap();
89        assert_eq!(id.fingerprint(), id2.fingerprint());
90    }
91
92    #[test]
93    fn fingerprint_format_is_correct() {
94        let id = Identity::generate().unwrap();
95        let fp = id.fingerprint();
96        let parts: Vec<&str> = fp.split('-').collect();
97        assert_eq!(parts.len(), 6);
98        for part in &parts {
99            assert_eq!(part.len(), 4);
100            assert!(part.chars().all(|c| c.is_ascii_hexdigit()));
101        }
102    }
103
104    #[test]
105    fn different_keys_produce_different_fingerprints() {
106        let id1 = Identity::generate().unwrap();
107        let id2 = Identity::generate().unwrap();
108        assert_ne!(id1.fingerprint(), id2.fingerprint());
109    }
110
111    #[test]
112    fn round_trip_through_bytes() {
113        let id1 = Identity::generate().unwrap();
114        let bytes = id1.secret_bytes();
115        let id2 = Identity::from_secret_bytes(bytes).unwrap();
116        assert_eq!(id1.fingerprint(), id2.fingerprint());
117        assert_eq!(id1.peer_id(), id2.peer_id());
118    }
119
120    #[test]
121    fn peer_id_is_derived_from_same_key() {
122        let id = Identity::generate().unwrap();
123        let pid = id.peer_id();
124        assert!(!pid.to_string().is_empty());
125    }
126}