1use hopr_crypto_sphinx::{
2 derivation::derive_packet_tag,
3 prp::{PRPParameters, PRP},
4 routing::{forward_header, header_length, ForwardedHeader, RoutingInfo},
5 shared_keys::{Alpha, GroupElement, SharedKeys, SharedSecret, SphinxSuite},
6};
7use hopr_crypto_types::{
8 keypairs::Keypair,
9 primitives::{DigestLike, SimpleMac},
10 types::PacketTag,
11};
12use hopr_internal_types::prelude::*;
13use hopr_primitive_types::prelude::*;
14use std::fmt::{Debug, Formatter};
15use std::marker::PhantomData;
16use typenum::Unsigned;
17
18use crate::{
19 errors::{PacketError::PacketDecodingError, Result},
20 packet::ForwardedMetaPacket::{Final, Relayed},
21 por::POR_SECRET_LENGTH,
22};
23
24const PADDING_TAG: &[u8] = b"HOPR";
26
27pub const fn packet_length<S: SphinxSuite>(
29 max_hops: usize,
30 additional_data_relayer_len: usize,
31 additional_data_last_hop_len: usize,
32) -> usize {
33 <S::P as Keypair>::Public::SIZE
34 + header_length::<S>(max_hops, additional_data_relayer_len, additional_data_last_hop_len)
35 + SimpleMac::SIZE
36 + PAYLOAD_SIZE
37 + PADDING_TAG.len()
38}
39
40fn add_padding(msg: &[u8]) -> Box<[u8]> {
43 assert!(msg.len() <= PAYLOAD_SIZE, "message too long for padding");
44
45 let padded_len = PAYLOAD_SIZE + PADDING_TAG.len();
46 let mut ret = vec![0u8; padded_len];
47 ret[padded_len - msg.len()..padded_len].copy_from_slice(msg);
48
49 ret[padded_len - msg.len() - PADDING_TAG.len()..padded_len - msg.len()].copy_from_slice(PADDING_TAG);
51 ret.into_boxed_slice()
52}
53
54fn remove_padding(msg: &[u8]) -> Option<&[u8]> {
55 assert_eq!(
56 PAYLOAD_SIZE + PADDING_TAG.len(),
57 msg.len(),
58 "padded message must be {} bytes long",
59 PAYLOAD_SIZE + PADDING_TAG.len()
60 );
61 let pos = msg
62 .windows(PADDING_TAG.len())
63 .position(|window| window == PADDING_TAG)?;
64 Some(&msg.split_at(pos).1[PADDING_TAG.len()..])
65}
66
67pub struct MetaPacket<S: SphinxSuite> {
77 packet: Box<[u8]>,
78 _s: PhantomData<S>,
79}
80
81impl<S: SphinxSuite> Debug for MetaPacket<S> {
82 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
83 write!(f, "[{}]", hex::encode(&self.packet))
84 }
85}
86
87impl<S: SphinxSuite> Clone for MetaPacket<S> {
89 fn clone(&self) -> Self {
90 Self {
91 packet: self.packet.clone(),
92 _s: PhantomData,
93 }
94 }
95}
96
97#[allow(dead_code)]
104pub enum ForwardedMetaPacket<S: SphinxSuite> {
105 Relayed {
107 packet: MetaPacket<S>,
109 next_node: <S::P as Keypair>::Public,
111 path_pos: u8,
113 additional_info: Box<[u8]>,
116 derived_secret: SharedSecret,
118 packet_tag: PacketTag,
120 },
121 Final {
123 plain_text: Box<[u8]>,
125 additional_data: Box<[u8]>,
127 derived_secret: SharedSecret,
129 packet_tag: PacketTag,
131 },
132}
133
134impl<S: SphinxSuite> MetaPacket<S> {
135 pub const HEADER_LEN: usize = header_length::<S>(INTERMEDIATE_HOPS + 1, POR_SECRET_LENGTH, 0);
137
138 pub const PACKET_LEN: usize = packet_length::<S>(INTERMEDIATE_HOPS + 1, POR_SECRET_LENGTH, 0);
140
141 pub fn new(
151 shared_keys: SharedKeys<S::E, S::G>,
152 msg: &[u8],
153 path: &[<S::P as Keypair>::Public],
154 max_hops: usize,
155 additional_relayer_data_len: usize,
156 additional_data_relayer: &[&[u8]],
157 additional_data_last_hop: Option<&[u8]>,
158 ) -> Self {
159 assert!(msg.len() <= PAYLOAD_SIZE, "message too long to fit into a packet");
160
161 let mut payload = add_padding(msg);
162
163 let routing_info = RoutingInfo::new::<S>(
164 max_hops,
165 path,
166 &shared_keys.secrets,
167 additional_relayer_data_len,
168 additional_data_relayer,
169 additional_data_last_hop,
170 );
171
172 for secret in shared_keys.secrets.iter().rev() {
174 let prp = PRP::from_parameters(PRPParameters::new(secret));
175 prp.forward_inplace(&mut payload)
176 .unwrap_or_else(|e| panic!("onion encryption error {e}"))
177 }
178
179 Self::new_from_parts(shared_keys.alpha, routing_info, &payload)
180 }
181
182 fn new_from_parts(
183 alpha: Alpha<<S::G as GroupElement<S::E>>::AlphaLen>,
184 routing_info: RoutingInfo,
185 payload: &[u8],
186 ) -> Self {
187 assert!(
188 !routing_info.routing_information.is_empty(),
189 "routing info must not be empty"
190 );
191 assert_eq!(
192 PAYLOAD_SIZE + PADDING_TAG.len(),
193 payload.len(),
194 "payload has incorrect length"
195 );
196
197 let mut packet = Vec::with_capacity(Self::SIZE);
198 packet.extend_from_slice(&alpha);
199 packet.extend_from_slice(&routing_info.routing_information);
200 packet.extend_from_slice(&routing_info.mac);
201 packet.extend_from_slice(payload);
202
203 Self {
204 packet: packet.into_boxed_slice(),
205 _s: PhantomData,
206 }
207 }
208
209 fn alpha(&self) -> &[u8] {
211 let len = <S::G as GroupElement<S::E>>::AlphaLen::USIZE;
212 &self.packet[..len]
213 }
214
215 fn routing_info(&self) -> &[u8] {
217 let base = <S::G as GroupElement<S::E>>::AlphaLen::USIZE;
218 &self.packet[base..base + Self::HEADER_LEN]
219 }
220
221 fn mac(&self) -> &[u8] {
223 let base = <S::G as GroupElement<S::E>>::AlphaLen::USIZE + Self::HEADER_LEN;
224 &self.packet[base..base + SimpleMac::SIZE]
225 }
226
227 fn payload(&self) -> &[u8] {
229 let base = <S::G as GroupElement<S::E>>::AlphaLen::USIZE + Self::HEADER_LEN + SimpleMac::SIZE;
230 &self.packet[base..base + PAYLOAD_SIZE + PADDING_TAG.len()]
231 }
232
233 pub fn into_forwarded(
236 self,
237 node_keypair: &S::P,
238 max_hops: usize,
239 additional_data_relayer_len: usize,
240 additional_data_last_hop_len: usize,
241 ) -> Result<ForwardedMetaPacket<S>> {
242 let (alpha, secret) = SharedKeys::<S::E, S::G>::forward_transform(
243 Alpha::<<S::G as GroupElement<S::E>>::AlphaLen>::from_slice(self.alpha()),
244 &(node_keypair.into()),
245 &(node_keypair.public().into()),
246 )?;
247
248 let mut routing_info_cpy: Vec<u8> = self.routing_info().into();
249 let fwd_header = forward_header::<S>(
250 &secret,
251 &mut routing_info_cpy,
252 self.mac(),
253 max_hops,
254 additional_data_relayer_len,
255 additional_data_last_hop_len,
256 )?;
257
258 let prp = PRP::from_parameters(PRPParameters::new(&secret));
259 let decrypted = prp.inverse(self.payload())?;
260
261 Ok(match fwd_header {
262 ForwardedHeader::RelayNode {
263 header,
264 mac,
265 path_pos,
266 next_node,
267 additional_info,
268 } => Relayed {
269 packet: Self::new_from_parts(
270 alpha,
271 RoutingInfo {
272 routing_information: header,
273 mac,
274 },
275 &decrypted,
276 ),
277 packet_tag: derive_packet_tag(&secret),
278 derived_secret: secret,
279 next_node: <S::P as Keypair>::Public::try_from(&next_node)
280 .map_err(|_| PacketDecodingError("couldn't parse next node id".into()))?,
281 path_pos,
282 additional_info,
283 },
284 ForwardedHeader::FinalNode { additional_data } => Final {
285 packet_tag: derive_packet_tag(&secret),
286 derived_secret: secret,
287 plain_text: remove_padding(&decrypted)
288 .ok_or(PacketDecodingError(format!(
289 "couldn't remove padding: {}",
290 hex::encode(decrypted.as_ref())
291 )))?
292 .into(),
293 additional_data,
294 },
295 })
296 }
297}
298
299impl<S: SphinxSuite> AsRef<[u8]> for MetaPacket<S> {
300 fn as_ref(&self) -> &[u8] {
301 self.packet.as_ref()
302 }
303}
304
305impl<S: SphinxSuite> TryFrom<&[u8]> for MetaPacket<S> {
306 type Error = GeneralError;
307
308 fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
309 if value.len() == Self::SIZE {
310 Ok(Self {
311 packet: value.into(),
312 _s: PhantomData,
313 })
314 } else {
315 Err(GeneralError::ParseError("MetaPacket".into()))
316 }
317 }
318}
319
320impl<S: SphinxSuite> BytesRepresentable for MetaPacket<S> {
321 const SIZE: usize = <S::G as GroupElement<S::E>>::AlphaLen::USIZE
322 + Self::HEADER_LEN
323 + SimpleMac::SIZE
324 + PAYLOAD_SIZE
325 + PADDING_TAG.len();
326}
327
328#[cfg(test)]
329mod tests {
330 use crate::packet::{add_padding, remove_padding, ForwardedMetaPacket, MetaPacket, PADDING_TAG};
331 use crate::por::{ProofOfRelayString, POR_SECRET_LENGTH};
332 use hopr_crypto_sphinx::{
333 ec_groups::{Ed25519Suite, Secp256k1Suite, X25519Suite},
334 shared_keys::SphinxSuite,
335 };
336 use hopr_crypto_types::keypairs::{ChainKeypair, Keypair, OffchainKeypair};
337 use hopr_internal_types::protocol::INTERMEDIATE_HOPS;
338 use parameterized::parameterized;
339
340 #[test]
341 fn test_padding() {
342 let data = b"test";
343 let padded = add_padding(data);
344
345 let mut expected = vec![0u8; 492];
346 expected.extend_from_slice(PADDING_TAG);
347 expected.extend_from_slice(data);
348 assert_eq!(&expected, padded.as_ref());
349
350 let unpadded = remove_padding(&padded);
351 assert!(unpadded.is_some());
352 assert_eq!(data, &unpadded.unwrap());
353 }
354
355 fn generic_test_meta_packet<S: SphinxSuite>(keypairs: Vec<S::P>) -> anyhow::Result<()> {
356 let pubkeys = keypairs.iter().map(|kp| kp.public().clone()).collect::<Vec<_>>();
357
358 let shared_keys = S::new_shared_keys(&pubkeys)?;
359 let por_strings = ProofOfRelayString::from_shared_secrets(&shared_keys.secrets);
360
361 assert_eq!(shared_keys.secrets.len() - 1, por_strings.len());
362
363 let msg = b"some random test message";
364
365 let mut mp = MetaPacket::<S>::new(
366 shared_keys,
367 msg,
368 &pubkeys,
369 INTERMEDIATE_HOPS + 1,
370 POR_SECRET_LENGTH,
371 &por_strings.iter().map(|v| v.as_ref()).collect::<Vec<_>>(),
372 None,
373 );
374
375 assert!(mp.as_ref().len() < 1492, "metapacket too long {}", mp.as_ref().len());
376
377 for (i, pair) in keypairs.iter().enumerate() {
378 let fwd = mp
379 .clone()
380 .into_forwarded(pair, INTERMEDIATE_HOPS + 1, POR_SECRET_LENGTH, 0)
381 .unwrap_or_else(|_| panic!("failed to unwrap at {i}"));
382
383 match fwd {
384 ForwardedMetaPacket::Relayed { packet, .. } => {
385 assert!(i < keypairs.len() - 1);
386 mp = packet;
387 }
388 ForwardedMetaPacket::Final {
389 plain_text,
390 additional_data,
391 ..
392 } => {
393 assert_eq!(keypairs.len() - 1, i);
394 assert_eq!(msg, plain_text.as_ref());
395 assert!(additional_data.is_empty());
396 }
397 }
398 }
399
400 Ok(())
401 }
402
403 #[parameterized(amount = { 4, 3, 2 })]
404 fn test_x25519_meta_packet(amount: usize) -> anyhow::Result<()> {
405 generic_test_meta_packet::<X25519Suite>((0..amount).map(|_| OffchainKeypair::random()).collect())
406 }
407
408 #[parameterized(amount = { 4, 3, 2 })]
409 fn test_ed25519_meta_packet(amount: usize) -> anyhow::Result<()> {
410 generic_test_meta_packet::<Ed25519Suite>((0..amount).map(|_| OffchainKeypair::random()).collect())
411 }
412
413 #[parameterized(amount = { 4, 3, 2 })]
414 fn test_secp256k1_meta_packet(amount: usize) -> anyhow::Result<()> {
415 generic_test_meta_packet::<Secp256k1Suite>((0..amount).map(|_| ChainKeypair::random()).collect())
416 }
417}