1use std::{borrow::Cow, fmt::Formatter, marker::PhantomData, ops::Not};
2
3use hopr_protocol_pix::{CofactorGroup, GroupEncoding, PixGroup, SsaCommitmentProof};
4use hopr_types::primitive::prelude::{BytesRepresentable, GeneralError};
5
6use crate::{
7 HoprEncryptedPartialSsaShare, HoprPixGroupRepr, HoprPixSpec, HoprSphinxHeaderSpec, HoprSphinxSuite,
8 PAYLOAD_SIZE_INT,
9 por::ProofOfRelayValues,
10 sphinx::{
11 errors::SphinxError,
12 prelude::{PaddedPayload, SURB, SphinxHeaderSpec, SphinxSuite},
13 },
14};
15
16flagset::flags! {
17 #[repr(u8)]
19 #[derive(PartialOrd, Ord, strum::EnumString, strum::Display)]
20 pub enum PacketSignal: u8 {
21 SurbDistress = 0b0000_0001,
25 OutOfSurbs = 0b0000_0011,
32 }
33}
34
35pub type PacketSignals = flagset::FlagSet<PacketSignal>;
40
41pub struct PacketMessage<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize>(PaddedPayload<P>, PhantomData<(S, H)>);
43
44pub type HoprPacketMessage = PacketMessage<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>;
46
47pub struct PacketParts<'a, S: SphinxSuite, H: SphinxHeaderSpec> {
50 pub surbs: Vec<SURB<S, H>>,
52 pub payload: Cow<'a, [u8]>,
54 pub signals: PacketSignals,
56}
57
58impl<S: SphinxSuite, H: SphinxHeaderSpec> Clone for PacketParts<'_, S, H>
59where
60 H::KeyId: Clone,
61 H::SurbReceiverData: Clone,
62{
63 fn clone(&self) -> Self {
64 Self {
65 surbs: self.surbs.clone(),
66 payload: self.payload.clone(),
67 signals: self.signals,
68 }
69 }
70}
71
72impl<S: SphinxSuite, H: SphinxHeaderSpec> std::fmt::Debug for PacketParts<'_, S, H>
73where
74 H::KeyId: std::fmt::Debug,
75 H::SurbReceiverData: std::fmt::Debug,
76{
77 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
78 f.debug_struct("PacketParts")
79 .field("surbs", &self.surbs)
80 .field("payload", &self.payload)
81 .field("signals", &self.signals)
82 .finish()
83 }
84}
85
86impl<S: SphinxSuite, H: SphinxHeaderSpec> PartialEq for PacketParts<'_, S, H>
87where
88 H::KeyId: PartialEq,
89 H::SurbReceiverData: PartialEq,
90{
91 fn eq(&self, other: &Self) -> bool {
92 self.surbs == other.surbs && self.payload == other.payload && self.signals == other.signals
93 }
94}
95
96impl<S: SphinxSuite, H: SphinxHeaderSpec> Eq for PacketParts<'_, S, H>
97where
98 H::KeyId: Eq,
99 H::SurbReceiverData: Eq,
100{
101}
102
103pub type HoprPacketParts<'a> = PacketParts<'a, HoprSphinxSuite, HoprSphinxHeaderSpec>;
105
106pub(crate) const S_MASK: u8 = 0b0000_1111;
108
109impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> PacketMessage<S, H, P> {
110 pub const HEADER_LEN: usize = 1;
114 pub const MAX_SURBS_PER_MESSAGE: usize = S_MASK as usize;
119}
120
121impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> TryFrom<PacketParts<'_, S, H>> for PacketMessage<S, H, P> {
122 type Error = SphinxError;
123
124 fn try_from(value: PacketParts<S, H>) -> Result<Self, Self::Error> {
125 if value.surbs.len() > Self::MAX_SURBS_PER_MESSAGE {
126 return Err(GeneralError::ParseError("HoprPacketMessage.num_surbs not valid".into()).into());
127 }
128
129 if value.signals.bits() > S_MASK {
130 return Err(GeneralError::ParseError("HoprPacketMessage.flags not valid".into()).into());
131 }
132
133 if Self::HEADER_LEN + value.surbs.len() * SURB::<S, H>::SIZE + value.payload.len() > P {
135 return Err(GeneralError::ParseError("HoprPacketMessage.size not valid".into()).into());
136 }
137
138 let mut ret = Vec::with_capacity(PaddedPayload::<P>::SIZE);
139 let flags_and_len = (value.signals.bits() << S_MASK.trailing_ones()) | (value.surbs.len() as u8 & S_MASK);
140 ret.push(flags_and_len);
141 for surb in value.surbs.into_iter().map(|s| s.into_boxed()) {
142 ret.extend(surb);
143 }
144 ret.extend_from_slice(value.payload.as_ref());
145
146 Ok(Self(PaddedPayload::new_from_vec(ret)?, PhantomData))
148 }
149}
150
151impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> TryFrom<PacketMessage<S, H, P>> for PacketParts<'_, S, H> {
152 type Error = SphinxError;
153
154 fn try_from(value: PacketMessage<S, H, P>) -> Result<Self, Self::Error> {
155 let data = value.0.into_unpadded()?;
156 if data.is_empty() {
157 return Err(GeneralError::ParseError("HoprPacketMessage.size".into()).into());
158 }
159
160 let num_surbs = (data[0] & S_MASK) as usize;
161 let signals = PacketSignals::new((data[0] & S_MASK.not()) >> S_MASK.trailing_ones())
162 .map_err(|_| GeneralError::ParseError("HoprPacketMessage.signals".into()))?;
163
164 if num_surbs > 0 {
165 let surb_end = num_surbs * SURB::<S, H>::SIZE;
166 if surb_end >= data.len() {
167 return Err(GeneralError::ParseError("HoprPacketMessage.num_surbs not valid".into()).into());
168 }
169
170 let mut data = data.into_vec();
171
172 let surbs = data[1..=surb_end]
173 .chunks_exact(SURB::<S, H>::SIZE)
174 .map(SURB::<S, H>::try_from)
175 .collect::<Result<Vec<_>, _>>()?;
176
177 data.drain(0..=surb_end).for_each(drop);
179
180 Ok(PacketParts {
181 surbs,
182 payload: Cow::Owned(data),
183 signals,
184 })
185 } else {
186 let mut data = data.into_vec();
187 data.remove(0);
188 Ok(PacketParts {
189 surbs: Vec::with_capacity(0),
190 payload: Cow::Owned(data),
191 signals,
192 })
193 }
194 }
195}
196
197impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> From<PaddedPayload<P>> for PacketMessage<S, H, P> {
198 fn from(value: PaddedPayload<P>) -> Self {
199 Self(value, PhantomData)
200 }
201}
202
203impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> From<PacketMessage<S, H, P>> for PaddedPayload<P> {
204 fn from(value: PacketMessage<S, H, P>) -> Self {
205 value.0
206 }
207}
208
209#[derive(Clone, Copy, Debug, PartialEq, Eq)]
212#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
213pub struct SurbReceiverInfo(#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; Self::SIZE]);
214
215impl SurbReceiverInfo {
216 pub fn new(
217 pov: ProofOfRelayValues,
218 encrypted_partial_ssa_share: HoprEncryptedPartialSsaShare,
219 generation: u8,
220 ) -> Self {
221 let mut ret = [0u8; Self::SIZE];
222 ret[0..ProofOfRelayValues::SIZE].copy_from_slice(pov.as_ref());
223 ret[ProofOfRelayValues::SIZE..ProofOfRelayValues::SIZE + HoprEncryptedPartialSsaShare::SIZE]
224 .copy_from_slice(encrypted_partial_ssa_share.as_ref());
225 ret[Self::SIZE - 1] = generation;
227 Self(ret)
228 }
229
230 pub fn proof_of_relay_values(&self) -> ProofOfRelayValues {
231 ProofOfRelayValues::try_from(&self.0[0..ProofOfRelayValues::SIZE])
232 .expect("SurbReceiverInfo always contains valid ProofOfRelayValues")
233 }
234
235 pub fn encrypted_partial_ssa_share(&self) -> HoprEncryptedPartialSsaShare {
236 HoprEncryptedPartialSsaShare::try_from(
237 &self.0[ProofOfRelayValues::SIZE..ProofOfRelayValues::SIZE + HoprEncryptedPartialSsaShare::SIZE],
238 )
239 .expect("SurbReceiverInfo always contains valid HoprEncryptedPartialSsaShare")
240 }
241
242 pub fn generation(&self) -> u8 {
250 self.0[Self::SIZE - 1]
251 }
252}
253
254impl AsRef<[u8]> for SurbReceiverInfo {
255 fn as_ref(&self) -> &[u8] {
256 &self.0
257 }
258}
259
260impl<'a> TryFrom<&'a [u8]> for SurbReceiverInfo {
261 type Error = GeneralError;
262
263 fn try_from(value: &'a [u8]) -> std::result::Result<Self, Self::Error> {
264 value
265 .try_into()
266 .map(Self)
267 .map_err(|_| GeneralError::ParseError("SurbReceiverInfo".into()))
268 }
269}
270
271impl BytesRepresentable for SurbReceiverInfo {
272 const SIZE: usize = ProofOfRelayValues::SIZE + HoprEncryptedPartialSsaShare::SIZE + size_of::<u8>();
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278pub struct HoprPixGroupElement(pub HoprPixGroupRepr);
279
280impl std::hash::Hash for HoprPixGroupElement {
281 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
282 AsRef::<[u8]>::as_ref(&self.0).hash(state);
285 }
286}
287
288impl HoprPixGroupElement {
289 pub fn try_into_pix_group(self) -> Result<PixGroup<HoprPixSpec>, GeneralError> {
291 Option::<PixGroup<HoprPixSpec>>::from(PixGroup::<HoprPixSpec>::from_bytes(&self.0))
292 .filter(|pt| {
293 bool::from(pt.is_torsion_free())
296 })
297 .ok_or(GeneralError::ParseError("pix group from bytes failed".into()))
298 }
299}
300
301impl From<HoprPixGroupRepr> for HoprPixGroupElement {
302 fn from(value: HoprPixGroupRepr) -> Self {
303 Self(value)
304 }
305}
306
307impl AsRef<[u8]> for HoprPixGroupElement {
308 fn as_ref(&self) -> &[u8] {
309 self.0.as_ref()
310 }
311}
312
313impl<'a> TryFrom<&'a [u8]> for HoprPixGroupElement {
314 type Error = GeneralError;
315
316 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
317 if value.len() != size_of::<HoprPixGroupRepr>() {
318 return Err(GeneralError::ParseError("pix repr length".into()));
319 }
320 let mut arr = HoprPixGroupRepr::default();
321 AsMut::<[u8]>::as_mut(&mut arr).copy_from_slice(value);
322 Ok(Self(arr))
323 }
324}
325
326impl std::fmt::Display for HoprPixGroupElement {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 write!(f, "{}", const_hex::encode(self.0))
329 }
330}
331
332pub const HOPR_PIX_COMMITMENT_PROOF_SIZE: usize = SsaCommitmentProof::<HoprPixSpec>::SIZE;
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341pub struct HoprPixCommitmentProof(pub [u8; HOPR_PIX_COMMITMENT_PROOF_SIZE]);
342
343impl HoprPixCommitmentProof {
344 pub fn try_into_pix_proof(self) -> Result<SsaCommitmentProof<HoprPixSpec>, GeneralError> {
349 SsaCommitmentProof::<HoprPixSpec>::try_from_bytes(&self.0)
350 .map_err(|_| GeneralError::ParseError("pix commitment proof from bytes failed".into()))
351 }
352}
353
354impl From<SsaCommitmentProof<HoprPixSpec>> for HoprPixCommitmentProof {
355 fn from(value: SsaCommitmentProof<HoprPixSpec>) -> Self {
356 let mut out = [0u8; HOPR_PIX_COMMITMENT_PROOF_SIZE];
357 out.copy_from_slice(&value.to_bytes());
358 Self(out)
359 }
360}
361
362impl AsRef<[u8]> for HoprPixCommitmentProof {
363 fn as_ref(&self) -> &[u8] {
364 &self.0
365 }
366}
367
368impl<'a> TryFrom<&'a [u8]> for HoprPixCommitmentProof {
369 type Error = GeneralError;
370
371 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
372 Ok(Self(value.try_into().map_err(|_| {
373 GeneralError::ParseError("pix commitment proof length".into())
374 })?))
375 }
376}
377
378impl std::fmt::Display for HoprPixCommitmentProof {
379 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 write!(f, "{}", const_hex::encode(self.0))
381 }
382}
383
384#[cfg(test)]
385mod tests {
386 use anyhow::anyhow;
387 use bimap::BiHashMap;
388 use hex_literal::hex;
389 use hopr_types::{
390 crypto::prelude::*, crypto_random::Randomizable, internal::routing::HoprSenderId, primitive::prelude::*,
391 };
392
393 use super::*;
394 use crate::{
395 HoprEncryptedPartialSsaShare, HoprSphinxHeaderSpec, HoprSphinxSuite, HoprSurb, packet::HoprPacket,
396 por::generate_proof_of_relay, sphinx::prelude::*, types::SurbReceiverInfo,
397 };
398
399 lazy_static::lazy_static! {
400 static ref PEERS: [(ChainKeypair, OffchainKeypair); 4] = [
401 (hex!("a7c486ceccf5ab53bd428888ab1543dc2667abd2d5e80aae918da8d4b503a426"), hex!("5eb212d4d6aa5948c4f71574d45dad43afef6d330edb873fca69d0e1b197e906")),
402 (hex!("9a82976f7182c05126313bead5617c623b93d11f9f9691c87b1a26f869d569ed"), hex!("e995db483ada5174666c46bafbf3628005aca449c94ebdc0c9239c3f65d61ae0")),
403 (hex!("ca4bdfd54a8467b5283a0216288fdca7091122479ccf3cfb147dfa59d13f3486"), hex!("9dec751c00f49e50fceff7114823f726a0425a68a8dc6af0e4287badfea8f4a4")),
404 (hex!("e306ebfb0d01d0da0952c9a567d758093a80622c6cb55052bf5f1a6ebd8d7b5c"), hex!("9a82976f7182c05126313bead5617c623b93d11f9f9691c87b1a26f869d569ed"))
405 ].map(|(p1,p2)| (ChainKeypair::from_secret(&p1).expect("lazy static keypair should be valid"), OffchainKeypair::from_secret(&p2).expect("lazy static keypair should be valid")));
406
407 static ref MAPPER: SimpleBiMapper<HoprSphinxSuite, HoprSphinxHeaderSpec> = PEERS
408 .iter()
409 .enumerate()
410 .map(|(i, (_, k))| (KeyIdent::from(i as u32), *k.public()))
411 .collect::<BiHashMap<_, _>>()
412 .into();
413 }
414
415 fn generate_surbs(count: usize) -> anyhow::Result<Vec<SURB<HoprSphinxSuite, HoprSphinxHeaderSpec>>> {
416 let path = PEERS.iter().map(|(_, k)| *k.public()).collect::<Vec<_>>();
417 let path_ids = MAPPER
418 .map_keys_to_ids(&path)
419 .into_iter()
420 .map(|v| v.ok_or(anyhow!("missing id")))
421 .collect::<Result<Vec<_>, _>>()?;
422 let pseudonym = SimplePseudonym::random();
423 let recv_data = HoprSenderId::new(&pseudonym);
424
425 Ok((0..count)
426 .map(|_| {
427 let shared_keys = HoprSphinxSuite::new_shared_keys(&path)?;
428 let (por_strings, (por_values, _)) = generate_proof_of_relay(&shared_keys.secrets)
429 .map_err(|e| CryptoError::Other(GeneralError::NonSpecificError(e.to_string())))?;
430
431 create_surb::<HoprSphinxSuite, HoprSphinxHeaderSpec>(
432 shared_keys,
433 &path_ids,
434 &por_strings,
435 recv_data,
436 SurbReceiverInfo::new(por_values, HoprEncryptedPartialSsaShare::default(), 0),
437 )
438 .map(|(s, _)| s)
439 })
440 .collect::<Result<Vec<_>, _>>()?)
441 }
442
443 #[test]
444 fn hopr_packet_message_message_only() -> anyhow::Result<()> {
445 let parts_1 = HoprPacketParts {
446 surbs: vec![],
447 payload: b"test message".into(),
448 signals: PacketSignal::OutOfSurbs.into(),
449 };
450
451 let parts_2: HoprPacketParts = HoprPacketMessage::try_from(parts_1.clone())?.try_into()?;
452 assert_eq!(parts_1, parts_2);
453
454 Ok(())
455 }
456
457 #[test]
458 fn hopr_packet_message_surbs_only() -> anyhow::Result<()> {
459 let parts_1 = HoprPacketParts {
460 surbs: generate_surbs(2)?,
461 payload: Cow::default(),
462 signals: PacketSignal::OutOfSurbs.into(),
463 };
464
465 let parts_2: HoprPacketParts = HoprPacketMessage::try_from(parts_1.clone())?.try_into()?;
466 assert_eq!(parts_1, parts_2);
467
468 Ok(())
469 }
470
471 #[test]
472 fn hopr_packet_message_surbs_and_msg() -> anyhow::Result<()> {
473 let parts_1 = HoprPacketParts {
474 surbs: generate_surbs(2)?,
475 payload: b"test msg".into(),
476 signals: PacketSignal::OutOfSurbs.into(),
477 };
478
479 let parts_2: HoprPacketParts = HoprPacketMessage::try_from(parts_1.clone())?.try_into()?;
480 assert_eq!(parts_1, parts_2);
481
482 Ok(())
483 }
484
485 #[test]
486 fn hopr_packet_size_msg_size_limit() {
487 let res = HoprPacketMessage::try_from(HoprPacketParts {
488 surbs: vec![],
489 payload: (&[1u8; HoprPacket::PAYLOAD_SIZE + 1]).into(),
490 signals: None.into(),
491 });
492 assert!(res.is_err());
493 }
494
495 #[test]
496 fn hopr_packet_message_surbs_size_limit() -> anyhow::Result<()> {
497 let res = HoprPacketMessage::try_from(PacketParts {
498 surbs: generate_surbs(HoprPacketMessage::MAX_SURBS_PER_MESSAGE + 1)?,
499 payload: Cow::default(),
500 signals: None.into(),
501 });
502 assert!(res.is_err());
503
504 let res = HoprPacketMessage::try_from(HoprPacketParts {
505 surbs: generate_surbs(3)?,
506 payload: Cow::default(),
507 signals: None.into(),
508 });
509 assert!(res.is_err());
510
511 Ok(())
512 }
513
514 #[test]
515 fn hopr_packet_message_surbs_flag_limit() -> anyhow::Result<()> {
516 let res = HoprPacketMessage::try_from(PacketParts {
517 surbs: generate_surbs(3)?,
518 payload: Cow::default(),
519 signals: unsafe { PacketSignals::new_unchecked(16) },
520 });
521 assert!(res.is_err());
522
523 Ok(())
524 }
525
526 #[test]
527 fn hopr_packet_size_msg_and_surb_size_limit() -> anyhow::Result<()> {
528 let res = HoprPacketMessage::try_from(PacketParts {
529 surbs: generate_surbs(2)?,
530 payload: (&[1u8; HoprPacket::PAYLOAD_SIZE - 2 * HoprSurb::SIZE + 1]).into(),
531 signals: None.into(),
532 });
533 assert!(res.is_err());
534
535 Ok(())
536 }
537
538 fn random_pix_group_element() -> HoprPixGroupElement {
539 #[cfg(all(feature = "pix-bjj", not(feature = "pix-secp256k1")))]
543 use hopr_protocol_pix::Group;
544
545 let scalar = <hopr_protocol_pix::PixScalar<HoprPixSpec> as crypto_traits::elliptic_curve::Field>::random(
546 &mut hopr_types::crypto_random::rng(),
547 );
548 HoprPixGroupElement(hopr_protocol_pix::GroupEncoding::to_bytes(
549 &PixGroup::<HoprPixSpec>::mul_by_generator(&scalar),
550 ))
551 }
552
553 #[test]
556 fn pix_group_element_round_trips_and_rejects_bad_input() -> anyhow::Result<()> {
557 let element = random_pix_group_element();
558
559 let point = element.try_into_pix_group()?;
560 assert_eq!(
561 HoprPixGroupElement::from(hopr_protocol_pix::GroupEncoding::to_bytes(&point)),
562 element,
563 "converting to the typed group and back must be lossless"
564 );
565
566 let bytes: &[u8] = element.as_ref();
567 assert_eq!(HoprPixGroupElement::try_from(bytes)?, element);
568 for wrong in [&bytes[..bytes.len() - 1], &[][..]] {
569 assert!(
570 HoprPixGroupElement::try_from(wrong).is_err(),
571 "a {}-byte buffer must not parse as a group element",
572 wrong.len()
573 );
574 }
575
576 let mut garbage_repr = HoprPixGroupRepr::default();
579 AsMut::<[u8]>::as_mut(&mut garbage_repr).fill(0xFF);
580 assert!(HoprPixGroupElement(garbage_repr).try_into_pix_group().is_err());
581
582 assert_eq!(element.to_string(), const_hex::encode(bytes));
584
585 let mut h1 = std::collections::hash_map::DefaultHasher::new();
587 let mut h2 = std::collections::hash_map::DefaultHasher::new();
588 std::hash::Hash::hash(&element, &mut h1);
589 std::hash::Hash::hash(&HoprPixGroupElement::try_from(bytes)?, &mut h2);
590 assert_eq!(
591 std::hash::Hasher::finish(&h1),
592 std::hash::Hasher::finish(&h2),
593 "equal elements must hash equal"
594 );
595
596 Ok(())
597 }
598
599 #[cfg(all(feature = "pix-bjj", not(feature = "pix-secp256k1")))]
614 #[test]
615 fn pix_group_element_rejects_a_small_order_point() {
616 use hopr_protocol_pix::Group;
617 type Affine = <BabyJubJub as crypto_traits::elliptic_curve::CurveArithmetic>::AffinePoint;
618
619 let identity = PixGroup::<HoprPixSpec>::identity().to_affine();
627 let order_two = Affine {
628 x: identity.x,
629 y: -identity.y,
630 };
631 assert!(order_two.is_on_curve(), "(0, -1) must be a valid curve point");
632 assert!(
633 !order_two.is_in_prime_order_subgroup(),
634 "a point of order 2 must not be in the prime-order subgroup"
635 );
636
637 let repr = hopr_protocol_pix::GroupEncoding::to_bytes(&PixGroup::<HoprPixSpec>::from(order_two));
638 assert!(
639 HoprPixGroupElement(repr).try_into_pix_group().is_err(),
640 "a small-order point must not parse into a pix group element"
641 );
642 }
643
644 #[test]
645 fn pix_commitment_proof_wire_wrapper_round_trips() -> anyhow::Result<()> {
646 #[cfg(all(feature = "pix-bjj", not(feature = "pix-secp256k1")))]
649 use hopr_protocol_pix::Group;
650 use hopr_protocol_pix::SsaId;
651
652 let ssa_id = SsaId::new(SimplePseudonym::random(), 1.try_into()?);
653 let secret = <hopr_protocol_pix::PixScalar<HoprPixSpec> as crypto_traits::elliptic_curve::Field>::random(
654 &mut hopr_types::crypto_random::rng(),
655 );
656 let commitment = PixGroup::<HoprPixSpec>::mul_by_generator(&secret);
657 let proof = SsaCommitmentProof::<HoprPixSpec>::prove(&ssa_id, &secret, &commitment)?;
658
659 let wire = HoprPixCommitmentProof::from(proof);
660 assert_eq!(wire.as_ref().len(), HOPR_PIX_COMMITMENT_PROOF_SIZE);
661 assert_eq!(wire.to_string(), const_hex::encode(wire.as_ref()));
662
663 let recovered = wire.try_into_pix_proof()?;
664 assert_eq!(recovered, proof);
665 assert!(
666 recovered.verify(&ssa_id, &commitment),
667 "the proof must survive the wire"
668 );
669
670 assert_eq!(HoprPixCommitmentProof::try_from(wire.as_ref())?, wire);
671 for wrong in [&wire.as_ref()[..HOPR_PIX_COMMITMENT_PROOF_SIZE - 1], &[][..]] {
672 assert!(
673 HoprPixCommitmentProof::try_from(wrong).is_err(),
674 "only the exact length may parse"
675 );
676 }
677
678 Ok(())
679 }
680
681 #[test]
693 fn pix_wire_element_sizes_are_fixed_by_the_curve_feature() {
694 #[cfg(all(feature = "pix-bjj", not(feature = "pix-secp256k1")))]
695 {
696 assert_eq!(32, size_of::<HoprPixGroupRepr>(), "Baby JubJub compressed point");
697 assert_eq!(64, HOPR_PIX_COMMITMENT_PROOF_SIZE, "Baby JubJub commitment proof");
698 assert_eq!(
699 hopr_protocol_pix::PixSuite::BabyJubJub,
700 <HoprPixSpec as hopr_protocol_pix::PixSpec>::PIX_SUITE,
701 "the announced suite must name the curve actually compiled in"
702 );
703 }
704 #[cfg(any(feature = "pix-secp256k1", not(feature = "pix-bjj")))]
705 {
706 assert_eq!(33, size_of::<HoprPixGroupRepr>(), "secp256k1 compressed point");
707 assert_eq!(65, HOPR_PIX_COMMITMENT_PROOF_SIZE, "secp256k1 commitment proof");
708 assert_eq!(
709 hopr_protocol_pix::PixSuite::Secp256k1,
710 <HoprPixSpec as hopr_protocol_pix::PixSpec>::PIX_SUITE,
711 "the announced suite must name the curve actually compiled in"
712 );
713 }
714 }
715}