1#[cfg(not(target_arch = "wasm32"))]
9use std::fs;
10use std::path::{Path, PathBuf};
11
12use blake3::Hasher;
13use ed25519_dalek::Keypair;
14use rand::rngs::OsRng;
15
16use crate::{CoreError, PeerId};
17
18#[derive(Debug)]
19pub struct Identity {
20 pub keypair: Keypair,
22 pub peer_id: PeerId,
24}
25
26impl Identity {
27 pub fn generate() -> Self {
29 let mut rng = OsRng;
30 let keypair = Keypair::generate(&mut rng);
31 Self::generate_from_keypair(keypair)
32 }
33
34 pub fn generate_from_keypair(keypair: Keypair) -> Self {
36 let peer_id = peer_id_from_keypair(&keypair);
37 Self { keypair, peer_id }
38 }
39
40 #[cfg(not(target_arch = "wasm32"))]
41 pub fn default_path() -> Result<PathBuf, CoreError> {
43 let base = dirs::config_dir().ok_or(CoreError::ConfigDirMissing)?;
44 Ok(base.join("rift").join("identity.key"))
45 }
46
47 #[cfg(target_arch = "wasm32")]
48 pub fn default_path() -> Result<PathBuf, CoreError> {
50 Err(CoreError::ConfigDirMissing)
51 }
52
53 #[cfg(not(target_arch = "wasm32"))]
54 pub fn load(path: Option<&Path>) -> Result<Self, CoreError> {
56 let path = match path {
57 Some(path) => path.to_path_buf(),
58 None => Self::default_path()?,
59 };
60 let bytes = fs::read(&path)
61 .map_err(|err| if err.kind() == std::io::ErrorKind::NotFound {
62 CoreError::IdentityMissing(path.display().to_string())
63 } else {
64 CoreError::Io(err)
65 })?;
66
67 if bytes.len() != 64 {
68 return Err(CoreError::InvalidKeyLength);
69 }
70 let keypair = Keypair::from_bytes(&bytes).map_err(|_| CoreError::InvalidKeyLength)?;
71 let peer_id = peer_id_from_keypair(&keypair);
72 Ok(Self { keypair, peer_id })
73 }
74
75 #[cfg(target_arch = "wasm32")]
76 pub fn load(path: Option<&Path>) -> Result<Self, CoreError> {
78 let _ = path;
79 Err(CoreError::IdentityMissing("identity storage unsupported on wasm".to_string()))
80 }
81
82 #[cfg(not(target_arch = "wasm32"))]
83 pub fn load_or_generate(path: Option<&Path>) -> Result<(Self, bool), CoreError> {
85 let path = match path {
86 Some(path) => path.to_path_buf(),
87 None => Self::default_path()?,
88 };
89 match Self::load(Some(&path)) {
90 Ok(identity) => Ok((identity, false)),
91 Err(CoreError::IdentityMissing(_)) => {
92 let identity = Self::generate();
93 identity.save(&path)?;
94 Ok((identity, true))
95 }
96 Err(err) => Err(err),
97 }
98 }
99
100 #[cfg(target_arch = "wasm32")]
101 pub fn load_or_generate(_path: Option<&Path>) -> Result<(Self, bool), CoreError> {
103 Ok((Self::generate(), true))
104 }
105
106 #[cfg(not(target_arch = "wasm32"))]
107 pub fn save(&self, path: &Path) -> Result<(), CoreError> {
109 if let Some(parent) = path.parent() {
110 fs::create_dir_all(parent)?;
111 }
112 let bytes = self.keypair.to_bytes();
113 fs::write(path, bytes)?;
114 Ok(())
115 }
116
117 #[cfg(target_arch = "wasm32")]
118 pub fn save(&self, _path: &Path) -> Result<(), CoreError> {
120 Err(CoreError::Io(std::io::Error::new(
121 std::io::ErrorKind::Unsupported,
122 "identity storage unsupported on wasm",
123 )))
124 }
125}
126
127fn peer_id_from_keypair(keypair: &Keypair) -> PeerId {
129 let mut hasher = Hasher::new();
130 hasher.update(keypair.public.as_bytes());
131 let hash = hasher.finalize();
132 PeerId(*hash.as_bytes())
133}
134
135pub fn peer_id_from_public_key_bytes(bytes: &[u8]) -> Result<PeerId, CoreError> {
137 if bytes.len() != 32 {
138 return Err(CoreError::InvalidKeyLength);
139 }
140 let mut hasher = Hasher::new();
141 hasher.update(bytes);
142 let hash = hasher.finalize();
143 Ok(PeerId(*hash.as_bytes()))
144}