1use rns_core::destination::destination_hash;
7use rns_core::types::{DestHash, DestinationType, Direction, IdentityHash, ProofStrategy};
8use rns_crypto::token::Token;
9use rns_crypto::OsRng;
10use rns_crypto::Rng;
11
12#[derive(Debug, PartialEq)]
14pub enum GroupKeyError {
15 NoKey,
17 InvalidKeyLength,
19 EncryptionFailed,
21 DecryptionFailed,
23}
24
25impl core::fmt::Display for GroupKeyError {
26 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
27 match self {
28 GroupKeyError::NoKey => write!(f, "No GROUP key loaded"),
29 GroupKeyError::InvalidKeyLength => write!(f, "Key must be 32 or 64 bytes"),
30 GroupKeyError::EncryptionFailed => write!(f, "Encryption failed"),
31 GroupKeyError::DecryptionFailed => write!(f, "Decryption failed"),
32 }
33 }
34}
35
36#[derive(Debug, Clone)]
41pub struct Destination {
42 pub hash: DestHash,
44 pub dest_type: DestinationType,
46 pub direction: Direction,
48 pub app_name: String,
50 pub aspects: Vec<String>,
52 pub identity_hash: Option<IdentityHash>,
54 pub public_key: Option<[u8; 64]>,
56 pub group_key: Option<Vec<u8>>,
58 pub proof_strategy: ProofStrategy,
60}
61
62impl Destination {
63 pub fn single_in(app_name: &str, aspects: &[&str], identity_hash: IdentityHash) -> Self {
67 let dh = destination_hash(app_name, aspects, Some(&identity_hash.0));
68 Destination {
69 hash: DestHash(dh),
70 dest_type: DestinationType::Single,
71 direction: Direction::In,
72 app_name: app_name.into(),
73 aspects: aspects.iter().map(|s| s.to_string()).collect(),
74 identity_hash: Some(identity_hash),
75 public_key: None,
76 group_key: None,
77 proof_strategy: ProofStrategy::ProveNone,
78 }
79 }
80
81 pub fn single_out(app_name: &str, aspects: &[&str], recalled: &AnnouncedIdentity) -> Self {
85 let dh = destination_hash(app_name, aspects, Some(&recalled.identity_hash.0));
86 Destination {
87 hash: DestHash(dh),
88 dest_type: DestinationType::Single,
89 direction: Direction::Out,
90 app_name: app_name.into(),
91 aspects: aspects.iter().map(|s| s.to_string()).collect(),
92 identity_hash: Some(recalled.identity_hash),
93 public_key: Some(recalled.public_key),
94 group_key: None,
95 proof_strategy: ProofStrategy::ProveNone,
96 }
97 }
98
99 pub fn plain(app_name: &str, aspects: &[&str]) -> Self {
101 let dh = destination_hash(app_name, aspects, None);
102 Destination {
103 hash: DestHash(dh),
104 dest_type: DestinationType::Plain,
105 direction: Direction::In,
106 app_name: app_name.into(),
107 aspects: aspects.iter().map(|s| s.to_string()).collect(),
108 identity_hash: None,
109 public_key: None,
110 group_key: None,
111 proof_strategy: ProofStrategy::ProveNone,
112 }
113 }
114
115 pub fn group(app_name: &str, aspects: &[&str]) -> Self {
120 let dh = destination_hash(app_name, aspects, None);
121 Destination {
122 hash: DestHash(dh),
123 dest_type: DestinationType::Group,
124 direction: Direction::In,
125 app_name: app_name.into(),
126 aspects: aspects.iter().map(|s| s.to_string()).collect(),
127 identity_hash: None,
128 public_key: None,
129 group_key: None,
130 proof_strategy: ProofStrategy::ProveNone,
131 }
132 }
133
134 pub fn create_keys(&mut self) {
136 let mut key = vec![0u8; 64];
137 OsRng.fill_bytes(&mut key);
138 self.group_key = Some(key);
139 }
140
141 pub fn load_private_key(&mut self, key: Vec<u8>) -> Result<(), GroupKeyError> {
145 if key.len() != 32 && key.len() != 64 {
146 return Err(GroupKeyError::InvalidKeyLength);
147 }
148 self.group_key = Some(key);
149 Ok(())
150 }
151
152 pub fn get_private_key(&self) -> Option<&[u8]> {
154 self.group_key.as_deref()
155 }
156
157 pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, GroupKeyError> {
159 let key = self.group_key.as_ref().ok_or(GroupKeyError::NoKey)?;
160 let token = Token::new(key).map_err(|_| GroupKeyError::EncryptionFailed)?;
161 Ok(token.encrypt(plaintext, &mut OsRng))
162 }
163
164 pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, GroupKeyError> {
166 let key = self.group_key.as_ref().ok_or(GroupKeyError::NoKey)?;
167 let token = Token::new(key).map_err(|_| GroupKeyError::DecryptionFailed)?;
168 token.decrypt(ciphertext).map_err(|_| GroupKeyError::DecryptionFailed)
169 }
170
171 pub fn set_proof_strategy(mut self, strategy: ProofStrategy) -> Self {
173 self.proof_strategy = strategy;
174 self
175 }
176}
177
178#[derive(Debug, Clone)]
180pub struct AnnouncedIdentity {
181 pub dest_hash: DestHash,
183 pub identity_hash: IdentityHash,
185 pub public_key: [u8; 64],
187 pub app_data: Option<Vec<u8>>,
189 pub hops: u8,
191 pub received_at: f64,
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 fn test_identity_hash() -> IdentityHash {
200 IdentityHash([0x42; 16])
201 }
202
203 fn test_announced() -> AnnouncedIdentity {
204 AnnouncedIdentity {
205 dest_hash: DestHash([0xAA; 16]),
206 identity_hash: IdentityHash([0x42; 16]),
207 public_key: [0xBB; 64],
208 app_data: Some(b"test_data".to_vec()),
209 hops: 3,
210 received_at: 1234567890.0,
211 }
212 }
213
214 #[test]
215 fn single_in_hash_matches_raw() {
216 let ih = test_identity_hash();
217 let dest = Destination::single_in("echo", &["app"], ih);
218
219 let raw = destination_hash("echo", &["app"], Some(&ih.0));
220 assert_eq!(dest.hash.0, raw);
221 assert_eq!(dest.dest_type, DestinationType::Single);
222 assert_eq!(dest.direction, Direction::In);
223 assert_eq!(dest.app_name, "echo");
224 assert_eq!(dest.aspects, vec!["app".to_string()]);
225 assert_eq!(dest.identity_hash, Some(ih));
226 assert!(dest.public_key.is_none());
227 }
228
229 #[test]
230 fn single_out_from_recalled() {
231 let recalled = test_announced();
232 let dest = Destination::single_out("echo", &["app"], &recalled);
233
234 let raw = destination_hash("echo", &["app"], Some(&recalled.identity_hash.0));
235 assert_eq!(dest.hash.0, raw);
236 assert_eq!(dest.dest_type, DestinationType::Single);
237 assert_eq!(dest.direction, Direction::Out);
238 assert_eq!(dest.public_key, Some([0xBB; 64]));
239 }
240
241 #[test]
242 fn plain_destination() {
243 let dest = Destination::plain("broadcast", &["test"]);
244
245 let raw = destination_hash("broadcast", &["test"], None);
246 assert_eq!(dest.hash.0, raw);
247 assert_eq!(dest.dest_type, DestinationType::Plain);
248 assert!(dest.identity_hash.is_none());
249 assert!(dest.public_key.is_none());
250 }
251
252 #[test]
253 fn destination_deterministic() {
254 let ih = test_identity_hash();
255 let d1 = Destination::single_in("app", &["a", "b"], ih);
256 let d2 = Destination::single_in("app", &["a", "b"], ih);
257 assert_eq!(d1.hash, d2.hash);
258 }
259
260 #[test]
261 fn different_identity_different_hash() {
262 let d1 = Destination::single_in("app", &["a"], IdentityHash([1; 16]));
263 let d2 = Destination::single_in("app", &["a"], IdentityHash([2; 16]));
264 assert_ne!(d1.hash, d2.hash);
265 }
266
267 #[test]
268 fn proof_strategy_builder() {
269 let dest = Destination::plain("app", &["a"])
270 .set_proof_strategy(ProofStrategy::ProveAll);
271 assert_eq!(dest.proof_strategy, ProofStrategy::ProveAll);
272 }
273
274 #[test]
275 fn announced_identity_fields() {
276 let ai = test_announced();
277 assert_eq!(ai.dest_hash, DestHash([0xAA; 16]));
278 assert_eq!(ai.identity_hash, IdentityHash([0x42; 16]));
279 assert_eq!(ai.public_key, [0xBB; 64]);
280 assert_eq!(ai.app_data, Some(b"test_data".to_vec()));
281 assert_eq!(ai.hops, 3);
282 assert_eq!(ai.received_at, 1234567890.0);
283 }
284
285 #[test]
286 fn multiple_aspects() {
287 let dest = Destination::plain("app", &["one", "two", "three"]);
288 assert_eq!(dest.aspects, vec!["one", "two", "three"]);
289 }
290
291 #[test]
294 fn group_destination_hash_deterministic() {
295 let d1 = Destination::group("myapp", &["chat", "room"]);
296 let d2 = Destination::group("myapp", &["chat", "room"]);
297 assert_eq!(d1.hash, d2.hash);
298 assert_eq!(d1.dest_type, DestinationType::Group);
299 assert_eq!(d1.direction, Direction::In);
300 assert!(d1.identity_hash.is_none());
301 assert!(d1.public_key.is_none());
302 assert!(d1.group_key.is_none());
303 }
304
305 #[test]
306 fn group_destination_hash_matches_plain_hash() {
307 let group = Destination::group("broadcast", &["test"]);
308 let plain = Destination::plain("broadcast", &["test"]);
309 assert_eq!(group.hash, plain.hash);
311 }
312
313 #[test]
314 fn group_create_keys() {
315 let mut dest = Destination::group("app", &["g"]);
316 assert!(dest.group_key.is_none());
317 dest.create_keys();
318 let key = dest.group_key.as_ref().unwrap();
319 assert_eq!(key.len(), 64);
320 assert!(key.iter().any(|&b| b != 0));
322 }
323
324 #[test]
325 fn group_load_private_key_64() {
326 let mut dest = Destination::group("app", &["g"]);
327 let key = vec![0x42u8; 64];
328 assert!(dest.load_private_key(key.clone()).is_ok());
329 assert_eq!(dest.get_private_key(), Some(key.as_slice()));
330 }
331
332 #[test]
333 fn group_load_private_key_32() {
334 let mut dest = Destination::group("app", &["g"]);
335 let key = vec![0xAB; 32];
336 assert!(dest.load_private_key(key.clone()).is_ok());
337 assert_eq!(dest.get_private_key(), Some(key.as_slice()));
338 }
339
340 #[test]
341 fn group_load_private_key_invalid_length() {
342 let mut dest = Destination::group("app", &["g"]);
343 assert_eq!(
344 dest.load_private_key(vec![0; 48]),
345 Err(GroupKeyError::InvalidKeyLength)
346 );
347 assert_eq!(
348 dest.load_private_key(vec![0; 16]),
349 Err(GroupKeyError::InvalidKeyLength)
350 );
351 }
352
353 #[test]
354 fn group_encrypt_decrypt_roundtrip() {
355 let mut dest = Destination::group("app", &["secure"]);
356 dest.load_private_key(vec![0x42u8; 64]).unwrap();
357
358 let plaintext = b"Hello, GROUP destination!";
359 let ciphertext = dest.encrypt(plaintext).unwrap();
360 assert_ne!(ciphertext.as_slice(), plaintext);
361 assert!(ciphertext.len() > plaintext.len()); let decrypted = dest.decrypt(&ciphertext).unwrap();
364 assert_eq!(decrypted, plaintext);
365 }
366
367 #[test]
368 fn group_decrypt_wrong_key_fails() {
369 let mut dest1 = Destination::group("app", &["a"]);
370 dest1.load_private_key(vec![0x42u8; 64]).unwrap();
371
372 let mut dest2 = Destination::group("app", &["a"]);
373 dest2.load_private_key(vec![0xBBu8; 64]).unwrap();
374
375 let ciphertext = dest1.encrypt(b"secret").unwrap();
376 assert_eq!(dest2.decrypt(&ciphertext), Err(GroupKeyError::DecryptionFailed));
377 }
378
379 #[test]
380 fn group_encrypt_without_key_fails() {
381 let dest = Destination::group("app", &["a"]);
382 assert_eq!(dest.encrypt(b"test"), Err(GroupKeyError::NoKey));
383 assert_eq!(dest.decrypt(b"test"), Err(GroupKeyError::NoKey));
384 }
385
386 #[test]
387 fn group_key_interop_with_token() {
388 let key = vec![0x42u8; 64];
390
391 let token = Token::new(&key).unwrap();
392 let ciphertext = token.encrypt(b"from token", &mut OsRng);
393
394 let mut dest = Destination::group("app", &["a"]);
395 dest.load_private_key(key.clone()).unwrap();
396 let decrypted = dest.decrypt(&ciphertext).unwrap();
397 assert_eq!(decrypted, b"from token");
398
399 let ciphertext2 = dest.encrypt(b"from dest").unwrap();
401 let decrypted2 = token.decrypt(&ciphertext2).unwrap();
402 assert_eq!(decrypted2, b"from dest");
403 }
404
405 #[test]
406 fn group_encrypt_decrypt_32byte_key() {
407 let mut dest = Destination::group("app", &["aes128"]);
408 dest.load_private_key(vec![0xABu8; 32]).unwrap();
409
410 let plaintext = b"AES-128 mode";
411 let ciphertext = dest.encrypt(plaintext).unwrap();
412 let decrypted = dest.decrypt(&ciphertext).unwrap();
413 assert_eq!(decrypted, plaintext);
414 }
415}