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