1use rns_core::destination::destination_hash;
7use rns_core::transport::types::InterfaceId;
8use rns_core::types::{DestHash, DestinationType, Direction, IdentityHash, ProofStrategy};
9use rns_crypto::token::Token;
10use rns_crypto::OsRng;
11use rns_crypto::Rng;
12
13#[derive(Debug, PartialEq)]
15pub enum GroupKeyError {
16 NoKey,
18 InvalidKeyLength,
20 EncryptionFailed,
22 DecryptionFailed,
24}
25
26impl core::fmt::Display for GroupKeyError {
27 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
28 match self {
29 GroupKeyError::NoKey => write!(f, "No GROUP key loaded"),
30 GroupKeyError::InvalidKeyLength => write!(f, "Key must be 32 or 64 bytes"),
31 GroupKeyError::EncryptionFailed => write!(f, "Encryption failed"),
32 GroupKeyError::DecryptionFailed => write!(f, "Decryption failed"),
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
42pub struct Destination {
43 pub hash: DestHash,
45 pub dest_type: DestinationType,
47 pub direction: Direction,
49 pub app_name: String,
51 pub aspects: Vec<String>,
53 pub identity_hash: Option<IdentityHash>,
55 pub public_key: Option<[u8; 64]>,
57 pub group_key: Option<Vec<u8>>,
59 pub proof_strategy: ProofStrategy,
61}
62
63impl Destination {
64 pub fn single_in(app_name: &str, aspects: &[&str], identity_hash: IdentityHash) -> Self {
68 let dh = destination_hash(app_name, aspects, Some(&identity_hash.0));
69 Destination {
70 hash: DestHash(dh),
71 dest_type: DestinationType::Single,
72 direction: Direction::In,
73 app_name: app_name.into(),
74 aspects: aspects.iter().map(|s| s.to_string()).collect(),
75 identity_hash: Some(identity_hash),
76 public_key: None,
77 group_key: None,
78 proof_strategy: ProofStrategy::ProveNone,
79 }
80 }
81
82 pub fn single_out(app_name: &str, aspects: &[&str], recalled: &AnnouncedIdentity) -> Self {
86 let dh = destination_hash(app_name, aspects, Some(&recalled.identity_hash.0));
87 Destination {
88 hash: DestHash(dh),
89 dest_type: DestinationType::Single,
90 direction: Direction::Out,
91 app_name: app_name.into(),
92 aspects: aspects.iter().map(|s| s.to_string()).collect(),
93 identity_hash: Some(recalled.identity_hash),
94 public_key: Some(recalled.public_key),
95 group_key: None,
96 proof_strategy: ProofStrategy::ProveNone,
97 }
98 }
99
100 pub fn plain(app_name: &str, aspects: &[&str]) -> Self {
102 let dh = destination_hash(app_name, aspects, None);
103 Destination {
104 hash: DestHash(dh),
105 dest_type: DestinationType::Plain,
106 direction: Direction::In,
107 app_name: app_name.into(),
108 aspects: aspects.iter().map(|s| s.to_string()).collect(),
109 identity_hash: None,
110 public_key: None,
111 group_key: None,
112 proof_strategy: ProofStrategy::ProveNone,
113 }
114 }
115
116 pub fn group(app_name: &str, aspects: &[&str]) -> Self {
121 let dh = destination_hash(app_name, aspects, None);
122 Destination {
123 hash: DestHash(dh),
124 dest_type: DestinationType::Group,
125 direction: Direction::In,
126 app_name: app_name.into(),
127 aspects: aspects.iter().map(|s| s.to_string()).collect(),
128 identity_hash: None,
129 public_key: None,
130 group_key: None,
131 proof_strategy: ProofStrategy::ProveNone,
132 }
133 }
134
135 pub fn create_keys(&mut self) {
137 let mut key = vec![0u8; 64];
138 OsRng.fill_bytes(&mut key);
139 self.group_key = Some(key);
140 }
141
142 pub fn load_private_key(&mut self, key: Vec<u8>) -> Result<(), GroupKeyError> {
146 if key.len() != 32 && key.len() != 64 {
147 return Err(GroupKeyError::InvalidKeyLength);
148 }
149 self.group_key = Some(key);
150 Ok(())
151 }
152
153 pub fn get_private_key(&self) -> Option<&[u8]> {
155 self.group_key.as_deref()
156 }
157
158 pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, GroupKeyError> {
160 let key = self.group_key.as_ref().ok_or(GroupKeyError::NoKey)?;
161 let token = Token::new(key).map_err(|_| GroupKeyError::EncryptionFailed)?;
162 Ok(token.encrypt(plaintext, &mut OsRng))
163 }
164
165 pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, GroupKeyError> {
167 let key = self.group_key.as_ref().ok_or(GroupKeyError::NoKey)?;
168 let token = Token::new(key).map_err(|_| GroupKeyError::DecryptionFailed)?;
169 token
170 .decrypt(ciphertext)
171 .map_err(|_| GroupKeyError::DecryptionFailed)
172 }
173
174 pub fn set_proof_strategy(mut self, strategy: ProofStrategy) -> Self {
176 self.proof_strategy = strategy;
177 self
178 }
179}
180
181#[derive(Debug, Clone)]
183pub struct AnnouncedIdentity {
184 pub dest_hash: DestHash,
186 pub identity_hash: IdentityHash,
188 pub public_key: [u8; 64],
190 pub app_data: Option<Vec<u8>>,
192 pub hops: u8,
194 pub received_at: f64,
196 pub receiving_interface: InterfaceId,
198 pub rssi: Option<i16>,
200 pub snr: Option<f32>,
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 fn test_identity_hash() -> IdentityHash {
209 IdentityHash([0x42; 16])
210 }
211
212 fn test_announced() -> AnnouncedIdentity {
213 AnnouncedIdentity {
214 dest_hash: DestHash([0xAA; 16]),
215 identity_hash: IdentityHash([0x42; 16]),
216 public_key: [0xBB; 64],
217 app_data: Some(b"test_data".to_vec()),
218 hops: 3,
219 received_at: 1234567890.0,
220 receiving_interface: InterfaceId(0),
221 rssi: Some(-100),
222 snr: Some(10.5),
223 }
224 }
225
226 #[test]
227 fn single_in_hash_matches_raw() {
228 let ih = test_identity_hash();
229 let dest = Destination::single_in("echo", &["app"], ih);
230
231 let raw = destination_hash("echo", &["app"], Some(&ih.0));
232 assert_eq!(dest.hash.0, raw);
233 assert_eq!(dest.dest_type, DestinationType::Single);
234 assert_eq!(dest.direction, Direction::In);
235 assert_eq!(dest.app_name, "echo");
236 assert_eq!(dest.aspects, vec!["app".to_string()]);
237 assert_eq!(dest.identity_hash, Some(ih));
238 assert!(dest.public_key.is_none());
239 }
240
241 #[test]
242 fn single_out_from_recalled() {
243 let recalled = test_announced();
244 let dest = Destination::single_out("echo", &["app"], &recalled);
245
246 let raw = destination_hash("echo", &["app"], Some(&recalled.identity_hash.0));
247 assert_eq!(dest.hash.0, raw);
248 assert_eq!(dest.dest_type, DestinationType::Single);
249 assert_eq!(dest.direction, Direction::Out);
250 assert_eq!(dest.public_key, Some([0xBB; 64]));
251 }
252
253 #[test]
254 fn plain_destination() {
255 let dest = Destination::plain("broadcast", &["test"]);
256
257 let raw = destination_hash("broadcast", &["test"], None);
258 assert_eq!(dest.hash.0, raw);
259 assert_eq!(dest.dest_type, DestinationType::Plain);
260 assert!(dest.identity_hash.is_none());
261 assert!(dest.public_key.is_none());
262 }
263
264 #[test]
265 fn destination_deterministic() {
266 let ih = test_identity_hash();
267 let d1 = Destination::single_in("app", &["a", "b"], ih);
268 let d2 = Destination::single_in("app", &["a", "b"], ih);
269 assert_eq!(d1.hash, d2.hash);
270 }
271
272 #[test]
273 fn different_identity_different_hash() {
274 let d1 = Destination::single_in("app", &["a"], IdentityHash([1; 16]));
275 let d2 = Destination::single_in("app", &["a"], IdentityHash([2; 16]));
276 assert_ne!(d1.hash, d2.hash);
277 }
278
279 #[test]
280 fn proof_strategy_builder() {
281 let dest = Destination::plain("app", &["a"]).set_proof_strategy(ProofStrategy::ProveAll);
282 assert_eq!(dest.proof_strategy, ProofStrategy::ProveAll);
283 }
284
285 #[test]
286 fn announced_identity_fields() {
287 let ai = test_announced();
288 assert_eq!(ai.dest_hash, DestHash([0xAA; 16]));
289 assert_eq!(ai.identity_hash, IdentityHash([0x42; 16]));
290 assert_eq!(ai.public_key, [0xBB; 64]);
291 assert_eq!(ai.app_data, Some(b"test_data".to_vec()));
292 assert_eq!(ai.hops, 3);
293 assert_eq!(ai.received_at, 1234567890.0);
294 assert_eq!(ai.receiving_interface, InterfaceId(0));
295 }
296
297 #[test]
298 fn announced_identity_receiving_interface_nonzero() {
299 let ai = AnnouncedIdentity {
300 receiving_interface: InterfaceId(42),
301 ..test_announced()
302 };
303 assert_eq!(ai.receiving_interface, InterfaceId(42));
304 }
305
306 #[test]
307 fn announced_identity_clone_preserves_receiving_interface() {
308 let ai = AnnouncedIdentity {
309 receiving_interface: InterfaceId(7),
310 ..test_announced()
311 };
312 let cloned = ai.clone();
313 assert_eq!(cloned.receiving_interface, ai.receiving_interface);
314 }
315
316 #[test]
317 fn single_out_from_recalled_with_interface() {
318 let recalled = AnnouncedIdentity {
319 receiving_interface: InterfaceId(5),
320 ..test_announced()
321 };
322 let dest = Destination::single_out("echo", &["app"], &recalled);
324 assert_eq!(dest.dest_type, DestinationType::Single);
325 assert_eq!(dest.direction, Direction::Out);
326 assert_eq!(dest.public_key, Some([0xBB; 64]));
327 }
328
329 #[test]
330 fn multiple_aspects() {
331 let dest = Destination::plain("app", &["one", "two", "three"]);
332 assert_eq!(dest.aspects, vec!["one", "two", "three"]);
333 }
334
335 #[test]
338 fn group_destination_hash_deterministic() {
339 let d1 = Destination::group("myapp", &["chat", "room"]);
340 let d2 = Destination::group("myapp", &["chat", "room"]);
341 assert_eq!(d1.hash, d2.hash);
342 assert_eq!(d1.dest_type, DestinationType::Group);
343 assert_eq!(d1.direction, Direction::In);
344 assert!(d1.identity_hash.is_none());
345 assert!(d1.public_key.is_none());
346 assert!(d1.group_key.is_none());
347 }
348
349 #[test]
350 fn group_destination_hash_matches_plain_hash() {
351 let group = Destination::group("broadcast", &["test"]);
352 let plain = Destination::plain("broadcast", &["test"]);
353 assert_eq!(group.hash, plain.hash);
355 }
356
357 #[test]
358 fn group_create_keys() {
359 let mut dest = Destination::group("app", &["g"]);
360 assert!(dest.group_key.is_none());
361 dest.create_keys();
362 let key = dest.group_key.as_ref().unwrap();
363 assert_eq!(key.len(), 64);
364 assert!(key.iter().any(|&b| b != 0));
366 }
367
368 #[test]
369 fn group_load_private_key_64() {
370 let mut dest = Destination::group("app", &["g"]);
371 let key = vec![0x42u8; 64];
372 assert!(dest.load_private_key(key.clone()).is_ok());
373 assert_eq!(dest.get_private_key(), Some(key.as_slice()));
374 }
375
376 #[test]
377 fn group_load_private_key_32() {
378 let mut dest = Destination::group("app", &["g"]);
379 let key = vec![0xAB; 32];
380 assert!(dest.load_private_key(key.clone()).is_ok());
381 assert_eq!(dest.get_private_key(), Some(key.as_slice()));
382 }
383
384 #[test]
385 fn group_load_private_key_invalid_length() {
386 let mut dest = Destination::group("app", &["g"]);
387 assert_eq!(
388 dest.load_private_key(vec![0; 48]),
389 Err(GroupKeyError::InvalidKeyLength)
390 );
391 assert_eq!(
392 dest.load_private_key(vec![0; 16]),
393 Err(GroupKeyError::InvalidKeyLength)
394 );
395 }
396
397 #[test]
398 fn group_encrypt_decrypt_roundtrip() {
399 let mut dest = Destination::group("app", &["secure"]);
400 dest.load_private_key(vec![0x42u8; 64]).unwrap();
401
402 let plaintext = b"Hello, GROUP destination!";
403 let ciphertext = dest.encrypt(plaintext).unwrap();
404 assert_ne!(ciphertext.as_slice(), plaintext);
405 assert!(ciphertext.len() > plaintext.len()); let decrypted = dest.decrypt(&ciphertext).unwrap();
408 assert_eq!(decrypted, plaintext);
409 }
410
411 #[test]
412 fn group_decrypt_wrong_key_fails() {
413 let mut dest1 = Destination::group("app", &["a"]);
414 dest1.load_private_key(vec![0x42u8; 64]).unwrap();
415
416 let mut dest2 = Destination::group("app", &["a"]);
417 dest2.load_private_key(vec![0xBBu8; 64]).unwrap();
418
419 let ciphertext = dest1.encrypt(b"secret").unwrap();
420 assert_eq!(
421 dest2.decrypt(&ciphertext),
422 Err(GroupKeyError::DecryptionFailed)
423 );
424 }
425
426 #[test]
427 fn group_encrypt_without_key_fails() {
428 let dest = Destination::group("app", &["a"]);
429 assert_eq!(dest.encrypt(b"test"), Err(GroupKeyError::NoKey));
430 assert_eq!(dest.decrypt(b"test"), Err(GroupKeyError::NoKey));
431 }
432
433 #[test]
434 fn group_key_interop_with_token() {
435 let key = vec![0x42u8; 64];
437
438 let token = Token::new(&key).unwrap();
439 let ciphertext = token.encrypt(b"from token", &mut OsRng);
440
441 let mut dest = Destination::group("app", &["a"]);
442 dest.load_private_key(key.clone()).unwrap();
443 let decrypted = dest.decrypt(&ciphertext).unwrap();
444 assert_eq!(decrypted, b"from token");
445
446 let ciphertext2 = dest.encrypt(b"from dest").unwrap();
448 let decrypted2 = token.decrypt(&ciphertext2).unwrap();
449 assert_eq!(decrypted2, b"from dest");
450 }
451
452 #[test]
453 fn group_encrypt_decrypt_32byte_key() {
454 let mut dest = Destination::group("app", &["aes128"]);
455 dest.load_private_key(vec![0xABu8; 32]).unwrap();
456
457 let plaintext = b"AES-128 mode";
458 let ciphertext = dest.encrypt(plaintext).unwrap();
459 let decrypted = dest.decrypt(&ciphertext).unwrap();
460 assert_eq!(decrypted, plaintext);
461 }
462}