1use alloc::vec::Vec;
2use core::fmt;
3
4use rns_crypto::identity::Identity;
5use rns_crypto::sha256::Sha256;
6
7use crate::constants;
8use crate::hash;
9
10#[derive(Debug)]
11pub enum AnnounceError {
12 DataTooShort,
13 InvalidSignature,
14 DestinationMismatch,
15 SigningError,
16}
17
18impl fmt::Display for AnnounceError {
19 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20 match self {
21 AnnounceError::DataTooShort => write!(f, "Announce data too short"),
22 AnnounceError::InvalidSignature => write!(f, "Invalid announce signature"),
23 AnnounceError::DestinationMismatch => write!(f, "Destination hash mismatch"),
24 AnnounceError::SigningError => write!(f, "Could not sign announce"),
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
41pub struct AnnounceData {
42 pub public_key: [u8; 64],
43 pub name_hash: [u8; 10],
44 pub random_hash: [u8; 10],
45 pub ratchet: Option<[u8; 32]>,
46 pub signature: [u8; 64],
47 pub app_data: Option<Vec<u8>>,
48}
49
50#[derive(Debug)]
52pub struct ValidatedAnnounce {
53 pub identity_hash: [u8; 16],
54 pub public_key: [u8; 64],
55 pub name_hash: [u8; 10],
56 pub random_hash: [u8; 10],
57 pub ratchet: Option<[u8; 32]>,
58 pub app_data: Option<Vec<u8>>,
59}
60
61impl AnnounceData {
62 pub fn pack(
66 identity: &Identity,
67 destination_hash: &[u8; 16],
68 name_hash: &[u8; 10],
69 random_hash: &[u8; 10],
70 ratchet: Option<&[u8; 32]>,
71 app_data: Option<&[u8]>,
72 ) -> Result<(Vec<u8>, bool), AnnounceError> {
73 let public_key = identity
74 .get_public_key()
75 .ok_or(AnnounceError::SigningError)?;
76
77 let mut signed_data = Vec::new();
79 signed_data.extend_from_slice(destination_hash);
80 signed_data.extend_from_slice(&public_key);
81 signed_data.extend_from_slice(name_hash);
82 signed_data.extend_from_slice(random_hash);
83
84 let has_ratchet = ratchet.is_some();
85 if let Some(r) = ratchet {
86 signed_data.extend_from_slice(r);
87 }
88 if let Some(ad) = app_data {
89 signed_data.extend_from_slice(ad);
90 }
91
92 let signature = identity
93 .sign(&signed_data)
94 .map_err(|_| AnnounceError::SigningError)?;
95
96 let mut announce_data = Vec::new();
98 announce_data.extend_from_slice(&public_key);
99 announce_data.extend_from_slice(name_hash);
100 announce_data.extend_from_slice(random_hash);
101 if let Some(r) = ratchet {
102 announce_data.extend_from_slice(r);
103 }
104 announce_data.extend_from_slice(&signature);
105 if let Some(ad) = app_data {
106 announce_data.extend_from_slice(ad);
107 }
108
109 Ok((announce_data, has_ratchet))
110 }
111
112 pub fn unpack(data: &[u8], has_ratchet: bool) -> Result<Self, AnnounceError> {
116 let keysize = constants::KEYSIZE / 8; let name_hash_len = constants::NAME_HASH_LENGTH / 8; let sig_len = constants::SIGLENGTH / 8; let ratchet_size = constants::RATCHETSIZE / 8; let min_len = if has_ratchet {
122 keysize + name_hash_len + 10 + ratchet_size + sig_len
123 } else {
124 keysize + name_hash_len + 10 + sig_len
125 };
126
127 if data.len() < min_len {
128 return Err(AnnounceError::DataTooShort);
129 }
130
131 let mut public_key = [0u8; 64];
132 public_key.copy_from_slice(&data[..keysize]);
133
134 let mut name_hash = [0u8; 10];
135 name_hash.copy_from_slice(&data[keysize..keysize + name_hash_len]);
136
137 let mut random_hash = [0u8; 10];
138 random_hash.copy_from_slice(&data[keysize + name_hash_len..keysize + name_hash_len + 10]);
139
140 let (ratchet, sig_start) = if has_ratchet {
141 let mut ratchet = [0u8; 32];
142 ratchet.copy_from_slice(
143 &data[keysize + name_hash_len + 10..keysize + name_hash_len + 10 + ratchet_size],
144 );
145 (Some(ratchet), keysize + name_hash_len + 10 + ratchet_size)
146 } else {
147 (None, keysize + name_hash_len + 10)
148 };
149
150 let mut signature = [0u8; 64];
151 signature.copy_from_slice(&data[sig_start..sig_start + sig_len]);
152
153 let app_data_start = sig_start + sig_len;
156 let app_data = if data.len() > app_data_start {
157 Some(data[app_data_start..].to_vec())
158 } else {
159 None
160 };
161
162 Ok(AnnounceData {
163 public_key,
164 name_hash,
165 random_hash,
166 ratchet,
167 signature,
168 app_data,
169 })
170 }
171
172 fn for_each_signed_part(&self, destination_hash: &[u8; 16], mut f: impl FnMut(&[u8])) {
178 f(destination_hash);
179 f(&self.public_key);
180 f(&self.name_hash);
181 f(&self.random_hash);
182 if let Some(ref ratchet) = self.ratchet {
183 f(ratchet);
184 }
185 if let Some(ref ad) = self.app_data {
186 f(ad);
187 }
188 }
189
190 fn signed_data(&self, destination_hash: &[u8; 16]) -> Vec<u8> {
192 let mut signed_data = Vec::new();
193 self.for_each_signed_part(destination_hash, |part| signed_data.extend_from_slice(part));
194 signed_data
195 }
196
197 pub fn signature_cache_key(&self, destination_hash: &[u8; 16]) -> [u8; 32] {
204 let mut hasher = Sha256::new();
205 self.for_each_signed_part(destination_hash, |part| hasher.update(part));
206 hasher.update(&self.signature);
207 hasher.digest()
208 }
209
210 fn check_destination_binding(
212 &self,
213 destination_hash: &[u8; 16],
214 identity_hash: &[u8; 16],
215 ) -> Result<(), AnnounceError> {
216 let mut hash_material = [0u8; 26];
217 hash_material[..10].copy_from_slice(&self.name_hash);
218 hash_material[10..].copy_from_slice(identity_hash);
219
220 let expected_hash = hash::truncated_hash(&hash_material);
221
222 if &expected_hash != destination_hash {
223 return Err(AnnounceError::DestinationMismatch);
224 }
225 Ok(())
226 }
227
228 fn to_validated(&self, identity_hash: [u8; 16]) -> ValidatedAnnounce {
229 ValidatedAnnounce {
230 identity_hash,
231 public_key: self.public_key,
232 name_hash: self.name_hash,
233 random_hash: self.random_hash,
234 ratchet: self.ratchet,
235 app_data: self.app_data.clone(),
236 }
237 }
238
239 pub fn validate(
249 &self,
250 destination_hash: &[u8; 16],
251 ) -> Result<ValidatedAnnounce, AnnounceError> {
252 let announced_identity = Identity::from_public_key(&self.public_key)
256 .map_err(|_| AnnounceError::InvalidSignature)?;
257
258 let signed_data = self.signed_data(destination_hash);
260 if !announced_identity.verify(&self.signature, &signed_data) {
261 return Err(AnnounceError::InvalidSignature);
262 }
263
264 let identity_hash = *announced_identity.hash();
266 self.check_destination_binding(destination_hash, &identity_hash)?;
267
268 Ok(self.to_validated(identity_hash))
269 }
270
271 pub fn to_validated_with_cached_signature(
280 &self,
281 destination_hash: &[u8; 16],
282 ) -> Result<ValidatedAnnounce, AnnounceError> {
283 let identity_hash = hash::truncated_hash(&self.public_key);
284 self.check_destination_binding(destination_hash, &identity_hash)?;
285 Ok(self.to_validated(identity_hash))
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use crate::destination;
293
294 #[test]
295 fn test_pack_unpack_roundtrip_no_ratchet() {
296 let identity = Identity::from_private_key(&[0x42; 64]);
297 let id_hash = *identity.hash();
298
299 let nh = destination::name_hash("testapp", &["aspect"]);
300 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
301 let random = [0xAA; 10];
302
303 let (data, has_ratchet) =
304 AnnounceData::pack(&identity, &dh, &nh, &random, None, None).unwrap();
305 assert!(!has_ratchet);
306
307 let parsed = AnnounceData::unpack(&data, false).unwrap();
308 assert_eq!(parsed.public_key, identity.get_public_key().unwrap());
309 assert_eq!(parsed.name_hash, nh);
310 assert_eq!(parsed.random_hash, random);
311 assert!(parsed.ratchet.is_none());
312 assert!(parsed.app_data.is_none());
313 }
314
315 #[test]
316 fn test_pack_unpack_roundtrip_with_ratchet() {
317 let identity = Identity::from_private_key(&[0x42; 64]);
318 let id_hash = *identity.hash();
319
320 let nh = destination::name_hash("testapp", &["aspect"]);
321 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
322 let random = [0xBB; 10];
323 let ratchet = [0xCC; 32];
324
325 let (data, has_ratchet) =
326 AnnounceData::pack(&identity, &dh, &nh, &random, Some(&ratchet), None).unwrap();
327 assert!(has_ratchet);
328
329 let parsed = AnnounceData::unpack(&data, true).unwrap();
330 assert_eq!(parsed.ratchet.unwrap(), ratchet);
331 }
332
333 #[test]
334 fn test_pack_unpack_with_app_data() {
335 let identity = Identity::from_private_key(&[0x42; 64]);
336 let id_hash = *identity.hash();
337
338 let nh = destination::name_hash("testapp", &["aspect"]);
339 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
340 let random = [0xDD; 10];
341 let app_data = b"hello app data";
342
343 let (data, _) =
344 AnnounceData::pack(&identity, &dh, &nh, &random, None, Some(app_data)).unwrap();
345
346 let parsed = AnnounceData::unpack(&data, false).unwrap();
347 assert_eq!(parsed.app_data.as_deref(), Some(app_data.as_slice()));
348 }
349
350 #[test]
351 fn test_validate_valid_announce() {
352 let identity = Identity::from_private_key(&[0x42; 64]);
353 let id_hash = *identity.hash();
354
355 let nh = destination::name_hash("testapp", &["aspect"]);
356 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
357 let random = [0xEE; 10];
358
359 let (data, _) =
360 AnnounceData::pack(&identity, &dh, &nh, &random, None, Some(b"data")).unwrap();
361
362 let parsed = AnnounceData::unpack(&data, false).unwrap();
363 let validated = parsed.validate(&dh).unwrap();
364
365 assert_eq!(validated.identity_hash, id_hash);
366 assert_eq!(validated.name_hash, nh);
367 }
368
369 #[test]
370 fn test_validate_tampered_signature() {
371 let identity = Identity::from_private_key(&[0x42; 64]);
372 let id_hash = *identity.hash();
373
374 let nh = destination::name_hash("testapp", &["aspect"]);
375 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
376 let random = [0xFF; 10];
377
378 let (mut data, _) = AnnounceData::pack(&identity, &dh, &nh, &random, None, None).unwrap();
379
380 data[84] ^= 0xFF;
382
383 let parsed = AnnounceData::unpack(&data, false).unwrap();
384 assert!(parsed.validate(&dh).is_err());
385 }
386
387 #[test]
388 fn test_validate_rejects_announce_with_invalid_signing_key() {
389 let identity = Identity::from_private_key(&[0x42; 64]);
390 let id_hash = *identity.hash();
391
392 let nh = destination::name_hash("testapp", &["aspect"]);
393 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
394 let random = [0x22; 10];
395
396 let (mut data, _) = AnnounceData::pack(&identity, &dh, &nh, &random, None, None).unwrap();
397
398 data[32..64].copy_from_slice(&[0; 32]);
401 data[32] = 2;
402
403 let parsed = AnnounceData::unpack(&data, false).unwrap();
404 assert!(matches!(
405 parsed.validate(&dh),
406 Err(AnnounceError::InvalidSignature)
407 ));
408 }
409
410 #[test]
411 fn test_validate_wrong_destination_hash() {
412 let identity = Identity::from_private_key(&[0x42; 64]);
413 let id_hash = *identity.hash();
414
415 let nh = destination::name_hash("testapp", &["aspect"]);
416 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
417 let random = [0x11; 10];
418
419 let (data, _) = AnnounceData::pack(&identity, &dh, &nh, &random, None, None).unwrap();
420
421 let parsed = AnnounceData::unpack(&data, false).unwrap();
422 let wrong_hash = [0x00; 16];
423 assert!(parsed.validate(&wrong_hash).is_err());
424 }
425
426 #[test]
427 fn test_to_validated_with_cached_signature_matches_validate() {
428 let identity = Identity::from_private_key(&[0x42; 64]);
429 let id_hash = *identity.hash();
430
431 let nh = destination::name_hash("testapp", &["aspect"]);
432 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
433 let random = [0xAB; 10];
434
435 let (data, _) =
436 AnnounceData::pack(&identity, &dh, &nh, &random, None, Some(b"appdata")).unwrap();
437
438 let parsed = AnnounceData::unpack(&data, false).unwrap();
439 let validated = parsed.validate(&dh).unwrap();
440 let unchecked = parsed.to_validated_with_cached_signature(&dh).unwrap();
441
442 assert_eq!(validated.identity_hash, unchecked.identity_hash);
443 assert_eq!(validated.public_key, unchecked.public_key);
444 assert_eq!(validated.name_hash, unchecked.name_hash);
445 assert_eq!(validated.random_hash, unchecked.random_hash);
446 assert_eq!(validated.ratchet, unchecked.ratchet);
447 assert_eq!(validated.app_data, unchecked.app_data);
448 }
449 fn signed_announce(
450 app_data: Option<&[u8]>,
451 ratchet: Option<&[u8; 32]>,
452 ) -> (AnnounceData, [u8; 16]) {
453 let identity = Identity::from_private_key(&[0x42; 64]);
454 let id_hash = *identity.hash();
455 let nh = destination::name_hash("testapp", &["aspect"]);
456 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
457 let (data, has_ratchet) =
458 AnnounceData::pack(&identity, &dh, &nh, &[0x31; 10], ratchet, app_data).unwrap();
459 (AnnounceData::unpack(&data, has_ratchet).unwrap(), dh)
460 }
461
462 #[test]
463 fn test_cached_signature_path_checks_destination_binding() {
464 let (parsed, dh) = signed_announce(None, None);
465 let mut other_dh = dh;
466 other_dh[0] ^= 0x01;
467 assert!(matches!(
468 parsed.to_validated_with_cached_signature(&other_dh),
469 Err(AnnounceError::DestinationMismatch)
470 ));
471
472 let other = Identity::from_private_key(&[0x43; 64]);
473 let mut swapped = parsed.clone();
474 swapped.public_key = other.get_public_key().unwrap();
475 assert!(matches!(
476 swapped.to_validated_with_cached_signature(&dh),
477 Err(AnnounceError::DestinationMismatch)
478 ));
479 }
480
481 #[test]
482 fn test_signature_cache_key_covers_every_signed_field() {
483 let ratchet = [0x44; 32];
484 let (base, dh) = signed_announce(Some(b"app"), Some(&ratchet));
485 let key = base.signature_cache_key(&dh);
486 assert_eq!(key, base.clone().signature_cache_key(&dh));
487
488 let mut other_dh = dh;
489 other_dh[15] ^= 0x01;
490 assert_ne!(key, base.signature_cache_key(&other_dh));
491
492 let mut variants = Vec::new();
493 let mut v = base.clone();
494 v.public_key[0] ^= 0x01;
495 variants.push(v);
496 let mut v = base.clone();
497 v.name_hash[0] ^= 0x01;
498 variants.push(v);
499 let mut v = base.clone();
500 v.random_hash[0] ^= 0x01;
501 variants.push(v);
502 let mut v = base.clone();
503 v.ratchet = None;
504 variants.push(v);
505 let mut v = base.clone();
506 v.ratchet.as_mut().unwrap()[0] ^= 0x01;
507 variants.push(v);
508 let mut v = base.clone();
509 v.app_data = None;
510 variants.push(v);
511 let mut v = base.clone();
512 v.app_data = Some(b"apq".to_vec());
513 variants.push(v);
514 let mut v = base.clone();
515 v.signature[0] ^= 0x01;
516 variants.push(v);
517
518 for variant in &variants {
519 assert_ne!(key, variant.signature_cache_key(&dh));
520 }
521 }
522
523 #[test]
524 fn test_signature_cache_key_is_hash_of_signed_data_and_signature() {
525 let (parsed, dh) = signed_announce(Some(b"payload"), None);
526 let mut material = parsed.signed_data(&dh);
527 material.extend_from_slice(&parsed.signature);
528 assert_eq!(parsed.signature_cache_key(&dh), hash::full_hash(&material));
529 }
530}