use hopr_crypto_sphinx::{
derivation::derive_packet_tag,
prp::{PRPParameters, PRP},
routing::{forward_header, header_length, ForwardedHeader, RoutingInfo},
shared_keys::{Alpha, GroupElement, SharedKeys, SharedSecret, SphinxSuite},
};
use hopr_crypto_types::{
keypairs::Keypair,
primitives::{DigestLike, SimpleMac},
types::PacketTag,
};
use hopr_internal_types::prelude::*;
use hopr_primitive_types::prelude::*;
use std::fmt::{Debug, Formatter};
use std::marker::PhantomData;
use typenum::Unsigned;
use crate::{
errors::{PacketError::PacketDecodingError, Result},
packet::ForwardedMetaPacket::{Final, Relayed},
por::POR_SECRET_LENGTH,
};
const PADDING_TAG: &[u8] = b"HOPR";
pub const fn packet_length<S: SphinxSuite>(
max_hops: usize,
additional_data_relayer_len: usize,
additional_data_last_hop_len: usize,
) -> usize {
<S::P as Keypair>::Public::SIZE
+ header_length::<S>(max_hops, additional_data_relayer_len, additional_data_last_hop_len)
+ SimpleMac::SIZE
+ PAYLOAD_SIZE
+ PADDING_TAG.len()
}
fn add_padding(msg: &[u8]) -> Box<[u8]> {
assert!(msg.len() <= PAYLOAD_SIZE, "message too long for padding");
let padded_len = PAYLOAD_SIZE + PADDING_TAG.len();
let mut ret = vec![0u8; padded_len];
ret[padded_len - msg.len()..padded_len].copy_from_slice(msg);
ret[padded_len - msg.len() - PADDING_TAG.len()..padded_len - msg.len()].copy_from_slice(PADDING_TAG);
ret.into_boxed_slice()
}
fn remove_padding(msg: &[u8]) -> Option<&[u8]> {
assert_eq!(
PAYLOAD_SIZE + PADDING_TAG.len(),
msg.len(),
"padded message must be {} bytes long",
PAYLOAD_SIZE + PADDING_TAG.len()
);
let pos = msg
.windows(PADDING_TAG.len())
.position(|window| window == PADDING_TAG)?;
Some(&msg.split_at(pos).1[PADDING_TAG.len()..])
}
pub struct MetaPacket<S: SphinxSuite> {
packet: Box<[u8]>,
_s: PhantomData<S>,
}
impl<S: SphinxSuite> Debug for MetaPacket<S> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}]", hex::encode(&self.packet))
}
}
impl<S: SphinxSuite> Clone for MetaPacket<S> {
fn clone(&self) -> Self {
Self {
packet: self.packet.clone(),
_s: PhantomData,
}
}
}
#[allow(dead_code)]
pub enum ForwardedMetaPacket<S: SphinxSuite> {
Relayed {
packet: MetaPacket<S>,
next_node: <S::P as Keypair>::Public,
path_pos: u8,
additional_info: Box<[u8]>,
derived_secret: SharedSecret,
packet_tag: PacketTag,
},
Final {
plain_text: Box<[u8]>,
additional_data: Box<[u8]>,
derived_secret: SharedSecret,
packet_tag: PacketTag,
},
}
impl<S: SphinxSuite> MetaPacket<S> {
pub const HEADER_LEN: usize = header_length::<S>(INTERMEDIATE_HOPS + 1, POR_SECRET_LENGTH, 0);
pub const PACKET_LEN: usize = packet_length::<S>(INTERMEDIATE_HOPS + 1, POR_SECRET_LENGTH, 0);
pub fn new(
shared_keys: SharedKeys<S::E, S::G>,
msg: &[u8],
path: &[<S::P as Keypair>::Public],
max_hops: usize,
additional_relayer_data_len: usize,
additional_data_relayer: &[&[u8]],
additional_data_last_hop: Option<&[u8]>,
) -> Self {
assert!(msg.len() <= PAYLOAD_SIZE, "message too long to fit into a packet");
let mut payload = add_padding(msg);
let routing_info = RoutingInfo::new::<S>(
max_hops,
path,
&shared_keys.secrets,
additional_relayer_data_len,
additional_data_relayer,
additional_data_last_hop,
);
for secret in shared_keys.secrets.iter().rev() {
let prp = PRP::from_parameters(PRPParameters::new(secret));
prp.forward_inplace(&mut payload)
.unwrap_or_else(|e| panic!("onion encryption error {e}"))
}
Self::new_from_parts(shared_keys.alpha, routing_info, &payload)
}
fn new_from_parts(
alpha: Alpha<<S::G as GroupElement<S::E>>::AlphaLen>,
routing_info: RoutingInfo,
payload: &[u8],
) -> Self {
assert!(
!routing_info.routing_information.is_empty(),
"routing info must not be empty"
);
assert_eq!(
PAYLOAD_SIZE + PADDING_TAG.len(),
payload.len(),
"payload has incorrect length"
);
let mut packet = Vec::with_capacity(Self::SIZE);
packet.extend_from_slice(&alpha);
packet.extend_from_slice(&routing_info.routing_information);
packet.extend_from_slice(&routing_info.mac);
packet.extend_from_slice(payload);
Self {
packet: packet.into_boxed_slice(),
_s: PhantomData,
}
}
fn alpha(&self) -> &[u8] {
let len = <S::G as GroupElement<S::E>>::AlphaLen::USIZE;
&self.packet[..len]
}
fn routing_info(&self) -> &[u8] {
let base = <S::G as GroupElement<S::E>>::AlphaLen::USIZE;
&self.packet[base..base + Self::HEADER_LEN]
}
fn mac(&self) -> &[u8] {
let base = <S::G as GroupElement<S::E>>::AlphaLen::USIZE + Self::HEADER_LEN;
&self.packet[base..base + SimpleMac::SIZE]
}
fn payload(&self) -> &[u8] {
let base = <S::G as GroupElement<S::E>>::AlphaLen::USIZE + Self::HEADER_LEN + SimpleMac::SIZE;
&self.packet[base..base + PAYLOAD_SIZE + PADDING_TAG.len()]
}
pub fn into_forwarded(
self,
node_keypair: &S::P,
max_hops: usize,
additional_data_relayer_len: usize,
additional_data_last_hop_len: usize,
) -> Result<ForwardedMetaPacket<S>> {
let (alpha, secret) = SharedKeys::<S::E, S::G>::forward_transform(
Alpha::<<S::G as GroupElement<S::E>>::AlphaLen>::from_slice(self.alpha()),
&(node_keypair.into()),
&(node_keypair.public().into()),
)?;
let mut routing_info_cpy: Vec<u8> = self.routing_info().into();
let fwd_header = forward_header::<S>(
&secret,
&mut routing_info_cpy,
self.mac(),
max_hops,
additional_data_relayer_len,
additional_data_last_hop_len,
)?;
let prp = PRP::from_parameters(PRPParameters::new(&secret));
let decrypted = prp.inverse(self.payload())?;
Ok(match fwd_header {
ForwardedHeader::RelayNode {
header,
mac,
path_pos,
next_node,
additional_info,
} => Relayed {
packet: Self::new_from_parts(
alpha,
RoutingInfo {
routing_information: header,
mac,
},
&decrypted,
),
packet_tag: derive_packet_tag(&secret),
derived_secret: secret,
next_node: <S::P as Keypair>::Public::try_from(&next_node)
.map_err(|_| PacketDecodingError("couldn't parse next node id".into()))?,
path_pos,
additional_info,
},
ForwardedHeader::FinalNode { additional_data } => Final {
packet_tag: derive_packet_tag(&secret),
derived_secret: secret,
plain_text: remove_padding(&decrypted)
.ok_or(PacketDecodingError(format!(
"couldn't remove padding: {}",
hex::encode(decrypted.as_ref())
)))?
.into(),
additional_data,
},
})
}
}
impl<S: SphinxSuite> AsRef<[u8]> for MetaPacket<S> {
fn as_ref(&self) -> &[u8] {
self.packet.as_ref()
}
}
impl<S: SphinxSuite> TryFrom<&[u8]> for MetaPacket<S> {
type Error = GeneralError;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
if value.len() == Self::SIZE {
Ok(Self {
packet: value.into(),
_s: PhantomData,
})
} else {
Err(GeneralError::ParseError("MetaPacket".into()))
}
}
}
impl<S: SphinxSuite> BytesRepresentable for MetaPacket<S> {
const SIZE: usize = <S::G as GroupElement<S::E>>::AlphaLen::USIZE
+ Self::HEADER_LEN
+ SimpleMac::SIZE
+ PAYLOAD_SIZE
+ PADDING_TAG.len();
}
#[cfg(test)]
mod tests {
use crate::packet::{add_padding, remove_padding, ForwardedMetaPacket, MetaPacket, PADDING_TAG};
use crate::por::{ProofOfRelayString, POR_SECRET_LENGTH};
use hopr_crypto_sphinx::{
ec_groups::{Ed25519Suite, Secp256k1Suite, X25519Suite},
shared_keys::SphinxSuite,
};
use hopr_crypto_types::keypairs::{ChainKeypair, Keypair, OffchainKeypair};
use hopr_internal_types::protocol::INTERMEDIATE_HOPS;
use parameterized::parameterized;
#[test]
fn test_padding() {
let data = b"test";
let padded = add_padding(data);
let mut expected = vec![0u8; 492];
expected.extend_from_slice(PADDING_TAG);
expected.extend_from_slice(data);
assert_eq!(&expected, padded.as_ref());
let unpadded = remove_padding(&padded);
assert!(unpadded.is_some());
assert_eq!(data, &unpadded.unwrap());
}
fn generic_test_meta_packet<S: SphinxSuite>(keypairs: Vec<S::P>) -> anyhow::Result<()> {
let pubkeys = keypairs.iter().map(|kp| kp.public().clone()).collect::<Vec<_>>();
let shared_keys = S::new_shared_keys(&pubkeys)?;
let por_strings = ProofOfRelayString::from_shared_secrets(&shared_keys.secrets);
assert_eq!(shared_keys.secrets.len() - 1, por_strings.len());
let msg = b"some random test message";
let mut mp = MetaPacket::<S>::new(
shared_keys,
msg,
&pubkeys,
INTERMEDIATE_HOPS + 1,
POR_SECRET_LENGTH,
&por_strings.iter().map(|v| v.as_ref()).collect::<Vec<_>>(),
None,
);
assert!(mp.as_ref().len() < 1492, "metapacket too long {}", mp.as_ref().len());
for (i, pair) in keypairs.iter().enumerate() {
let fwd = mp
.clone()
.into_forwarded(pair, INTERMEDIATE_HOPS + 1, POR_SECRET_LENGTH, 0)
.unwrap_or_else(|_| panic!("failed to unwrap at {i}"));
match fwd {
ForwardedMetaPacket::Relayed { packet, .. } => {
assert!(i < keypairs.len() - 1);
mp = packet;
}
ForwardedMetaPacket::Final {
plain_text,
additional_data,
..
} => {
assert_eq!(keypairs.len() - 1, i);
assert_eq!(msg, plain_text.as_ref());
assert!(additional_data.is_empty());
}
}
}
Ok(())
}
#[parameterized(amount = { 4, 3, 2 })]
fn test_x25519_meta_packet(amount: usize) -> anyhow::Result<()> {
generic_test_meta_packet::<X25519Suite>((0..amount).map(|_| OffchainKeypair::random()).collect())
}
#[parameterized(amount = { 4, 3, 2 })]
fn test_ed25519_meta_packet(amount: usize) -> anyhow::Result<()> {
generic_test_meta_packet::<Ed25519Suite>((0..amount).map(|_| OffchainKeypair::random()).collect())
}
#[parameterized(amount = { 4, 3, 2 })]
fn test_secp256k1_meta_packet(amount: usize) -> anyhow::Result<()> {
generic_test_meta_packet::<Secp256k1Suite>((0..amount).map(|_| ChainKeypair::random()).collect())
}
}