further propagation of key rotation information

This commit is contained in:
Jędrzej Stuczyński
2025-04-24 14:17:34 +01:00
parent 85b51f21e5
commit b95afd11af
18 changed files with 272 additions and 171 deletions
@@ -21,8 +21,8 @@ use nym_crypto::asymmetric::ed25519;
use nym_gateway_requests::registration::handshake::client_handshake;
use nym_gateway_requests::{
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
SensitiveServerResponse, ServerResponse, SharedGatewayKey, SharedSymmetricKey,
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
GatewayRequestsError, SensitiveServerResponse, ServerResponse, SharedGatewayKey,
SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
};
use nym_sphinx::forwarding::packet::MixPacket;
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
@@ -662,6 +662,7 @@ impl<C, St> GatewayClient<C, St> {
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
let supports_key_rotation_info = gw_protocol.supports_key_rotation_packet();
if !supports_aes_gcm_siv {
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
@@ -669,6 +670,9 @@ impl<C, St> GatewayClient<C, St> {
if !supports_auth_v2 {
warn!("this gateway is on an old version that doesn't support authentication v2")
}
if !supports_key_rotation_info {
warn!("this gateway is on an old version that doesn't support key rotation packets")
}
if self.authenticated {
debug!("Already authenticated");
@@ -849,6 +853,22 @@ impl<C, St> GatewayClient<C, St> {
}
}
fn mix_packet_to_ws_message(&self, packet: MixPacket) -> Result<Message, GatewayRequestsError> {
// note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should
// be more explicit in the naming?
let req = if self.negotiated_protocol.supports_key_rotation_packet() {
BinaryRequest::ForwardSphinxV2 { packet }
} else {
BinaryRequest::ForwardSphinx { packet }
};
req.into_ws_message(
self.shared_key
.as_ref()
.expect("no shared key present even though we're authenticated!"),
)
}
pub async fn batch_send_mix_packets(
&mut self,
packets: Vec<MixPacket>,
@@ -877,13 +897,7 @@ impl<C, St> GatewayClient<C, St> {
let messages: Result<Vec<_>, _> = packets
.into_iter()
.map(|mix_packet| {
BinaryRequest::ForwardSphinx { packet: mix_packet }.into_ws_message(
self.shared_key
.as_ref()
.expect("no shared key present even though we're authenticated!"),
)
})
.map(|mix_packet| self.mix_packet_to_ws_message(mix_packet))
.collect();
if let Err(err) = self
@@ -949,13 +963,8 @@ impl<C, St> GatewayClient<C, St> {
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
}
// note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should
// be more explicit in the naming?
let msg = BinaryRequest::ForwardSphinx { packet: mix_packet }.into_ws_message(
self.shared_key
.as_ref()
.expect("no shared key present even though we're authenticated!"),
)?;
let msg = self.mix_packet_to_ws_message(mix_packet)?;
self.send_with_reconnection_on_failure(msg).await
}
+12 -22
View File
@@ -3,11 +3,10 @@
use dashmap::DashMap;
use futures::StreamExt;
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_sphinx::framing::codec::NymCodec;
use nym_sphinx::framing::packet::FramedNymPacket;
use nym_sphinx::params::PacketType;
use nym_sphinx::NymPacket;
use nym_sphinx::params::SphinxKeyRotation;
use std::io;
use std::net::SocketAddr;
use std::ops::Deref;
@@ -49,12 +48,7 @@ impl Config {
pub trait SendWithoutResponse {
// Without response in this context means we will not listen for anything we might get back (not
// that we should get anything), including any possible io errors
fn send_without_response(
&self,
address: NymNodeRoutingAddress,
packet: NymPacket,
packet_type: PacketType,
) -> io::Result<()>;
fn send_without_response(&self, packet: MixPacket) -> io::Result<()>;
}
pub struct Client {
@@ -65,7 +59,7 @@ pub struct Client {
#[derive(Default, Clone)]
pub struct ActiveConnections {
inner: Arc<DashMap<NymNodeRoutingAddress, ConnectionSender>>,
inner: Arc<DashMap<SocketAddr, ConnectionSender>>,
}
impl ActiveConnections {
@@ -82,7 +76,7 @@ impl ActiveConnections {
}
impl Deref for ActiveConnections {
type Target = DashMap<NymNodeRoutingAddress, ConnectionSender>;
type Target = DashMap<SocketAddr, ConnectionSender>;
fn deref(&self) -> &Self::Target {
&self.inner
}
@@ -196,7 +190,7 @@ impl Client {
}
}
fn make_connection(&self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
fn make_connection(&self, address: SocketAddr, pending_packet: FramedNymPacket) {
let (sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
// this CAN'T fail because we just created the channel which has a non-zero capacity
@@ -233,7 +227,7 @@ impl Client {
connections_count.fetch_add(1, Ordering::SeqCst);
ManagedConnection::new(
address.into(),
address,
receiver,
initial_connection_timeout,
current_reconnection_attempt,
@@ -246,18 +240,14 @@ impl Client {
}
impl SendWithoutResponse for Client {
fn send_without_response(
&self,
address: NymNodeRoutingAddress,
packet: NymPacket,
packet_type: PacketType,
) -> io::Result<()> {
trace!("Sending packet to {address:?}");
let framed_packet = FramedNymPacket::new(packet, packet_type);
fn send_without_response(&self, packet: MixPacket) -> io::Result<()> {
let address = packet.next_hop_address();
trace!("Sending packet to {address}");
let framed_packet = FramedNymPacket::from(packet);
let Some(sender) = self.active_connections.get_mut(&address) else {
// there was never a connection to begin with
debug!("establishing initial connection to {}", address);
debug!("establishing initial connection to {address}");
// it's not a 'big' error, but we did not manage to send the packet, but queue the packet
// for sending for as soon as the connection is created
self.make_connection(address, framed_packet);
+9 -1
View File
@@ -19,7 +19,7 @@ pub use shared_key::{
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
};
pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
pub const CURRENT_PROTOCOL_VERSION: u8 = EMBEDDED_KEY_ROTATION_INFO_VERSION;
/// Defines the current version of the communication protocol between gateway and clients.
/// It has to be incremented for any breaking change.
@@ -28,10 +28,12 @@ pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
// 2 - changes to client credentials structure
// 3 - change to AES-GCM-SIV and non-zero IVs
// 4 - introduction of v2 authentication protocol to prevent reply attacks
// 5 - add key rotation information to the serialised mix packet
pub const INITIAL_PROTOCOL_VERSION: u8 = 1;
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4;
pub const EMBEDDED_KEY_ROTATION_INFO_VERSION: u8 = 5;
// TODO: could using `Mac` trait here for OutputSize backfire?
// Should hmac itself be exposed, imported and used instead?
@@ -40,6 +42,7 @@ pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser
pub trait GatewayProtocolVersionExt {
fn supports_aes256_gcm_siv(&self) -> bool;
fn supports_authenticate_v2(&self) -> bool;
fn supports_key_rotation_packet(&self) -> bool;
}
impl GatewayProtocolVersionExt for Option<u8> {
@@ -52,4 +55,9 @@ impl GatewayProtocolVersionExt for Option<u8> {
let Some(protocol) = *self else { return false };
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
}
fn supports_key_rotation_packet(&self) -> bool {
let Some(protocol) = *self else { return false };
protocol >= EMBEDDED_KEY_ROTATION_INFO_VERSION
}
}
@@ -11,6 +11,9 @@ use tungstenite::Message;
#[non_exhaustive]
pub enum BinaryRequest {
ForwardSphinx { packet: MixPacket },
// identical to `ForwardSphinx`, but also contains information about sphinx key rotation used
ForwardSphinxV2 { packet: MixPacket },
}
#[repr(u8)]
@@ -18,6 +21,9 @@ pub enum BinaryRequest {
#[non_exhaustive]
pub enum BinaryRequestKind {
ForwardSphinx = 1,
// identical to `ForwardSphinx`, but also contains information about sphinx key rotation used
ForwardSphinxV2 = 2,
}
// Right now the only valid `BinaryRequest` is a request to forward a sphinx packet.
@@ -29,6 +35,7 @@ impl BinaryRequest {
pub fn kind(&self) -> BinaryRequestKind {
match self {
BinaryRequest::ForwardSphinx { .. } => BinaryRequestKind::ForwardSphinx,
BinaryRequest::ForwardSphinxV2 { .. } => BinaryRequestKind::ForwardSphinxV2,
}
}
@@ -38,9 +45,13 @@ impl BinaryRequest {
) -> Result<Self, GatewayRequestsError> {
match kind {
BinaryRequestKind::ForwardSphinx => {
let packet = MixPacket::try_from_bytes(plaintext)?;
let packet = MixPacket::try_from_v1_bytes(plaintext)?;
Ok(BinaryRequest::ForwardSphinx { packet })
}
BinaryRequestKind::ForwardSphinxV2 => {
let packet = MixPacket::try_from_v2_bytes(plaintext)?;
Ok(BinaryRequest::ForwardSphinxV2 { packet })
}
}
}
@@ -58,7 +69,8 @@ impl BinaryRequest {
let kind = self.kind();
let plaintext = match self {
BinaryRequest::ForwardSphinx { packet } => packet.into_bytes()?,
BinaryRequest::ForwardSphinx { packet } => packet.into_v1_bytes()?,
BinaryRequest::ForwardSphinxV2 { packet } => packet.into_v2_bytes()?,
};
BinaryData::make_encrypted_blob(kind as u8, &plaintext, shared_key)
@@ -70,7 +82,9 @@ impl BinaryRequest {
) -> Result<Message, GatewayRequestsError> {
// all variants are currently encrypted
let blob = match self {
BinaryRequest::ForwardSphinx { .. } => self.into_encrypted_tagged_bytes(shared_key)?,
BinaryRequest::ForwardSphinx { .. } | BinaryRequest::ForwardSphinxV2 { .. } => {
self.into_encrypted_tagged_bytes(shared_key)?
}
};
Ok(Message::Binary(blob))
@@ -8,7 +8,7 @@ use nym_sphinx_addressing::nodes::{
NymNodeRoutingAddress, NymNodeRoutingAddressError, MAX_NODE_ADDRESS_UNPADDED_LEN,
};
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm};
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, SphinxKeyRotation};
use nym_sphinx_types::{
NymPacket, SURBMaterial, SphinxError, HEADER_SIZE, NODE_ADDRESS_LENGTH, SURB,
X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION,
@@ -46,44 +46,7 @@ pub enum ReplySurbError {
pub struct ReplySurb {
pub(crate) surb: SURB,
pub(crate) encryption_key: SurbEncryptionKey,
}
// Serialize + Deserialize is not really used anymore (it was for a CBOR experiment)
// however, if we decided we needed it again, it's already here
impl Serialize for ReplySurb {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&self.to_bytes())
}
}
impl<'de> Deserialize<'de> for ReplySurb {
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
struct ReplySurbVisitor;
impl Visitor<'_> for ReplySurbVisitor {
type Value = ReplySurb;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, "A replySURB must contain a valid symmetric encryption key and a correctly formed sphinx header")
}
fn visit_bytes<E>(self, bytes: &[u8]) -> Result<Self::Value, E>
where
E: SerdeError,
{
ReplySurb::from_bytes(bytes)
.map_err(|_| SerdeError::invalid_length(bytes.len(), &self))
}
}
deserializer.deserialize_bytes(ReplySurbVisitor)
}
// pub(crate) used_key_rotation: SphinxKeyRotation,
}
impl ReplySurb {
@@ -123,6 +86,7 @@ impl ReplySurb {
Ok(ReplySurb {
surb: surb_material.construct_SURB().unwrap(),
encryption_key: SurbEncryptionKey::new(rng),
// used_key_rotation: SphinxKeyRotation::from(topology.current_key_rotation()),
})
}
@@ -198,8 +162,14 @@ impl ReplySurb {
.use_surb(message_bytes, packet_size.payload_size())
.expect("this error indicates inconsistent message length checking - it shouldn't have happened!");
let first_hop_address = NymNodeRoutingAddress::try_from(first_hop).unwrap();
let first_hop_address = NymNodeRoutingAddress::try_from(first_hop)?;
Ok((NymPacket::Sphinx(packet), first_hop_address))
}
}
pub struct AppliedReplySurb {
packet: NymPacket,
first_hop_address: NymNodeRoutingAddress,
key_rotation: SphinxKeyRotation,
}
+2 -1
View File
@@ -146,7 +146,8 @@ where
)?,
};
Ok(MixPacket::new(first_hop_address, packet, packet_type))
todo!()
// Ok(MixPacket::new(first_hop_address, packet, packet_type))
}
/// Helper function used to determine if given message represents a loop cover message.
+93 -35
View File
@@ -2,24 +2,35 @@
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
use nym_sphinx_params::{PacketSize, PacketType};
use nym_sphinx_params::{PacketSize, PacketType, SphinxKeyRotation};
use nym_sphinx_types::{NymPacket, NymPacketError};
use std::fmt::{self, Debug, Formatter};
use nym_sphinx_params::key_rotation::InvalidSphinxKeyRotation;
use nym_sphinx_params::packet_sizes::InvalidPacketSize;
use nym_sphinx_params::packet_types::InvalidPacketType;
use std::net::SocketAddr;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MixPacketFormattingError {
#[error("too few bytes provided to recover from bytes")]
TooFewBytesProvided,
#[error("provided packet mode is invalid")]
InvalidPacketType,
#[error("received request had invalid size - received {0}")]
InvalidPacketSize(usize),
#[error("provided packet mode is invalid: {0}")]
InvalidPacketType(#[from] InvalidPacketType),
#[error("received request had an invalid packet size: {0}")]
InvalidPacketSize(#[from] InvalidPacketSize),
#[error("provided key rotation is invalid: {0}")]
InvalidKeyRotation(#[from] InvalidSphinxKeyRotation),
#[error("address field was incorrectly encoded")]
InvalidAddress,
#[error("received sphinx packet was malformed")]
MalformedSphinxPacket,
#[error("Packet: {0}")]
Packet(#[from] NymPacketError),
}
@@ -30,20 +41,12 @@ impl From<NymNodeRoutingAddressError> for MixPacketFormattingError {
}
}
#[derive(Debug)]
pub struct MixPacket {
next_hop: NymNodeRoutingAddress,
packet: NymPacket,
packet_type: PacketType,
}
impl Debug for MixPacket {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"MixPacket to {:?} with packet_type {:?}. Packet {:?}",
self.next_hop, self.packet_type, self.packet
)
}
key_rotation: SphinxKeyRotation,
}
impl MixPacket {
@@ -51,11 +54,13 @@ impl MixPacket {
next_hop: NymNodeRoutingAddress,
packet: NymPacket,
packet_type: PacketType,
key_rotation: SphinxKeyRotation,
) -> Self {
MixPacket {
next_hop,
packet,
packet_type,
key_rotation,
}
}
@@ -63,6 +68,10 @@ impl MixPacket {
self.next_hop
}
pub fn next_hop_address(&self) -> SocketAddr {
self.next_hop.into()
}
pub fn packet(&self) -> &NymPacket {
&self.packet
}
@@ -71,45 +80,94 @@ impl MixPacket {
self.packet
}
pub fn key_rotation(&self) -> SphinxKeyRotation {
self.key_rotation
}
pub fn packet_type(&self) -> PacketType {
self.packet_type
}
// the message is formatted as follows:
// packet_type || FIRST_HOP || packet
pub fn try_from_bytes(b: &[u8]) -> Result<Self, MixPacketFormattingError> {
let packet_type = match PacketType::try_from(b[0]) {
Ok(mode) => mode,
Err(_) => return Err(MixPacketFormattingError::InvalidPacketType),
};
pub fn try_from_v1_bytes(b: &[u8]) -> Result<Self, MixPacketFormattingError> {
// we need at least 1 byte to read packet type and another one to read type of the encoded first hop address
if b.len() < 2 {
return Err(MixPacketFormattingError::TooFewBytesProvided);
}
let packet_type = PacketType::try_from(b[0])?;
let next_hop = NymNodeRoutingAddress::try_from_bytes(&b[1..])?;
let addr_offset = next_hop.bytes_min_len();
let packet_data = &b[addr_offset + 1..];
let packet_size = packet_data.len();
if PacketSize::get_type(packet_size).is_err() {
Err(MixPacketFormattingError::InvalidPacketSize(packet_size))
} else {
let packet = match packet_type {
PacketType::Outfox => NymPacket::outfox_from_bytes(packet_data)?,
_ => NymPacket::sphinx_from_bytes(packet_data)?,
};
Ok(MixPacket {
next_hop,
packet,
packet_type,
})
}
// make sure the received data length corresponds to a valid packet
let _ = PacketSize::get_type(packet_size)?;
let packet = match packet_type {
PacketType::Mix => NymPacket::sphinx_from_bytes(packet_data)?,
PacketType::Outfox => NymPacket::outfox_from_bytes(packet_data)?,
};
Ok(MixPacket {
next_hop,
packet,
packet_type,
key_rotation: SphinxKeyRotation::Unknown,
})
}
pub fn into_bytes(self) -> Result<Vec<u8>, MixPacketFormattingError> {
pub fn into_v1_bytes(self) -> Result<Vec<u8>, MixPacketFormattingError> {
Ok(std::iter::once(self.packet_type as u8)
.chain(self.next_hop.as_bytes())
.chain(self.packet.to_bytes()?)
.collect())
}
// the message is formatted as follows:
// packet_type || KEY_ROTATION || FIRST_HOP || packet
pub fn try_from_v2_bytes(b: &[u8]) -> Result<Self, MixPacketFormattingError> {
// we need at least 1 byte to read packet type, 1 byte to read key rotation
// and finally another one to read type of the encoded first hop address
if b.len() < 3 {
return Err(MixPacketFormattingError::TooFewBytesProvided);
}
let packet_type = PacketType::try_from(b[0])?;
let key_rotation = SphinxKeyRotation::try_from(b[1])?;
let next_hop = NymNodeRoutingAddress::try_from_bytes(&b[2..])?;
let addr_offset = next_hop.bytes_min_len();
let packet_data = &b[addr_offset + 2..];
let packet_size = packet_data.len();
// make sure the received data length corresponds to a valid packet
let _ = PacketSize::get_type(packet_size)?;
let packet = match packet_type {
PacketType::Mix => NymPacket::sphinx_from_bytes(packet_data)?,
PacketType::Outfox => NymPacket::outfox_from_bytes(packet_data)?,
};
Ok(MixPacket {
next_hop,
packet,
packet_type,
key_rotation,
})
}
pub fn into_v2_bytes(self) -> Result<Vec<u8>, MixPacketFormattingError> {
Ok(std::iter::once(self.packet_type as u8)
.chain(std::iter::once(self.key_rotation as u8))
.chain(self.next_hop.as_bytes())
.chain(self.packet.to_bytes()?)
.collect())
}
}
// TODO: test for serialization and errors!
+13
View File
@@ -3,6 +3,7 @@
use crate::codec::NymCodecError;
use bytes::{BufMut, BytesMut};
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_params::key_rotation::SphinxKeyRotation;
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::packet_version::{PacketVersion, CURRENT_PACKET_VERSION};
@@ -18,6 +19,14 @@ pub struct FramedNymPacket {
pub(crate) packet: NymPacket,
}
impl From<MixPacket> for FramedNymPacket {
fn from(packet: MixPacket) -> Self {
let typ = packet.packet_type();
let rot = packet.key_rotation();
FramedNymPacket::new(packet.into_packet(), typ, rot)
}
}
impl FramedNymPacket {
pub fn new(
packet: NymPacket,
@@ -58,6 +67,10 @@ impl FramedNymPacket {
&self.packet
}
pub fn key_rotation(&self) -> SphinxKeyRotation {
self.header.key_rotation
}
pub fn is_sphinx(&self) -> bool {
self.packet.is_sphinx()
}
+21 -8
View File
@@ -5,7 +5,7 @@ use crate::packet::FramedNymPacket;
use nym_sphinx_acknowledgements::surb_ack::{SurbAck, SurbAckRecoveryError};
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_params::{PacketSize, PacketType};
use nym_sphinx_params::{PacketSize, PacketType, SphinxKeyRotation};
use nym_sphinx_types::header::shared_secret::ExpandedSharedSecret;
use nym_sphinx_types::{
Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, NymPacket, NymPacketError,
@@ -153,6 +153,7 @@ impl PartiallyUnwrappedPacket {
let packet_size = self.received_data.packet_size();
let packet_type = self.received_data.packet_type();
let key_rotation = self.received_data.header.key_rotation;
let packet = self.received_data.into_inner();
// currently partial unwrapping is only implemented for sphinx packets.
@@ -167,7 +168,7 @@ impl PartiallyUnwrappedPacket {
return Err(PacketProcessingError::PartialOutfoxProcessing);
};
let processed_packet = packet.process_with_expanded_secret(&expanded_shared_secret)?;
wrap_processed_sphinx_packet(processed_packet, packet_size, packet_type)
wrap_processed_sphinx_packet(processed_packet, packet_size, packet_type, key_rotation)
}
pub fn replay_tag(&self) -> Option<&[u8; REPLAY_TAG_SIZE]> {
@@ -192,13 +193,14 @@ pub fn process_framed_packet(
) -> Result<MixProcessingResult, PacketProcessingError> {
let packet_size = received.packet_size();
let packet_type = received.packet_type();
let key_rotation = received.key_rotation();
// unwrap the sphinx packet
let processed_packet = perform_framed_unwrapping(received, sphinx_key)?;
// for forward packets, extract next hop and set delay (but do NOT delay here)
// for final packets, extract SURBAck
perform_final_processing(processed_packet, packet_size, packet_type)
perform_final_processing(processed_packet, packet_size, packet_type, key_rotation)
}
fn perform_framed_unwrapping(
@@ -223,6 +225,7 @@ fn wrap_processed_sphinx_packet(
packet: nym_sphinx_types::ProcessedPacket,
packet_size: PacketSize,
packet_type: PacketType,
key_rotation: SphinxKeyRotation,
) -> Result<MixProcessingResult, PacketProcessingError> {
let processing_data = match packet.data {
ProcessedPacketData::ForwardHop {
@@ -234,6 +237,7 @@ fn wrap_processed_sphinx_packet(
next_hop_address,
delay,
packet_type,
key_rotation,
),
// right now there's no use for the surb_id included in the header - probably it should get removed from the
// sphinx all together?
@@ -246,6 +250,7 @@ fn wrap_processed_sphinx_packet(
payload.recover_plaintext()?,
packet_size,
packet_type,
key_rotation,
),
}?;
@@ -259,6 +264,7 @@ fn wrap_processed_outfox_packet(
packet: OutfoxProcessedPacket,
packet_size: PacketSize,
packet_type: PacketType,
key_rotation: SphinxKeyRotation,
) -> Result<MixProcessingResult, PacketProcessingError> {
let next_address = *packet.next_address();
let packet = packet.into_packet();
@@ -268,6 +274,7 @@ fn wrap_processed_outfox_packet(
packet.recover_plaintext()?.to_vec(),
packet_size,
packet_type,
key_rotation,
)?;
Ok(MixProcessingResult {
packet_version: MixPacketVersion::Outfox,
@@ -278,6 +285,7 @@ fn wrap_processed_outfox_packet(
NymNodeRoutingAddress::try_from_bytes(&next_address)?,
NymPacket::Outfox(packet),
PacketType::Outfox,
SphinxKeyRotation::Unknown,
);
Ok(MixProcessingResult {
packet_version: MixPacketVersion::Outfox,
@@ -293,13 +301,14 @@ fn perform_final_processing(
packet: NymProcessedPacket,
packet_size: PacketSize,
packet_type: PacketType,
key_rotation: SphinxKeyRotation,
) -> Result<MixProcessingResult, PacketProcessingError> {
match packet {
NymProcessedPacket::Sphinx(packet) => {
wrap_processed_sphinx_packet(packet, packet_size, packet_type)
wrap_processed_sphinx_packet(packet, packet_size, packet_type, key_rotation)
}
NymProcessedPacket::Outfox(packet) => {
wrap_processed_outfox_packet(packet, packet_size, packet_type)
wrap_processed_outfox_packet(packet, packet_size, packet_type, key_rotation)
}
}
}
@@ -309,8 +318,10 @@ fn process_final_hop(
payload: Vec<u8>,
packet_size: PacketSize,
packet_type: PacketType,
key_rotation: SphinxKeyRotation,
) -> Result<MixProcessingResultData, PacketProcessingError> {
let (forward_ack, message) = split_into_ack_and_message(payload, packet_size, packet_type)?;
let (forward_ack, message) =
split_into_ack_and_message(payload, packet_size, packet_type, key_rotation)?;
Ok(MixProcessingResultData::FinalHop {
final_hop_data: ProcessedFinalHop {
@@ -325,6 +336,7 @@ fn split_into_ack_and_message(
data: Vec<u8>,
packet_size: PacketSize,
packet_type: PacketType,
key_rotation: SphinxKeyRotation,
) -> Result<(Option<MixPacket>, Vec<u8>), PacketProcessingError> {
match packet_size {
PacketSize::AckPacket | PacketSize::OutfoxAckPacket => {
@@ -346,7 +358,7 @@ fn split_into_ack_and_message(
return Err(err.into());
}
};
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type);
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type, key_rotation);
Ok((Some(forward_ack), message))
}
}
@@ -374,10 +386,11 @@ fn process_forward_hop(
forward_address: NodeAddressBytes,
delay: SphinxDelay,
packet_type: PacketType,
key_rotation: SphinxKeyRotation,
) -> Result<MixProcessingResultData, PacketProcessingError> {
let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?;
let packet = MixPacket::new(next_hop_address, packet, packet_type);
let packet = MixPacket::new(next_hop_address, packet, packet_type, key_rotation);
Ok(MixProcessingResultData::ForwardHop {
packet,
delay: Some(delay),
+1 -1
View File
@@ -11,7 +11,7 @@ use std::fmt;
use thiserror::Error;
#[derive(Error, Debug)]
#[error("{received} is not a valid packet mode tag")]
#[error("{received} is not a valid packet type tag")]
pub struct InvalidPacketType {
received: u8,
}
+15 -10
View File
@@ -13,7 +13,7 @@ use nym_sphinx_anonymous_replies::reply_surb::ReplySurb;
use nym_sphinx_chunking::fragment::{Fragment, FragmentIdentifier};
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm};
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, SphinxKeyRotation};
use nym_sphinx_types::{Delay, NymPacket};
use nym_topology::{NymRouteProvider, NymTopologyError};
use rand::{CryptoRng, Rng, SeedableRng};
@@ -152,14 +152,16 @@ pub trait FragmentPreparer {
.apply_surb(packet_payload, packet_size, packet_type)
.unwrap();
Ok(PreparedFragment {
// the round-trip delay is the sum of delays of all hops on the forward route as
// well as the total delay of the ack packet.
// we don't know the delays inside the reply surbs so we use best-effort estimation from our poisson distribution
total_delay: expected_forward_delay + ack_delay,
mix_packet: MixPacket::new(first_hop_address, sphinx_packet, packet_type),
fragment_identifier,
})
todo!("somehow get key rotation information here")
// Ok(PreparedFragment {
// // the round-trip delay is the sum of delays of all hops on the forward route as
// // well as the total delay of the ack packet.
// // we don't know the delays inside the reply surbs so we use best-effort estimation from our poisson distribution
// total_delay: expected_forward_delay + ack_delay,
// mix_packet: MixPacket::new(first_hop_address, sphinx_packet, packet_type, key_rotation),
// fragment_identifier,
// })
}
/// Tries to convert this [`Fragment`] into a [`SphinxPacket`] that can be sent through the Nym mix-network,
@@ -211,6 +213,9 @@ pub trait FragmentPreparer {
let packet_size = PacketSize::get_type_from_plaintext(expected_plaintext, packet_type)
.expect("the message has been incorrectly fragmented");
let rotation_id = topology.current_key_rotation();
let sphinx_key_rotation = SphinxKeyRotation::from(rotation_id);
let fragment_identifier = fragment.fragment_identifier();
// create an ack
@@ -279,7 +284,7 @@ pub trait FragmentPreparer {
// well as the total delay of the ack packet.
// note that the last hop of the packet is a gateway that does not do any delays
total_delay: delays.iter().take(delays.len() - 1).sum::<Delay>() + ack_delay,
mix_packet: MixPacket::new(first_hop_address, packet, packet_type),
mix_packet: MixPacket::new(first_hop_address, packet, packet_type, sphinx_key_rotation),
fragment_identifier,
})
}
+5
View File
@@ -129,6 +129,11 @@ impl NymRouteProvider {
}
}
pub fn current_key_rotation(&self) -> u32 {
todo!()
// self.topology.rewarded_set.epoch_id
}
pub fn new_empty(ignore_egress_epoch_roles: bool) -> NymRouteProvider {
let this: Self = NymTopology::default().into();
this.with_ignore_egress_epoch_roles(ignore_egress_epoch_roles)
-1
View File
@@ -16,7 +16,6 @@ use nym_node_requests::api::v1::mixnode::models::Mixnode;
use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy;
use nym_node_requests::api::v1::network_requester::models::NetworkRequester;
use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, HostSystem, NodeDescription};
use nym_node_requests::api::SignedHostInformation;
use nym_node_requests::routes;
use std::net::SocketAddr;
use std::path::Path;
+25 -8
View File
@@ -28,33 +28,33 @@ impl ActiveSphinxKeys {
}
}
pub(crate) fn even(&self) -> Option<impl Deref<Target = SphinxPrivateKey>> {
pub(crate) fn even(&self) -> Option<SphinxKeyGuard> {
let primary = self.inner.primary_key.load();
if primary.is_even_rotation() {
return Some(primary);
return Some(SphinxKeyGuard::Primary(primary));
}
self.secondary()
}
pub(crate) fn odd(&self) -> Option<impl Deref<Target = SphinxPrivateKey>> {
pub(crate) fn odd(&self) -> Option<SphinxKeyGuard> {
let primary = self.inner.primary_key.load();
if !primary.is_even_rotation() {
return Some(primary);
return Some(SphinxKeyGuard::Primary(primary));
}
self.secondary()
}
pub(crate) fn primary(&self) -> impl Deref<Target = SphinxPrivateKey> {
self.inner.primary_key.map(|k: &SphinxPrivateKey| k).load()
pub(crate) fn primary(&self) -> SphinxKeyGuard {
SphinxKeyGuard::Primary(self.inner.primary_key.load())
}
pub(crate) fn secondary(&self) -> Option<impl Deref<Target = SphinxPrivateKey>> {
pub(crate) fn secondary(&self) -> Option<SphinxKeyGuard> {
let guard = self.inner.secondary_key.load();
if guard.is_none() {
return None;
}
Some(SecondaryKeyGuard { guard })
Some(SphinxKeyGuard::Secondary(SecondaryKeyGuard { guard }))
}
// 1. generate new key
@@ -87,6 +87,23 @@ impl ActiveSphinxKeys {
}
}
pub(crate) enum SphinxKeyGuard {
// Primary(Guard<Arc<SphinxPrivateKey>>),
Primary(Guard<Arc<SphinxPrivateKey>>),
Secondary(SecondaryKeyGuard),
}
impl Deref for SphinxKeyGuard {
type Target = SphinxPrivateKey;
fn deref(&self) -> &Self::Target {
match self {
SphinxKeyGuard::Primary(g) => g.deref(),
SphinxKeyGuard::Secondary(g) => g.deref(),
}
}
}
pub(crate) struct SecondaryKeyGuard {
guard: Guard<Option<Arc<SphinxPrivateKey>>>,
}
+4
View File
@@ -32,6 +32,10 @@ impl SphinxPrivateKey {
self.inner.public_key()
}
pub(crate) fn inner(&self) -> &x25519::PrivateKey {
&self.inner
}
pub(crate) fn is_even_rotation(&self) -> bool {
self.rotation_id & 1 == 0
}
+13 -7
View File
@@ -223,11 +223,15 @@ impl ConnectionHandler {
SphinxKeyRotation::Unknown => {
// we have to try both keys, start with the primary as it has higher likelihood of being correct
// if let Ok(partially_unwrapped) = PartiallyUnwrappedPacket::new()
match PartiallyUnwrappedPacket::new(packet, self.shared.sphinx_keys.primary()) {
match PartiallyUnwrappedPacket::new(
packet,
self.shared.sphinx_keys.primary().inner().as_ref(),
) {
Ok(unwrapped_packet) => Ok(unwrapped_packet),
Err((packet, err)) => {
if let Some(secondary) = self.shared.sphinx_keys.secondary() {
PartiallyUnwrappedPacket::new(packet, secondary).map_err(|(_, err)| err)
PartiallyUnwrappedPacket::new(packet, secondary.inner().as_ref())
.map_err(|(_, err)| err)
} else {
Err(err)
}
@@ -238,13 +242,15 @@ impl ConnectionHandler {
let Some(odd_key) = self.shared.sphinx_keys.odd() else {
return Err(PacketProcessingError::ExpiredKey);
};
PartiallyUnwrappedPacket::new(packet, odd_key).map_err(|(_, err)| err)
PartiallyUnwrappedPacket::new(packet, odd_key.inner().as_ref())
.map_err(|(_, err)| err)
}
SphinxKeyRotation::EvenRotation => {
let Some(even_key) = self.shared.sphinx_keys.even() else {
return Err(PacketProcessingError::ExpiredKey);
};
PartiallyUnwrappedPacket::new(packet, even_key).map_err(|(_, err)| err)
PartiallyUnwrappedPacket::new(packet, even_key.inner().as_ref())
.map_err(|(_, err)| err)
}
}
}
@@ -385,19 +391,19 @@ impl ConnectionHandler {
// and by the time we need it, the rotation info should be present)
match packet.header().key_rotation {
SphinxKeyRotation::Unknown => {
process_framed_packet(packet, self.shared.sphinx_keys.primary())
process_framed_packet(packet, self.shared.sphinx_keys.primary().inner().as_ref())
}
SphinxKeyRotation::OddRotation => {
let Some(odd_key) = self.shared.sphinx_keys.odd() else {
return Err(PacketProcessingError::ExpiredKey);
};
process_framed_packet(packet, odd_key)
process_framed_packet(packet, odd_key.inner().as_ref())
}
SphinxKeyRotation::EvenRotation => {
let Some(even_key) = self.shared.sphinx_keys.even() else {
return Err(PacketProcessingError::ExpiredKey);
};
process_framed_packet(packet, even_key)
process_framed_packet(packet, even_key.inner().as_ref())
}
}
}
@@ -58,32 +58,20 @@ impl<C, F> PacketForwarder<C, F> {
C: SendWithoutResponse,
F: RoutingFilter,
{
let next_hop = packet.next_hop();
let next_hop = packet.next_hop_address();
let packet_type = packet.packet_type();
let packet = packet.into_packet();
if let Err(err) = self
.mixnet_client
.send_without_response(next_hop, packet, packet_type)
{
if let Err(err) = self.mixnet_client.send_without_response(packet) {
if err.kind() == io::ErrorKind::WouldBlock {
// we only know for sure if we dropped a packet if our sending queue was full
// in any other case the connection might still be re-established (or created for the first time)
// and the packet might get sent, but we won't know about it
self.metrics
.mixnet
.egress_dropped_forward_packet(next_hop.into())
self.metrics.mixnet.egress_dropped_forward_packet(next_hop)
} else if err.kind() == io::ErrorKind::NotConnected {
// let's give the benefit of the doubt and assume we manage to establish connection
self.metrics
.mixnet
.egress_sent_forward_packet(next_hop.into())
self.metrics.mixnet.egress_sent_forward_packet(next_hop)
}
} else {
self.metrics
.mixnet
.egress_sent_forward_packet(next_hop.into())
self.metrics.mixnet.egress_sent_forward_packet(next_hop)
}
}
+2 -1
View File
@@ -256,7 +256,8 @@ impl ThroughputTestingClient {
packet_bytes.append(&mut payload_bytes);
let forward_packet = NymPacket::sphinx_from_bytes(&packet_bytes)?;
Ok(FramedNymPacket::new(forward_packet, Default::default()))
todo!()
// Ok(FramedNymPacket::new(forward_packet, Default::default()))
}
async fn send_packets(&mut self) -> anyhow::Result<()> {