pmetal_distributed/
identity.rs1use crate::error::DistributedError;
13use anyhow::Result;
14use libp2p::PeerId;
15use libp2p::identity::{Keypair, ed25519};
16use std::fs::{self, File};
17use std::io::{Read, Write};
18use std::path::PathBuf;
19use tracing::{debug, info};
20
21const PMETAL_DIR: &str = ".pmetal";
23
24const KEYPAIR_FILE: &str = "node_keypair";
26
27#[derive(Clone)]
29pub struct NodeIdentity {
30 keypair: Keypair,
32 peer_id: PeerId,
34}
35
36impl NodeIdentity {
37 pub fn load_or_generate() -> Result<Self> {
42 let keypair_path = Self::keypair_path()?;
43
44 let keypair = if keypair_path.exists() {
45 Self::load_keypair(&keypair_path)?
46 } else {
47 let kp = Self::generate_and_save(&keypair_path)?;
48 info!("Generated new node identity");
49 kp
50 };
51
52 let peer_id = PeerId::from(keypair.public());
53 info!("Node identity: {}", peer_id);
54
55 Ok(Self { keypair, peer_id })
56 }
57
58 pub fn ephemeral() -> Self {
60 let keypair = Keypair::generate_ed25519();
61 let peer_id = PeerId::from(keypair.public());
62 debug!("Generated ephemeral identity: {}", peer_id);
63 Self { keypair, peer_id }
64 }
65
66 pub fn keypair(&self) -> &Keypair {
68 &self.keypair
69 }
70
71 pub fn peer_id(&self) -> &PeerId {
73 &self.peer_id
74 }
75
76 pub fn peer_id_string(&self) -> String {
78 self.peer_id.to_base58()
79 }
80
81 fn keypair_path() -> Result<PathBuf> {
83 let home = dirs::home_dir()
84 .ok_or_else(|| DistributedError::Config("Cannot determine home directory".into()))?;
85
86 let pmetal_dir = home.join(PMETAL_DIR);
87 if !pmetal_dir.exists() {
88 fs::create_dir_all(&pmetal_dir).map_err(|e| {
89 DistributedError::Config(format!(
90 "Failed to create {}: {}",
91 pmetal_dir.display(),
92 e
93 ))
94 })?;
95 }
96
97 Ok(pmetal_dir.join(KEYPAIR_FILE))
98 }
99
100 fn load_keypair(path: &PathBuf) -> Result<Keypair> {
102 let mut file = File::open(path)
103 .map_err(|e| DistributedError::Config(format!("Failed to open keypair file: {}", e)))?;
104
105 let mut bytes = Vec::new();
106 file.read_to_end(&mut bytes)
107 .map_err(|e| DistributedError::Config(format!("Failed to read keypair file: {}", e)))?;
108
109 if bytes.len() != 32 {
111 return Err(DistributedError::Config(format!(
112 "Invalid keypair file: expected 32 bytes, got {}",
113 bytes.len()
114 ))
115 .into());
116 }
117
118 let secret = ed25519::SecretKey::try_from_bytes(&mut bytes)
119 .map_err(|e| DistributedError::Config(format!("Invalid Ed25519 secret key: {}", e)))?;
120
121 let keypair = ed25519::Keypair::from(secret);
122 debug!("Loaded keypair from {}", path.display());
123
124 Ok(keypair.into())
125 }
126
127 fn generate_and_save(path: &PathBuf) -> Result<Keypair> {
129 let ed25519_keypair = ed25519::Keypair::generate();
131 let keypair: Keypair = ed25519_keypair.clone().into();
132
133 let secret = ed25519_keypair.secret();
135 let secret_bytes = secret.as_ref();
136
137 let mut file = File::create(path).map_err(|e| {
139 DistributedError::Config(format!("Failed to create keypair file: {}", e))
140 })?;
141
142 #[cfg(unix)]
144 {
145 use std::os::unix::fs::PermissionsExt;
146 let permissions = fs::Permissions::from_mode(0o600);
147 fs::set_permissions(path, permissions).map_err(|e| {
148 DistributedError::Config(format!("Failed to set keypair permissions: {}", e))
149 })?;
150 }
151
152 file.write_all(secret_bytes)
153 .map_err(|e| DistributedError::Config(format!("Failed to write keypair: {}", e)))?;
154
155 debug!("Saved keypair to {}", path.display());
156 Ok(keypair)
157 }
158}
159
160impl std::fmt::Debug for NodeIdentity {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 f.debug_struct("NodeIdentity")
163 .field("peer_id", &self.peer_id.to_base58())
164 .finish()
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_ephemeral_identity() {
174 let id1 = NodeIdentity::ephemeral();
175 let id2 = NodeIdentity::ephemeral();
176
177 assert_ne!(id1.peer_id(), id2.peer_id());
179 }
180
181 #[test]
182 fn test_peer_id_string() {
183 let id = NodeIdentity::ephemeral();
184 let s = id.peer_id_string();
185
186 assert!(s.len() > 40);
188 assert!(s.len() < 60);
189 }
190}