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;
9use crate::stamp::{stamp_valid, stamp_value, stamp_workblock, StampCost, StampValue};
10
11#[derive(Debug)]
12pub enum AnnounceError {
13 DataTooShort,
14 InvalidSignature,
15 DestinationMismatch,
16 SigningError,
17}
18
19impl fmt::Display for AnnounceError {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 AnnounceError::DataTooShort => write!(f, "Announce data too short"),
23 AnnounceError::InvalidSignature => write!(f, "Invalid announce signature"),
24 AnnounceError::DestinationMismatch => write!(f, "Destination hash mismatch"),
25 AnnounceError::SigningError => write!(f, "Could not sign announce"),
26 }
27 }
28}
29
30#[derive(Debug, Clone)]
42pub struct AnnounceData {
43 pub public_key: [u8; 64],
44 pub name_hash: [u8; 10],
45 pub random_hash: [u8; 10],
46 pub ratchet: Option<[u8; 32]>,
47 pub signature: [u8; 64],
48 pub app_data: Option<Vec<u8>>,
49}
50
51#[derive(Debug)]
53pub struct ValidatedAnnounce {
54 pub identity_hash: [u8; 16],
55 pub public_key: [u8; 64],
56 pub name_hash: [u8; 10],
57 pub random_hash: [u8; 10],
58 pub ratchet: Option<[u8; 32]>,
59 pub app_data: Option<Vec<u8>>,
60 pub stamp: Option<[u8; constants::STAMP_LENGTH]>,
62 pub stamp_value: StampValue,
65 pub lr_cost: Option<StampCost>,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum AnnounceStampStatus {
73 NotStamped,
75 Valid,
77 Invalid,
79}
80
81impl ValidatedAnnounce {
82 pub fn apply_stamp(
89 &mut self,
90 destination_hash: &[u8; 16],
91 required_value: StampCost,
92 ) -> AnnounceStampStatus {
93 if !constants::STAMPS_ENABLED {
94 return AnnounceStampStatus::NotStamped;
95 }
96 let Some(app_data) = self.app_data.as_ref() else {
97 return AnnounceStampStatus::NotStamped;
98 };
99 if app_data.len() < constants::EXT_PAYLOAD_LENGTH {
100 return AnnounceStampStatus::NotStamped;
101 }
102
103 let ext = &app_data[app_data.len() - constants::EXT_PAYLOAD_LENGTH..];
104 if ext[0] != constants::EXT_PAYLOAD_MARKER {
105 return AnnounceStampStatus::NotStamped;
106 }
107
108 let Some(lr_cost) = crate::stamp::link_cost_from_ext_bytes(
113 ext[constants::EXT_PAYLOAD_LENGTH - 2],
114 ext[constants::EXT_PAYLOAD_LENGTH - 1],
115 ) else {
116 return AnnounceStampStatus::NotStamped;
117 };
118
119 let mut stamp = [0u8; constants::STAMP_LENGTH];
120 stamp.copy_from_slice(
121 &ext[constants::EXT_HEADER_LENGTH
122 ..constants::EXT_HEADER_LENGTH + constants::STAMP_LENGTH],
123 );
124 self.stamp = Some(stamp);
125
126 let mut material = Vec::with_capacity(16 + 64 + 10);
127 material.extend_from_slice(destination_hash);
128 material.extend_from_slice(&self.public_key);
129 material.extend_from_slice(&self.name_hash);
130 let workblock = stamp_workblock(&material, 0);
131 self.stamp_value = stamp_value(&workblock, &stamp);
132
133 if stamp_valid(&stamp, required_value, &workblock) {
134 let stripped_len = app_data.len() - constants::EXT_PAYLOAD_LENGTH;
135 self.app_data = if stripped_len == 0 {
136 None
137 } else {
138 Some(app_data[..stripped_len].to_vec())
139 };
140 self.lr_cost = Some(lr_cost);
141 AnnounceStampStatus::Valid
142 } else {
143 AnnounceStampStatus::Invalid
144 }
145 }
146}
147
148impl AnnounceData {
149 pub fn pack(
153 identity: &Identity,
154 destination_hash: &[u8; 16],
155 name_hash: &[u8; 10],
156 random_hash: &[u8; 10],
157 ratchet: Option<&[u8; 32]>,
158 app_data: Option<&[u8]>,
159 ) -> Result<(Vec<u8>, bool), AnnounceError> {
160 let public_key = identity
161 .get_public_key()
162 .ok_or(AnnounceError::SigningError)?;
163
164 let mut signed_data = Vec::new();
166 signed_data.extend_from_slice(destination_hash);
167 signed_data.extend_from_slice(&public_key);
168 signed_data.extend_from_slice(name_hash);
169 signed_data.extend_from_slice(random_hash);
170
171 let has_ratchet = ratchet.is_some();
172 if let Some(r) = ratchet {
173 signed_data.extend_from_slice(r);
174 }
175 if let Some(ad) = app_data {
176 signed_data.extend_from_slice(ad);
177 }
178
179 let signature = identity
180 .sign(&signed_data)
181 .map_err(|_| AnnounceError::SigningError)?;
182
183 let mut announce_data = Vec::new();
185 announce_data.extend_from_slice(&public_key);
186 announce_data.extend_from_slice(name_hash);
187 announce_data.extend_from_slice(random_hash);
188 if let Some(r) = ratchet {
189 announce_data.extend_from_slice(r);
190 }
191 announce_data.extend_from_slice(&signature);
192 if let Some(ad) = app_data {
193 announce_data.extend_from_slice(ad);
194 }
195
196 Ok((announce_data, has_ratchet))
197 }
198
199 pub fn unpack(data: &[u8], has_ratchet: bool) -> Result<Self, AnnounceError> {
203 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 {
209 keysize + name_hash_len + 10 + ratchet_size + sig_len
210 } else {
211 keysize + name_hash_len + 10 + sig_len
212 };
213
214 if data.len() < min_len {
215 return Err(AnnounceError::DataTooShort);
216 }
217
218 let mut public_key = [0u8; 64];
219 public_key.copy_from_slice(&data[..keysize]);
220
221 let mut name_hash = [0u8; 10];
222 name_hash.copy_from_slice(&data[keysize..keysize + name_hash_len]);
223
224 let mut random_hash = [0u8; 10];
225 random_hash.copy_from_slice(&data[keysize + name_hash_len..keysize + name_hash_len + 10]);
226
227 let (ratchet, sig_start) = if has_ratchet {
228 let mut ratchet = [0u8; 32];
229 ratchet.copy_from_slice(
230 &data[keysize + name_hash_len + 10..keysize + name_hash_len + 10 + ratchet_size],
231 );
232 (Some(ratchet), keysize + name_hash_len + 10 + ratchet_size)
233 } else {
234 (None, keysize + name_hash_len + 10)
235 };
236
237 let mut signature = [0u8; 64];
238 signature.copy_from_slice(&data[sig_start..sig_start + sig_len]);
239
240 let app_data_start = sig_start + sig_len;
243 let app_data = if data.len() > app_data_start {
244 Some(data[app_data_start..].to_vec())
245 } else {
246 None
247 };
248
249 Ok(AnnounceData {
250 public_key,
251 name_hash,
252 random_hash,
253 ratchet,
254 signature,
255 app_data,
256 })
257 }
258
259 fn for_each_signed_part(&self, destination_hash: &[u8; 16], mut f: impl FnMut(&[u8])) {
265 f(destination_hash);
266 f(&self.public_key);
267 f(&self.name_hash);
268 f(&self.random_hash);
269 if let Some(ref ratchet) = self.ratchet {
270 f(ratchet);
271 }
272 if let Some(ref ad) = self.app_data {
273 f(ad);
274 }
275 }
276
277 fn signed_data(&self, destination_hash: &[u8; 16]) -> Vec<u8> {
279 let mut signed_data = Vec::new();
280 self.for_each_signed_part(destination_hash, |part| signed_data.extend_from_slice(part));
281 signed_data
282 }
283
284 pub fn signature_cache_key(&self, destination_hash: &[u8; 16]) -> [u8; 32] {
291 let mut hasher = Sha256::new();
292 self.for_each_signed_part(destination_hash, |part| hasher.update(part));
293 hasher.update(&self.signature);
294 hasher.digest()
295 }
296
297 fn check_destination_binding(
299 &self,
300 destination_hash: &[u8; 16],
301 identity_hash: &[u8; 16],
302 ) -> Result<(), AnnounceError> {
303 let mut hash_material = [0u8; 26];
304 hash_material[..10].copy_from_slice(&self.name_hash);
305 hash_material[10..].copy_from_slice(identity_hash);
306
307 let expected_hash = hash::truncated_hash(&hash_material);
308
309 if &expected_hash != destination_hash {
310 return Err(AnnounceError::DestinationMismatch);
311 }
312 Ok(())
313 }
314
315 fn to_validated(&self, identity_hash: [u8; 16]) -> ValidatedAnnounce {
316 ValidatedAnnounce {
317 identity_hash,
318 public_key: self.public_key,
319 name_hash: self.name_hash,
320 random_hash: self.random_hash,
321 ratchet: self.ratchet,
322 app_data: self.app_data.clone(),
323 stamp: None,
324 stamp_value: StampValue::ZERO,
325 lr_cost: None,
326 }
327 }
328
329 pub fn validate(
339 &self,
340 destination_hash: &[u8; 16],
341 ) -> Result<ValidatedAnnounce, AnnounceError> {
342 let announced_identity = Identity::from_public_key(&self.public_key)
346 .map_err(|_| AnnounceError::InvalidSignature)?;
347
348 let signed_data = self.signed_data(destination_hash);
350 if !announced_identity.verify(&self.signature, &signed_data) {
351 return Err(AnnounceError::InvalidSignature);
352 }
353
354 let identity_hash = *announced_identity.hash();
356 self.check_destination_binding(destination_hash, &identity_hash)?;
357
358 Ok(self.to_validated(identity_hash))
359 }
360
361 pub fn to_validated_with_cached_signature(
370 &self,
371 destination_hash: &[u8; 16],
372 ) -> Result<ValidatedAnnounce, AnnounceError> {
373 let identity_hash = hash::truncated_hash(&self.public_key);
374 self.check_destination_binding(destination_hash, &identity_hash)?;
375 Ok(self.to_validated(identity_hash))
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382 use crate::destination;
383
384 #[test]
385 fn test_pack_unpack_roundtrip_no_ratchet() {
386 let identity = Identity::from_private_key(&[0x42; 64]);
387 let id_hash = *identity.hash();
388
389 let nh = destination::name_hash("testapp", &["aspect"]);
390 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
391 let random = [0xAA; 10];
392
393 let (data, has_ratchet) =
394 AnnounceData::pack(&identity, &dh, &nh, &random, None, None).unwrap();
395 assert!(!has_ratchet);
396
397 let parsed = AnnounceData::unpack(&data, false).unwrap();
398 assert_eq!(parsed.public_key, identity.get_public_key().unwrap());
399 assert_eq!(parsed.name_hash, nh);
400 assert_eq!(parsed.random_hash, random);
401 assert!(parsed.ratchet.is_none());
402 assert!(parsed.app_data.is_none());
403 }
404
405 #[test]
406 fn test_pack_unpack_roundtrip_with_ratchet() {
407 let identity = Identity::from_private_key(&[0x42; 64]);
408 let id_hash = *identity.hash();
409
410 let nh = destination::name_hash("testapp", &["aspect"]);
411 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
412 let random = [0xBB; 10];
413 let ratchet = [0xCC; 32];
414
415 let (data, has_ratchet) =
416 AnnounceData::pack(&identity, &dh, &nh, &random, Some(&ratchet), None).unwrap();
417 assert!(has_ratchet);
418
419 let parsed = AnnounceData::unpack(&data, true).unwrap();
420 assert_eq!(parsed.ratchet.unwrap(), ratchet);
421 }
422
423 #[test]
424 fn test_pack_unpack_with_app_data() {
425 let identity = Identity::from_private_key(&[0x42; 64]);
426 let id_hash = *identity.hash();
427
428 let nh = destination::name_hash("testapp", &["aspect"]);
429 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
430 let random = [0xDD; 10];
431 let app_data = b"hello app data";
432
433 let (data, _) =
434 AnnounceData::pack(&identity, &dh, &nh, &random, None, Some(app_data)).unwrap();
435
436 let parsed = AnnounceData::unpack(&data, false).unwrap();
437 assert_eq!(parsed.app_data.as_deref(), Some(app_data.as_slice()));
438 }
439
440 #[test]
441 fn test_validate_valid_announce() {
442 let identity = Identity::from_private_key(&[0x42; 64]);
443 let id_hash = *identity.hash();
444
445 let nh = destination::name_hash("testapp", &["aspect"]);
446 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
447 let random = [0xEE; 10];
448
449 let (data, _) =
450 AnnounceData::pack(&identity, &dh, &nh, &random, None, Some(b"data")).unwrap();
451
452 let parsed = AnnounceData::unpack(&data, false).unwrap();
453 let validated = parsed.validate(&dh).unwrap();
454
455 assert_eq!(validated.identity_hash, id_hash);
456 assert_eq!(validated.name_hash, nh);
457 }
458
459 #[test]
460 fn test_validate_tampered_signature() {
461 let identity = Identity::from_private_key(&[0x42; 64]);
462 let id_hash = *identity.hash();
463
464 let nh = destination::name_hash("testapp", &["aspect"]);
465 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
466 let random = [0xFF; 10];
467
468 let (mut data, _) = AnnounceData::pack(&identity, &dh, &nh, &random, None, None).unwrap();
469
470 data[84] ^= 0xFF;
472
473 let parsed = AnnounceData::unpack(&data, false).unwrap();
474 assert!(parsed.validate(&dh).is_err());
475 }
476
477 #[test]
478 fn test_validate_rejects_announce_with_invalid_signing_key() {
479 let identity = Identity::from_private_key(&[0x42; 64]);
480 let id_hash = *identity.hash();
481
482 let nh = destination::name_hash("testapp", &["aspect"]);
483 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
484 let random = [0x22; 10];
485
486 let (mut data, _) = AnnounceData::pack(&identity, &dh, &nh, &random, None, None).unwrap();
487
488 data[32..64].copy_from_slice(&[0; 32]);
491 data[32] = 2;
492
493 let parsed = AnnounceData::unpack(&data, false).unwrap();
494 assert!(matches!(
495 parsed.validate(&dh),
496 Err(AnnounceError::InvalidSignature)
497 ));
498 }
499
500 #[test]
501 fn test_validate_wrong_destination_hash() {
502 let identity = Identity::from_private_key(&[0x42; 64]);
503 let id_hash = *identity.hash();
504
505 let nh = destination::name_hash("testapp", &["aspect"]);
506 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
507 let random = [0x11; 10];
508
509 let (data, _) = AnnounceData::pack(&identity, &dh, &nh, &random, None, None).unwrap();
510
511 let parsed = AnnounceData::unpack(&data, false).unwrap();
512 let wrong_hash = [0x00; 16];
513 assert!(parsed.validate(&wrong_hash).is_err());
514 }
515
516 #[test]
517 fn test_to_validated_with_cached_signature_matches_validate() {
518 let identity = Identity::from_private_key(&[0x42; 64]);
519 let id_hash = *identity.hash();
520
521 let nh = destination::name_hash("testapp", &["aspect"]);
522 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
523 let random = [0xAB; 10];
524
525 let (data, _) =
526 AnnounceData::pack(&identity, &dh, &nh, &random, None, Some(b"appdata")).unwrap();
527
528 let parsed = AnnounceData::unpack(&data, false).unwrap();
529 let validated = parsed.validate(&dh).unwrap();
530 let unchecked = parsed.to_validated_with_cached_signature(&dh).unwrap();
531
532 assert_eq!(validated.identity_hash, unchecked.identity_hash);
533 assert_eq!(validated.public_key, unchecked.public_key);
534 assert_eq!(validated.name_hash, unchecked.name_hash);
535 assert_eq!(validated.random_hash, unchecked.random_hash);
536 assert_eq!(validated.ratchet, unchecked.ratchet);
537 assert_eq!(validated.app_data, unchecked.app_data);
538 }
539 fn signed_announce(
540 app_data: Option<&[u8]>,
541 ratchet: Option<&[u8; 32]>,
542 ) -> (AnnounceData, [u8; 16]) {
543 let identity = Identity::from_private_key(&[0x42; 64]);
544 let id_hash = *identity.hash();
545 let nh = destination::name_hash("testapp", &["aspect"]);
546 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
547 let (data, has_ratchet) =
548 AnnounceData::pack(&identity, &dh, &nh, &[0x31; 10], ratchet, app_data).unwrap();
549 (AnnounceData::unpack(&data, has_ratchet).unwrap(), dh)
550 }
551
552 #[test]
553 fn test_cached_signature_path_checks_destination_binding() {
554 let (parsed, dh) = signed_announce(None, None);
555 let mut other_dh = dh;
556 other_dh[0] ^= 0x01;
557 assert!(matches!(
558 parsed.to_validated_with_cached_signature(&other_dh),
559 Err(AnnounceError::DestinationMismatch)
560 ));
561
562 let other = Identity::from_private_key(&[0x43; 64]);
563 let mut swapped = parsed.clone();
564 swapped.public_key = other.get_public_key().unwrap();
565 assert!(matches!(
566 swapped.to_validated_with_cached_signature(&dh),
567 Err(AnnounceError::DestinationMismatch)
568 ));
569 }
570
571 #[test]
572 fn test_signature_cache_key_covers_every_signed_field() {
573 let ratchet = [0x44; 32];
574 let (base, dh) = signed_announce(Some(b"app"), Some(&ratchet));
575 let key = base.signature_cache_key(&dh);
576 assert_eq!(key, base.clone().signature_cache_key(&dh));
577
578 let mut other_dh = dh;
579 other_dh[15] ^= 0x01;
580 assert_ne!(key, base.signature_cache_key(&other_dh));
581
582 let mut variants = Vec::new();
583 let mut v = base.clone();
584 v.public_key[0] ^= 0x01;
585 variants.push(v);
586 let mut v = base.clone();
587 v.name_hash[0] ^= 0x01;
588 variants.push(v);
589 let mut v = base.clone();
590 v.random_hash[0] ^= 0x01;
591 variants.push(v);
592 let mut v = base.clone();
593 v.ratchet = None;
594 variants.push(v);
595 let mut v = base.clone();
596 v.ratchet.as_mut().unwrap()[0] ^= 0x01;
597 variants.push(v);
598 let mut v = base.clone();
599 v.app_data = None;
600 variants.push(v);
601 let mut v = base.clone();
602 v.app_data = Some(b"apq".to_vec());
603 variants.push(v);
604 let mut v = base.clone();
605 v.signature[0] ^= 0x01;
606 variants.push(v);
607
608 for variant in &variants {
609 assert_ne!(key, variant.signature_cache_key(&dh));
610 }
611 }
612
613 #[test]
614 fn test_signature_cache_key_is_hash_of_signed_data_and_signature() {
615 let (parsed, dh) = signed_announce(Some(b"payload"), None);
616 let mut material = parsed.signed_data(&dh);
617 material.extend_from_slice(&parsed.signature);
618 assert_eq!(parsed.signature_cache_key(&dh), hash::full_hash(&material));
619 }
620
621 const LINK_COST: StampCost = StampCost::from_fp(11 * 256 + 64);
623
624 fn stamped_announce(
625 app_data: Option<&[u8]>,
626 cost: StampCost,
627 ) -> (
628 AnnounceData,
629 [u8; 16],
630 [u8; constants::STAMP_LENGTH],
631 StampValue,
632 ) {
633 use rns_crypto::{OsRng, Rng};
634
635 let identity = Identity::from_private_key(&[0x42; 64]);
636 let id_hash = *identity.hash();
637 let nh = destination::name_hash("testapp", &["aspect"]);
638 let dh = destination::destination_hash("testapp", &["aspect"], Some(&id_hash));
639 let public_key = identity.get_public_key().unwrap();
640
641 let mut material = Vec::new();
642 material.extend_from_slice(&dh);
643 material.extend_from_slice(&public_key);
644 material.extend_from_slice(&nh);
645 let workblock = stamp_workblock(&material, 0);
646
647 let mut rng = OsRng;
648 let mut stamp = [0u8; constants::STAMP_LENGTH];
649 loop {
650 rng.fill_bytes(&mut stamp);
651 if stamp_valid(&stamp, cost, &workblock) {
652 break;
653 }
654 }
655 let value = stamp_value(&workblock, &stamp);
656
657 let mut wire = app_data.map(|data| data.to_vec()).unwrap_or_default();
658 wire.push(constants::EXT_PAYLOAD_MARKER);
659 wire.push(0);
660 wire.extend_from_slice(&stamp);
661 wire.extend_from_slice(&crate::stamp::link_cost_ext_bytes(LINK_COST));
662
663 let (data, _) =
664 AnnounceData::pack(&identity, &dh, &nh, &[0x31; 10], None, Some(&wire)).unwrap();
665 (
666 AnnounceData::unpack(&data, false).unwrap(),
667 dh,
668 stamp,
669 value,
670 )
671 }
672
673 #[test]
674 fn test_announce_stamp_valid_is_stripped_and_valued() {
675 let (parsed, dh, stamp, value) =
676 stamped_announce(Some(b"app-data"), StampCost::from_whole(4));
677 let mut validated = parsed.validate(&dh).unwrap();
678 assert_eq!(
679 validated.apply_stamp(&dh, StampCost::from_whole(4)),
680 AnnounceStampStatus::Valid
681 );
682 assert_eq!(validated.stamp, Some(stamp));
683 assert_eq!(validated.stamp_value, value);
684 assert_eq!(validated.lr_cost, Some(LINK_COST));
685 assert_eq!(validated.app_data.as_deref(), Some(b"app-data".as_slice()));
686 }
687
688 #[test]
689 fn test_announce_stamp_valid_without_app_data_strips_to_none() {
690 let (parsed, dh, _, _) = stamped_announce(None, StampCost::from_whole(4));
691 let mut validated = parsed.validate(&dh).unwrap();
692 assert_eq!(
693 validated.apply_stamp(&dh, StampCost::from_whole(4)),
694 AnnounceStampStatus::Valid
695 );
696 assert_eq!(validated.app_data, None);
697 }
698
699 #[test]
700 fn test_announce_stamp_below_required_value_is_kept_invalid_and_valued() {
701 let (parsed, dh, stamp, value) =
702 stamped_announce(Some(b"app-data"), StampCost::from_whole(4));
703 let mut validated = parsed.validate(&dh).unwrap();
704 assert_eq!(
705 validated.apply_stamp(&dh, StampCost::from_whole(200)),
706 AnnounceStampStatus::Invalid
707 );
708 assert_eq!(validated.stamp, Some(stamp));
709 assert!(value > StampValue::ZERO);
710 assert_eq!(validated.stamp_value, value);
711 assert_eq!(validated.lr_cost, None);
712 assert_eq!(
713 validated.app_data.as_ref().map(|data| data.len()),
714 Some(b"app-data".len() + constants::EXT_PAYLOAD_LENGTH)
715 );
716 }
717
718 #[test]
719 fn test_announce_without_stamp_is_not_stamped() {
720 let (parsed, dh) = signed_announce(Some(b"app-data"), None);
721 let mut validated = parsed.validate(&dh).unwrap();
722 assert_eq!(
723 validated.apply_stamp(&dh, StampCost::from_whole(4)),
724 AnnounceStampStatus::NotStamped
725 );
726 assert_eq!(validated.stamp, None);
727 assert_eq!(validated.stamp_value, StampValue::ZERO);
728 assert_eq!(validated.lr_cost, None);
729 assert_eq!(validated.app_data.as_deref(), Some(b"app-data".as_slice()));
730 }
731
732 #[test]
733 fn test_announce_stamp_with_one_byte_link_cost_is_not_stamped() {
734 let (parsed, dh, _, _) = stamped_announce(Some(b"app-data"), StampCost::from_whole(4));
738 let mut app_data = parsed.app_data.clone().unwrap();
739 let fraction = app_data.len() - 2;
740 app_data.remove(fraction);
741 let identity = Identity::from_private_key(&[0x42; 64]);
742 let (data, _) = AnnounceData::pack(
743 &identity,
744 &dh,
745 &parsed.name_hash,
746 &parsed.random_hash,
747 None,
748 Some(&app_data),
749 )
750 .unwrap();
751 let mut validated = AnnounceData::unpack(&data, false)
752 .unwrap()
753 .validate(&dh)
754 .unwrap();
755 assert_eq!(
756 validated.apply_stamp(&dh, StampCost::from_whole(4)),
757 AnnounceStampStatus::NotStamped
758 );
759 assert_eq!(validated.lr_cost, None);
760 assert_eq!(validated.app_data, Some(app_data));
761 }
762
763 #[test]
764 fn test_announce_stamp_extended_format_is_not_interpreted() {
765 let (parsed, dh, _, _) = stamped_announce(Some(b"app-data"), StampCost::from_whole(4));
766 let mut announce = parsed.clone();
767 let app_data = announce.app_data.as_mut().unwrap();
769 let last = app_data.len() - 1;
770 app_data[last] |= 0x80;
771
772 let identity = Identity::from_private_key(&[0x42; 64]);
774 let (data, _) = AnnounceData::pack(
775 &identity,
776 &dh,
777 &announce.name_hash,
778 &announce.random_hash,
779 None,
780 Some(app_data),
781 )
782 .unwrap();
783 let reparsed = AnnounceData::unpack(&data, false).unwrap();
784 let mut validated = reparsed.validate(&dh).unwrap();
785 assert_eq!(
786 validated.apply_stamp(&dh, StampCost::from_whole(4)),
787 AnnounceStampStatus::NotStamped
788 );
789 assert_eq!(validated.stamp, None);
790 assert_eq!(validated.lr_cost, None);
791 }
792}