Outfox compatibility layer (#3317)

* Outfox compatibility layer

* Lint
This commit is contained in:
Drazen Urch
2023-04-18 14:07:34 +02:00
committed by GitHub
parent c4780c8af2
commit a50c9bfa1a
8 changed files with 196 additions and 90 deletions
Generated
+26 -1
View File
@@ -3810,6 +3810,7 @@ dependencies = [
"fastrand",
"getrandom 0.2.8",
"rayon",
"sphinx-packet 0.1.0 (git+https://github.com/nymtech/sphinx.git)",
"thiserror",
"zeroize",
]
@@ -4056,6 +4057,7 @@ dependencies = [
name = "nym-sphinx-forwarding"
version = "0.1.0"
dependencies = [
"nym-outfox",
"nym-sphinx-addressing",
"nym-sphinx-params",
"nym-sphinx-types",
@@ -4086,7 +4088,7 @@ dependencies = [
name = "nym-sphinx-types"
version = "0.2.0"
dependencies = [
"sphinx-packet",
"sphinx-packet 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -5887,6 +5889,29 @@ dependencies = [
"subtle 2.4.1",
]
[[package]]
name = "sphinx-packet"
version = "0.1.0"
source = "git+https://github.com/nymtech/sphinx.git#ca107d94360cdf8bbfbdb12fe5320ed74f80e40c"
dependencies = [
"aes 0.7.5",
"arrayref",
"blake2",
"bs58",
"byteorder",
"chacha",
"curve25519-dalek",
"digest 0.9.0",
"hkdf 0.11.0",
"hmac 0.11.0",
"lioness",
"log",
"rand 0.7.3",
"rand_distr",
"sha2 0.9.9",
"subtle 2.4.1",
]
[[package]]
name = "spin"
version = "0.5.2"
+1
View File
@@ -11,3 +11,4 @@ repository = { workspace = true }
nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-params = { path = "../params" }
nym-sphinx-types = { path = "../types" }
nym-outfox = { path = "../../../nym-outfox" }
+26 -1
View File
@@ -3566,6 +3566,7 @@ dependencies = [
"curve25519-dalek",
"getrandom 0.2.8",
"rayon",
"sphinx-packet 0.1.0 (git+https://github.com/nymtech/sphinx.git)",
"thiserror",
"zeroize",
]
@@ -3736,6 +3737,7 @@ dependencies = [
name = "nym-sphinx-forwarding"
version = "0.1.0"
dependencies = [
"nym-outfox",
"nym-sphinx-addressing",
"nym-sphinx-params",
"nym-sphinx-types",
@@ -3766,7 +3768,7 @@ dependencies = [
name = "nym-sphinx-types"
version = "0.2.0"
dependencies = [
"sphinx-packet",
"sphinx-packet 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -5375,6 +5377,29 @@ dependencies = [
"subtle 2.4.1",
]
[[package]]
name = "sphinx-packet"
version = "0.1.0"
source = "git+https://github.com/nymtech/sphinx.git#ca107d94360cdf8bbfbdb12fe5320ed74f80e40c"
dependencies = [
"aes 0.7.5",
"arrayref",
"blake2",
"bs58",
"byteorder",
"chacha",
"curve25519-dalek",
"digest 0.9.0",
"hkdf 0.11.0",
"hmac 0.11.0",
"lioness",
"log",
"rand 0.7.3",
"rand_distr",
"sha2 0.9.9",
"subtle 2.4.1",
]
[[package]]
name = "spin"
version = "0.5.2"
+3
View File
@@ -16,6 +16,9 @@ chacha20poly1305 = "0.10.1"
getrandom = { version = "*", features = ["js"] }
thiserror = "1"
sphinx-packet = { git = "https://github.com/nymtech/sphinx.git" }
[dev-dependencies]
criterion = "0.4"
fastrand = "1.8"
+9 -2
View File
@@ -1,3 +1,5 @@
use std::array::TryFromSliceError;
use crate::lion::MIN_MESSAGE_LEN;
use chacha20::cipher::InvalidLength;
use thiserror::Error;
@@ -11,10 +13,15 @@ pub enum OutfoxError {
#[from]
source: InvalidLength,
},
#[error("ChaCha20Poly1305 - Opaque error")]
ChaCha20Poly1305Error,
#[error("ChaCha20Poly1305 - {0}")]
ChaCha20Poly1305Error(String),
#[error("Key length must be 32 bytes")]
InvalidKeyLength,
#[error("Message length must be greater then {MIN_MESSAGE_LEN} bytes")]
InvalidMessageLength,
#[error("{source}")]
TryFromSluce {
#[from]
source: TryFromSliceError,
},
}
+21 -14
View File
@@ -19,7 +19,7 @@
//! traffic or dropping messages can be empued at each mix to frustrate traffic analysis.
//!
//!
//! A layer of mix processing is defined by three parameters, includes in the structure [MixStageParameters]:
//! A layer of mix processing is defined by three parameters, included in the structure [MixStageParameters]:
//! * The `routing_information_length_bytes` (`R`) states the number of bytes representing
//! routing information at this layer.
//! * The `remaining_header_length_bytes` (`H`) represents the remaining bytes of the packet header.
@@ -62,6 +62,7 @@ use chacha20poly1305::Tag;
use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
use curve25519_dalek::montgomery::MontgomeryPoint;
use curve25519_dalek::scalar::Scalar;
use sphinx_packet::route::Node;
use std::convert::TryInto;
@@ -69,6 +70,7 @@ const GROUPELEMENTBYTES: usize = 32;
const TAGBYTES: usize = 16;
use std::ops::Range;
use std::u8;
use crate::error::OutfoxError;
use crate::lion::*;
@@ -181,10 +183,13 @@ impl MixStageParameters {
pub fn encode_mix_layer(
&self,
buffer: &mut [u8],
user_secret_key: &Scalar,
mix_public_key: &MontgomeryPoint,
routing_data: &[u8],
user_secret_key: &[u8],
node: &Node,
) -> Result<MontgomeryPoint, OutfoxError> {
let routing_data = node.address.as_bytes().to_vec();
let mix_public_key = MontgomeryPoint(*node.pub_key.as_bytes());
let user_secret_key = Scalar::from_bytes_mod_order(user_secret_key.try_into()?);
if buffer.len() != self.incoming_packet_length() {
return Err(OutfoxError::LenMismatch {
expected: buffer.len(),
@@ -199,20 +204,19 @@ impl MixStageParameters {
});
}
let user_public_key = (&ED25519_BASEPOINT_TABLE * user_secret_key).to_montgomery();
let user_public_key = (&ED25519_BASEPOINT_TABLE * &user_secret_key).to_montgomery();
let shared_key = user_secret_key * mix_public_key;
// Copy rounting data into buffer
buffer[self.routing_data_range()].copy_from_slice(routing_data);
buffer[self.routing_data_range()].copy_from_slice(&routing_data);
// Perform the AEAD
let header_aead_key = ChaCha20Poly1305::new_from_slice(&shared_key.0[..])?;
// TODO: Should this be all 0s?
let nonce = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let nonce = [0u8; 12];
let tag = header_aead_key
.encrypt_in_place_detached(&nonce.into(), &[], &mut buffer[self.header_range()])
.map_err(|_| OutfoxError::ChaCha20Poly1305Error)?;
.map_err(|e| OutfoxError::ChaCha20Poly1305Error(e.to_string()))?;
// Copy Tag into buffer
buffer[self.tag_range()].copy_from_slice(&tag[..]);
@@ -229,9 +233,12 @@ impl MixStageParameters {
pub fn decode_mix_layer(
&self,
buffer: &mut [u8],
mix_secret_key: &Scalar,
mix_secret_key: &[u8],
) -> Result<MontgomeryPoint, OutfoxError> {
// Check the length of the incoming buffer is correct.
let mix_secret_key = Scalar::from_bytes_mod_order(mix_secret_key.try_into()?);
if buffer.len() != self.incoming_packet_length() {
return Err(OutfoxError::LenMismatch {
expected: buffer.len(),
@@ -240,12 +247,12 @@ impl MixStageParameters {
}
// Derive the shared key for this packet
let user_public_key = MontgomeryPoint(buffer[self.pub_element_range()].try_into().unwrap());
let user_public_key = MontgomeryPoint(buffer[self.pub_element_range()].try_into()?);
let shared_key = mix_secret_key * user_public_key;
// Compute the AEAD and check the Tag, if wrong return Err
let header_aead_key = ChaCha20Poly1305::new_from_slice(&shared_key.0[..]).unwrap();
let nonce = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let header_aead_key = ChaCha20Poly1305::new_from_slice(&shared_key.0[..])?;
let nonce = [0; 12];
let tag_bytes = buffer[self.tag_range()].to_vec();
let tag = Tag::from_slice(&tag_bytes);
@@ -257,7 +264,7 @@ impl MixStageParameters {
&mut buffer[self.header_range()],
tag.as_slice().try_into().unwrap(),
)
.map_err(|_| OutfoxError::ChaCha20Poly1305Error)?;
.map_err(|e| OutfoxError::ChaCha20Poly1305Error(e.to_string()))?;
// Do a round of LION on the payload
lion_transform_decrypt(&mut buffer[self.payload_range()], &shared_key.0)?;
+69 -3
View File
@@ -1,7 +1,73 @@
use crate::format::MixCreationParameters;
use std::ops::Range;
use crate::{
error::OutfoxError,
format::{MixCreationParameters, MixStageParameters},
};
use sphinx_packet::{packet::builder::DEFAULT_PAYLOAD_SIZE, route::Node};
#[allow(dead_code)]
pub struct OutfoxPacket {
params: MixCreationParameters,
mix_params: MixCreationParameters,
payload: Vec<u8>,
}
pub const DEFAULT_ROUTING_INFO_SIZE: usize = 32;
impl OutfoxPacket {
pub fn build(
payload: &[u8],
route: &[Node; 3],
user_secret_key: &[u8],
) -> Result<OutfoxPacket, OutfoxError> {
let mut mix_params = MixCreationParameters::new(DEFAULT_PAYLOAD_SIZE);
for node in route.iter() {
mix_params.add_outer_layer(node.address.as_bytes_ref().len());
}
let padding = mix_params.total_packet_length() - payload.len();
let mut buffer = vec![0; padding];
buffer.extend_from_slice(payload);
for (idx, node) in route.iter().rev().enumerate() {
let (range, stage_params) = mix_params.get_stage_params(idx);
stage_params.encode_mix_layer(&mut buffer[range], user_secret_key, node)?;
}
Ok(OutfoxPacket {
mix_params,
payload: buffer,
})
}
pub fn stage_params(&self, layer_number: usize) -> (Range<usize>, MixStageParameters) {
self.mix_params().get_stage_params(layer_number)
}
pub fn mix_params(&self) -> &MixCreationParameters {
&self.mix_params
}
pub fn payload(&self) -> &[u8] {
&self.payload
}
pub fn payload_range(&self) -> Range<usize> {
self.stage_params(2).1.payload_range()
}
pub fn payload_mut(&mut self) -> &mut [u8] {
&mut self.payload
}
pub fn decode_mix_layer(
&mut self,
layer: usize,
mix_secret_key: &[u8; 32],
) -> Result<(), OutfoxError> {
let (range, params) = self.stage_params(layer);
params.decode_mix_layer(&mut self.payload_mut()[range], mix_secret_key)?;
Ok(())
}
}
+41 -69
View File
@@ -5,6 +5,12 @@ mod tests {
use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
use curve25519_dalek::scalar::Scalar;
use nym_outfox::packet::OutfoxPacket;
use sphinx_packet::constants::NODE_ADDRESS_LENGTH;
use sphinx_packet::crypto::PublicKey;
use sphinx_packet::packet::builder::DEFAULT_PAYLOAD_SIZE;
use sphinx_packet::route::Node;
use sphinx_packet::route::NodeAddressBytes;
use std::convert::TryInto;
use nym_outfox::format::*;
@@ -24,12 +30,11 @@ mod tests {
payload_length_bytes: 1024, // 1kb
};
let user_secret_bytes = randombytes(32);
let mix_secret_bytes = randombytes(32);
let user_secret = Scalar::from_bytes_mod_order(user_secret_bytes.try_into().unwrap());
let mix_secret = Scalar::from_bytes_mod_order(mix_secret_bytes.try_into().unwrap());
let mix_public_key = (&ED25519_BASEPOINT_TABLE * &mix_secret).to_montgomery();
let user_secret = randombytes(32);
let mix_secret = randombytes(32);
let mix_secret_scalar =
Scalar::from_bytes_mod_order(mix_secret.clone().try_into().unwrap());
let mix_public_key = (&ED25519_BASEPOINT_TABLE * &mix_secret_scalar).to_montgomery();
let routing = [0; 32];
@@ -37,13 +42,13 @@ mod tests {
let mut new_buffer = buffer.clone();
let node_address_bytes = NodeAddressBytes::from_bytes(routing);
let mix_public_key = PublicKey::from(*mix_public_key.as_bytes());
let node = Node::new(node_address_bytes, mix_public_key);
let _ = mix_params
.encode_mix_layer(
&mut new_buffer[..],
&user_secret,
&mix_public_key,
&routing[..],
)
.encode_mix_layer(&mut new_buffer[..], &user_secret, &node)
.unwrap();
assert!(new_buffer[mix_params.payload_range()] != buffer[mix_params.payload_range()]);
@@ -76,67 +81,34 @@ mod tests {
#[test]
fn test_packet_params() {
// Dummy keys -- we will use the same key for each layer
let user_secret_bytes = randombytes(32);
let mix_secret_bytes = randombytes(32);
let user_secret = randombytes(32);
let user_secret = Scalar::from_bytes_mod_order(user_secret_bytes.try_into().unwrap());
let mix_secret = Scalar::from_bytes_mod_order(mix_secret_bytes.try_into().unwrap());
let mix_public_key = (&ED25519_BASEPOINT_TABLE * &mix_secret).to_montgomery();
let routing = [0; 32];
let mut params = MixCreationParameters::new(1025);
params.add_outer_layer(32);
params.add_outer_layer(32);
params.add_outer_layer(32);
let mut buf = vec![0; params.total_packet_length()];
let (range0, layer_params0) = params.get_stage_params(0);
let _ = layer_params0
.encode_mix_layer(
&mut buf[range0.clone()],
&user_secret,
&mix_public_key,
&routing[..],
)
.unwrap();
let (range1, layer_params1) = params.get_stage_params(1);
let _ = layer_params1
.encode_mix_layer(
&mut buf[range1.clone()],
&user_secret,
&mix_public_key,
&routing[..],
)
.unwrap();
let (range2, layer_params2) = params.get_stage_params(2);
let _ = layer_params2
.encode_mix_layer(
&mut buf[range2.clone()],
&user_secret,
&mix_public_key,
&routing[..],
)
.unwrap();
assert!(
buf[params.total_packet_length() - 1025..params.total_packet_length()] != [0; 1025]
let (node1_pk, node1_pub) = sphinx_packet::crypto::keygen();
let node1 = Node::new(
NodeAddressBytes::from_bytes([0u8; NODE_ADDRESS_LENGTH]),
node1_pub,
);
let (node2_pk, node2_pub) = sphinx_packet::crypto::keygen();
let node2 = Node::new(
NodeAddressBytes::from_bytes([1u8; NODE_ADDRESS_LENGTH]),
node2_pub,
);
let (node3_pk, node3_pub) = sphinx_packet::crypto::keygen();
let node3 = Node::new(
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node3_pub,
);
let _ = layer_params2
.decode_mix_layer(&mut buf[range2], &mix_secret)
.unwrap();
let route = [node1, node2, node3];
let _ = layer_params1
.decode_mix_layer(&mut buf[range1], &mix_secret)
.unwrap();
let payload = randombytes(DEFAULT_PAYLOAD_SIZE);
let _ = layer_params0
.decode_mix_layer(&mut buf[range0], &mix_secret)
.unwrap();
let mut packet = OutfoxPacket::build(&payload, &route, &user_secret).unwrap();
packet.decode_mix_layer(2, &node1_pk.to_bytes()).unwrap();
packet.decode_mix_layer(1, &node2_pk.to_bytes()).unwrap();
packet.decode_mix_layer(0, &node3_pk.to_bytes()).unwrap();
assert_eq!(payload, &packet.payload()[packet.payload_range()]);
}
}