Skip to main content

mesh_llm_identity/
keys.rs

1use ed25519_dalek::{Signer, SigningKey, VerifyingKey};
2use sha2::{Digest, Sha256};
3
4use super::error::CryptoError;
5
6/// Owner keypair: Ed25519 signing key + X25519 encryption key.
7#[derive(Debug)]
8pub struct OwnerKeypair {
9    pub(crate) signing: SigningKey,
10    pub(crate) encryption: crypto_box::SecretKey,
11}
12
13impl OwnerKeypair {
14    /// Generate a new random owner keypair.
15    pub fn generate() -> Self {
16        // ed25519-dalek (rand_core 0.9) and crypto_box (rand_core 0.6)
17        // need different RNG types due to version mismatch.
18        let signing = SigningKey::generate(&mut rand::rng());
19        let encryption = crypto_box::SecretKey::generate(&mut crypto_box::aead::OsRng);
20        Self {
21            signing,
22            encryption,
23        }
24    }
25
26    /// Derive the stable owner ID from the signing public key.
27    ///
28    /// Returns `sha256(signing_public_key_bytes)` as a 64-char lowercase hex string.
29    pub fn owner_id(&self) -> String {
30        owner_id_from_verifying_key(&self.signing.verifying_key())
31    }
32
33    /// The Ed25519 verifying (public) key for signature verification.
34    pub fn verifying_key(&self) -> VerifyingKey {
35        self.signing.verifying_key()
36    }
37
38    /// The X25519 public key for encrypting messages to this owner.
39    pub fn encryption_public_key(&self) -> crypto_box::PublicKey {
40        self.encryption.public_key()
41    }
42
43    /// Reconstruct from raw key bytes (used by keystore deserialization).
44    pub fn from_bytes(signing_bytes: &[u8], encryption_bytes: &[u8]) -> Result<Self, CryptoError> {
45        let signing_arr: [u8; 32] =
46            signing_bytes
47                .try_into()
48                .map_err(|_| CryptoError::InvalidKeyMaterial {
49                    reason: "signing key must be 32 bytes".into(),
50                })?;
51        let encryption_arr: [u8; 32] =
52            encryption_bytes
53                .try_into()
54                .map_err(|_| CryptoError::InvalidKeyMaterial {
55                    reason: "encryption key must be 32 bytes".into(),
56                })?;
57
58        let signing = SigningKey::from_bytes(&signing_arr);
59        let encryption = crypto_box::SecretKey::from(encryption_arr);
60
61        Ok(Self {
62            signing,
63            encryption,
64        })
65    }
66
67    /// Raw signing secret key bytes (for keystore serialization).
68    pub fn signing_bytes(&self) -> &[u8; 32] {
69        self.signing.as_bytes()
70    }
71
72    /// Raw encryption secret key bytes (for keystore serialization).
73    pub fn encryption_bytes(&self) -> [u8; 32] {
74        self.encryption.to_bytes()
75    }
76
77    /// Sign arbitrary domain-separated bytes with the owner signing key.
78    pub fn sign_bytes(&self, bytes: &[u8]) -> [u8; 64] {
79        self.signing.sign(bytes).to_bytes()
80    }
81}
82
83impl Clone for OwnerKeypair {
84    fn clone(&self) -> Self {
85        Self {
86            signing: self.signing.clone(),
87            encryption: crypto_box::SecretKey::from(self.encryption.to_bytes()),
88        }
89    }
90}
91
92// Both ed25519_dalek::SigningKey and crypto_box::SecretKey implement Zeroize on drop.
93
94/// Derive owner ID from a verifying key (public operation, no secret needed).
95pub fn owner_id_from_verifying_key(vk: &VerifyingKey) -> String {
96    let hash = Sha256::digest(vk.as_bytes());
97    hex::encode(hash)
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn key_generation_produces_valid_owner_id() {
106        let kp = OwnerKeypair::generate();
107        let id = kp.owner_id();
108        assert_eq!(id.len(), 64, "owner_id should be 64 hex chars");
109        assert!(
110            id.chars().all(|c| c.is_ascii_hexdigit()),
111            "owner_id should be hex"
112        );
113    }
114
115    #[test]
116    fn owner_id_is_deterministic_from_public_key() {
117        let kp = OwnerKeypair::generate();
118        let id1 = kp.owner_id();
119        let id2 = owner_id_from_verifying_key(&kp.verifying_key());
120        assert_eq!(id1, id2);
121    }
122
123    #[test]
124    fn different_keypairs_produce_different_owner_ids() {
125        let kp1 = OwnerKeypair::generate();
126        let kp2 = OwnerKeypair::generate();
127        assert_ne!(kp1.owner_id(), kp2.owner_id());
128    }
129
130    #[test]
131    fn round_trip_from_bytes() {
132        let kp = OwnerKeypair::generate();
133        let signing = kp.signing_bytes().to_vec();
134        let encryption = kp.encryption_bytes().to_vec();
135
136        let restored = OwnerKeypair::from_bytes(&signing, &encryption).unwrap();
137        assert_eq!(kp.owner_id(), restored.owner_id());
138        assert_eq!(
139            kp.encryption_public_key().as_bytes(),
140            restored.encryption_public_key().as_bytes()
141        );
142    }
143}