1use std::fs;
7use std::net::{IpAddr, SocketAddr};
8use std::path::Path;
9
10use cid::Cid;
11use libp2p_identity::PeerId;
12
13use crate::error::{Error, Result};
14use crate::{Did, Document, EncryptionKey, MaError, SigningKey, VerificationMethod};
15
16#[derive(Debug, Clone)]
23pub struct GeneratedIdentity {
24 pub subject_url: Did,
25 pub document: Document,
26 pub signing_private_key_hex: String,
27 pub encryption_private_key_hex: String,
28}
29
30fn build_identity(ipns: &str) -> Result<GeneratedIdentity> {
31 let sign_url = Did::new_url(ipns, None::<String>).map_err(Error::Validation)?;
32 let enc_url = Did::new_url(ipns, None::<String>).map_err(Error::Validation)?;
33
34 let signing_key = SigningKey::generate(sign_url).map_err(Error::Validation)?;
35 let encryption_key = EncryptionKey::generate(enc_url).map_err(Error::Validation)?;
36
37 build_identity_from_keys(ipns, &signing_key, &encryption_key)
38}
39
40pub(crate) fn build_identity_from_keys(
51 ipns: &str,
52 signing_key: &SigningKey,
53 encryption_key: &EncryptionKey,
54) -> Result<GeneratedIdentity> {
55 let subject_url = Did::new_identity(ipns).map_err(Error::Validation)?;
56
57 let mut document = Document::new(&subject_url, &subject_url);
58
59 let assertion_vm = VerificationMethod::new(
61 subject_url.base_id(),
62 subject_url.base_id(),
63 signing_key.key_type.clone(),
64 "sign",
65 signing_key.public_key_multibase.clone(),
66 )
67 .map_err(Error::Validation)?;
68
69 let key_agreement_vm = VerificationMethod::new(
70 subject_url.base_id(),
71 subject_url.base_id(),
72 encryption_key.key_type.clone(),
73 "enc",
74 encryption_key.public_key_multibase.clone(),
75 )
76 .map_err(Error::Validation)?;
77
78 let assertion_vm_id = assertion_vm.id.clone();
79 document
80 .add_verification_method(assertion_vm.clone())
81 .map_err(Error::Validation)?;
82 document
83 .add_verification_method(key_agreement_vm.clone())
84 .map_err(Error::Validation)?;
85 document.assertion_method = vec![assertion_vm_id];
86 document.key_agreement = vec![key_agreement_vm.id.clone()];
87 document
88 .sign(signing_key, &assertion_vm)
89 .map_err(Error::Validation)?;
90
91 Ok(GeneratedIdentity {
92 subject_url,
93 document,
94 signing_private_key_hex: hex::encode(signing_key.private_key_bytes()),
95 encryption_private_key_hex: hex::encode(encryption_key.private_key_bytes()),
96 })
97}
98
99pub fn ipns_from_secret(secret: [u8; 32]) -> Result<String> {
101 let keypair = libp2p_identity::Keypair::ed25519_from_bytes(secret)
102 .map_err(|_| Error::Validation(MaError::InvalidIdentitySecret))?;
103 let peer_id = PeerId::from_public_key(&keypair.public());
104 let cid = Cid::new_v1(0x72, peer_id.into());
108 Ok(multibase::encode(
109 multibase::Base::Base36Lower,
110 cid.to_bytes(),
111 ))
112}
113
114pub fn generate_identity(ipns: &str) -> Result<GeneratedIdentity> {
116 build_identity(ipns)
117}
118
119pub fn generate_identity_from_secret(secret: [u8; 32]) -> Result<GeneratedIdentity> {
122 let ipns = ipns_from_secret(secret)?;
123 build_identity(&ipns)
124}
125
126pub fn load_secret_key_bytes(path: &Path) -> Result<Option<[u8; 32]>> {
132 if !path.exists() {
133 return Ok(None);
134 }
135
136 let bytes = fs::read(path).map_err(|e| Error::SecretKey(e.to_string()))?;
137 let key_bytes: [u8; 32] = bytes
138 .as_slice()
139 .try_into()
140 .map_err(|_| Error::SecretKey(format!("invalid key file length in {}", path.display())))?;
141
142 Ok(Some(key_bytes))
143}
144
145pub fn generate_secret_key_file(path: &Path) -> Result<[u8; 32]> {
151 if path.exists() {
152 return Err(Error::SecretKey(format!(
153 "secret key already exists at {}",
154 path.display()
155 )));
156 }
157
158 let mut key_bytes = [0u8; 32];
159 use rand::RngCore;
160 rand::rngs::OsRng.fill_bytes(&mut key_bytes);
161
162 if let Some(parent) = path.parent() {
164 fs::create_dir_all(parent).map_err(|e| {
165 Error::SecretKey(format!("failed to create dir {}: {}", parent.display(), e))
166 })?;
167 }
168
169 fs::write(path, key_bytes)
170 .map_err(|e| Error::SecretKey(format!("failed to write {}: {}", path.display(), e)))?;
171
172 #[cfg(unix)]
174 {
175 use std::os::unix::fs::PermissionsExt;
176 let _ = fs::set_permissions(path, fs::Permissions::from_mode(0o400));
177 }
178
179 Ok(key_bytes)
180}
181
182pub fn socket_addr_to_multiaddr(addr: &SocketAddr) -> String {
184 match addr.ip() {
185 IpAddr::V4(ip) => format!("/ip4/{}/udp/{}/quic-v1", ip, addr.port()),
186 IpAddr::V6(ip) => format!("/ip6/{}/udp/{}/quic-v1", ip, addr.port()),
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use std::net::{Ipv4Addr, Ipv6Addr};
194 use std::path::PathBuf;
195
196 fn test_tmp_file(name: &str) -> PathBuf {
197 let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
198 .join("tmp")
199 .join("identity-tests");
200 fs::create_dir_all(&root).expect("failed creating test tmp directory");
201 root.join(name)
202 }
203
204 #[test]
205 fn multiaddr_ipv4() {
206 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4433);
207 assert_eq!(
208 socket_addr_to_multiaddr(&addr),
209 "/ip4/127.0.0.1/udp/4433/quic-v1"
210 );
211 }
212
213 #[test]
214 fn multiaddr_ipv6() {
215 let addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 5555);
216 assert_eq!(socket_addr_to_multiaddr(&addr), "/ip6/::1/udp/5555/quic-v1");
217 }
218
219 #[test]
220 fn load_missing_returns_none() {
221 let path = test_tmp_file("nonexistent-key");
222 let _ = fs::remove_file(&path);
223 assert!(load_secret_key_bytes(&path).unwrap().is_none());
224 }
225
226 #[test]
227 fn generate_and_load_round_trip() {
228 let path = test_tmp_file("round-trip-key");
229 let _ = fs::remove_file(&path);
230
231 let generated = generate_secret_key_file(&path).unwrap();
232 let loaded = load_secret_key_bytes(&path).unwrap().unwrap();
233 assert_eq!(generated, loaded);
234
235 let _ = fs::remove_file(&path);
237 }
238
239 #[test]
240 fn generate_refuses_overwrite() {
241 let path = test_tmp_file("no-overwrite-key");
242 let _ = fs::remove_file(&path);
243
244 generate_secret_key_file(&path).unwrap();
245 let err = generate_secret_key_file(&path).unwrap_err();
246 assert!(matches!(err, crate::error::Error::SecretKey(_)));
247
248 let _ = fs::remove_file(&path);
250 }
251}