Feature/updated sphinx payload keys (#5698)
* removed support for legacy packet types from NymCodec I think nodes had plenty of time to upgrade given versioned variant was introduced in 2022 * temp: use local sphinx packet for development * introduce new messages that use more efficient reply surbs encoding * checks for incorrect encoding * generate correct message depending on config value * fixed current packet version * made packet type selection configurable * updated sphinx packet crate to the published version * fixed wasm build * fixes in outfox due to sphinx api changes * additional tests * clippy * fixed log/tracing import
This commit is contained in:
committed by
GitHub
parent
02ed64557d
commit
af2c4f50b6
Generated
+3
-2
@@ -6645,6 +6645,7 @@ dependencies = [
|
||||
"rand_chacha 0.3.1",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@@ -9230,9 +9231,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-packet"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63a72efe7dce8a546d5cb855e60699ae69203d0d7e4335a654eb87e93d7d141"
|
||||
checksum = "c26f0c20d909fdda1c5d0ece3973127ca421984d55b000215df365e93722fc6e"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arrayref",
|
||||
|
||||
+6
-1
@@ -312,7 +312,7 @@ serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
sha2 = "0.10.8"
|
||||
si-scale = "0.2.3"
|
||||
sphinx-packet = "=0.5.0"
|
||||
sphinx-packet = "=0.6.0"
|
||||
sqlx = "0.7.4"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
@@ -402,6 +402,11 @@ wasm-bindgen-futures = "0.4.49"
|
||||
wasmtimer = "0.4.1"
|
||||
web-sys = "0.3.76"
|
||||
|
||||
|
||||
# for local development:
|
||||
#[patch.crates-io]
|
||||
#sphinx-packet = { path = "../sphinx" }
|
||||
|
||||
# Profile settings for individual crates
|
||||
|
||||
# Compile-time verified queries do quite a bit of work at compile time. Incremental
|
||||
|
||||
@@ -65,7 +65,7 @@ const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60
|
||||
|
||||
// stats reporting related
|
||||
|
||||
/// Time interval between reporting statistics to the given provider if it exist
|
||||
/// Time interval between reporting statistics to the given provider if it exists
|
||||
const STATS_REPORT_INTERVAL_SECS: Duration = Duration::from_secs(300);
|
||||
|
||||
use crate::error::InvalidTrafficModeFailure;
|
||||
@@ -405,6 +405,14 @@ pub struct Traffic {
|
||||
/// Do not set it unless you understand the consequences of that change.
|
||||
pub secondary_packet_size: Option<PacketSize>,
|
||||
|
||||
/// Specify whether any constructed sphinx packets should use the legacy format,
|
||||
/// where the payload keys are explicitly attached rather than using the seeds
|
||||
/// this affects any forward packets, acks and reply surbs
|
||||
/// this flag should remain disabled until sufficient number of nodes on the network has upgraded
|
||||
/// and support updated format.
|
||||
/// in the case of reply surbs, the recipient must also understand the new encoding
|
||||
pub use_legacy_sphinx_format: bool,
|
||||
|
||||
pub packet_type: PacketType,
|
||||
}
|
||||
|
||||
@@ -432,6 +440,10 @@ impl Default for Traffic {
|
||||
primary_packet_size: PacketSize::RegularPacket,
|
||||
secondary_packet_size: None,
|
||||
packet_type: PacketType::Mix,
|
||||
|
||||
// we should use the legacy format until sufficient number of nodes understand the
|
||||
// improved encoding
|
||||
use_legacy_sphinx_format: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,10 @@ where
|
||||
/// Optional secondary predefined packet size used for the loop cover messages.
|
||||
secondary_packet_size: Option<PacketSize>,
|
||||
|
||||
/// Specify whether any constructed packets should use the legacy format,
|
||||
/// where the payload keys are explicitly attached rather than using the seeds
|
||||
use_legacy_sphinx_format: bool,
|
||||
|
||||
packet_type: PacketType,
|
||||
|
||||
stats_tx: ClientStatsSender,
|
||||
@@ -130,6 +134,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
topology_access,
|
||||
primary_packet_size: traffic_config.primary_packet_size,
|
||||
secondary_packet_size: traffic_config.secondary_packet_size,
|
||||
use_legacy_sphinx_format: traffic_config.use_legacy_sphinx_format,
|
||||
packet_type: traffic_config.packet_type,
|
||||
stats_tx,
|
||||
task_client,
|
||||
@@ -182,6 +187,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
|
||||
let cover_message = match generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
self.use_legacy_sphinx_format,
|
||||
topology_ref,
|
||||
&self.ack_key,
|
||||
&self.our_full_destination,
|
||||
|
||||
@@ -109,6 +109,10 @@ pub(crate) struct Config {
|
||||
|
||||
/// Optional secondary predefined packet size used for the encapsulated messages.
|
||||
secondary_packet_size: Option<PacketSize>,
|
||||
|
||||
/// Specify whether any constructed reply surbs should use the legacy format,
|
||||
/// where the payload keys are explicitly attached rather than using the seeds
|
||||
use_legacy_sphinx_format: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -118,6 +122,7 @@ impl Config {
|
||||
average_packet_delay: Duration,
|
||||
average_ack_delay: Duration,
|
||||
deterministic_route_selection: bool,
|
||||
use_legacy_reply_surb_format: bool,
|
||||
) -> Self {
|
||||
Config {
|
||||
ack_key,
|
||||
@@ -127,6 +132,7 @@ impl Config {
|
||||
average_ack_delay,
|
||||
primary_packet_size: PacketSize::default(),
|
||||
secondary_packet_size: None,
|
||||
use_legacy_sphinx_format: use_legacy_reply_surb_format,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +192,7 @@ where
|
||||
config.sender_address,
|
||||
config.average_packet_delay,
|
||||
config.average_ack_delay,
|
||||
config.use_legacy_sphinx_format,
|
||||
);
|
||||
MessageHandler {
|
||||
config,
|
||||
@@ -254,9 +261,11 @@ where
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = self.get_topology(&topology_permit)?;
|
||||
|
||||
let reply_surbs = self
|
||||
.message_preparer
|
||||
.generate_reply_surbs(amount, topology)?;
|
||||
let reply_surbs = self.message_preparer.generate_reply_surbs(
|
||||
self.config.use_legacy_sphinx_format,
|
||||
amount,
|
||||
topology,
|
||||
)?;
|
||||
|
||||
let reply_keys = reply_surbs
|
||||
.iter()
|
||||
@@ -522,6 +531,7 @@ where
|
||||
self.generate_reply_surbs_with_keys(amount as usize).await?;
|
||||
|
||||
let message = NymMessage::new_repliable(RepliableMessage::new_additional_surbs(
|
||||
self.config.use_legacy_sphinx_format,
|
||||
sender_tag,
|
||||
reply_surbs,
|
||||
));
|
||||
@@ -559,8 +569,12 @@ where
|
||||
.generate_reply_surbs_with_keys(num_reply_surbs as usize)
|
||||
.await?;
|
||||
|
||||
let message =
|
||||
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
|
||||
let message = NymMessage::new_repliable(RepliableMessage::new_data(
|
||||
self.config.use_legacy_sphinx_format,
|
||||
message,
|
||||
sender_tag,
|
||||
reply_surbs,
|
||||
));
|
||||
|
||||
self.try_split_and_send_non_reply_message(
|
||||
message,
|
||||
|
||||
@@ -99,6 +99,7 @@ impl<'a> From<&'a Config> for message_handler::Config {
|
||||
cfg.traffic.average_packet_delay,
|
||||
cfg.acks.average_ack_delay,
|
||||
cfg.traffic.deterministic_route_selection,
|
||||
cfg.traffic.use_legacy_sphinx_format,
|
||||
)
|
||||
.with_custom_primary_packet_size(cfg.traffic.primary_packet_size)
|
||||
.with_custom_secondary_packet_size(cfg.traffic.secondary_packet_size)
|
||||
|
||||
@@ -252,6 +252,7 @@ where
|
||||
(
|
||||
generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
self.config.traffic.use_legacy_sphinx_format,
|
||||
topology_ref,
|
||||
&self.config.ack_key,
|
||||
&self.config.our_full_destination,
|
||||
|
||||
@@ -250,10 +250,10 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
let mut reconstructed = Vec::new();
|
||||
for msg in msgs {
|
||||
let (reply_surbs, from_surb_request) = match msg.content {
|
||||
RepliableMessageContent::Data {
|
||||
message,
|
||||
reply_surbs,
|
||||
} => {
|
||||
RepliableMessageContent::Data(content) => {
|
||||
let reply_surbs = content.reply_surbs;
|
||||
let message = content.message;
|
||||
|
||||
trace!(
|
||||
"received message that also contained additional {} reply surbs from {:?}!",
|
||||
reply_surbs.len(),
|
||||
@@ -264,7 +264,9 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
|
||||
(reply_surbs, false)
|
||||
}
|
||||
RepliableMessageContent::AdditionalSurbs { reply_surbs } => {
|
||||
RepliableMessageContent::AdditionalSurbs(content) => {
|
||||
let reply_surbs = content.reply_surbs;
|
||||
|
||||
trace!(
|
||||
"received additional {} reply surbs from {:?}!",
|
||||
reply_surbs.len(),
|
||||
@@ -272,9 +274,37 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
);
|
||||
(reply_surbs, true)
|
||||
}
|
||||
RepliableMessageContent::Heartbeat {
|
||||
additional_reply_surbs,
|
||||
} => {
|
||||
RepliableMessageContent::Heartbeat(content) => {
|
||||
let additional_reply_surbs = content.additional_reply_surbs;
|
||||
error!("received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)");
|
||||
(additional_reply_surbs, false)
|
||||
}
|
||||
RepliableMessageContent::DataV2(content) => {
|
||||
let reply_surbs = content.reply_surbs;
|
||||
let message = content.message;
|
||||
|
||||
trace!(
|
||||
"received message that also contained additional {} reply surbs from {:?}!",
|
||||
reply_surbs.len(),
|
||||
msg.sender_tag
|
||||
);
|
||||
|
||||
reconstructed.push(ReconstructedMessage::new(message, msg.sender_tag));
|
||||
|
||||
(reply_surbs, false)
|
||||
}
|
||||
RepliableMessageContent::AdditionalSurbsV2(content) => {
|
||||
let reply_surbs = content.reply_surbs;
|
||||
|
||||
trace!(
|
||||
"received additional {} reply surbs from {:?}!",
|
||||
reply_surbs.len(),
|
||||
msg.sender_tag
|
||||
);
|
||||
(reply_surbs, true)
|
||||
}
|
||||
RepliableMessageContent::HeartbeatV2(content) => {
|
||||
let additional_reply_surbs = content.additional_reply_surbs;
|
||||
error!("received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)");
|
||||
(additional_reply_surbs, false)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ pub struct NodeTester<R> {
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
average_ack_delay: Duration,
|
||||
|
||||
/// Specify whether any constructed packets should use the legacy format,
|
||||
/// where the payload keys are explicitly attached rather than using the seeds
|
||||
use_legacy_sphinx_format: bool,
|
||||
|
||||
// while acks are going to be ignored they still need to be constructed
|
||||
// so that the gateway would be able to correctly process and forward the message
|
||||
ack_key: Arc<AckKey>,
|
||||
@@ -57,6 +61,7 @@ where
|
||||
deterministic_route_selection: bool,
|
||||
average_packet_delay: Duration,
|
||||
average_ack_delay: Duration,
|
||||
use_legacy_sphinx_format: bool,
|
||||
ack_key: Arc<AckKey>,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -67,6 +72,7 @@ where
|
||||
deterministic_route_selection,
|
||||
average_packet_delay,
|
||||
average_ack_delay,
|
||||
use_legacy_sphinx_format,
|
||||
ack_key,
|
||||
}
|
||||
}
|
||||
@@ -245,6 +251,10 @@ where
|
||||
impl<R: CryptoRng + Rng> FragmentPreparer for NodeTester<R> {
|
||||
type Rng = R;
|
||||
|
||||
fn use_legacy_sphinx_format(&self) -> bool {
|
||||
self.use_legacy_sphinx_format
|
||||
}
|
||||
|
||||
fn deterministic_route_selection(&self) -> bool {
|
||||
self.deterministic_route_selection
|
||||
}
|
||||
|
||||
@@ -37,8 +37,10 @@ pub enum SurbAckRecoveryError {
|
||||
}
|
||||
|
||||
impl SurbAck {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn construct<R>(
|
||||
rng: &mut R,
|
||||
use_legacy_sphinx_format: bool,
|
||||
recipient: &Recipient,
|
||||
ack_key: &AckKey,
|
||||
marshaled_fragment_id: [u8; 5],
|
||||
@@ -67,6 +69,7 @@ impl SurbAck {
|
||||
Some(packet_size),
|
||||
)?,
|
||||
PacketType::Mix => NymPacket::sphinx_build(
|
||||
use_legacy_sphinx_format,
|
||||
packet_size,
|
||||
surb_ack_payload,
|
||||
&route,
|
||||
|
||||
@@ -199,7 +199,7 @@ impl TryFrom<NodeAddressBytes> for NymNodeRoutingAddress {
|
||||
type Error = NymNodeRoutingAddressError;
|
||||
|
||||
fn try_from(value: NodeAddressBytes) -> Result<Self, Self::Error> {
|
||||
Self::try_from_bytes(value.as_bytes_ref())
|
||||
Self::try_from_bytes(value.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ rand = { workspace = true }
|
||||
bs58 = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-crypto = { path = "../../crypto", features = ["stream_cipher", "rand"] }
|
||||
nym-sphinx-addressing = { path = "../addressing" }
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::encryption_key::{SurbEncryptionKey, SurbEncryptionKeyError, SurbEncryptionKeySize};
|
||||
use nym_crypto::{generic_array::typenum::Unsigned, Digest};
|
||||
use nym_sphinx_addressing::clients::Recipient;
|
||||
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN};
|
||||
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_types::{NymPacket, SURBMaterial, SphinxError, SURB};
|
||||
use nym_sphinx_types::constants::PAYLOAD_KEY_SEED_SIZE;
|
||||
use nym_sphinx_types::{
|
||||
NymPacket, SURBMaterial, SphinxError, HEADER_SIZE, NODE_ADDRESS_LENGTH, SURB,
|
||||
X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION,
|
||||
};
|
||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use serde::de::{Error as SerdeError, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use std::fmt::{self, Formatter};
|
||||
use std::time;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -31,6 +36,9 @@ pub enum ReplySurbError {
|
||||
#[error("failed to recover reply SURB from bytes: {0}")]
|
||||
RecoveryError(#[from] SphinxError),
|
||||
|
||||
#[error("failed to validate the first hop address of the recovered reply SURB: {0}")]
|
||||
MalformedSurbFirstHop(#[from] NymNodeRoutingAddressError),
|
||||
|
||||
#[error("failed to recover reply SURB encryption key from bytes: {0}")]
|
||||
InvalidEncryptionKeyData(#[from] SurbEncryptionKeyError),
|
||||
}
|
||||
@@ -80,6 +88,10 @@ impl<'de> Deserialize<'de> for ReplySurb {
|
||||
}
|
||||
|
||||
impl ReplySurb {
|
||||
/// base overhead of a reply surb that exists regardless of type or number of key materials.
|
||||
pub(crate) const BASE_OVERHEAD: usize =
|
||||
SurbEncryptionKeySize::USIZE + HEADER_SIZE + NODE_ADDRESS_LENGTH;
|
||||
|
||||
pub fn max_msg_len(packet_size: PacketSize) -> usize {
|
||||
// For detailed explanation (of ack overhead) refer to common\nymsphinx\src\preparer.rs::available_plaintext_per_packet()
|
||||
let ack_overhead = MAX_NODE_ADDRESS_UNPADDED_LEN + PacketSize::AckPacket.size();
|
||||
@@ -91,7 +103,8 @@ impl ReplySurb {
|
||||
pub fn construct<R>(
|
||||
rng: &mut R,
|
||||
recipient: &Recipient,
|
||||
average_delay: time::Duration,
|
||||
average_delay: Duration,
|
||||
use_legacy_surb_format: bool,
|
||||
topology: &NymRouteProvider,
|
||||
) -> Result<Self, NymTopologyError>
|
||||
where
|
||||
@@ -101,7 +114,10 @@ impl ReplySurb {
|
||||
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
|
||||
let destination = recipient.as_sphinx_destination();
|
||||
|
||||
let surb_material = SURBMaterial::new(route, delays, destination);
|
||||
let mut surb_material = SURBMaterial::new(route, delays, destination);
|
||||
if use_legacy_surb_format {
|
||||
surb_material = surb_material.with_version(X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION)
|
||||
}
|
||||
|
||||
// this can't fail as we know we have a valid route to gateway and have correct number of delays
|
||||
Ok(ReplySurb {
|
||||
@@ -110,14 +126,10 @@ impl ReplySurb {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the expected number of bytes the [`ReplySURB`] will take after serialization.
|
||||
/// Returns the expected number of bytes the [`ReplySURB`] will take after serialization using the new encoding format.
|
||||
/// Useful for deserialization from a bytes stream.
|
||||
pub fn serialized_len() -> usize {
|
||||
use nym_sphinx_types::{HEADER_SIZE, NODE_ADDRESS_LENGTH, PAYLOAD_KEY_SIZE};
|
||||
|
||||
// the SURB itself consists of SURB_header, first hop address and set of payload keys
|
||||
// for each hop (3x mix + egress)
|
||||
SurbEncryptionKeySize::USIZE + HEADER_SIZE + NODE_ADDRESS_LENGTH + 4 * PAYLOAD_KEY_SIZE
|
||||
pub fn v2_serialised_len(num_hops: u8) -> usize {
|
||||
Self::BASE_OVERHEAD + num_hops as usize * PAYLOAD_KEY_SEED_SIZE
|
||||
}
|
||||
|
||||
pub fn encryption_key(&self) -> &SurbEncryptionKey {
|
||||
@@ -143,7 +155,12 @@ impl ReplySurb {
|
||||
|
||||
let surb = match SURB::from_bytes(&bytes[SurbEncryptionKeySize::USIZE..]) {
|
||||
Err(err) => return Err(ReplySurbError::RecoveryError(err)),
|
||||
Ok(surb) => surb,
|
||||
Ok(surb) => {
|
||||
// we can't really check fully validity of the header, but at the very least we could make a sanity check
|
||||
// to make sure the first hop address is a valid socket address
|
||||
let _ = NymNodeRoutingAddress::try_from(surb.first_hop())?;
|
||||
surb
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ReplySurb {
|
||||
|
||||
+301
-171
@@ -8,9 +8,15 @@ use std::fmt::{Display, Formatter};
|
||||
use std::mem;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::requests::v1::{AdditionalSurbsV1, DataV1, HeartbeatV1};
|
||||
use crate::requests::v2::{AdditionalSurbsV2, DataV2, HeartbeatV2};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub(crate) mod v1;
|
||||
pub(crate) mod v2;
|
||||
|
||||
pub const SENDER_TAG_SIZE: usize = 16;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -103,31 +109,23 @@ pub struct RepliableMessage {
|
||||
impl Display for RepliableMessage {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.content {
|
||||
RepliableMessageContent::Data {
|
||||
message,
|
||||
reply_surbs,
|
||||
} => write!(
|
||||
f,
|
||||
"repliable {:.2} kiB data message with {} reply surbs attached from {}",
|
||||
message.len() as f64 / 1024.0,
|
||||
reply_surbs.len(),
|
||||
self.sender_tag,
|
||||
),
|
||||
RepliableMessageContent::AdditionalSurbs { reply_surbs } => write!(
|
||||
f,
|
||||
"repliable additional surbs message ({} reply surbs attached) from {}",
|
||||
reply_surbs.len(),
|
||||
self.sender_tag,
|
||||
),
|
||||
RepliableMessageContent::Heartbeat {
|
||||
additional_reply_surbs,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"repliable heartbeat message ({} reply surbs attached) from {}",
|
||||
additional_reply_surbs.len(),
|
||||
self.sender_tag,
|
||||
)
|
||||
RepliableMessageContent::Data(content) => {
|
||||
write!(f, "{content} from {}", self.sender_tag)
|
||||
}
|
||||
RepliableMessageContent::AdditionalSurbs(content) => {
|
||||
write!(f, "{content} from {}", self.sender_tag)
|
||||
}
|
||||
RepliableMessageContent::Heartbeat(content) => {
|
||||
write!(f, "{content} from {}", self.sender_tag)
|
||||
}
|
||||
RepliableMessageContent::DataV2(content) => {
|
||||
write!(f, "{content} from {}", self.sender_tag)
|
||||
}
|
||||
RepliableMessageContent::AdditionalSurbsV2(content) => {
|
||||
write!(f, "{content} from {}", self.sender_tag)
|
||||
}
|
||||
RepliableMessageContent::HeartbeatV2(content) => {
|
||||
write!(f, "{content} from {}", self.sender_tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,26 +133,43 @@ impl Display for RepliableMessage {
|
||||
|
||||
impl RepliableMessage {
|
||||
pub fn new_data(
|
||||
use_legacy_surb_format: bool,
|
||||
data: Vec<u8>,
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
) -> Self {
|
||||
RepliableMessage {
|
||||
sender_tag,
|
||||
content: RepliableMessageContent::Data {
|
||||
let content = if use_legacy_surb_format {
|
||||
RepliableMessageContent::Data(DataV1 {
|
||||
message: data,
|
||||
reply_surbs,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
RepliableMessageContent::DataV2(DataV2 {
|
||||
message: data,
|
||||
reply_surbs,
|
||||
})
|
||||
};
|
||||
|
||||
RepliableMessage {
|
||||
sender_tag,
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_additional_surbs(
|
||||
use_legacy_surb_format: bool,
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
) -> Self {
|
||||
let content = if use_legacy_surb_format {
|
||||
RepliableMessageContent::AdditionalSurbs(AdditionalSurbsV1 { reply_surbs })
|
||||
} else {
|
||||
RepliableMessageContent::AdditionalSurbsV2(AdditionalSurbsV2 { reply_surbs })
|
||||
};
|
||||
|
||||
RepliableMessage {
|
||||
sender_tag,
|
||||
content: RepliableMessageContent::AdditionalSurbs { reply_surbs },
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,35 +207,18 @@ impl RepliableMessage {
|
||||
}
|
||||
}
|
||||
|
||||
// this recovery code is shared between all variants containing reply surbs
|
||||
fn recover_reply_surbs(bytes: &[u8]) -> Result<(Vec<ReplySurb>, usize), InvalidReplyRequestError> {
|
||||
let mut consumed = mem::size_of::<u32>();
|
||||
if bytes.len() < consumed {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
let num_surbs = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
|
||||
let surb_size = ReplySurb::serialized_len();
|
||||
if bytes[consumed..].len() < num_surbs as usize * surb_size {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
|
||||
let mut reply_surbs = Vec::with_capacity(num_surbs as usize);
|
||||
for _ in 0..num_surbs as usize {
|
||||
let surb_bytes = &bytes[consumed..consumed + surb_size];
|
||||
let reply_surb = ReplySurb::from_bytes(surb_bytes)?;
|
||||
reply_surbs.push(reply_surb);
|
||||
|
||||
consumed += surb_size;
|
||||
}
|
||||
|
||||
Ok((reply_surbs, consumed))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(u8)]
|
||||
enum RepliableMessageContentTag {
|
||||
Data = 0,
|
||||
AdditionalSurbs = 1,
|
||||
Heartbeat = 2,
|
||||
|
||||
// updated variants that slightly change SURB encoding
|
||||
// to allow for variable number of hops as well as using payload key seeds
|
||||
DataV2 = 3,
|
||||
AdditionalSurbsV2 = 4,
|
||||
HeartbeatV2 = 5,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for RepliableMessageContentTag {
|
||||
@@ -233,6 +231,11 @@ impl TryFrom<u8> for RepliableMessageContentTag {
|
||||
Ok(Self::AdditionalSurbs)
|
||||
}
|
||||
_ if value == (RepliableMessageContentTag::Heartbeat as u8) => Ok(Self::Heartbeat),
|
||||
_ if value == (RepliableMessageContentTag::DataV2 as u8) => Ok(Self::DataV2),
|
||||
_ if value == (RepliableMessageContentTag::AdditionalSurbsV2 as u8) => {
|
||||
Ok(Self::AdditionalSurbsV2)
|
||||
}
|
||||
_ if value == (RepliableMessageContentTag::HeartbeatV2 as u8) => Ok(Self::HeartbeatV2),
|
||||
val => Err(InvalidReplyRequestError::InvalidRepliableContentTag { received: val }),
|
||||
}
|
||||
}
|
||||
@@ -241,58 +244,24 @@ impl TryFrom<u8> for RepliableMessageContentTag {
|
||||
// sent by original sender that initialised the communication that knows address of the remote
|
||||
#[derive(Debug)]
|
||||
pub enum RepliableMessageContent {
|
||||
Data {
|
||||
message: Vec<u8>,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
},
|
||||
AdditionalSurbs {
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
},
|
||||
Heartbeat {
|
||||
additional_reply_surbs: Vec<ReplySurb>,
|
||||
},
|
||||
Data(DataV1),
|
||||
AdditionalSurbs(AdditionalSurbsV1),
|
||||
Heartbeat(HeartbeatV1),
|
||||
|
||||
DataV2(DataV2),
|
||||
AdditionalSurbsV2(AdditionalSurbsV2),
|
||||
HeartbeatV2(HeartbeatV2),
|
||||
}
|
||||
|
||||
impl RepliableMessageContent {
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
match self {
|
||||
RepliableMessageContent::Data {
|
||||
message,
|
||||
reply_surbs,
|
||||
} => {
|
||||
let num_surbs = reply_surbs.len() as u32;
|
||||
|
||||
num_surbs
|
||||
.to_be_bytes()
|
||||
.into_iter()
|
||||
.chain(reply_surbs.into_iter().flat_map(|s| s.to_bytes()))
|
||||
.chain(message)
|
||||
.collect()
|
||||
}
|
||||
RepliableMessageContent::AdditionalSurbs { reply_surbs } => {
|
||||
let num_surbs = reply_surbs.len() as u32;
|
||||
|
||||
num_surbs
|
||||
.to_be_bytes()
|
||||
.into_iter()
|
||||
.chain(reply_surbs.into_iter().flat_map(|s| s.to_bytes()))
|
||||
.collect()
|
||||
}
|
||||
RepliableMessageContent::Heartbeat {
|
||||
additional_reply_surbs,
|
||||
} => {
|
||||
let num_surbs = additional_reply_surbs.len() as u32;
|
||||
|
||||
num_surbs
|
||||
.to_be_bytes()
|
||||
.into_iter()
|
||||
.chain(
|
||||
additional_reply_surbs
|
||||
.into_iter()
|
||||
.flat_map(|s| s.to_bytes()),
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
RepliableMessageContent::Data(content) => content.into_bytes(),
|
||||
RepliableMessageContent::AdditionalSurbs(content) => content.into_bytes(),
|
||||
RepliableMessageContent::Heartbeat(content) => content.into_bytes(),
|
||||
RepliableMessageContent::DataV2(content) => content.into_bytes(),
|
||||
RepliableMessageContent::AdditionalSurbsV2(content) => content.into_bytes(),
|
||||
RepliableMessageContent::HeartbeatV2(content) => content.into_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,19 +273,25 @@ impl RepliableMessageContent {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
|
||||
let (reply_surbs, n) = recover_reply_surbs(bytes)?;
|
||||
|
||||
match tag {
|
||||
RepliableMessageContentTag::Data => Ok(RepliableMessageContent::Data {
|
||||
message: bytes[n..].to_vec(),
|
||||
reply_surbs,
|
||||
}),
|
||||
RepliableMessageContentTag::AdditionalSurbs => {
|
||||
Ok(RepliableMessageContent::AdditionalSurbs { reply_surbs })
|
||||
RepliableMessageContentTag::Data => {
|
||||
Ok(RepliableMessageContent::Data(DataV1::from_bytes(bytes)?))
|
||||
}
|
||||
RepliableMessageContentTag::Heartbeat => Ok(RepliableMessageContent::Heartbeat {
|
||||
additional_reply_surbs: reply_surbs,
|
||||
}),
|
||||
RepliableMessageContentTag::AdditionalSurbs => Ok(
|
||||
RepliableMessageContent::AdditionalSurbs(AdditionalSurbsV1::from_bytes(bytes)?),
|
||||
),
|
||||
RepliableMessageContentTag::Heartbeat => Ok(RepliableMessageContent::Heartbeat(
|
||||
HeartbeatV1::from_bytes(bytes)?,
|
||||
)),
|
||||
RepliableMessageContentTag::DataV2 => {
|
||||
Ok(RepliableMessageContent::DataV2(DataV2::from_bytes(bytes)?))
|
||||
}
|
||||
RepliableMessageContentTag::AdditionalSurbsV2 => Ok(
|
||||
RepliableMessageContent::AdditionalSurbsV2(AdditionalSurbsV2::from_bytes(bytes)?),
|
||||
),
|
||||
RepliableMessageContentTag::HeartbeatV2 => Ok(RepliableMessageContent::HeartbeatV2(
|
||||
HeartbeatV2::from_bytes(bytes)?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,30 +302,22 @@ impl RepliableMessageContent {
|
||||
RepliableMessageContentTag::AdditionalSurbs
|
||||
}
|
||||
RepliableMessageContent::Heartbeat { .. } => RepliableMessageContentTag::Heartbeat,
|
||||
RepliableMessageContent::DataV2(_) => RepliableMessageContentTag::DataV2,
|
||||
RepliableMessageContent::AdditionalSurbsV2(_) => {
|
||||
RepliableMessageContentTag::AdditionalSurbsV2
|
||||
}
|
||||
RepliableMessageContent::HeartbeatV2(_) => RepliableMessageContentTag::HeartbeatV2,
|
||||
}
|
||||
}
|
||||
|
||||
fn serialized_size(&self) -> usize {
|
||||
match self {
|
||||
RepliableMessageContent::Data {
|
||||
message,
|
||||
reply_surbs,
|
||||
} => {
|
||||
let num_reply_surbs_tag = mem::size_of::<u32>();
|
||||
num_reply_surbs_tag
|
||||
+ reply_surbs.len() * ReplySurb::serialized_len()
|
||||
+ message.len()
|
||||
}
|
||||
RepliableMessageContent::AdditionalSurbs { reply_surbs } => {
|
||||
let num_reply_surbs_tag = mem::size_of::<u32>();
|
||||
num_reply_surbs_tag + reply_surbs.len() * ReplySurb::serialized_len()
|
||||
}
|
||||
RepliableMessageContent::Heartbeat {
|
||||
additional_reply_surbs,
|
||||
} => {
|
||||
let num_reply_surbs_tag = mem::size_of::<u32>();
|
||||
num_reply_surbs_tag + additional_reply_surbs.len() * ReplySurb::serialized_len()
|
||||
}
|
||||
RepliableMessageContent::Data(content) => content.serialized_len(),
|
||||
RepliableMessageContent::AdditionalSurbs(content) => content.serialized_len(),
|
||||
RepliableMessageContent::Heartbeat(content) => content.serialized_len(),
|
||||
RepliableMessageContent::DataV2(content) => content.serialized_len(),
|
||||
RepliableMessageContent::AdditionalSurbsV2(content) => content.serialized_len(),
|
||||
RepliableMessageContent::HeartbeatV2(content) => content.serialized_len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -514,18 +481,22 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
mod fixtures {
|
||||
use crate::requests::v1::{AdditionalSurbsV1, DataV1, HeartbeatV1};
|
||||
use crate::requests::v2::{AdditionalSurbsV2, DataV2, HeartbeatV2};
|
||||
use crate::requests::{AnonymousSenderTag, RepliableMessageContent, ReplyMessageContent};
|
||||
use crate::{ReplySurb, SurbEncryptionKey};
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_sphinx_addressing::clients::Recipient;
|
||||
use nym_sphinx_types::{
|
||||
Delay, Destination, DestinationAddressBytes, Node, NodeAddressBytes, PrivateKey,
|
||||
SURBMaterial, NODE_ADDRESS_LENGTH,
|
||||
SURBMaterial, NODE_ADDRESS_LENGTH, X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION,
|
||||
};
|
||||
use rand::{Rng, RngCore};
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
||||
pub(crate) const LEGACY_HOPS: u8 = 4;
|
||||
|
||||
pub(super) fn test_rng() -> ChaCha20Rng {
|
||||
let dummy_seed = [42u8; 32];
|
||||
ChaCha20Rng::from_seed(dummy_seed)
|
||||
@@ -567,11 +538,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn reply_surb(rng: &mut ChaCha20Rng) -> ReplySurb {
|
||||
// due to gateway
|
||||
const HOPS: u8 = 4;
|
||||
let route = (0..HOPS).map(|_| node(rng)).collect();
|
||||
let delays = (0..HOPS)
|
||||
pub(super) fn reply_surb(rng: &mut ChaCha20Rng, legacy: bool, hops: u8) -> ReplySurb {
|
||||
let route = (0..hops).map(|_| node(rng)).collect();
|
||||
let delays = (0..hops)
|
||||
.map(|_| Delay::new_from_nanos(rng.next_u64()))
|
||||
.collect();
|
||||
let mut destination_bytes = [0u8; 32];
|
||||
@@ -585,50 +554,58 @@ mod tests {
|
||||
identifier_bytes,
|
||||
);
|
||||
|
||||
let surb = SURBMaterial::new(route, delays, destination)
|
||||
.construct_SURB()
|
||||
.unwrap();
|
||||
let mut surb_material = SURBMaterial::new(route, delays, destination);
|
||||
if legacy {
|
||||
surb_material =
|
||||
surb_material.with_version(X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION);
|
||||
}
|
||||
|
||||
ReplySurb {
|
||||
surb,
|
||||
surb: surb_material.construct_SURB().unwrap(),
|
||||
encryption_key: SurbEncryptionKey::new(rng),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn reply_surbs(rng: &mut ChaCha20Rng, n: usize) -> Vec<ReplySurb> {
|
||||
pub(super) fn reply_surbs(
|
||||
rng: &mut ChaCha20Rng,
|
||||
n: usize,
|
||||
legacy: bool,
|
||||
hops: u8,
|
||||
) -> Vec<ReplySurb> {
|
||||
let mut surbs = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
surbs.push(reply_surb(rng))
|
||||
surbs.push(reply_surb(rng, legacy, hops))
|
||||
}
|
||||
surbs
|
||||
}
|
||||
|
||||
pub(super) fn repliable_content_data(
|
||||
pub(super) fn repliable_content_data_v1(
|
||||
rng: &mut ChaCha20Rng,
|
||||
msg_len: usize,
|
||||
surbs: usize,
|
||||
) -> RepliableMessageContent {
|
||||
RepliableMessageContent::Data {
|
||||
RepliableMessageContent::Data(DataV1 {
|
||||
message: random_vec_u8(rng, msg_len),
|
||||
reply_surbs: reply_surbs(rng, surbs),
|
||||
}
|
||||
reply_surbs: reply_surbs(rng, surbs, true, LEGACY_HOPS),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn repliable_content_surbs(
|
||||
pub(super) fn repliable_content_surbs_v1(
|
||||
rng: &mut ChaCha20Rng,
|
||||
surbs: usize,
|
||||
) -> RepliableMessageContent {
|
||||
RepliableMessageContent::AdditionalSurbs {
|
||||
reply_surbs: reply_surbs(rng, surbs),
|
||||
}
|
||||
RepliableMessageContent::AdditionalSurbs(AdditionalSurbsV1 {
|
||||
reply_surbs: reply_surbs(rng, surbs, true, LEGACY_HOPS),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn repliable_content_heartbeat(
|
||||
pub(super) fn repliable_content_heartbeat_v1(
|
||||
rng: &mut ChaCha20Rng,
|
||||
surbs: usize,
|
||||
) -> RepliableMessageContent {
|
||||
RepliableMessageContent::Heartbeat {
|
||||
additional_reply_surbs: reply_surbs(rng, surbs),
|
||||
}
|
||||
RepliableMessageContent::Heartbeat(HeartbeatV1 {
|
||||
additional_reply_surbs: reply_surbs(rng, surbs, true, LEGACY_HOPS),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn reply_content_data(
|
||||
@@ -649,37 +626,70 @@ mod tests {
|
||||
amount: surbs,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn repliable_content_data_v2(
|
||||
rng: &mut ChaCha20Rng,
|
||||
msg_len: usize,
|
||||
surbs: usize,
|
||||
surb_hops: u8,
|
||||
) -> RepliableMessageContent {
|
||||
RepliableMessageContent::DataV2(DataV2 {
|
||||
message: random_vec_u8(rng, msg_len),
|
||||
reply_surbs: reply_surbs(rng, surbs, false, surb_hops),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn repliable_content_surbs_v2(
|
||||
rng: &mut ChaCha20Rng,
|
||||
surbs: usize,
|
||||
surb_hops: u8,
|
||||
) -> RepliableMessageContent {
|
||||
RepliableMessageContent::AdditionalSurbsV2(AdditionalSurbsV2 {
|
||||
reply_surbs: reply_surbs(rng, surbs, false, surb_hops),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn repliable_content_heartbeat_v2(
|
||||
rng: &mut ChaCha20Rng,
|
||||
surbs: usize,
|
||||
surb_hops: u8,
|
||||
) -> RepliableMessageContent {
|
||||
RepliableMessageContent::HeartbeatV2(HeartbeatV2 {
|
||||
additional_reply_surbs: reply_surbs(rng, surbs, false, surb_hops),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod repliable_message {
|
||||
use super::*;
|
||||
use crate::requests::tests::fixtures::LEGACY_HOPS;
|
||||
|
||||
#[test]
|
||||
fn serialized_size_matches_actual_serialization() {
|
||||
fn serialized_size_matches_actual_serialization_for_v1_messages() {
|
||||
let mut rng = fixtures::test_rng();
|
||||
|
||||
let data1 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_data(&mut rng, 10000, 0),
|
||||
content: fixtures::repliable_content_data_v1(&mut rng, 10000, 0),
|
||||
};
|
||||
assert_eq!(data1.serialized_size(), data1.into_bytes().len());
|
||||
|
||||
let data2 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_data(&mut rng, 10, 100),
|
||||
content: fixtures::repliable_content_data_v1(&mut rng, 10, 100),
|
||||
};
|
||||
assert_eq!(data2.serialized_size(), data2.into_bytes().len());
|
||||
|
||||
let data3 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_data(&mut rng, 100000, 1000),
|
||||
content: fixtures::repliable_content_data_v1(&mut rng, 100000, 1000),
|
||||
};
|
||||
assert_eq!(data3.serialized_size(), data3.into_bytes().len());
|
||||
|
||||
let additional_surbs1 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_surbs(&mut rng, 1),
|
||||
content: fixtures::repliable_content_surbs_v1(&mut rng, 1),
|
||||
};
|
||||
assert_eq!(
|
||||
additional_surbs1.serialized_size(),
|
||||
@@ -688,7 +698,7 @@ mod tests {
|
||||
|
||||
let additional_surbs2 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_surbs(&mut rng, 1000),
|
||||
content: fixtures::repliable_content_surbs_v1(&mut rng, 1000),
|
||||
};
|
||||
assert_eq!(
|
||||
additional_surbs2.serialized_size(),
|
||||
@@ -697,53 +707,173 @@ mod tests {
|
||||
|
||||
let heartbeat1 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_heartbeat(&mut rng, 1),
|
||||
content: fixtures::repliable_content_heartbeat_v1(&mut rng, 1),
|
||||
};
|
||||
assert_eq!(heartbeat1.serialized_size(), heartbeat1.into_bytes().len());
|
||||
|
||||
let heartbeat2 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_heartbeat(&mut rng, 1000),
|
||||
content: fixtures::repliable_content_heartbeat_v1(&mut rng, 1000),
|
||||
};
|
||||
assert_eq!(heartbeat2.serialized_size(), heartbeat2.into_bytes().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialized_size_matches_actual_serialization_for_v2_messages() {
|
||||
let mut rng = fixtures::test_rng();
|
||||
|
||||
let data1 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_data_v2(&mut rng, 10000, 0, LEGACY_HOPS),
|
||||
};
|
||||
assert_eq!(data1.serialized_size(), data1.into_bytes().len());
|
||||
|
||||
let data2 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_data_v2(&mut rng, 10, 100, LEGACY_HOPS),
|
||||
};
|
||||
assert_eq!(data2.serialized_size(), data2.into_bytes().len());
|
||||
|
||||
let data3 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_data_v2(&mut rng, 100000, 1000, LEGACY_HOPS),
|
||||
};
|
||||
assert_eq!(data3.serialized_size(), data3.into_bytes().len());
|
||||
|
||||
let data4 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_data_v2(&mut rng, 100000, 1000, 1),
|
||||
};
|
||||
assert_eq!(data4.serialized_size(), data4.into_bytes().len());
|
||||
|
||||
let additional_surbs1 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_surbs_v2(&mut rng, 1, LEGACY_HOPS),
|
||||
};
|
||||
assert_eq!(
|
||||
additional_surbs1.serialized_size(),
|
||||
additional_surbs1.into_bytes().len()
|
||||
);
|
||||
|
||||
let additional_surbs2 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_surbs_v2(&mut rng, 1000, LEGACY_HOPS),
|
||||
};
|
||||
assert_eq!(
|
||||
additional_surbs2.serialized_size(),
|
||||
additional_surbs2.into_bytes().len()
|
||||
);
|
||||
|
||||
let additional_surbs3 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_surbs_v2(&mut rng, 1000, 1),
|
||||
};
|
||||
assert_eq!(
|
||||
additional_surbs3.serialized_size(),
|
||||
additional_surbs3.into_bytes().len()
|
||||
);
|
||||
|
||||
let heartbeat1 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_heartbeat_v2(&mut rng, 1, LEGACY_HOPS),
|
||||
};
|
||||
assert_eq!(heartbeat1.serialized_size(), heartbeat1.into_bytes().len());
|
||||
|
||||
let heartbeat2 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_heartbeat_v2(&mut rng, 1000, LEGACY_HOPS),
|
||||
};
|
||||
assert_eq!(heartbeat2.serialized_size(), heartbeat2.into_bytes().len());
|
||||
|
||||
let heartbeat3 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_heartbeat_v2(&mut rng, 1000, 1),
|
||||
};
|
||||
assert_eq!(heartbeat3.serialized_size(), heartbeat3.into_bytes().len());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod repliable_message_content {
|
||||
use super::*;
|
||||
use crate::requests::tests::fixtures::LEGACY_HOPS;
|
||||
|
||||
#[test]
|
||||
fn serialized_size_matches_actual_serialization() {
|
||||
fn serialized_size_matches_actual_serialization_for_v1_messages() {
|
||||
let mut rng = fixtures::test_rng();
|
||||
|
||||
let data1 = fixtures::repliable_content_data(&mut rng, 10000, 0);
|
||||
let data1 = fixtures::repliable_content_data_v1(&mut rng, 10000, 0);
|
||||
assert_eq!(data1.serialized_size(), data1.into_bytes().len());
|
||||
|
||||
let data2 = fixtures::repliable_content_data(&mut rng, 10, 100);
|
||||
let data2 = fixtures::repliable_content_data_v1(&mut rng, 10, 100);
|
||||
assert_eq!(data2.serialized_size(), data2.into_bytes().len());
|
||||
|
||||
let data3 = fixtures::repliable_content_data(&mut rng, 100000, 1000);
|
||||
let data3 = fixtures::repliable_content_data_v1(&mut rng, 100000, 1000);
|
||||
assert_eq!(data3.serialized_size(), data3.into_bytes().len());
|
||||
|
||||
let additional_surbs1 = fixtures::repliable_content_surbs(&mut rng, 1);
|
||||
let additional_surbs1 = fixtures::repliable_content_surbs_v1(&mut rng, 1);
|
||||
assert_eq!(
|
||||
additional_surbs1.serialized_size(),
|
||||
additional_surbs1.into_bytes().len()
|
||||
);
|
||||
|
||||
let additional_surbs2 = fixtures::repliable_content_surbs(&mut rng, 1000);
|
||||
let additional_surbs2 = fixtures::repliable_content_surbs_v1(&mut rng, 1000);
|
||||
assert_eq!(
|
||||
additional_surbs2.serialized_size(),
|
||||
additional_surbs2.into_bytes().len()
|
||||
);
|
||||
|
||||
let heartbeat1 = fixtures::repliable_content_heartbeat(&mut rng, 1);
|
||||
let heartbeat1 = fixtures::repliable_content_heartbeat_v1(&mut rng, 1);
|
||||
assert_eq!(heartbeat1.serialized_size(), heartbeat1.into_bytes().len());
|
||||
|
||||
let heartbeat2 = fixtures::repliable_content_heartbeat(&mut rng, 1000);
|
||||
let heartbeat2 = fixtures::repliable_content_heartbeat_v1(&mut rng, 1000);
|
||||
assert_eq!(heartbeat2.serialized_size(), heartbeat2.into_bytes().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialized_size_matches_actual_serialization_for_v2_messages() {
|
||||
let mut rng = fixtures::test_rng();
|
||||
|
||||
let data1 = fixtures::repliable_content_data_v2(&mut rng, 10000, 0, LEGACY_HOPS);
|
||||
assert_eq!(data1.serialized_size(), data1.into_bytes().len());
|
||||
|
||||
let data2 = fixtures::repliable_content_data_v2(&mut rng, 10, 100, LEGACY_HOPS);
|
||||
assert_eq!(data2.serialized_size(), data2.into_bytes().len());
|
||||
|
||||
let data3 = fixtures::repliable_content_data_v2(&mut rng, 100000, 1000, LEGACY_HOPS);
|
||||
assert_eq!(data3.serialized_size(), data3.into_bytes().len());
|
||||
|
||||
let data4 = fixtures::repliable_content_data_v2(&mut rng, 100000, 1000, 1);
|
||||
assert_eq!(data4.serialized_size(), data4.into_bytes().len());
|
||||
|
||||
let additional_surbs1 = fixtures::repliable_content_surbs_v2(&mut rng, 1, LEGACY_HOPS);
|
||||
assert_eq!(
|
||||
additional_surbs1.serialized_size(),
|
||||
additional_surbs1.into_bytes().len()
|
||||
);
|
||||
|
||||
let additional_surbs2 =
|
||||
fixtures::repliable_content_surbs_v2(&mut rng, 1000, LEGACY_HOPS);
|
||||
assert_eq!(
|
||||
additional_surbs2.serialized_size(),
|
||||
additional_surbs2.into_bytes().len()
|
||||
);
|
||||
|
||||
let additional_surbs3 = fixtures::repliable_content_surbs_v2(&mut rng, 1000, 1);
|
||||
assert_eq!(
|
||||
additional_surbs3.serialized_size(),
|
||||
additional_surbs3.into_bytes().len()
|
||||
);
|
||||
|
||||
let heartbeat1 = fixtures::repliable_content_heartbeat_v2(&mut rng, 1, LEGACY_HOPS);
|
||||
assert_eq!(heartbeat1.serialized_size(), heartbeat1.into_bytes().len());
|
||||
|
||||
let heartbeat2 = fixtures::repliable_content_heartbeat_v2(&mut rng, 1000, LEGACY_HOPS);
|
||||
assert_eq!(heartbeat2.serialized_size(), heartbeat2.into_bytes().len());
|
||||
|
||||
let heartbeat3 = fixtures::repliable_content_heartbeat_v2(&mut rng, 1000, 1);
|
||||
assert_eq!(heartbeat3.serialized_size(), heartbeat3.into_bytes().len());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::requests::InvalidReplyRequestError;
|
||||
use crate::ReplySurb;
|
||||
use nym_sphinx_types::PAYLOAD_KEY_SIZE;
|
||||
use std::fmt::Display;
|
||||
use std::mem;
|
||||
use tracing::{error, warn};
|
||||
|
||||
const fn v1_reply_surb_serialised_len() -> usize {
|
||||
// the SURB itself consists of SURB_header, first hop address and set of payload keys
|
||||
// for each hop (3x mix + egress)
|
||||
ReplySurb::BASE_OVERHEAD + 4 * PAYLOAD_KEY_SIZE
|
||||
}
|
||||
|
||||
fn v1_reply_surbs_serialised_len(surbs: &[ReplySurb]) -> usize {
|
||||
// sanity checks; this should probably be removed later on
|
||||
if let Some(reply_surb) = surbs.first() {
|
||||
if reply_surb.surb.uses_key_seeds() {
|
||||
error!("using v1 surbs encoding with updated structure - the surbs will be unusable")
|
||||
}
|
||||
}
|
||||
|
||||
// when serialising surbs are always prepended with u32-encoded count
|
||||
4 + surbs.len() * v1_reply_surb_serialised_len()
|
||||
}
|
||||
|
||||
// this recovery code is shared between all legacy variants containing reply surbs
|
||||
// NUM_SURBS (u32) || SURB_DATA
|
||||
fn recover_reply_surbs_v1(
|
||||
bytes: &[u8],
|
||||
) -> Result<(Vec<ReplySurb>, usize), InvalidReplyRequestError> {
|
||||
let mut consumed = mem::size_of::<u32>();
|
||||
if bytes.len() < consumed {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
let num_surbs = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
|
||||
|
||||
let surb_size = v1_reply_surb_serialised_len();
|
||||
if bytes[consumed..].len() < num_surbs as usize * surb_size {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
|
||||
let mut reply_surbs = Vec::with_capacity(num_surbs as usize);
|
||||
for _ in 0..num_surbs as usize {
|
||||
let surb_bytes = &bytes[consumed..consumed + surb_size];
|
||||
let reply_surb = ReplySurb::from_bytes(surb_bytes)?;
|
||||
reply_surbs.push(reply_surb);
|
||||
|
||||
consumed += surb_size;
|
||||
}
|
||||
|
||||
Ok((reply_surbs, consumed))
|
||||
}
|
||||
|
||||
// length (u32) prefixed reply surbs with legacy serialisation of 4 hops and full payload keys attached
|
||||
fn reply_surbs_bytes_v1(reply_surbs: &[ReplySurb]) -> impl Iterator<Item = u8> + use<'_> {
|
||||
let num_surbs = reply_surbs.len() as u32;
|
||||
|
||||
num_surbs
|
||||
.to_be_bytes()
|
||||
.into_iter()
|
||||
.chain(reply_surbs.iter().flat_map(|s| s.to_bytes()))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataV1 {
|
||||
pub message: Vec<u8>,
|
||||
pub reply_surbs: Vec<ReplySurb>,
|
||||
}
|
||||
|
||||
impl Display for DataV1 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"V1 repliable {:.2} kiB data message with {} reply surbs attached",
|
||||
self.message.len() as f64 / 1024.0,
|
||||
self.reply_surbs.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AdditionalSurbsV1 {
|
||||
pub reply_surbs: Vec<ReplySurb>,
|
||||
}
|
||||
|
||||
impl Display for AdditionalSurbsV1 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"V1 repliable additional surbs message ({} reply surbs attached)",
|
||||
self.reply_surbs.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HeartbeatV1 {
|
||||
pub additional_reply_surbs: Vec<ReplySurb>,
|
||||
}
|
||||
|
||||
impl Display for HeartbeatV1 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"V1 repliable heartbeat message ({} reply surbs attached)",
|
||||
self.additional_reply_surbs.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl DataV1 {
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
reply_surbs_bytes_v1(&self.reply_surbs)
|
||||
.chain(self.message)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
|
||||
let (reply_surbs, n) = recover_reply_surbs_v1(bytes)?;
|
||||
Ok(DataV1 {
|
||||
message: bytes[n..].to_vec(),
|
||||
reply_surbs,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn serialized_len(&self) -> usize {
|
||||
v1_reply_surbs_serialised_len(&self.reply_surbs) + self.message.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl AdditionalSurbsV1 {
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
reply_surbs_bytes_v1(&self.reply_surbs).collect()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
|
||||
let (reply_surbs, n) = recover_reply_surbs_v1(bytes)?;
|
||||
if n != bytes.len() {
|
||||
let trailing = bytes.len() - n;
|
||||
|
||||
warn!("trailing {trailing} bytes after v1 additional surbs message");
|
||||
}
|
||||
|
||||
Ok(AdditionalSurbsV1 { reply_surbs })
|
||||
}
|
||||
|
||||
pub fn serialized_len(&self) -> usize {
|
||||
v1_reply_surbs_serialised_len(&self.reply_surbs)
|
||||
}
|
||||
}
|
||||
|
||||
impl HeartbeatV1 {
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
reply_surbs_bytes_v1(&self.additional_reply_surbs).collect()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
|
||||
let (additional_reply_surbs, n) = recover_reply_surbs_v1(bytes)?;
|
||||
if n != bytes.len() {
|
||||
let trailing = bytes.len() - n;
|
||||
|
||||
warn!("trailing {trailing} bytes after v1 heartbeat message");
|
||||
}
|
||||
|
||||
Ok(HeartbeatV1 {
|
||||
additional_reply_surbs,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn serialized_len(&self) -> usize {
|
||||
v1_reply_surbs_serialised_len(&self.additional_reply_surbs)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::requests::InvalidReplyRequestError;
|
||||
use crate::ReplySurb;
|
||||
use nym_sphinx_types::constants::PAYLOAD_KEY_SEED_SIZE;
|
||||
use std::fmt::Display;
|
||||
use std::iter::once;
|
||||
use tracing::{error, warn};
|
||||
|
||||
const fn v2_reply_surb_serialised_len(num_hops: u8) -> usize {
|
||||
ReplySurb::BASE_OVERHEAD + num_hops as usize * PAYLOAD_KEY_SEED_SIZE
|
||||
}
|
||||
|
||||
// sphinx doesn't support more than 5 hops (so cast to u8 is safe)
|
||||
// ASSUMPTION: all surbs are generated with the same parameters (if they're not, then the client is hurting itself)
|
||||
fn reply_surbs_hops(reply_surbs: &[ReplySurb]) -> u8 {
|
||||
reply_surbs
|
||||
.first()
|
||||
.map(|reply_surb| reply_surb.surb.materials_count() as u8)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn v2_reply_surbs_serialised_len(surbs: &[ReplySurb]) -> usize {
|
||||
let num_surbs = surbs.len();
|
||||
let num_hops = reply_surbs_hops(surbs);
|
||||
|
||||
// sanity checks; this should probably be removed later on
|
||||
if let Some(reply_surb) = surbs.first() {
|
||||
if !reply_surb.surb.uses_key_seeds() {
|
||||
error!("using v2 surbs encoding with legacy structure - the surbs will be unusable")
|
||||
}
|
||||
}
|
||||
|
||||
// when serialising surbs are always prepended with u16-encoded count an u8-encoded number of hops
|
||||
3 + num_surbs * v2_reply_surb_serialised_len(num_hops)
|
||||
}
|
||||
|
||||
// NUM_SURBS (u16) || HOPS (u8) || SURB_DATA
|
||||
fn recover_reply_surbs_v2(
|
||||
bytes: &[u8],
|
||||
) -> Result<(Vec<ReplySurb>, usize), InvalidReplyRequestError> {
|
||||
if bytes.len() < 2 {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
|
||||
// we're not attaching more than 65k surbs...
|
||||
let num_surbs = u16::from_be_bytes([bytes[0], bytes[1]]);
|
||||
let num_hops = bytes[2];
|
||||
let mut consumed = 3;
|
||||
|
||||
let surb_size = ReplySurb::v2_serialised_len(num_hops);
|
||||
if bytes[consumed..].len() < num_surbs as usize * surb_size {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
|
||||
let mut reply_surbs = Vec::with_capacity(num_surbs as usize);
|
||||
for _ in 0..num_surbs as usize {
|
||||
let surb_bytes = &bytes[consumed..consumed + surb_size];
|
||||
let reply_surb = ReplySurb::from_bytes(surb_bytes)?;
|
||||
reply_surbs.push(reply_surb);
|
||||
|
||||
consumed += surb_size;
|
||||
}
|
||||
|
||||
Ok((reply_surbs, consumed))
|
||||
}
|
||||
|
||||
fn reply_surbs_bytes_v2(reply_surbs: &[ReplySurb]) -> impl Iterator<Item = u8> + use<'_> {
|
||||
let num_surbs = reply_surbs.len() as u16;
|
||||
let num_hops = reply_surbs_hops(reply_surbs);
|
||||
|
||||
num_surbs
|
||||
.to_be_bytes()
|
||||
.into_iter()
|
||||
.chain(once(num_hops))
|
||||
.chain(reply_surbs.iter().flat_map(|surb| surb.to_bytes()))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataV2 {
|
||||
pub message: Vec<u8>,
|
||||
pub reply_surbs: Vec<ReplySurb>,
|
||||
}
|
||||
|
||||
impl Display for DataV2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"V2 repliable {:.2} kiB data message with {} reply surbs attached",
|
||||
self.message.len() as f64 / 1024.0,
|
||||
self.reply_surbs.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AdditionalSurbsV2 {
|
||||
pub reply_surbs: Vec<ReplySurb>,
|
||||
}
|
||||
|
||||
impl Display for AdditionalSurbsV2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"V2 repliable additional surbs message ({} reply surbs attached)",
|
||||
self.reply_surbs.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HeartbeatV2 {
|
||||
pub additional_reply_surbs: Vec<ReplySurb>,
|
||||
}
|
||||
|
||||
impl Display for HeartbeatV2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"V2 repliable heartbeat message ({} reply surbs attached)",
|
||||
self.additional_reply_surbs.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl DataV2 {
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
reply_surbs_bytes_v2(&self.reply_surbs)
|
||||
.chain(self.message)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
|
||||
let (reply_surbs, n) = recover_reply_surbs_v2(bytes)?;
|
||||
Ok(DataV2 {
|
||||
message: bytes[n..].to_vec(),
|
||||
reply_surbs,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn serialized_len(&self) -> usize {
|
||||
v2_reply_surbs_serialised_len(&self.reply_surbs) + self.message.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl AdditionalSurbsV2 {
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
reply_surbs_bytes_v2(&self.reply_surbs).collect()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
|
||||
let (reply_surbs, n) = recover_reply_surbs_v2(bytes)?;
|
||||
if n != bytes.len() {
|
||||
let trailing = bytes.len() - n;
|
||||
warn!("trailing {trailing} bytes after v2 additional surbs message");
|
||||
}
|
||||
|
||||
Ok(AdditionalSurbsV2 { reply_surbs })
|
||||
}
|
||||
|
||||
pub fn serialized_len(&self) -> usize {
|
||||
v2_reply_surbs_serialised_len(&self.reply_surbs)
|
||||
}
|
||||
}
|
||||
|
||||
impl HeartbeatV2 {
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
reply_surbs_bytes_v2(&self.additional_reply_surbs).collect()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
|
||||
let (additional_reply_surbs, n) = recover_reply_surbs_v2(bytes)?;
|
||||
if n != bytes.len() {
|
||||
let trailing = bytes.len() - n;
|
||||
warn!("trailing {trailing} bytes after v2 heartbeat message");
|
||||
}
|
||||
|
||||
Ok(HeartbeatV2 {
|
||||
additional_reply_surbs,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn serialized_len(&self) -> usize {
|
||||
v2_reply_surbs_serialised_len(&self.additional_reply_surbs)
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ pub enum CoverMessageError {
|
||||
|
||||
pub fn generate_loop_cover_surb_ack<R>(
|
||||
rng: &mut R,
|
||||
use_legacy_sphinx_format: bool,
|
||||
topology: &NymRouteProvider,
|
||||
ack_key: &AckKey,
|
||||
full_address: &Recipient,
|
||||
@@ -45,6 +46,7 @@ where
|
||||
{
|
||||
Ok(SurbAck::construct(
|
||||
rng,
|
||||
use_legacy_sphinx_format,
|
||||
full_address,
|
||||
ack_key,
|
||||
COVER_FRAG_ID.to_bytes(),
|
||||
@@ -57,6 +59,7 @@ where
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn generate_loop_cover_packet<R>(
|
||||
rng: &mut R,
|
||||
use_legacy_sphinx_format: bool,
|
||||
topology: &NymRouteProvider,
|
||||
ack_key: &AckKey,
|
||||
full_address: &Recipient,
|
||||
@@ -71,6 +74,7 @@ where
|
||||
// we don't care about total ack delay - we will not be retransmitting it anyway
|
||||
let (_, ack_bytes) = generate_loop_cover_surb_ack(
|
||||
rng,
|
||||
use_legacy_sphinx_format,
|
||||
topology,
|
||||
ack_key,
|
||||
full_address,
|
||||
@@ -126,6 +130,7 @@ where
|
||||
// once merged, that's an easy rng injection point for sphinx packets : )
|
||||
let packet = match packet_type {
|
||||
PacketType::Mix => NymPacket::sphinx_build(
|
||||
use_legacy_sphinx_format,
|
||||
packet_size.payload_size(),
|
||||
packet_payload,
|
||||
&route,
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::packet::{FramedNymPacket, Header};
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use nym_sphinx_params::packet_sizes::{InvalidPacketSize, PacketSize};
|
||||
use nym_sphinx_params::packet_types::InvalidPacketType;
|
||||
use nym_sphinx_params::packet_version::{InvalidPacketVersion, PacketVersion};
|
||||
use nym_sphinx_params::PacketType;
|
||||
use nym_sphinx_types::{NymPacket, NymPacketError};
|
||||
use std::io;
|
||||
@@ -13,16 +14,25 @@ use tokio_util::codec::{Decoder, Encoder};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum NymCodecError {
|
||||
#[error("the packet size information was malformed - {0}")]
|
||||
#[error("the packet size information was malformed: {0}")]
|
||||
InvalidPacketSize(#[from] InvalidPacketSize),
|
||||
|
||||
#[error("the packet mode information was malformed - {0}")]
|
||||
#[error("the packet mode information was malformed: {0}")]
|
||||
InvalidPacketType(#[from] InvalidPacketType),
|
||||
|
||||
#[error("encountered an IO error - {0}")]
|
||||
#[error("the packet version information was malformed: {0}")]
|
||||
InvalidPacketVersion(#[from] InvalidPacketVersion),
|
||||
|
||||
#[error("received unsupported packet version {received}. max supported is {max_supported}")]
|
||||
UnsupportedPacketVersion {
|
||||
received: PacketVersion,
|
||||
max_supported: PacketVersion,
|
||||
},
|
||||
|
||||
#[error("encountered an IO error: {0}")]
|
||||
IoError(#[from] io::Error),
|
||||
|
||||
#[error("encountered a packet error - {0}")]
|
||||
#[error("encountered a packet error: {0}")]
|
||||
NymPacket(#[from] NymPacketError),
|
||||
|
||||
#[error("could not convert to bytes")]
|
||||
@@ -56,7 +66,7 @@ impl Decoder for NymCodec {
|
||||
if src.is_empty() {
|
||||
// can't do anything if we have no bytes, but let's reserve enough for the most
|
||||
// conservative case, i.e. receiving an ack packet
|
||||
src.reserve(Header::LEGACY_SIZE + PacketSize::AckPacket.size());
|
||||
src.reserve(Header::SIZE + PacketSize::AckPacket.size());
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@@ -68,7 +78,7 @@ impl Decoder for NymCodec {
|
||||
};
|
||||
|
||||
let packet_size = header.packet_size.size();
|
||||
let frame_len = header.size() + packet_size;
|
||||
let frame_len = Header::SIZE + packet_size;
|
||||
|
||||
if src.len() < frame_len {
|
||||
// we don't have enough bytes to read the rest of frame
|
||||
@@ -77,7 +87,7 @@ impl Decoder for NymCodec {
|
||||
}
|
||||
|
||||
// advance buffer past the header - at this point we have enough bytes
|
||||
src.advance(header.size());
|
||||
src.advance(Header::SIZE);
|
||||
let packet_bytes = src.split_to(packet_size);
|
||||
let packet = if let Some(slice) = packet_bytes.get(..) {
|
||||
// here it could be debatable whether stream is corrupt or not,
|
||||
@@ -104,11 +114,11 @@ impl Decoder for NymCodec {
|
||||
// we also assume the next packet coming from the same client will use exactly the same versioning
|
||||
// as the current packet
|
||||
|
||||
let mut allocate_for_next_packet = header.size() + PacketSize::AckPacket.size();
|
||||
let mut allocate_for_next_packet = Header::SIZE + PacketSize::AckPacket.size();
|
||||
if !src.is_empty() {
|
||||
match Header::decode(src) {
|
||||
Ok(Some(next_header)) => {
|
||||
allocate_for_next_packet = next_header.size() + next_header.packet_size.size();
|
||||
allocate_for_next_packet = Header::SIZE + next_header.packet_size.size();
|
||||
}
|
||||
Ok(None) => {
|
||||
// we don't have enough information to know how much to reserve, fallback to the ack case
|
||||
@@ -199,8 +209,15 @@ mod packet_encoding {
|
||||
SphinxDelay::new_from_nanos(42),
|
||||
SphinxDelay::new_from_nanos(42),
|
||||
];
|
||||
NymPacket::sphinx_build(size.payload_size(), b"foomp", &route, &destination, &delays)
|
||||
.unwrap()
|
||||
NymPacket::sphinx_build(
|
||||
false,
|
||||
size.payload_size(),
|
||||
b"foomp",
|
||||
&route,
|
||||
&destination,
|
||||
&delays,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -252,34 +269,10 @@ mod packet_encoding {
|
||||
assert!(NymCodec.decode(&mut empty_bytes).unwrap().is_none());
|
||||
assert_eq!(
|
||||
empty_bytes.capacity(),
|
||||
Header::LEGACY_SIZE + PacketSize::AckPacket.size()
|
||||
Header::SIZE + PacketSize::AckPacket.size()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_bytes_with_legacy_header() {
|
||||
// if header gets decoded there should be enough bytes for the entire frame
|
||||
let packet_sizes = vec![
|
||||
PacketSize::AckPacket,
|
||||
PacketSize::RegularPacket,
|
||||
PacketSize::ExtendedPacket8,
|
||||
PacketSize::ExtendedPacket16,
|
||||
PacketSize::ExtendedPacket32,
|
||||
];
|
||||
for packet_size in packet_sizes {
|
||||
let header = Header {
|
||||
packet_version: PacketVersion::Legacy,
|
||||
packet_size,
|
||||
..Default::default()
|
||||
};
|
||||
let mut bytes = BytesMut::new();
|
||||
header.encode(&mut bytes);
|
||||
assert!(NymCodec.decode(&mut bytes).unwrap().is_none());
|
||||
|
||||
assert_eq!(bytes.capacity(), Header::LEGACY_SIZE + packet_size.size())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_bytes_with_versioned_header() {
|
||||
// if header gets decoded there should be enough bytes for the entire frame
|
||||
@@ -292,7 +285,7 @@ mod packet_encoding {
|
||||
];
|
||||
for packet_size in packet_sizes {
|
||||
let header = Header {
|
||||
packet_version: PacketVersion::Versioned(123),
|
||||
packet_version: PacketVersion::new(),
|
||||
packet_size,
|
||||
..Default::default()
|
||||
};
|
||||
@@ -300,33 +293,10 @@ mod packet_encoding {
|
||||
header.encode(&mut bytes);
|
||||
assert!(NymCodec.decode(&mut bytes).unwrap().is_none());
|
||||
|
||||
assert_eq!(
|
||||
bytes.capacity(),
|
||||
Header::VERSIONED_SIZE + packet_size.size()
|
||||
)
|
||||
assert_eq!(bytes.capacity(), Header::SIZE + packet_size.size())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_full_frame_with_legacy_header() {
|
||||
// if full frame is used exactly, there should be enough space for header + ack packet
|
||||
let packet = FramedNymPacket {
|
||||
header: Header {
|
||||
packet_version: PacketVersion::Legacy,
|
||||
..Default::default()
|
||||
},
|
||||
packet: make_valid_sphinx_packet(Default::default()),
|
||||
};
|
||||
|
||||
let mut bytes = BytesMut::new();
|
||||
NymCodec.encode(packet, &mut bytes).unwrap();
|
||||
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
|
||||
assert_eq!(
|
||||
bytes.capacity(),
|
||||
Header::LEGACY_SIZE + PacketSize::AckPacket.size()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_full_frame_with_versioned_header() {
|
||||
// if full frame is used exactly, there should be enough space for header + ack packet
|
||||
@@ -340,40 +310,10 @@ mod packet_encoding {
|
||||
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
|
||||
assert_eq!(
|
||||
bytes.capacity(),
|
||||
Header::VERSIONED_SIZE + PacketSize::AckPacket.size()
|
||||
Header::SIZE + PacketSize::AckPacket.size()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_full_frame_with_extra_bytes_with_legacy_header() {
|
||||
// if there was at least 2 byte left, there should be enough space for entire next frame
|
||||
let packet_sizes = vec![
|
||||
PacketSize::AckPacket,
|
||||
PacketSize::RegularPacket,
|
||||
PacketSize::ExtendedPacket8,
|
||||
PacketSize::ExtendedPacket16,
|
||||
PacketSize::ExtendedPacket32,
|
||||
];
|
||||
|
||||
for packet_size in packet_sizes {
|
||||
let first_packet = FramedNymPacket {
|
||||
header: Header {
|
||||
packet_version: PacketVersion::Legacy,
|
||||
..Default::default()
|
||||
},
|
||||
packet: make_valid_sphinx_packet(Default::default()),
|
||||
};
|
||||
|
||||
let mut bytes = BytesMut::new();
|
||||
NymCodec.encode(first_packet, &mut bytes).unwrap();
|
||||
bytes.put_u8(packet_size as u8);
|
||||
bytes.put_u8(PacketType::default() as u8);
|
||||
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
|
||||
|
||||
assert!(bytes.capacity() >= Header::LEGACY_SIZE + packet_size.size())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_full_frame_with_extra_bytes_with_versioned_header() {
|
||||
// if there was at least 3 byte left, there should be enough space for entire next frame
|
||||
@@ -393,7 +333,7 @@ mod packet_encoding {
|
||||
|
||||
let mut bytes = BytesMut::new();
|
||||
NymCodec.encode(first_packet, &mut bytes).unwrap();
|
||||
bytes.put_u8(PacketVersion::new_versioned(123).as_u8().unwrap());
|
||||
bytes.put_u8(PacketVersion::new().as_u8());
|
||||
bytes.put_u8(packet_size as u8);
|
||||
bytes.put_u8(PacketType::default() as u8);
|
||||
assert!(NymCodec.decode(&mut bytes).unwrap().is_some());
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::codec::NymCodecError;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use nym_sphinx_params::packet_sizes::PacketSize;
|
||||
use nym_sphinx_params::packet_version::PacketVersion;
|
||||
use nym_sphinx_params::packet_version::{PacketVersion, CURRENT_PACKET_VERSION};
|
||||
use nym_sphinx_params::PacketType;
|
||||
use nym_sphinx_types::NymPacket;
|
||||
|
||||
@@ -81,8 +81,7 @@ pub struct Header {
|
||||
}
|
||||
|
||||
impl Header {
|
||||
pub(crate) const LEGACY_SIZE: usize = 2;
|
||||
pub(crate) const VERSIONED_SIZE: usize = 3;
|
||||
pub(crate) const SIZE: usize = 3;
|
||||
|
||||
pub fn outfox() -> Header {
|
||||
Header {
|
||||
@@ -92,53 +91,39 @@ impl Header {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn size(&self) -> usize {
|
||||
if self.packet_version.is_legacy() {
|
||||
Self::LEGACY_SIZE
|
||||
} else {
|
||||
Self::VERSIONED_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn encode(&self, dst: &mut BytesMut) {
|
||||
// we reserve one byte for `packet_size` and the other for `mode`
|
||||
dst.reserve(Self::LEGACY_SIZE);
|
||||
if let Some(version) = self.packet_version.as_u8() {
|
||||
dst.reserve(Self::VERSIONED_SIZE);
|
||||
dst.put_u8(version)
|
||||
}
|
||||
dst.reserve(Self::SIZE);
|
||||
|
||||
dst.put_u8(self.packet_version.as_u8());
|
||||
dst.put_u8(self.packet_size as u8);
|
||||
dst.put_u8(self.packet_type as u8);
|
||||
|
||||
// reserve bytes for the actual packet
|
||||
dst.reserve(self.packet_size.size());
|
||||
}
|
||||
|
||||
pub(crate) fn decode(src: &mut BytesMut) -> Result<Option<Self>, NymCodecError> {
|
||||
if src.len() < Self::LEGACY_SIZE {
|
||||
if src.len() < Self::SIZE {
|
||||
// can't do anything if we don't have enough bytes - but reserve enough for the next call
|
||||
src.reserve(Self::LEGACY_SIZE);
|
||||
src.reserve(Self::SIZE);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let packet_version = PacketVersion::from(src[0]);
|
||||
if packet_version.is_legacy() {
|
||||
Ok(Some(Header {
|
||||
packet_version,
|
||||
packet_size: PacketSize::try_from(src[0])?,
|
||||
packet_type: PacketType::try_from(src[1])?,
|
||||
}))
|
||||
} else if src.len() < Self::VERSIONED_SIZE {
|
||||
// we're missing that 1 byte to read the full header...
|
||||
src.reserve(Self::VERSIONED_SIZE);
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(Header {
|
||||
packet_version,
|
||||
packet_size: PacketSize::try_from(src[1])?,
|
||||
packet_type: PacketType::try_from(src[2])?,
|
||||
}))
|
||||
let packet_version = PacketVersion::try_from(src[0])?;
|
||||
if packet_version > CURRENT_PACKET_VERSION {
|
||||
// received an unsupported packet version - we don't know how it's meant to look like!
|
||||
// (this is in preparation for the dual support of breaking sphinx changes)
|
||||
return Err(NymCodecError::UnsupportedPacketVersion {
|
||||
received: packet_version,
|
||||
max_supported: CURRENT_PACKET_VERSION,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Some(Header {
|
||||
packet_version,
|
||||
packet_size: PacketSize::try_from(src[1])?,
|
||||
packet_type: PacketType::try_from(src[2])?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +150,7 @@ mod header_encoding {
|
||||
// due to the hack used to get legacy mode compatibility
|
||||
let mut bytes = BytesMut::from(
|
||||
[
|
||||
PacketVersion::new_versioned(123).as_u8().unwrap(),
|
||||
PacketVersion::new().as_u8(),
|
||||
unknown_packet_size,
|
||||
PacketType::default() as u8,
|
||||
]
|
||||
@@ -180,7 +165,14 @@ mod header_encoding {
|
||||
// make sure this is still 'unknown' for if we make changes in the future
|
||||
assert!(PacketType::try_from(unknown_packet_type).is_err());
|
||||
|
||||
let mut bytes = BytesMut::from([PacketSize::default() as u8, unknown_packet_type].as_ref());
|
||||
let mut bytes = BytesMut::from(
|
||||
[
|
||||
PacketVersion::new().as_u8(),
|
||||
PacketSize::default() as u8,
|
||||
unknown_packet_type,
|
||||
]
|
||||
.as_ref(),
|
||||
);
|
||||
assert!(Header::decode(&mut bytes).is_err())
|
||||
}
|
||||
|
||||
@@ -189,16 +181,16 @@ mod header_encoding {
|
||||
let mut empty_bytes = BytesMut::new();
|
||||
let decode_attempt_1 = Header::decode(&mut empty_bytes).unwrap();
|
||||
assert!(decode_attempt_1.is_none());
|
||||
assert!(empty_bytes.capacity() > Header::LEGACY_SIZE);
|
||||
assert!(empty_bytes.capacity() > Header::SIZE);
|
||||
|
||||
let mut empty_bytes = BytesMut::with_capacity(1);
|
||||
let decode_attempt_2 = Header::decode(&mut empty_bytes).unwrap();
|
||||
assert!(decode_attempt_2.is_none());
|
||||
assert!(empty_bytes.capacity() > Header::LEGACY_SIZE);
|
||||
assert!(empty_bytes.capacity() > Header::SIZE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_encoding_reserves_enough_bytes_for_full_sphinx_packet_in_legacy_mode() {
|
||||
fn header_encoding_reserves_enough_bytes_for_full_sphinx_packet_() {
|
||||
let packet_sizes = vec![
|
||||
PacketSize::AckPacket,
|
||||
PacketSize::RegularPacket,
|
||||
@@ -208,7 +200,7 @@ mod header_encoding {
|
||||
];
|
||||
for packet_size in packet_sizes {
|
||||
let header = Header {
|
||||
packet_version: PacketVersion::Legacy,
|
||||
packet_version: PacketVersion::new(),
|
||||
packet_size,
|
||||
..Default::default()
|
||||
};
|
||||
@@ -219,23 +211,26 @@ mod header_encoding {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_encoding_reserves_enough_bytes_for_full_sphinx_packet_in_versioned_mode() {
|
||||
let packet_sizes = vec![
|
||||
PacketSize::AckPacket,
|
||||
PacketSize::RegularPacket,
|
||||
PacketSize::ExtendedPacket8,
|
||||
PacketSize::ExtendedPacket16,
|
||||
PacketSize::ExtendedPacket32,
|
||||
];
|
||||
for packet_size in packet_sizes {
|
||||
let header = Header {
|
||||
packet_version: PacketVersion::Versioned(123),
|
||||
packet_size,
|
||||
..Default::default()
|
||||
};
|
||||
let mut bytes = BytesMut::new();
|
||||
header.encode(&mut bytes);
|
||||
assert_eq!(bytes.capacity(), bytes.len() + packet_size.size())
|
||||
fn header_decoding_will_reject_future_versions() {
|
||||
let future_version = PacketVersion::try_from(123).unwrap();
|
||||
|
||||
let unchecked_header = Header {
|
||||
packet_version: future_version,
|
||||
packet_size: PacketSize::RegularPacket,
|
||||
packet_type: PacketType::Mix,
|
||||
};
|
||||
let mut bytes = BytesMut::new();
|
||||
unchecked_header.encode(&mut bytes);
|
||||
|
||||
match Header::decode(&mut bytes).unwrap_err() {
|
||||
NymCodecError::UnsupportedPacketVersion {
|
||||
received,
|
||||
max_supported,
|
||||
} => {
|
||||
assert_eq!(received, future_version);
|
||||
assert_eq!(max_supported, CURRENT_PACKET_VERSION);
|
||||
}
|
||||
_ => panic!("unexpected error variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,17 +22,6 @@ pub mod packet_version;
|
||||
pub const FRAG_ID_LEN: usize = 5;
|
||||
pub type SerializedFragmentIdentifier = [u8; FRAG_ID_LEN];
|
||||
|
||||
// wait, wait, but why are we starting with version 7?
|
||||
// when packet header gets serialized, the following bytes (in that order) are put onto the wire:
|
||||
// - packet_version (starting with v1.1.0)
|
||||
// - packet_size indicator
|
||||
// - packet_type
|
||||
// it also just so happens that the only valid values for packet_size indicator include values 1-6
|
||||
// therefore if we receive byte `7` (or larger than that) we'll know we received a versioned packet,
|
||||
// otherwise we should treat it as legacy
|
||||
/// Increment it whenever we perform any breaking change in the wire format!
|
||||
const CURRENT_PACKET_VERSION_NUMBER: u8 = 7;
|
||||
|
||||
// TODO: ask @AP about the choice of below algorithms
|
||||
|
||||
/// Hashing algorithm used during hkdf for ephemeral shared key generation per sphinx packet payload.
|
||||
|
||||
@@ -2,56 +2,70 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{PacketSize, CURRENT_PACKET_VERSION_NUMBER};
|
||||
// wait, wait, but why are we starting with version 7?
|
||||
// when packet header gets serialized, the following bytes (in that order) are put onto the wire:
|
||||
// - packet_version (starting with v1.1.0)
|
||||
// - packet_size indicator
|
||||
// - packet_type
|
||||
// it also just so happens that the only valid values for packet_size indicator include values 1-6
|
||||
// therefore if we receive byte `7` (or larger than that) we'll know we received a versioned packet,
|
||||
// otherwise we should treat it as legacy
|
||||
/// Increment it whenever we perform any breaking change in the wire format!
|
||||
pub const INITIAL_PACKET_VERSION_NUMBER: u8 = 7;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum PacketVersion {
|
||||
// this will allow updated mixnodes to still understand packets from before the update
|
||||
Legacy,
|
||||
Versioned(u8),
|
||||
pub const CURRENT_PACKET_VERSION_NUMBER: u8 = INITIAL_PACKET_VERSION_NUMBER;
|
||||
pub const CURRENT_PACKET_VERSION: PacketVersion =
|
||||
PacketVersion::unchecked(CURRENT_PACKET_VERSION_NUMBER);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct PacketVersion(u8);
|
||||
|
||||
impl Display for PacketVersion {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("attempted to use legacy packet version")]
|
||||
pub struct InvalidPacketVersion;
|
||||
|
||||
impl PacketVersion {
|
||||
pub fn new() -> Self {
|
||||
Self::new_versioned(CURRENT_PACKET_VERSION_NUMBER)
|
||||
PacketVersion(CURRENT_PACKET_VERSION_NUMBER)
|
||||
}
|
||||
|
||||
pub fn new_legacy() -> Self {
|
||||
PacketVersion::Legacy
|
||||
const fn unchecked(version: u8) -> PacketVersion {
|
||||
PacketVersion(version)
|
||||
}
|
||||
|
||||
pub fn new_versioned(version: u8) -> Self {
|
||||
PacketVersion::Versioned(version)
|
||||
}
|
||||
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
matches!(self, PacketVersion::Legacy)
|
||||
}
|
||||
|
||||
pub fn as_u8(&self) -> Option<u8> {
|
||||
match self {
|
||||
PacketVersion::Legacy => None,
|
||||
PacketVersion::Versioned(version) => Some(*version),
|
||||
}
|
||||
pub fn as_u8(&self) -> u8 {
|
||||
(*self).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for PacketVersion {
|
||||
fn from(v: u8) -> Self {
|
||||
match v {
|
||||
n if n == PacketSize::RegularPacket as u8 => PacketVersion::Legacy,
|
||||
n if n == PacketSize::AckPacket as u8 => PacketVersion::Legacy,
|
||||
n if n == PacketSize::ExtendedPacket8 as u8 => PacketVersion::Legacy,
|
||||
n if n == PacketSize::ExtendedPacket16 as u8 => PacketVersion::Legacy,
|
||||
n if n == PacketSize::ExtendedPacket32 as u8 => PacketVersion::Legacy,
|
||||
n => PacketVersion::Versioned(n),
|
||||
impl TryFrom<u8> for PacketVersion {
|
||||
type Error = InvalidPacketVersion;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
if value < INITIAL_PACKET_VERSION_NUMBER {
|
||||
return Err(InvalidPacketVersion);
|
||||
}
|
||||
Ok(PacketVersion(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PacketVersion> for u8 {
|
||||
fn from(packet_version: PacketVersion) -> Self {
|
||||
packet_version.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PacketVersion {
|
||||
fn default() -> Self {
|
||||
PacketVersion::Versioned(CURRENT_PACKET_VERSION_NUMBER)
|
||||
PacketVersion::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,8 @@ impl NymMessage {
|
||||
match self {
|
||||
NymMessage::Plain(data) => data,
|
||||
NymMessage::Repliable(repliable) => match repliable.content {
|
||||
RepliableMessageContent::Data { message, .. } => message,
|
||||
RepliableMessageContent::Data(content) => content.message,
|
||||
RepliableMessageContent::DataV2(content) => content.message,
|
||||
_ => Vec::new(),
|
||||
},
|
||||
NymMessage::Reply(reply) => match reply.content {
|
||||
@@ -309,6 +310,7 @@ mod tests {
|
||||
// a single variant for each repliable and reply is enough as they are more thoroughly tested
|
||||
// internally
|
||||
let repliable = NymMessage::new_repliable(RepliableMessage::new_data(
|
||||
true,
|
||||
vec![1, 2, 3, 4, 5],
|
||||
[42u8; 16].into(),
|
||||
vec![],
|
||||
|
||||
@@ -51,29 +51,14 @@ impl From<PreparedFragment> for MixPacket {
|
||||
pub trait FragmentPreparer {
|
||||
type Rng: CryptoRng + Rng;
|
||||
|
||||
fn use_legacy_sphinx_format(&self) -> bool;
|
||||
|
||||
fn deterministic_route_selection(&self) -> bool;
|
||||
fn rng(&mut self) -> &mut Self::Rng;
|
||||
fn nonce(&self) -> i32;
|
||||
fn average_packet_delay(&self) -> Duration;
|
||||
fn average_ack_delay(&self) -> Duration;
|
||||
|
||||
fn generate_reply_surbs(
|
||||
&mut self,
|
||||
amount: usize,
|
||||
topology: &NymRouteProvider,
|
||||
reply_recipient: &Recipient,
|
||||
) -> Result<Vec<ReplySurb>, NymTopologyError> {
|
||||
let mut reply_surbs = Vec::with_capacity(amount);
|
||||
let packet_delay = self.average_packet_delay();
|
||||
for _ in 0..amount {
|
||||
let reply_surb =
|
||||
ReplySurb::construct(self.rng(), reply_recipient, packet_delay, topology)?;
|
||||
reply_surbs.push(reply_surb)
|
||||
}
|
||||
|
||||
Ok(reply_surbs)
|
||||
}
|
||||
|
||||
fn generate_surb_ack(
|
||||
&mut self,
|
||||
recipient: &Recipient,
|
||||
@@ -83,9 +68,11 @@ pub trait FragmentPreparer {
|
||||
packet_type: PacketType,
|
||||
) -> Result<SurbAck, NymTopologyError> {
|
||||
let ack_delay = self.average_ack_delay();
|
||||
let use_legacy_sphinx_format = self.use_legacy_sphinx_format();
|
||||
|
||||
SurbAck::construct(
|
||||
self.rng(),
|
||||
use_legacy_sphinx_format,
|
||||
recipient,
|
||||
ack_key,
|
||||
fragment_id.to_bytes(),
|
||||
@@ -264,6 +251,7 @@ pub trait FragmentPreparer {
|
||||
Some(packet_size.plaintext_size()),
|
||||
)?,
|
||||
PacketType::Mix => NymPacket::sphinx_build(
|
||||
self.use_legacy_sphinx_format(),
|
||||
packet_size.payload_size(),
|
||||
packet_payload,
|
||||
&route,
|
||||
@@ -323,6 +311,10 @@ pub struct MessagePreparer<R> {
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
average_ack_delay: Duration,
|
||||
|
||||
/// Specify whether any constructed packets should use the legacy format,
|
||||
/// where the payload keys are explicitly attached rather than using the seeds
|
||||
use_legacy_sphinx_format: bool,
|
||||
|
||||
nonce: i32,
|
||||
}
|
||||
|
||||
@@ -336,6 +328,7 @@ where
|
||||
sender_address: Recipient,
|
||||
average_packet_delay: Duration,
|
||||
average_ack_delay: Duration,
|
||||
use_legacy_sphinx_format: bool,
|
||||
) -> Self {
|
||||
let mut rng = rng;
|
||||
let nonce = rng.gen();
|
||||
@@ -345,6 +338,7 @@ where
|
||||
sender_address,
|
||||
average_packet_delay,
|
||||
average_ack_delay,
|
||||
use_legacy_sphinx_format,
|
||||
nonce,
|
||||
}
|
||||
}
|
||||
@@ -356,6 +350,7 @@ where
|
||||
|
||||
pub fn generate_reply_surbs(
|
||||
&mut self,
|
||||
use_legacy_reply_surb_format: bool,
|
||||
amount: usize,
|
||||
topology: &NymRouteProvider,
|
||||
) -> Result<Vec<ReplySurb>, NymTopologyError> {
|
||||
@@ -365,6 +360,7 @@ where
|
||||
&mut self.rng,
|
||||
&self.sender_address,
|
||||
self.average_packet_delay,
|
||||
use_legacy_reply_surb_format,
|
||||
topology,
|
||||
)?;
|
||||
reply_surbs.push(reply_surb)
|
||||
@@ -446,6 +442,10 @@ where
|
||||
impl<R: CryptoRng + Rng> FragmentPreparer for MessagePreparer<R> {
|
||||
type Rng = R;
|
||||
|
||||
fn use_legacy_sphinx_format(&self) -> bool {
|
||||
self.use_legacy_sphinx_format
|
||||
}
|
||||
|
||||
fn deterministic_route_selection(&self) -> bool {
|
||||
self.deterministic_route_selection
|
||||
}
|
||||
|
||||
@@ -26,10 +26,13 @@ pub use sphinx_packet::{
|
||||
crypto::{self, PrivateKey, PublicKey},
|
||||
header::{self, delays, delays::Delay, ProcessedHeader, SphinxHeader, HEADER_SIZE},
|
||||
packet::builder::DEFAULT_PAYLOAD_SIZE,
|
||||
payload::{Payload, PAYLOAD_OVERHEAD_SIZE},
|
||||
payload::{
|
||||
key::{PayloadKey, PayloadKeySeed},
|
||||
Payload, PAYLOAD_OVERHEAD_SIZE,
|
||||
},
|
||||
route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier},
|
||||
surb::{SURBMaterial, SURB},
|
||||
version::Version,
|
||||
version::*,
|
||||
Error as SphinxError, ProcessedPacket, ProcessedPacketData,
|
||||
};
|
||||
|
||||
@@ -84,17 +87,25 @@ impl fmt::Debug for NymPacket {
|
||||
impl NymPacket {
|
||||
#[cfg(feature = "sphinx")]
|
||||
pub fn sphinx_build<M: AsRef<[u8]>>(
|
||||
use_legacy_sphinx_format: bool,
|
||||
size: usize,
|
||||
message: M,
|
||||
route: &[Node],
|
||||
destination: &Destination,
|
||||
delays: &[Delay],
|
||||
) -> Result<NymPacket, NymPacketError> {
|
||||
Ok(NymPacket::Sphinx(
|
||||
SphinxPacketBuilder::new()
|
||||
.with_payload_size(size)
|
||||
.build_packet(message, route, destination, delays)?,
|
||||
))
|
||||
let mut builder = SphinxPacketBuilder::new().with_payload_size(size);
|
||||
|
||||
if use_legacy_sphinx_format {
|
||||
builder = builder.with_version(X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION)
|
||||
};
|
||||
|
||||
Ok(NymPacket::Sphinx(builder.build_packet(
|
||||
message,
|
||||
route,
|
||||
destination,
|
||||
delays,
|
||||
)?))
|
||||
}
|
||||
#[cfg(feature = "sphinx")]
|
||||
pub fn sphinx_from_bytes(bytes: &[u8]) -> Result<NymPacket, NymPacketError> {
|
||||
|
||||
@@ -181,6 +181,14 @@ pub struct TrafficWasm {
|
||||
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: bool,
|
||||
|
||||
/// Specify whether any constructed sphinx packets should use the legacy format,
|
||||
/// where the payload keys are explicitly attached rather than using the seeds
|
||||
/// this affects any forward packets, acks and reply surbs
|
||||
/// this flag should remain disabled until sufficient number of nodes on the network has upgraded
|
||||
/// and support updated format.
|
||||
/// in the case of reply surbs, the recipient must also understand the new encoding
|
||||
pub use_legacy_sphinx_format: bool,
|
||||
|
||||
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
|
||||
pub use_outfox: bool,
|
||||
}
|
||||
@@ -214,6 +222,7 @@ impl From<TrafficWasm> for ConfigTraffic {
|
||||
.disable_main_poisson_packet_distribution,
|
||||
primary_packet_size: PacketSize::RegularPacket,
|
||||
secondary_packet_size: use_extended_packet_size,
|
||||
use_legacy_sphinx_format: traffic.use_legacy_sphinx_format,
|
||||
packet_type,
|
||||
}
|
||||
}
|
||||
@@ -229,6 +238,7 @@ impl From<ConfigTraffic> for TrafficWasm {
|
||||
maximum_number_of_retransmissions: traffic.maximum_number_of_retransmissions,
|
||||
disable_main_poisson_packet_distribution: traffic
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_legacy_sphinx_format: traffic.use_legacy_sphinx_format,
|
||||
use_extended_packet_size: traffic.secondary_packet_size.is_some(),
|
||||
use_outfox: traffic.packet_type == PacketType::Outfox,
|
||||
}
|
||||
|
||||
@@ -109,6 +109,15 @@ pub struct TrafficWasmOverride {
|
||||
#[tsify(optional)]
|
||||
pub use_extended_packet_size: Option<bool>,
|
||||
|
||||
/// Specify whether any constructed sphinx packets should use the legacy format,
|
||||
/// where the payload keys are explicitly attached rather than using the seeds
|
||||
/// this affects any forward packets, acks and reply surbs
|
||||
/// this flag should remain disabled until sufficient number of nodes on the network has upgraded
|
||||
/// and support updated format.
|
||||
/// in the case of reply surbs, the recipient must also understand the new encoding
|
||||
#[tsify(optional)]
|
||||
pub use_legacy_sphinx_format: Option<bool>,
|
||||
|
||||
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
|
||||
#[tsify(optional)]
|
||||
pub use_outfox: Option<bool>,
|
||||
@@ -132,6 +141,9 @@ impl From<TrafficWasmOverride> for TrafficWasm {
|
||||
disable_main_poisson_packet_distribution: value
|
||||
.disable_main_poisson_packet_distribution
|
||||
.unwrap_or(def.disable_main_poisson_packet_distribution),
|
||||
use_legacy_sphinx_format: value
|
||||
.use_legacy_sphinx_format
|
||||
.unwrap_or(def.use_legacy_sphinx_format),
|
||||
use_extended_packet_size: value
|
||||
.use_extended_packet_size
|
||||
.unwrap_or(def.use_extended_packet_size),
|
||||
|
||||
@@ -129,6 +129,7 @@ impl PacketPreparer {
|
||||
false,
|
||||
DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
DEFAULT_AVERAGE_ACK_DELAY,
|
||||
true,
|
||||
self.ack_key.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@ use nym_sphinx_framing::codec::{NymCodec, NymCodecError};
|
||||
use nym_sphinx_framing::packet::FramedNymPacket;
|
||||
use nym_sphinx_params::PacketSize;
|
||||
use nym_sphinx_routing::generate_hop_delays;
|
||||
use nym_sphinx_types::constants::{EXPANDED_SHARED_SECRET_LENGTH, HKDF_INPUT_SEED};
|
||||
use nym_sphinx_types::header::keys::PayloadKey;
|
||||
use nym_sphinx_types::constants::{
|
||||
EXPANDED_SHARED_SECRET_HKDF_INFO, EXPANDED_SHARED_SECRET_HKDF_SALT,
|
||||
EXPANDED_SHARED_SECRET_LENGTH,
|
||||
};
|
||||
use nym_sphinx_types::{
|
||||
Destination, DestinationAddressBytes, Node, NymPacket, DESTINATION_ADDRESS_LENGTH,
|
||||
Destination, DestinationAddressBytes, Node, NymPacket, PayloadKey, DESTINATION_ADDRESS_LENGTH,
|
||||
IDENTIFIER_LENGTH,
|
||||
};
|
||||
use nym_task::ShutdownToken;
|
||||
@@ -100,13 +102,14 @@ pub(crate) struct ThroughputTestingClient {
|
||||
}
|
||||
|
||||
fn rederive_lioness_payload_key(shared_secret: &[u8; 32]) -> PayloadKey {
|
||||
let hkdf = Hkdf::<Sha256>::new(None, shared_secret);
|
||||
let hkdf = Hkdf::<Sha256>::new(Some(EXPANDED_SHARED_SECRET_HKDF_SALT), shared_secret);
|
||||
|
||||
// expanded shared secret
|
||||
let mut output = [0u8; EXPANDED_SHARED_SECRET_LENGTH];
|
||||
// SAFETY: the length of the provided okm is within the allowed range
|
||||
#[allow(clippy::unwrap_used)]
|
||||
hkdf.expand(HKDF_INPUT_SEED, &mut output).unwrap();
|
||||
hkdf.expand(EXPANDED_SHARED_SECRET_HKDF_INFO, &mut output)
|
||||
.unwrap();
|
||||
|
||||
*array_ref!(&output, 32, 192)
|
||||
}
|
||||
@@ -156,7 +159,7 @@ impl ThroughputTestingClient {
|
||||
let payload = PacketSize::RegularPacket.payload_size();
|
||||
|
||||
let forward_packet =
|
||||
NymPacket::sphinx_build(payload, b"foomp", &route, &destination, &delays)?;
|
||||
NymPacket::sphinx_build(true, payload, b"foomp", &route, &destination, &delays)?;
|
||||
|
||||
// SAFETY: we constructed a sphinx packet...
|
||||
#[allow(clippy::unwrap_used)]
|
||||
@@ -171,7 +174,7 @@ impl ThroughputTestingClient {
|
||||
.diffie_hellman(&header.shared_secret);
|
||||
let payload_key = rederive_lioness_payload_key(shared_secret.as_bytes());
|
||||
|
||||
let unwrapped_payload = sphinx_packet.payload.unwrap(&payload_key)?;
|
||||
let unwrapped_payload = sphinx_packet.payload.unwrap(payload_key)?;
|
||||
let unwrapped_forward_payload_bytes = unwrapped_payload.into_bytes();
|
||||
|
||||
let start = Instant::now();
|
||||
@@ -230,11 +233,7 @@ impl ThroughputTestingClient {
|
||||
}
|
||||
|
||||
fn lioness_encrypt(&self, block: &mut [u8]) -> anyhow::Result<()> {
|
||||
let lioness_cipher = Lioness::<VarBlake2b, ChaCha>::new_raw(array_ref!(
|
||||
self.payload_key,
|
||||
0,
|
||||
lioness::RAW_KEY_SIZE
|
||||
));
|
||||
let lioness_cipher = Lioness::<VarBlake2b, ChaCha>::new_raw(&self.payload_key);
|
||||
lioness_cipher.encrypt(block)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ impl OutfoxPacket {
|
||||
&mut buffer[range],
|
||||
&secret_key,
|
||||
processing_node.pub_key,
|
||||
destination_node.address.as_bytes_ref(),
|
||||
destination_node.address.as_bytes(),
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,11 +138,11 @@ mod tests {
|
||||
let mut packet = OutfoxPacket::try_from(packet_bytes.as_slice()).unwrap();
|
||||
|
||||
let next_address = packet.decode_next_layer(&node1_pk).unwrap();
|
||||
assert_eq!(next_address, node2.address.as_bytes());
|
||||
assert_eq!(&next_address, node2.address.as_bytes());
|
||||
let next_address = packet.decode_next_layer(&node2_pk).unwrap();
|
||||
assert_eq!(next_address, node3.address.as_bytes());
|
||||
assert_eq!(&next_address, node3.address.as_bytes());
|
||||
let next_address = packet.decode_next_layer(&node3_pk).unwrap();
|
||||
assert_eq!(next_address, gateway.address.as_bytes());
|
||||
assert_eq!(&next_address, gateway.address.as_bytes());
|
||||
let destination_address = packet.decode_next_layer(&gateway_pk).unwrap();
|
||||
assert_eq!(destination_address, destination.address.as_bytes());
|
||||
|
||||
@@ -194,11 +194,11 @@ mod tests {
|
||||
let mut packet = OutfoxPacket::try_from(packet_bytes.as_slice()).unwrap();
|
||||
|
||||
let next_address = packet.decode_next_layer(&node1_pk).unwrap();
|
||||
assert_eq!(next_address, node2.address.as_bytes());
|
||||
assert_eq!(&next_address, node2.address.as_bytes());
|
||||
let next_address = packet.decode_next_layer(&node2_pk).unwrap();
|
||||
assert_eq!(next_address, node3.address.as_bytes());
|
||||
assert_eq!(&next_address, node3.address.as_bytes());
|
||||
let next_address = packet.decode_next_layer(&node3_pk).unwrap();
|
||||
assert_eq!(next_address, gateway.address.as_bytes());
|
||||
assert_eq!(&next_address, gateway.address.as_bytes());
|
||||
let destination_address = packet.decode_next_layer(&gateway_pk).unwrap();
|
||||
assert_eq!(destination_address, destination.address.as_bytes());
|
||||
|
||||
|
||||
@@ -238,6 +238,7 @@ impl NymNodeTesterBuilder {
|
||||
false,
|
||||
Duration::from_millis(5),
|
||||
Duration::from_millis(5),
|
||||
true,
|
||||
managed_keys.ack_key(),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user