Skip to main content

hopr_crypto_packet/
lib.rs

1//! # core-packet
2//!
3//! This crate contains the main packet processing functionality for the HOPR protocol.
4//! It implements the following important protocol building blocks:
5//!
6//! - HOPR specific instantiation of the SPHINX packet format
7//! - Proof of Relay
8//!
9//! Finally, it also implements a utility function which is used to validate tickets (module `validation`).
10//!
11//! The currently used implementation is selected using the [`HoprSphinxSuite`] type in the `packet` module.
12//!
13//! The implementation can be easily extended for different elliptic curves (or even arithmetic multiplicative groups).
14//! In particular, as soon as there is a way to represent `Ed448` PeerIDs, it would be straightforward to create e.g.
15//! `X448Suite`.
16//!
17//! This crate implements [RFC-0003](https://github.com/hoprnet/rfc/tree/main/rfcs/RFC-0003-hopr-packet-protocol).
18
19pub mod sphinx;
20
21/// Lists all errors in this crate.
22pub mod errors;
23/// Implements the overlay packet intermediary object.
24mod packet;
25/// Implements the Proof of Relay.
26mod por;
27/// Contains various helper types.
28mod types;
29/// Implements ticket validation logic.
30mod validation;
31
32#[doc(hidden)]
33pub mod prelude {
34    pub use hopr_types::internal::routing::{HoprSenderId, HoprSurbId};
35
36    pub use super::*;
37    pub use crate::{
38        packet::{
39            HoprForwardedPacket, HoprIncomingPacket, HoprOutgoingPacket, HoprPacket, PacketRouting, PartialHoprPacket,
40        },
41        types::{
42            HOPR_PIX_COMMITMENT_PROOF_SIZE, HoprPixCommitmentProof, HoprPixGroupElement, PacketSignal, PacketSignals,
43        },
44        validation::validate_unacknowledged_ticket,
45    };
46}
47
48use hopr_protocol_pix::{PixGroup, PixScalar};
49use hopr_types::{crypto::prelude::*, internal::prelude::*, primitive::prelude::*};
50use sphinx::prelude::*;
51pub use sphinx::prelude::{ProtocolKeyIdMapper, ReplyOpener};
52
53/// Currently used public key cipher suite for Sphinx.
54///
55/// This is currently the [`Ed25519Suite`], because it is faster than `X25519Suite`.
56pub type HoprSphinxSuite = Ed25519Suite;
57
58/// Current Sphinx header specification for the HOPR protocol.
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
61pub struct HoprSphinxHeaderSpec;
62
63impl SphinxHeaderSpec for HoprSphinxHeaderSpec {
64    type KeyId = HoprKeyIdent;
65    type PRG = ChaCha20;
66    type PacketReceiverData = HoprSenderId;
67    type Pseudonym = HoprPseudonym;
68    type RelayerData = por::ProofOfRelayString;
69    type SurbReceiverData = types::SurbReceiverInfo;
70    type UH = Poly1305;
71
72    const MAX_HOPS: std::num::NonZeroUsize = std::num::NonZeroUsize::new(INTERMEDIATE_HOPS + 1).unwrap();
73}
74
75/// Type alias for 32-bit HOPR Offchain Public Key Identifier.
76pub type HoprKeyIdent = KeyIdent<4>;
77
78/// Single Use Reply Block representation for HOPR protocol.
79pub type HoprSurb = SURB<HoprSphinxSuite, HoprSphinxHeaderSpec>;
80
81/// Type alias for identifiable [`ReplyOpener`].
82pub type HoprReplyOpener = (HoprSurbId, ReplyOpener);
83
84/// Size of the maximum packet payload.
85///
86/// Adjust this value to change the maximum packet size.
87/// The calculation here is based on the fact that libp2p Stream over QUIC
88/// leaves space for 1460 bytes in the packet payload.
89///
90/// **DO NOT USE this value for calculations outside of this crate: use `HoprPacket::PAYLOAD_SIZE` instead!**
91pub(crate) const PAYLOAD_SIZE_INT: usize = DefaultSphinxPacketSize::USIZE - 1; // minus padding byte
92
93/// Current specification of the PIX protocol in HOPR.
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Default)]
95#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96pub struct HoprPixSpec;
97
98#[cfg(any(feature = "pix-secp256k1", not(feature = "pix-bjj")))]
99impl hopr_protocol_pix::PixSpec for HoprPixSpec {
100    type AddressPrivateKey = ChainKeypair;
101    type Cipher = ChaCha20;
102    type Curve = Secp256k1;
103    type DepositAddress = Address;
104    type Digest = Blake3;
105    type Pseudonym = SimplePseudonym;
106
107    const HASH_TO_SCALAR_SUITE_ID: &'static [u8] = b"Secp256k1_XMD:BLAKE3_SSWU_RO_";
108    const PIX_SUITE: hopr_protocol_pix::PixSuite = hopr_protocol_pix::PixSuite::Secp256k1;
109
110    fn group_to_deposit_address(group: PixGroup<Self>) -> Option<Self::DepositAddress> {
111        PublicKey::try_from(group.to_affine()).ok().map(|pk| pk.to_address())
112    }
113
114    fn scalar_to_private_key(scalar: PixScalar<Self>) -> Option<Self::AddressPrivateKey> {
115        ChainKeypair::from_secret(scalar.to_bytes().as_ref()).ok()
116    }
117}
118
119#[cfg(all(feature = "pix-bjj", not(feature = "pix-secp256k1")))]
120impl hopr_protocol_pix::PixSpec for HoprPixSpec {
121    type AddressPrivateKey = BjjKeypair;
122    type Cipher = ChaCha20;
123    type Curve = BabyJubJub;
124    type DepositAddress = BjjPublicKey;
125    type Digest = Blake3;
126    type Pseudonym = SimplePseudonym;
127
128    const HASH_TO_SCALAR_SUITE_ID: &'static [u8] = b"BabyJubJub_XMD:BLAKE3_SSWU_RO_";
129    const PIX_SUITE: hopr_protocol_pix::PixSuite = hopr_protocol_pix::PixSuite::BabyJubJub;
130
131    fn group_to_deposit_address(group: PixGroup<Self>) -> Option<Self::DepositAddress> {
132        BjjPublicKey::try_from(group).ok()
133    }
134
135    fn scalar_to_private_key(scalar: PixScalar<Self>) -> Option<Self::AddressPrivateKey> {
136        BjjKeypair::from_secret(scalar.to_bytes().as_ref()).ok()
137    }
138}
139
140/// HOPR-specific encrypted partial SSA share type from the PIX protocol.
141pub type HoprEncryptedPartialSsaShare = hopr_protocol_pix::EncryptedPartialSsaShare<HoprPixSpec>;
142
143/// HOPR-specific [`hopr_protocol_pix::ShareResolution`].
144#[cfg(any(feature = "pix-secp256k1", not(feature = "pix-bjj")))]
145pub type HoprShareResolution = hopr_protocol_pix::ShareResolution<SimplePseudonym, ChainKeypair>;
146#[cfg(all(feature = "pix-bjj", not(feature = "pix-secp256k1")))]
147pub type HoprShareResolution = hopr_protocol_pix::ShareResolution<SimplePseudonym, BjjKeypair>;
148
149/// HOPR-specific [`hopr_protocol_pix::SsaCommitmentState`].
150#[cfg(any(feature = "pix-secp256k1", not(feature = "pix-bjj")))]
151pub type HoprSsaCommitmentState = hopr_protocol_pix::SsaCommitmentState<SimplePseudonym, Address>;
152#[cfg(all(feature = "pix-bjj", not(feature = "pix-secp256k1")))]
153pub type HoprSsaCommitmentState = hopr_protocol_pix::SsaCommitmentState<SimplePseudonym, BjjPublicKey>;
154
155/// HOPR-specific PIX scalar type.
156///
157/// This is the normalized form of `hopr_protocol_pix::PixScalar<HoprPixSpec>`
158/// (i.e. `<<HoprPixSpec as PixSpec>::Curve as CurveArithmetic>::Scalar`),
159/// re-exported here so downstream crates can name it without depending on
160/// directly.
161///
162/// This also avoids a Rust compiler issue due to deep nesting of PixScalar<HoprPixSpec> when used
163/// itself as another generic argument.
164#[cfg(any(feature = "pix-secp256k1", not(feature = "pix-bjj")))]
165pub type HoprPixScalar = crypto_traits::elliptic_curve::Scalar<Secp256k1>;
166#[cfg(all(feature = "pix-bjj", not(feature = "pix-secp256k1")))]
167pub type HoprPixScalar = BabyJubJubScalar;
168
169/// HOPR-specific PIX group element representation type.
170///
171/// This is the normalized (concrete) form of `hopr_protocol_pix::PixGroupRepr<HoprPixSpec>`
172/// (i.e. `<PixGroup<HoprPixSpec> as GroupEncoding>::Repr`), re-exported here as the concrete
173/// type.
174///
175/// Using the concrete type instead of the associated-type projection avoids a Rust coherence
176/// error (E0119): implementing `From`/`TryFrom` for a new-type wrapping the projection conflicts
177/// with the blanket `impl<T> From<T> for T` because the compiler cannot prove the unresolved
178/// projection is distinct from the wrapper type.
179#[cfg(any(feature = "pix-secp256k1", not(feature = "pix-bjj")))]
180pub type HoprPixGroupRepr = crypto_traits::elliptic_curve::array::Array<u8, crypto_traits::elliptic_curve::consts::U33>;
181#[cfg(all(feature = "pix-bjj", not(feature = "pix-secp256k1")))]
182pub type HoprPixGroupRepr = BabyJubJubCompressedPoint;
183
184#[cfg(test)]
185mod tests {
186    use sphinx::prelude::MetaPacket;
187
188    use super::*;
189    use crate::packet::HoprPacket;
190
191    #[test]
192    fn header_and_packet_lengths() {
193        let hopr_packet_len = HoprPacket::SIZE;
194        assert_eq!(
195            MetaPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>::PACKET_LEN + Ticket::SIZE,
196            hopr_packet_len
197        );
198
199        assert!(
200            hopr_packet_len <= 1492 - 31,
201            "HOPR packet of {hopr_packet_len} bytes must fit within a layer 4 packet with libp2p overhead"
202        );
203    }
204
205    #[test]
206    fn packet_length() {
207        let packet_len = HoprPacket::SIZE;
208        assert_eq!(packet_len, 422 + PAYLOAD_SIZE_INT);
209    }
210
211    #[test]
212    fn header_length() {
213        let header_len = HoprSphinxHeaderSpec::HEADER_LEN;
214        assert_eq!(header_len, 241);
215    }
216
217    #[test]
218    fn surb_length() {
219        let surb_len = HoprSurb::SIZE;
220        // 401 bytes + 1 for the SURB-batch generation byte in `SurbReceiverInfo`.
221        assert_eq!(surb_len, 402);
222        assert!(HoprPacket::PAYLOAD_SIZE > surb_len * 2);
223    }
224
225    #[test]
226    fn max_surbs_per_packet_must_be_at_least_2() {
227        const _: () = {
228            assert!(HoprPacket::MAX_SURBS_IN_PACKET >= 2);
229        };
230    }
231}