1use crate::{Error, Result};
12use libp2p_identity::{ed25519, Keypair, PeerId};
13use once_cell::sync::OnceCell;
14use rand::rngs::OsRng;
15use serde::{Deserialize, Serialize};
16use std::{fs, path::Path, sync::Arc};
17use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret as X25519SecretKey};
18
19static AGENT_IDENTITY: OnceCell<Arc<AgentIdentity>> = OnceCell::new();
23
24pub fn init_global_identity(identity: Arc<AgentIdentity>) {
32 AGENT_IDENTITY
33 .set(identity)
34 .expect("Global identity already initialized");
35}
36
37pub fn get_global_identity() -> Arc<AgentIdentity> {
45 AGENT_IDENTITY
46 .get()
47 .expect("Global identity not initialized - call init_global_identity() first")
48 .clone()
49}
50
51#[derive(Serialize, Deserialize)]
52struct PersistedKeys {
53 ed25519_bytes: Vec<u8>,
54 x25519_bytes: Vec<u8>,
55}
56
57pub struct AgentIdentity {
58 ed25519_keypair: Keypair,
59 x25519_secret: X25519SecretKey,
60 #[allow(dead_code)]
63 x25519_public: X25519PublicKey,
64 peer_id: PeerId,
65 ed25519_public_key_hex: String,
66 x25519_public_key_hex: String,
67}
68
69impl std::fmt::Debug for AgentIdentity {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 f.debug_struct("AgentIdentity")
73 .field("peer_id", &self.peer_id)
74 .field("ed25519_public_key_hex", &self.ed25519_public_key_hex)
75 .field("x25519_public_key_hex", &self.x25519_public_key_hex)
76 .finish_non_exhaustive()
77 }
78}
79
80impl AgentIdentity {
81 pub fn from_file_or_generate_new(path: &Path) -> Result<Self> {
82 let (ed25519_keypair, x25519_secret, x25519_public) = if path.exists() {
83 Self::load_keypairs_from_file(path)?
84 } else {
85 Self::generate_and_save_new_keypairs(path)?
86 };
87
88 let peer_id = PeerId::from_public_key(&ed25519_keypair.public());
89 let ed25519_public_key_hex = hex::encode(ed25519_keypair.public().encode_protobuf());
90 let x25519_public_key_hex = hex::encode(x25519_public.as_bytes());
91
92 Ok(Self {
93 ed25519_keypair,
94 x25519_secret,
95 x25519_public,
96 peer_id,
97 ed25519_public_key_hex,
98 x25519_public_key_hex,
99 })
100 }
101
102 pub fn peer_id(&self) -> &PeerId {
103 &self.peer_id
104 }
105
106 pub fn ed25519_public_key_hex(&self) -> &str {
107 &self.ed25519_public_key_hex
108 }
109
110 pub fn x25519_public_key_hex(&self) -> &str {
111 &self.x25519_public_key_hex
112 }
113
114 pub fn sign_message_as_hex(&self, message: &[u8]) -> Result<String> {
115 let signature = self
116 .ed25519_keypair
117 .sign(message)
118 .map_err(|e| Self::err("sign", e))?;
119 Ok(hex::encode(signature))
120 }
121
122 #[allow(dead_code)]
132 pub fn decrypt_x25519(
133 &self,
134 encrypted_data: &[u8],
135 nonce: &[u8],
136 ) -> std::result::Result<Vec<u8>, String> {
137 use chacha20poly1305::{
138 aead::{Aead, KeyInit},
139 XChaCha20Poly1305, XNonce,
140 };
141
142 if encrypted_data.len() < 32 {
144 return Err("encrypted data too short".to_string());
145 }
146
147 let (ephemeral_public_bytes, ciphertext) = encrypted_data.split_at(32);
148 let ephemeral_public_array: [u8; 32] = ephemeral_public_bytes
149 .try_into()
150 .map_err(|_| "invalid ephemeral public key")?;
151 let ephemeral_public = X25519PublicKey::from(ephemeral_public_array);
152
153 let shared_secret = self.x25519_secret.diffie_hellman(&ephemeral_public);
155
156 let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into());
158
159 let nonce_array: XNonce = nonce
161 .try_into()
162 .map_err(|_| "Invalid nonce length".to_string())?;
163 let plaintext = cipher
164 .decrypt(&nonce_array, ciphertext)
165 .map_err(|e| format!("decryption failed: {}", e))?;
166
167 Ok(plaintext)
168 }
169
170 fn load_keypairs_from_file(path: &Path) -> Result<(Keypair, X25519SecretKey, X25519PublicKey)> {
171 tracing::info!("Loading existing agent identity from {}", path.display());
172
173 if let Ok(json) = fs::read_to_string(path) {
175 if let Ok(persisted) = serde_json::from_str::<PersistedKeys>(&json) {
176 let mut ed25519_bytes = persisted.ed25519_bytes;
177 let ed25519_kp = ed25519::Keypair::try_from_bytes(&mut ed25519_bytes)
178 .map_err(|e| Self::err("decode Ed25519 key", e))?;
179
180 let x25519_bytes: [u8; 32] = persisted
181 .x25519_bytes
182 .try_into()
183 .map_err(|_| Self::err("invalid X25519 key length", "expected 32 bytes"))?;
184 let x25519_secret = X25519SecretKey::from(x25519_bytes);
185 let x25519_public = X25519PublicKey::from(&x25519_secret);
186
187 return Ok((ed25519_kp.into(), x25519_secret, x25519_public));
188 }
189 }
190
191 tracing::info!("Migrating old identity format to JSON");
193 let mut bytes = fs::read(path).map_err(|e| Self::err("read identity file", e))?;
194 let ed25519_kp = ed25519::Keypair::try_from_bytes(&mut bytes)
195 .map_err(|e| Self::err("decode Ed25519 key", e))?;
196
197 let x25519_secret = X25519SecretKey::random_from_rng(OsRng);
199 let x25519_public = X25519PublicKey::from(&x25519_secret);
200
201 let ed25519_full = ed25519_kp.to_bytes();
203 let persisted = PersistedKeys {
204 ed25519_bytes: ed25519_full.to_vec(),
205 x25519_bytes: x25519_secret.to_bytes().to_vec(),
206 };
207 let json = serde_json::to_string_pretty(&persisted)
208 .map_err(|e| Self::err("serialize keys during migration", e))?;
209 fs::write(path, json).map_err(|e| Self::err("write migrated identity file", e))?;
210
211 tracing::info!("Identity migration complete");
212 Ok((ed25519_kp.into(), x25519_secret, x25519_public))
213 }
214
215 fn generate_and_save_new_keypairs(
216 path: &Path,
217 ) -> Result<(Keypair, X25519SecretKey, X25519PublicKey)> {
218 tracing::info!(
219 "Generating new agent identity (saved to {})",
220 path.display()
221 );
222
223 let mut bytes = [0u8; 32];
225 use rand::RngCore;
226 OsRng.fill_bytes(&mut bytes);
227 let ed25519_kp = ed25519::Keypair::from(
228 ed25519::SecretKey::try_from_bytes(&mut bytes)
229 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?,
230 );
231 let ed25519_bytes = ed25519_kp.to_bytes();
232
233 let x25519_secret = X25519SecretKey::random_from_rng(OsRng);
234 let x25519_public = X25519PublicKey::from(&x25519_secret);
235 let x25519_bytes = x25519_secret.to_bytes();
236
237 let persisted = PersistedKeys {
238 ed25519_bytes: ed25519_bytes.to_vec(),
239 x25519_bytes: x25519_bytes.to_vec(),
240 };
241
242 let json =
243 serde_json::to_string_pretty(&persisted).map_err(|e| Self::err("serialize keys", e))?;
244 fs::write(path, json).map_err(|e| Self::err("write identity file", e))?;
245
246 Ok((ed25519_kp.into(), x25519_secret, x25519_public))
247 }
248
249 fn err(context: &str, error: impl std::fmt::Display) -> Error {
250 Error::Io(std::io::Error::new(
251 std::io::ErrorKind::Other,
252 format!("{}: {}", context, error),
253 ))
254 }
255}