1use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
10use rand::RngCore;
11use x25519_dalek::{PublicKey, StaticSecret};
12
13use crate::ecies::{perform_dh_and_derive, EciesError};
14use crate::kdf::{
15 NostrSecretKey, RecipientSecretKey, SigningSecretKey,
16};
17use crate::keys::{
18 NostrPubKey, RecipientPubKey, RepoKey, SigningPubKey, WrappedKey,
19};
20use crate::seed;
21use crate::seed::SeedError;
22use crate::{decrypt, encrypt, CryptoError, CryptoResult};
23
24const ECIES_OVERHEAD: usize = 32 + 12 + 16;
26
27const AAD_ECIES: &[u8] = b"void:ecies:v1";
29
30#[derive(Debug, thiserror::Error)]
32pub enum IdentityError {
33 #[error("invalid ciphertext length")]
34 InvalidCiphertextLength,
35
36 #[error("decryption failed")]
37 DecryptionFailed,
38
39 #[error("encryption failed")]
40 EncryptionFailed,
41
42 #[error("invalid identity string format")]
43 InvalidIdentityString,
44
45 #[error("invalid hex encoding")]
46 InvalidHex,
47
48 #[error("invalid shared secret: possible low-order point attack")]
49 InvalidSharedSecret,
50
51 #[error("seed derivation failed: {0}")]
52 SeedDerivation(#[from] SeedError),
53}
54
55pub struct Identity {
61 signing: SigningKey,
62 recipient: StaticSecret,
63 nostr: Option<NostrSecretKey>,
64}
65
66impl Identity {
67 pub fn generate() -> Self {
69 let mut signing_bytes = [0u8; 32];
70 let mut recipient_bytes = [0u8; 32];
71 let mut nostr_bytes = [0u8; 32];
72
73 let mut rng = rand::thread_rng();
74 rng.fill_bytes(&mut signing_bytes);
75 rng.fill_bytes(&mut recipient_bytes);
76 rng.fill_bytes(&mut nostr_bytes);
77
78 Self {
79 signing: SigningKey::from_bytes(&signing_bytes),
80 recipient: StaticSecret::from(recipient_bytes),
81 nostr: Some(NostrSecretKey::from_bytes(nostr_bytes)),
82 }
83 }
84
85 pub fn from_bytes(signing: &SigningSecretKey, recipient: &RecipientSecretKey) -> Self {
87 Self {
88 signing: SigningKey::from_bytes(signing.as_bytes()),
89 recipient: StaticSecret::from(*recipient.as_bytes()),
90 nostr: None,
91 }
92 }
93
94 pub fn from_bytes_with_nostr(
96 signing: &SigningSecretKey,
97 recipient: &RecipientSecretKey,
98 nostr: NostrSecretKey,
99 ) -> Self {
100 Self {
101 signing: SigningKey::from_bytes(signing.as_bytes()),
102 recipient: StaticSecret::from(*recipient.as_bytes()),
103 nostr: Some(nostr),
104 }
105 }
106
107 pub fn from_seed(
112 seed: &crate::kdf::IdentitySeed,
113 ) -> Result<Self, IdentityError> {
114 let signing_secret = seed::derive_signing_key(seed)?;
115 let recipient_secret = seed::derive_recipient_key(seed)?;
116 let nostr_secret = seed::derive_nostr_key(seed)?;
117 Ok(Self::from_bytes_with_nostr(&signing_secret, &recipient_secret, nostr_secret))
118 }
119
120 pub fn generate_with_mnemonic() -> Result<(Self, String), IdentityError> {
126 let mnemonic = seed::generate_mnemonic();
127 let seed_val = seed::mnemonic_to_seed(&mnemonic)?;
128 let identity = Self::from_seed(&seed_val)?;
129 Ok((identity, mnemonic))
130 }
131
132 pub fn signing_pubkey(&self) -> SigningPubKey {
134 SigningPubKey::from_bytes(self.signing.verifying_key().to_bytes())
135 }
136
137 pub fn recipient_pubkey(&self) -> RecipientPubKey {
139 RecipientPubKey::from_bytes(PublicKey::from(&self.recipient).to_bytes())
140 }
141
142 pub fn signing_key_bytes(&self) -> SigningSecretKey {
144 SigningSecretKey::from_bytes(self.signing.to_bytes())
145 }
146
147 pub fn signing_key(&self) -> &SigningKey {
149 &self.signing
150 }
151
152 pub fn recipient_key_bytes(&self) -> RecipientSecretKey {
154 RecipientSecretKey::from_bytes(self.recipient.to_bytes())
155 }
156
157 pub fn nostr_pubkey(&self) -> Option<NostrPubKey> {
159 self.nostr.as_ref().map(|secret| {
160 let sk = secp256k1::SecretKey::from_slice(secret.as_bytes())
161 .expect("valid 32-byte key from HKDF");
162 let kp = secp256k1::Keypair::from_secret_key(secp256k1::SECP256K1, &sk);
163 let (xonly, _parity) = kp.x_only_public_key();
164 NostrPubKey::from_bytes(xonly.serialize())
165 })
166 }
167
168 pub fn nostr_key_bytes(&self) -> Option<NostrSecretKey> {
170 self.nostr.clone()
171 }
172
173 pub fn sign(&self, message: &[u8]) -> [u8; 64] {
175 self.signing.sign(message).to_bytes()
176 }
177
178 pub fn verify(pubkey: &SigningPubKey, message: &[u8], signature: &[u8; 64]) -> bool {
180 let Ok(verifying_key) = VerifyingKey::from_bytes(pubkey.as_bytes()) else {
181 return false;
182 };
183
184 let signature = Signature::from_bytes(signature);
185 verifying_key.verify(message, &signature).is_ok()
186 }
187
188 pub fn encrypt_for(
194 recipient_pubkey: &RecipientPubKey,
195 plaintext: &[u8],
196 ) -> Result<Vec<u8>, IdentityError> {
197 let mut ephemeral_bytes = [0u8; 32];
199 rand::thread_rng().fill_bytes(&mut ephemeral_bytes);
200 let ephemeral_x25519 = StaticSecret::from(ephemeral_bytes);
201 let ephemeral_secret = RecipientSecretKey::from_bytes(ephemeral_bytes);
202 let ephemeral_public = RecipientPubKey::from_bytes(
203 PublicKey::from(&ephemeral_x25519).to_bytes(),
204 );
205
206 let encryption_key = perform_dh_and_derive(&ephemeral_secret, recipient_pubkey)
208 .map_err(|e| match e {
209 EciesError::InvalidSharedSecret => IdentityError::InvalidSharedSecret,
210 EciesError::KeyDerivationFailed => IdentityError::DecryptionFailed,
211 })?;
212
213 let encrypted = encrypt(encryption_key.as_bytes(), plaintext, AAD_ECIES)
215 .map_err(|_| IdentityError::EncryptionFailed)?;
216
217 let mut output = Vec::with_capacity(32 + encrypted.len());
219 output.extend_from_slice(ephemeral_public.as_bytes());
220 output.extend_from_slice(&encrypted);
221
222 Ok(output)
223 }
224
225 pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, IdentityError> {
227 if ciphertext.len() < ECIES_OVERHEAD {
229 return Err(IdentityError::InvalidCiphertextLength);
230 }
231
232 let ephemeral_pubkey_bytes: [u8; 32] = ciphertext[..32]
234 .try_into()
235 .map_err(|_| IdentityError::InvalidCiphertextLength)?;
236 let ephemeral_public = RecipientPubKey::from_bytes(ephemeral_pubkey_bytes);
237
238 let our_secret = RecipientSecretKey::from_bytes(self.recipient.to_bytes());
240 let decryption_key = perform_dh_and_derive(&our_secret, &ephemeral_public)
241 .map_err(|e| match e {
242 EciesError::InvalidSharedSecret => IdentityError::InvalidSharedSecret,
243 EciesError::KeyDerivationFailed => IdentityError::DecryptionFailed,
244 })?;
245
246 let encrypted = &ciphertext[32..];
248 decrypt(decryption_key.as_bytes(), encrypted, AAD_ECIES)
249 .map_err(|_| IdentityError::DecryptionFailed)
250 }
251
252 pub fn to_identity_string(&self) -> String {
256 let signing_hex = self.signing_pubkey().to_hex();
257 let recipient_hex = self.recipient_pubkey().to_hex();
258 let mut s = format!("void://ed25519:{signing_hex}/x25519:{recipient_hex}");
259 if let Some(nostr) = self.nostr_pubkey() {
260 s.push_str(&format!("/nostr:{}", nostr.to_hex()));
261 }
262 s
263 }
264
265 pub fn to_identity_string_with_username(&self, username: &str) -> String {
269 let signing_hex = self.signing_pubkey().to_hex();
270 let recipient_hex = self.recipient_pubkey().to_hex();
271 let mut s = format!("void://{username}@ed25519:{signing_hex}/x25519:{recipient_hex}");
272 if let Some(nostr) = self.nostr_pubkey() {
273 s.push_str(&format!("/nostr:{}", nostr.to_hex()));
274 }
275 s
276 }
277
278 pub fn parse_identity_string(s: &str) -> Result<ParsedIdentity, IdentityError> {
285 let s = s
286 .strip_prefix("void://")
287 .ok_or(IdentityError::InvalidIdentityString)?;
288
289 let (username, key_part) = if let Some(at_pos) = s.find('@') {
291 let name = &s[..at_pos];
292 if name.is_empty() {
293 return Err(IdentityError::InvalidIdentityString);
294 }
295 (Some(name.to_string()), &s[at_pos + 1..])
296 } else {
297 (None, s)
298 };
299
300 let parts: Vec<&str> = key_part.split('/').collect();
301 if parts.len() < 2 || parts.len() > 3 {
302 return Err(IdentityError::InvalidIdentityString);
303 }
304
305 let signing = parse_prefixed_key(parts[0], "ed25519:")?;
306 let recipient = parse_prefixed_key(parts[1], "x25519:")?;
307
308 let nostr_pubkey = if parts.len() == 3 {
309 let nostr = parse_prefixed_key(parts[2], "nostr:")?;
310 Some(NostrPubKey::from_bytes(nostr))
311 } else {
312 None
313 };
314
315 Ok(ParsedIdentity {
316 username,
317 signing_pubkey: SigningPubKey::from_bytes(signing),
318 recipient_pubkey: RecipientPubKey::from_bytes(recipient),
319 nostr_pubkey,
320 })
321 }
322
323 pub fn parse_identity_string_full(s: &str) -> Result<ParsedIdentity, IdentityError> {
325 Self::parse_identity_string(s)
326 }
327}
328
329pub fn derive_repo_owner_signing_key(
338 seed_val: &crate::kdf::IdentitySeed,
339 repo_id: &str,
340) -> Result<SigningKey, IdentityError> {
341 let secret = seed::derive_repo_owner_key(seed_val, repo_id)?;
342 Ok(SigningKey::from_bytes(secret.as_bytes()))
343}
344
345pub fn ecies_wrap_key(
355 key: &RepoKey,
356 recipient_pubkey: &RecipientPubKey,
357) -> CryptoResult<WrappedKey> {
358 let ciphertext = Identity::encrypt_for(recipient_pubkey, key.as_bytes())
359 .map_err(|e| CryptoError::Encryption(format!("failed to wrap key: {}", e)))?;
360 Ok(WrappedKey::from_bytes(ciphertext))
361}
362
363pub fn ecies_unwrap_key(
367 wrapped: &WrappedKey,
368 identity: &Identity,
369) -> CryptoResult<RepoKey> {
370 let plaintext = identity
371 .decrypt(wrapped.as_bytes())
372 .map_err(|e| CryptoError::Decryption(format!("failed to unwrap key: {}", e)))?;
373
374 let arr: [u8; 32] = plaintext
375 .try_into()
376 .map_err(|_| CryptoError::Decryption("unwrapped key is not 32 bytes".into()))?;
377 Ok(RepoKey::from_bytes(arr))
378}
379
380#[derive(Debug, Clone)]
384pub struct ParsedIdentity {
385 pub username: Option<String>,
387 pub signing_pubkey: SigningPubKey,
389 pub recipient_pubkey: RecipientPubKey,
391 pub nostr_pubkey: Option<NostrPubKey>,
393}
394
395fn parse_prefixed_key(s: &str, prefix: &str) -> Result<[u8; 32], IdentityError> {
396 let hex_str = s
397 .strip_prefix(prefix)
398 .ok_or(IdentityError::InvalidIdentityString)?;
399 let bytes = hex::decode(hex_str).map_err(|_| IdentityError::InvalidHex)?;
400 bytes
401 .try_into()
402 .map_err(|_| IdentityError::InvalidIdentityString)
403}