Compare commits

...

8 Commits

Author SHA1 Message Date
Georgio Nicolas 5c0f8a7dc8 add support for 5 nodes 2025-07-15 22:06:52 +02:00
Georgio Nicolas f94acf95b2 Outfox notes 2025-07-14 23:45:23 +02:00
Georgio Nicolas ae607d300a Outfox notes 2025-07-14 23:43:48 +02:00
Georgio Nicolas f895cbd91d add hazard note! 2025-07-14 23:30:43 +02:00
Georgio Nicolas ff253aed1f add benchmarks 2025-07-14 23:14:26 +02:00
Georgio Nicolas d36bcad11b add benchmarks 2025-07-14 23:14:26 +02:00
Georgio Nicolas 77d1f2b845 switch to libcrux + add support to pq primitives 2025-07-14 23:14:26 +02:00
Georgio Nicolas 19661bc172 decouple outfox from sphinx 2025-07-14 23:14:26 +02:00
11 changed files with 1083 additions and 247 deletions
Generated
+220 -30
View File
@@ -1446,6 +1446,17 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "core-models"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94950e87ea550d6d68f1993f3e7bebc8cb7235157bff84337d46195c3aa0b3f0"
dependencies = [
"hax-lib",
"pastey",
"rand 0.9.1",
]
[[package]]
name = "cosmos-sdk-proto"
version = "0.26.1"
@@ -3150,6 +3161,43 @@ dependencies = [
"hashbrown 0.15.2",
]
[[package]]
name = "hax-lib"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89999d4446ba7d86c777bc6a30c106b9bf60d12eb1952242af5d1d7cb33943d"
dependencies = [
"hax-lib-macros",
"num-bigint",
"num-traits",
]
[[package]]
name = "hax-lib-macros"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704a31fb12a8c50243e8d4c88d006f64852822b0e24488a0de9986803a661792"
dependencies = [
"hax-lib-macros-types",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "hax-lib-macros-types"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f302d2a43ba5888c61454ffeb8719dc43e7c0e83082d0fe302ac82d8d898b075"
dependencies = [
"proc-macro2",
"quote",
"serde",
"serde_json",
"uuid",
]
[[package]]
name = "hdrhistogram"
version = "7.5.4"
@@ -3257,7 +3305,7 @@ dependencies = [
"idna",
"ipnet",
"once_cell",
"rand 0.9.0",
"rand 0.9.1",
"ring",
"rustls 0.23.25",
"thiserror 2.0.12",
@@ -3282,7 +3330,7 @@ dependencies = [
"moka",
"once_cell",
"parking_lot",
"rand 0.9.0",
"rand 0.9.1",
"resolv-conf",
"rustls 0.23.25",
"smallvec",
@@ -4157,6 +4205,143 @@ version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "libcrux-curve25519"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5514645ba1ee6c55dd71d62a50cc37ad8aab3f956826001aa8dad17482655c46"
dependencies = [
"libcrux-hacl-rs",
"libcrux-macros",
]
[[package]]
name = "libcrux-ecdh"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c4fa67cad871d7be9175141b23a174b77536b039945c91b6a5a6d697acd6371"
dependencies = [
"libcrux-curve25519",
"libcrux-p256",
"rand 0.9.1",
]
[[package]]
name = "libcrux-hacl-rs"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1134af11da3f24ae8d1a7e2b60ee871c9e3ffd3d8857deaeebab8088b005addd"
dependencies = [
"libcrux-macros",
]
[[package]]
name = "libcrux-intrinsics"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d3b41dcbc21a5fb7efbbb5af7405b2e79c4bfe443924e90b13afc0080318d31"
dependencies = [
"core-models",
"hax-lib",
]
[[package]]
name = "libcrux-kem"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eefe0e9579f058b99995cbaf918de3cbab90c4d2dde544fe75247fb027ff5af9"
dependencies = [
"libcrux-ecdh",
"libcrux-ml-kem",
"libcrux-sha3",
"libcrux-traits",
"rand 0.9.1",
]
[[package]]
name = "libcrux-macros"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffd6aa2dcd5be681662001b81d493f1569c6d49a32361f470b0c955465cd0338"
dependencies = [
"quote",
"syn 2.0.98",
]
[[package]]
name = "libcrux-ml-kem"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d368d3e8d6a74e277178d54921eca112a1e6b7837d7d8bc555091acb5d817f5"
dependencies = [
"hax-lib",
"libcrux-intrinsics",
"libcrux-platform",
"libcrux-secrets",
"libcrux-sha3",
"rand 0.9.1",
]
[[package]]
name = "libcrux-p256"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b00d21690ebcc7ce1f242e6c4bdadfd8529f9cf2d7b961c0344c9bcb2c82f78f"
dependencies = [
"libcrux-hacl-rs",
"libcrux-macros",
"libcrux-sha2",
]
[[package]]
name = "libcrux-platform"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db82d058aa76ea315a3b2092f69dfbd67ddb0e462038a206e1dcd73f058c0778"
dependencies = [
"libc",
]
[[package]]
name = "libcrux-secrets"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "332737e629fe6ba7547f5c0f90559eac865d5dbecf98138ffae8f16ab8cbe33f"
dependencies = [
"hax-lib",
]
[[package]]
name = "libcrux-sha2"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91eed3bb0ae073f46ae03c83318013fba6e3302bf3292639417b68e908fec4bf"
dependencies = [
"libcrux-hacl-rs",
"libcrux-macros",
"libcrux-traits",
]
[[package]]
name = "libcrux-sha3"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29d95de4257eafdfaf3bffecadb615219b0ca920c553722b3646d32dde76c797"
dependencies = [
"hax-lib",
"libcrux-intrinsics",
"libcrux-platform",
]
[[package]]
name = "libcrux-traits"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cdbf9591a39f04d6da6b9bad51ac58378604a80708c2173dadf92029891b9e2"
dependencies = [
"rand 0.9.1",
]
[[package]]
name = "libm"
version = "0.2.11"
@@ -6856,25 +7041,6 @@ dependencies = [
"thiserror 2.0.12",
]
[[package]]
name = "nym-outfox"
version = "0.1.0"
dependencies = [
"blake3",
"chacha20",
"chacha20poly1305",
"criterion",
"fastrand 2.3.0",
"getrandom 0.2.15",
"log",
"rand 0.8.5",
"rayon",
"sphinx-packet",
"thiserror 2.0.12",
"x25519-dalek",
"zeroize",
]
[[package]]
name = "nym-outfox"
version = "0.1.0"
@@ -6893,6 +7059,25 @@ dependencies = [
"zeroize",
]
[[package]]
name = "nym-outfox"
version = "0.2.0"
dependencies = [
"blake3",
"bs58",
"chacha20",
"chacha20poly1305",
"criterion",
"fastrand 2.3.0",
"getrandom 0.2.15",
"libcrux-kem",
"log",
"rand 0.9.1",
"rayon",
"thiserror 2.0.12",
"zeroize",
]
[[package]]
name = "nym-pemstore"
version = "0.3.0"
@@ -7371,7 +7556,7 @@ name = "nym-sphinx-forwarding"
version = "0.1.0"
source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f"
dependencies = [
"nym-outfox 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)",
"nym-outfox 0.1.0",
"nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)",
"nym-sphinx-params 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)",
"nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)",
@@ -7454,7 +7639,7 @@ dependencies = [
name = "nym-sphinx-types"
version = "0.2.0"
dependencies = [
"nym-outfox 0.1.0",
"nym-outfox 0.2.0",
"sphinx-packet",
"thiserror 2.0.12",
]
@@ -7464,7 +7649,7 @@ name = "nym-sphinx-types"
version = "0.2.0"
source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f"
dependencies = [
"nym-outfox 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)",
"nym-outfox 0.1.0",
"sphinx-packet",
"thiserror 2.0.12",
]
@@ -8318,6 +8503,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pastey"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3a8cb46bdc156b1c90460339ae6bfd45ba0394e5effbaa640badb4987fdc261"
[[package]]
name = "peg"
version = "0.8.4"
@@ -8638,7 +8829,7 @@ dependencies = [
"hmac",
"md-5",
"memchr",
"rand 0.9.0",
"rand 0.9.1",
"sha2 0.10.9",
"stringprep",
]
@@ -8889,7 +9080,7 @@ checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc"
dependencies = [
"bytes",
"getrandom 0.3.1",
"rand 0.9.0",
"rand 0.9.1",
"ring",
"rustc-hash",
"rustls 0.23.25",
@@ -8943,13 +9134,12 @@ dependencies = [
[[package]]
name = "rand"
version = "0.9.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.1",
"zerocopy 0.8.20",
]
[[package]]
@@ -11016,7 +11206,7 @@ dependencies = [
"pin-project-lite",
"postgres-protocol",
"postgres-types",
"rand 0.9.0",
"rand 0.9.1",
"socket2",
"tokio",
"tokio-util",
+10 -5
View File
@@ -1,27 +1,32 @@
[package]
name = "nym-outfox"
version = "0.1.0"
version = "0.2.0"
description = "Outfox package format"
edition = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rayon = { workspace = true }
blake3 = { workspace = true }
zeroize = { workspace = true }
chacha20 = { workspace = true, features = ["std"] }
x25519-dalek = { workspace = true }
chacha20poly1305 = { workspace = true }
getrandom = { workspace = true, features = ["js"] }
thiserror = { workspace = true }
sphinx-packet = { workspace = true }
rand = { workspace = true }
log = { workspace = true }
rand = "0.9.1"
bs58 = "0.5.1"
libcrux-kem = "0.0.3"
[dev-dependencies]
criterion = { workspace = true }
fastrand = { workspace = true }
[[bench]]
name = "bench"
harness = false
+13
View File
@@ -0,0 +1,13 @@
# Outfox
## Notes from Georgio:
### DO NOT INTEGRATE BEFORE:
- Checking the situation with empty Nonces/Additional Data for `ChaCha20Poly1305` calls marked by HAZARD.
- Programmatically checking which KEM is used during packet handling, and handling related errors.
- Crypto audit of `src/lion.rs`.
- Updating documentation to replace discrete-log operations with KEM operations.
### Things to optimize:
- Copying and allocations.
- Using libcrux for `ChaCha20`/`ChaCha20Poly1305`.
+285
View File
@@ -0,0 +1,285 @@
use criterion::{criterion_group, criterion_main, Criterion};
use libcrux_kem::key_gen;
use nym_outfox::{
format::MixStageParameters,
packet::OutfoxPacket,
route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, NODE_ADDRESS_LENGTH},
};
use std::iter::repeat_with;
pub fn randombytes(n: usize) -> Vec<u8> {
repeat_with(|| fastrand::u8(..)).take(n).collect()
}
fn test_encode_decode(c: &mut Criterion) {
for kem in [
libcrux_kem::Algorithm::X25519,
libcrux_kem::Algorithm::XWingKemDraft06,
libcrux_kem::Algorithm::MlKem768,
] {
let mix_params = MixStageParameters {
kem: kem,
routing_information_length_bytes: 32,
remaining_header_length_bytes: (32 + 16 + 32) * 4,
payload_length_bytes: 1024, // 1kb
};
let mut rng = rand::rng();
let (mix_decapsulation_key, mix_encapsulation_key) = key_gen(kem, &mut rng).unwrap();
let routing = [0; 32];
let destination = [0; 32];
let buffer = randombytes(mix_params.incoming_packet_length());
let mut new_buffer = buffer.clone();
let node_address_bytes = NodeAddressBytes::from_bytes(routing);
let node = Node::new(kem, node_address_bytes, mix_encapsulation_key);
let _ = mix_params
.encode_mix_layer(&mut rng, &mut new_buffer[..], &node.pub_key, &destination)
.unwrap();
assert_ne!(
new_buffer[mix_params.payload_range()],
buffer[mix_params.payload_range()]
);
assert_ne!(new_buffer[mix_params.routing_data_range()], routing[..]);
let _ = mix_params
.decode_mix_layer(&mut new_buffer[..], &mix_decapsulation_key)
.unwrap();
assert_eq!(
new_buffer[mix_params.payload_range()],
buffer[mix_params.payload_range()]
);
assert_eq!(new_buffer[mix_params.routing_data_range()], routing[..]);
}
}
fn kem_str(kem: libcrux_kem::Algorithm) -> &'static str {
match kem {
libcrux_kem::Algorithm::X25519 => "KEM: x25519",
libcrux_kem::Algorithm::XWingKemDraft06 => "KEM: XWing",
libcrux_kem::Algorithm::MlKem768 => "KEM: MlKem768",
_ => unreachable!(),
}
}
fn test_packet(c: &mut Criterion) {
let mut rng = rand::rng();
for kem in [
libcrux_kem::Algorithm::X25519,
libcrux_kem::Algorithm::XWingKemDraft06,
libcrux_kem::Algorithm::MlKem768,
] {
let (entry_sk, entry_pk) = key_gen(kem, &mut rng).unwrap();
let entry_node = Node::new(
kem,
NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
entry_pk,
);
let (node1_sk, node1_pk) = key_gen(kem, &mut rng).unwrap();
let node1 = Node::new(
kem,
NodeAddressBytes::from_bytes([0u8; NODE_ADDRESS_LENGTH]),
node1_pk,
);
let (node2_sk, node2_pk) = key_gen(kem, &mut rng).unwrap();
let node2 = Node::new(
kem,
NodeAddressBytes::from_bytes([1u8; NODE_ADDRESS_LENGTH]),
node2_pk,
);
let (node3_sk, node3_pk) = key_gen(kem, &mut rng).unwrap();
let node3 = Node::new(
kem,
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node3_pk,
);
let (exit_sk, exit_pk) = key_gen(kem, &mut rng).unwrap();
let exit = Node::new(
kem,
NodeAddressBytes::from_bytes([3u8; NODE_ADDRESS_LENGTH]),
exit_pk,
);
c.bench_function(&format!("{} | Key Generation", kem_str(kem)), |b| {
b.iter(|| key_gen(kem, &mut rng).unwrap())
});
let destination = Destination::new(
DestinationAddressBytes::from_bytes([9u8; NODE_ADDRESS_LENGTH]),
[0u8; 16],
);
let route = [
entry_node,
node1.clone(),
node2.clone(),
node3.clone(),
exit.clone(),
];
for payload_size in [512, 1000, 1024, 2048, 4096] {
c.bench_function(
&format!(
"{} | Packet Construction | Payload: {} bytes",
kem_str(kem),
payload_size
),
|b| {
b.iter_batched(
|| (rand::rng(), randombytes(payload_size)),
|(mut rng, payload)| {
OutfoxPacket::build(
&mut rng,
kem,
&payload,
&route,
&destination,
Some(payload.len()),
)
},
criterion::BatchSize::PerIteration,
)
},
);
let payload = randombytes(payload_size);
let packet = OutfoxPacket::build(
&mut rng,
kem,
&payload,
&route,
&destination,
Some(payload.len()),
)
.unwrap();
let packet_bytes = packet.to_bytes().unwrap();
let mut packet = OutfoxPacket::try_from((kem, packet_bytes.as_slice())).unwrap();
c.bench_function(
&format!(
"{} | Packet Decoding (Entry) | Payload: {} bytes",
kem_str(kem),
payload_size
),
|b| {
b.iter_batched(
|| OutfoxPacket::try_from((kem, packet_bytes.as_slice())).unwrap(),
|mut packet| packet.decode_next_layer(&entry_sk).unwrap(),
criterion::BatchSize::PerIteration,
)
},
);
c.bench_function(
&format!(
"{} | Packet Decoding (Layer 1) | Payload: {} bytes",
kem_str(kem),
payload_size
),
|b| {
b.iter_batched(
|| {
let mut packet =
OutfoxPacket::try_from((kem, packet_bytes.as_slice())).unwrap();
packet.decode_next_layer(&entry_sk).unwrap();
packet
},
|mut packet| packet.decode_next_layer(&node1_sk).unwrap(),
criterion::BatchSize::PerIteration,
)
},
);
c.bench_function(
&format!(
"{} | Packet Decoding (Layer 2) | Payload: {} bytes",
kem_str(kem),
payload_size
),
|b| {
b.iter_batched(
|| {
let mut packet =
OutfoxPacket::try_from((kem, packet_bytes.as_slice())).unwrap();
packet.decode_next_layer(&entry_sk).unwrap();
packet.decode_next_layer(&node1_sk).unwrap();
packet
},
|mut packet| packet.decode_next_layer(&node2_sk).unwrap(),
criterion::BatchSize::PerIteration,
)
},
);
c.bench_function(
&format!(
"{} | Packet Decoding (Layer 3) | Payload: {} bytes",
kem_str(kem),
payload_size
),
|b| {
b.iter_batched(
|| {
let mut packet =
OutfoxPacket::try_from((kem, packet_bytes.as_slice())).unwrap();
packet.decode_next_layer(&entry_sk).unwrap();
packet.decode_next_layer(&node1_sk).unwrap();
packet.decode_next_layer(&node2_sk).unwrap();
packet
},
|mut packet| packet.decode_next_layer(&node3_sk).unwrap(),
criterion::BatchSize::PerIteration,
)
},
);
c.bench_function(
&format!(
"{} | Packet Decoding + Plaintext Recovery (exit) | Payload: {} bytes",
kem_str(kem),
payload_size
),
|b| {
b.iter_batched(
|| {
let mut packet =
OutfoxPacket::try_from((kem, packet_bytes.as_slice())).unwrap();
packet.decode_next_layer(&entry_sk).unwrap();
packet.decode_next_layer(&node1_sk).unwrap();
packet.decode_next_layer(&node2_sk).unwrap();
packet.decode_next_layer(&node3_sk).unwrap();
packet
},
|mut packet| {
packet.decode_next_layer(&exit_sk).unwrap();
packet.recover_plaintext()
},
criterion::BatchSize::PerIteration,
)
},
);
let next_address = packet.decode_next_layer(&entry_sk).unwrap();
assert_eq!(&next_address, node1.address.as_bytes());
let next_address = packet.decode_next_layer(&node1_sk).unwrap();
assert_eq!(&next_address, node2.address.as_bytes());
let next_address = packet.decode_next_layer(&node2_sk).unwrap();
assert_eq!(&next_address, node3.address.as_bytes());
let next_address = packet.decode_next_layer(&node3_sk).unwrap();
assert_eq!(&next_address, exit.address.as_bytes());
let destination_address = packet.decode_next_layer(&exit_sk).unwrap();
assert_eq!(destination_address, destination.address.as_bytes());
assert_eq!(payload, packet.recover_plaintext().unwrap());
}
}
}
criterion_group!(benches, test_packet);
criterion_main!(benches);
+15 -6
View File
@@ -1,3 +1,5 @@
use libcrux_kem::Algorithm;
pub const GROUPELEMENTBYTES: u8 = 32;
pub const TAGBYTES: u8 = 16;
pub const MIX_PARAMS_LEN: usize = DEFAULT_HOPS + 2;
@@ -5,18 +7,25 @@ pub const MIN_MESSAGE_LEN: usize = 24 * 2;
pub(crate) const CONTEXT: &str = "LIONKEYS";
pub(crate) const TAG_LEN: usize = 24;
pub const DEFAULT_ROUTING_INFO_SIZE: u8 = 32;
pub const DEFAULT_HOPS: usize = 4;
pub const DEFAULT_HOPS: usize = 5;
pub const ROUTING_INFORMATION_LENGTH_BY_STAGE: [u8; DEFAULT_HOPS] =
[DEFAULT_ROUTING_INFO_SIZE; DEFAULT_HOPS];
pub const MIN_PACKET_SIZE: usize = 48;
pub const MAGIC_SLICE: &[u8] = &[111, 102, 120];
pub const OUTFOX_PACKET_OVERHEAD: usize = MIX_PARAMS_LEN
+ (groupelementbytes() + tagbytes() + DEFAULT_ROUTING_INFO_SIZE as usize) * DEFAULT_HOPS
+ MAGIC_SLICE.len();
pub const fn outfox_packet_overhead(kem: Algorithm) -> usize {
MIX_PARAMS_LEN
+ (groupelementbytes(kem) + tagbytes() + DEFAULT_ROUTING_INFO_SIZE as usize) * DEFAULT_HOPS
+ MAGIC_SLICE.len()
}
pub const fn groupelementbytes() -> usize {
GROUPELEMENTBYTES as usize
pub const fn groupelementbytes(kem: Algorithm) -> usize {
match kem {
Algorithm::XWingKemDraft06 => 1120,
Algorithm::X25519 => 32,
Algorithm::MlKem768 => 1088,
_ => unreachable!(),
}
}
pub const fn tagbytes() -> usize {
+3
View File
@@ -28,4 +28,7 @@ pub enum OutfoxError {
InvalidHeaderLength(usize),
#[error("Invalid magic bytes, expected: {:?}, got: {:?}", MAGIC_SLICE, 0)]
InvalidMagicBytes(Vec<u8>),
#[error("routing information processing failure {0}")]
InvalidRouting(String),
}
+77 -44
View File
@@ -58,22 +58,30 @@ use crate::constants::groupelementbytes;
use crate::constants::tagbytes;
use crate::constants::DEFAULT_HOPS;
use crate::constants::DEFAULT_ROUTING_INFO_SIZE;
use crate::constants::GROUPELEMENTBYTES;
use crate::constants::MIX_PARAMS_LEN;
use crate::constants::ROUTING_INFORMATION_LENGTH_BY_STAGE;
use crate::constants::TAGBYTES;
use crate::error::OutfoxError;
use crate::lion::*;
use rand::CryptoRng;
use chacha20poly1305::AeadInPlace;
use chacha20poly1305::ChaCha20Poly1305;
use chacha20poly1305::KeyInit;
use chacha20poly1305::Tag;
use libcrux_kem::Algorithm;
use libcrux_kem::Ct;
use libcrux_kem::PrivateKey;
use libcrux_kem::PublicKey;
use libcrux_kem::Ss;
use std::ops::Range;
/// A structure that holds mix packet construction parameters. These incluse the length
/// of the routing information at each hop, the number of hops, and the payload length.
#[derive(Eq, PartialEq, Debug)]
#[derive(PartialEq, Debug)]
pub struct MixCreationParameters {
pub kem: Algorithm,
/// The routing length is inner first, so \[0\] is the innermost routing length, etc (in bytes)
/// In our stratified topology this will always be 4
pub routing_information_length_by_stage: [u8; DEFAULT_HOPS],
@@ -81,15 +89,16 @@ pub struct MixCreationParameters {
pub payload_length_bytes: u16,
}
impl TryFrom<&[u8]> for MixCreationParameters {
impl TryFrom<(Algorithm, &[u8])> for MixCreationParameters {
type Error = OutfoxError;
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
if v.len() != MIX_PARAMS_LEN {
return Err(OutfoxError::InvalidHeaderLength(v.len()));
fn try_from(v: (Algorithm, &[u8])) -> Result<Self, Self::Error> {
if v.1.len() != MIX_PARAMS_LEN {
return Err(OutfoxError::InvalidHeaderLength(v.1.len()));
}
let (routing, payload) = v.split_at(DEFAULT_HOPS);
let (routing, payload) = v.1.split_at(DEFAULT_HOPS);
Ok(MixCreationParameters {
kem: v.0,
routing_information_length_by_stage: routing.try_into()?,
payload_length_bytes: u16::from_le_bytes(payload.try_into()?),
})
@@ -98,7 +107,7 @@ impl TryFrom<&[u8]> for MixCreationParameters {
impl MixCreationParameters {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(5);
let mut bytes = Vec::with_capacity(DEFAULT_HOPS + 1);
bytes.extend_from_slice(self.routing_information_length_by_stage.as_slice());
bytes.extend_from_slice(&self.payload_length_bytes.to_le_bytes());
bytes
@@ -109,8 +118,9 @@ impl MixCreationParameters {
}
/// Create a set of parameters for a mix packet format.
pub fn new(payload_length_bytes: u16) -> MixCreationParameters {
pub fn new(kem: Algorithm, payload_length_bytes: u16) -> MixCreationParameters {
MixCreationParameters {
kem: kem,
routing_information_length_by_stage: [DEFAULT_ROUTING_INFO_SIZE; DEFAULT_HOPS],
payload_length_bytes,
}
@@ -120,7 +130,7 @@ impl MixCreationParameters {
pub fn total_packet_length(&self) -> usize {
let mut len = self.payload_length_bytes();
for stage_len in ROUTING_INFORMATION_LENGTH_BY_STAGE.iter() {
len += *stage_len as usize + groupelementbytes() + tagbytes()
len += *stage_len as usize + groupelementbytes(self.kem) + tagbytes()
}
len
}
@@ -133,6 +143,7 @@ impl MixCreationParameters {
for (i, stage_len) in ROUTING_INFORMATION_LENGTH_BY_STAGE.iter().enumerate() {
if i == layer_number {
let params = MixStageParameters {
kem: self.kem,
routing_information_length_bytes: *stage_len,
remaining_header_length_bytes,
payload_length_bytes: self.payload_length_bytes,
@@ -143,7 +154,8 @@ impl MixCreationParameters {
return (total_size - inner_size..total_size, params);
} else {
remaining_header_length_bytes += (stage_len + GROUPELEMENTBYTES + TAGBYTES) as u16;
remaining_header_length_bytes +=
groupelementbytes(self.kem) as u16 + (stage_len + TAGBYTES) as u16;
}
}
@@ -153,6 +165,7 @@ impl MixCreationParameters {
/// A structure representing the parameters of a single stage of mixing.
pub struct MixStageParameters {
pub kem: Algorithm,
/// The routing information length for this stage of mixing
pub routing_information_length_bytes: u8,
/// The reamining header length for this stage of mixing
@@ -175,7 +188,7 @@ impl MixStageParameters {
}
pub fn incoming_packet_length(&self) -> usize {
groupelementbytes() + tagbytes() + self.outgoing_packet_length()
groupelementbytes(self.kem) + tagbytes() + self.outgoing_packet_length()
}
pub fn outgoing_packet_length(&self) -> usize {
@@ -184,22 +197,22 @@ impl MixStageParameters {
+ self.payload_length_bytes()
}
pub fn pub_element_range(&self) -> Range<usize> {
0..groupelementbytes()
pub fn encaps_element_range(&self) -> Range<usize> {
0..groupelementbytes(self.kem)
}
pub fn tag_range(&self) -> Range<usize> {
groupelementbytes()..groupelementbytes() + tagbytes()
groupelementbytes(self.kem)..groupelementbytes(self.kem) + tagbytes()
}
pub fn routing_data_range(&self) -> Range<usize> {
groupelementbytes() + tagbytes()
..groupelementbytes() + tagbytes() + self.routing_information_length_bytes()
groupelementbytes(self.kem) + tagbytes()
..groupelementbytes(self.kem) + tagbytes() + self.routing_information_length_bytes()
}
pub fn header_range(&self) -> Range<usize> {
groupelementbytes() + tagbytes()
..groupelementbytes()
groupelementbytes(self.kem) + tagbytes()
..groupelementbytes(self.kem)
+ tagbytes()
+ self.routing_information_length_bytes()
+ self.remaining_header_length_bytes()
@@ -209,13 +222,16 @@ impl MixStageParameters {
self.incoming_packet_length() - self.payload_length_bytes()..self.incoming_packet_length()
}
pub fn encode_mix_layer(
pub fn encode_mix_layer<R>(
&self,
rng: &mut R,
buffer: &mut [u8],
user_secret_key: &x25519_dalek::StaticSecret,
mix_public_key: x25519_dalek::PublicKey,
mix_encapsulation_key: &PublicKey,
destination: &[u8; 32],
) -> Result<x25519_dalek::SharedSecret, OutfoxError> {
) -> Result<Ss, OutfoxError>
where
R: CryptoRng,
{
let routing_data = destination;
if buffer.len() != self.incoming_packet_length() {
@@ -232,14 +248,18 @@ impl MixStageParameters {
});
}
let user_public_key = x25519_dalek::PublicKey::from(user_secret_key);
let shared_key = user_secret_key.diffie_hellman(&mix_public_key);
let (ss, ct) = mix_encapsulation_key.encapsulate(rng).unwrap();
let shared_key = ss.encode();
// Copy rounting data into buffer
buffer[self.routing_data_range()].copy_from_slice(routing_data);
// Perform the AEAD
let header_aead_key = ChaCha20Poly1305::new_from_slice(shared_key.as_bytes())?;
// HAZARD: ENCRYPTION WITH EMPTY NONCES AND NO AD!!
let header_aead_key = ChaCha20Poly1305::new_from_slice(&shared_key)?;
let nonce = [0u8; 12];
let tag = header_aead_key
@@ -250,18 +270,18 @@ impl MixStageParameters {
buffer[self.tag_range()].copy_from_slice(&tag[..]);
// Copy own public key into buffer
buffer[self.pub_element_range()].copy_from_slice(user_public_key.as_bytes());
buffer[self.encaps_element_range()].copy_from_slice(&ct.encode());
// Do a round of LION on the payload
lion_transform_encrypt(&mut buffer[self.payload_range()], shared_key.as_bytes())?;
lion_transform_encrypt(&mut buffer[self.payload_range()], &shared_key)?;
Ok(shared_key)
Ok(ss)
}
pub fn decode_mix_layer(
&self,
buffer: &mut [u8],
mix_secret_key: &x25519_dalek::StaticSecret,
mix_decapsulation_key: &PrivateKey,
) -> Result<Vec<u8>, OutfoxError> {
// Check the length of the incoming buffer is correct.
if buffer.len() != self.incoming_packet_length() {
@@ -271,13 +291,14 @@ impl MixStageParameters {
});
}
// Derive the shared key for this packet
let user_public_key_bytes: [u8; 32] = buffer[self.pub_element_range()].try_into()?;
let user_public_key = x25519_dalek::PublicKey::from(user_public_key_bytes);
let shared_key = mix_secret_key.diffie_hellman(&user_public_key);
let ct = Ct::decode(self.kem, &buffer[self.encaps_element_range()]).unwrap();
let shared_key = ct.decapsulate(&mix_decapsulation_key).unwrap();
let shared_key = shared_key.encode();
// Compute the AEAD and check the Tag, if wrong return Err
let header_aead_key = ChaCha20Poly1305::new_from_slice(shared_key.as_bytes())?;
let header_aead_key = ChaCha20Poly1305::new_from_slice(&shared_key)?;
// HAZARD: ENCRYPTION WITH EMPTY NONCES AND NO AD!!
let nonce = [0; 12];
let tag_bytes = buffer[self.tag_range()].to_vec();
@@ -294,7 +315,7 @@ impl MixStageParameters {
let routing_data = buffer[self.routing_data_range()].to_vec();
// Do a round of LION on the payload
lion_transform_decrypt(&mut buffer[self.payload_range()], shared_key.as_bytes())?;
lion_transform_decrypt(&mut buffer[self.payload_range()], &shared_key)?;
Ok(routing_data)
}
@@ -306,17 +327,29 @@ mod test {
#[test]
fn test_to_bytes() {
let mix_params = MixCreationParameters::new(1024);
assert_eq!(mix_params.to_bytes(), vec![32, 32, 32, 32, 0, 4])
for kem in [
libcrux_kem::Algorithm::X25519,
libcrux_kem::Algorithm::XWingKemDraft06,
libcrux_kem::Algorithm::MlKem768,
] {
let mix_params = MixCreationParameters::new(kem, 1024);
assert_eq!(mix_params.to_bytes(), vec![32, 32, 32, 32, 32, 0, 4]);
}
}
#[test]
fn test_from_bytes() {
let params_bytes = vec![32, 32, 32, 32, 0, 4];
let mix_params = MixCreationParameters::new(1024);
assert_eq!(
mix_params,
MixCreationParameters::try_from(params_bytes.as_slice()).unwrap()
)
let params_bytes = vec![32, 32, 32, 32, 32, 0, 4];
for kem in [
libcrux_kem::Algorithm::X25519,
libcrux_kem::Algorithm::XWingKemDraft06,
libcrux_kem::Algorithm::MlKem768,
] {
let mix_params = MixCreationParameters::new(kem, 1024);
assert_eq!(
mix_params,
MixCreationParameters::try_from((kem, params_bytes.as_slice())).unwrap()
)
}
}
}
+31
View File
@@ -3,3 +3,34 @@ pub mod error;
pub mod format;
pub mod lion;
pub mod packet;
pub mod route;
#[cfg(test)]
mod test {
use libcrux_kem::*;
use rand::rngs::OsRng;
use rand::TryRngCore;
#[test]
fn test_kem() {
let mut os_rng = OsRng;
let mut rng = os_rng.unwrap_mut();
let (sk_a, pk_a) = key_gen(Algorithm::MlKem768, &mut rng).unwrap();
let received_sk = sk_a.encode();
let received_pk = pk_a.encode();
let pk = PublicKey::decode(Algorithm::MlKem768, &received_pk).unwrap();
let (ss_b, ct_b) = pk.encapsulate(&mut rng).unwrap();
let received_ct = ct_b.encode();
println!("pk: {}", received_pk.len());
println!("sk: {}", received_sk.len());
println!("kem encaps: {}", received_ct.len());
let ct_a = Ct::decode(Algorithm::MlKem768, &received_ct).unwrap();
let ss_a = ct_a.decapsulate(&sk_a).unwrap();
assert_eq!(ss_b.encode(), ss_a.encode());
}
}
+35 -26
View File
@@ -1,29 +1,32 @@
use libcrux_kem::{Algorithm, PrivateKey};
use rand::CryptoRng;
use crate::{
constants::{DEFAULT_HOPS, MAGIC_SLICE, MIN_PACKET_SIZE, MIX_PARAMS_LEN},
error::OutfoxError,
format::{MixCreationParameters, MixStageParameters},
route::{Destination, Node, DEFAULT_PAYLOAD_SIZE},
};
use sphinx_packet::{
crypto::PrivateKey,
packet::builder::DEFAULT_PAYLOAD_SIZE,
route::{Destination, Node},
};
use std::{array::TryFromSliceError, collections::VecDeque, ops::Range};
#[derive(Debug)]
pub struct OutfoxPacket {
kem: Algorithm,
mix_params: MixCreationParameters,
payload: Vec<u8>,
}
pub struct OutfoxProcessedPacket {
kem: Algorithm,
packet: OutfoxPacket,
next_address: [u8; 32],
}
impl OutfoxProcessedPacket {
pub fn new(packet: OutfoxPacket, next_address: [u8; 32]) -> Self {
pub fn new(kem: Algorithm, packet: OutfoxPacket, next_address: [u8; 32]) -> Self {
OutfoxProcessedPacket {
kem,
packet,
next_address,
}
@@ -38,13 +41,14 @@ impl OutfoxProcessedPacket {
}
}
impl TryFrom<&[u8]> for OutfoxPacket {
impl TryFrom<(Algorithm, &[u8])> for OutfoxPacket {
type Error = OutfoxError;
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
let (header, payload) = v.split_at(MIX_PARAMS_LEN);
fn try_from(v: (Algorithm, &[u8])) -> Result<Self, Self::Error> {
let (header, payload) = v.1.split_at(MIX_PARAMS_LEN);
Ok(OutfoxPacket {
mix_params: MixCreationParameters::try_from(header)?,
kem: v.0,
mix_params: MixCreationParameters::try_from((v.0, header))?,
payload: payload.to_vec(),
})
}
@@ -81,20 +85,24 @@ impl OutfoxPacket {
Ok(bytes)
}
pub fn build<M: AsRef<[u8]>>(
pub fn build<R, M: AsRef<[u8]>>(
rng: &mut R,
kem: Algorithm,
payload: M,
route: &[Node; 4],
route: &[Node; DEFAULT_HOPS],
destination: &Destination,
packet_size: Option<usize>,
) -> Result<OutfoxPacket, OutfoxError> {
let secret_key = x25519_dalek::StaticSecret::random();
) -> Result<OutfoxPacket, OutfoxError>
where
R: CryptoRng,
{
let packet_size = packet_size.unwrap_or(DEFAULT_PAYLOAD_SIZE);
let packet_size = if packet_size < MIN_PACKET_SIZE {
MIN_PACKET_SIZE
} else {
packet_size
} + MAGIC_SLICE.len();
let mix_params = MixCreationParameters::new(packet_size as u16);
let mix_params = MixCreationParameters::new(kem, packet_size as u16);
let padding = mix_params.total_packet_length() - payload.as_ref().len() - MAGIC_SLICE.len();
let mut buffer = vec![0; padding];
@@ -104,38 +112,39 @@ impl OutfoxPacket {
// Last node in the route is a gateway, it will decrypt last, and get the final destination address
let (range, stage_params) = mix_params.get_stage_params(0);
stage_params.encode_mix_layer(
rng,
&mut buffer[range],
&secret_key,
route.last().unwrap().pub_key,
&route.last().unwrap().pub_key,
destination.address.as_bytes_ref(),
)?;
let route = route.iter().rev().collect::<Vec<&Node>>();
// We've reversed the route, and we iterate pairs of node, first node in the pair is the destination, and the second(last) is the processing node
// Route: [N1, N2, N3, G]
// Reverse: [G, N3, N2, N1]
// Pairs: [(G, N3), (N3, N2), (N2, N1)]
// Route: [Entry, N1, N2, N3, Exit]
// Reverse: [Exit, N3, N2, N1, Entry]
// Pairs: [(Exit, N3), (N3, N2), (N2, N1), (N1, Entry)]
// We iterate over pairs, and encode the mix layer for each pair
// For the first pair, we encode the mix layer for N3, and the destination is G
// For the first pair, we encode the mix layer for N3, and the destination is Exit
// For the second pair, we encode the mix layer for N2, and the destination is N3
// For the third pair, we encode the mix layer for N1, and the destination is N2
// Entry gateway will simply forward the packet to N1 and processing will continue from there
for (idx, nodes) in route.windows(2).enumerate() {
let (range, stage_params) = mix_params.get_stage_params(idx + 1);
// We know that we'll always get 4 nodes, so we can unwrap here
// We know that we'll always get DEFAULT_HOPS nodes, so we can unwrap here
let processing_node = nodes.last().unwrap();
let destination_node = nodes.first().unwrap();
let secret_key = x25519_dalek::StaticSecret::random();
stage_params.encode_mix_layer(
rng,
&mut buffer[range],
&secret_key,
processing_node.pub_key,
&processing_node.pub_key,
destination_node.address.as_bytes(),
)?;
}
Ok(OutfoxPacket {
kem,
mix_params,
payload: buffer,
})
@@ -164,7 +173,7 @@ impl OutfoxPacket {
pub fn decode_mix_layer(
&mut self,
layer: usize,
mix_secret_key: &x25519_dalek::StaticSecret,
mix_secret_key: &PrivateKey,
) -> Result<Vec<u8>, OutfoxError> {
let (range, params) = self.stage_params(layer);
let routing_data =
+201
View File
@@ -0,0 +1,201 @@
// I took everything from sphinx to decouple the crates
use std::fmt::{self, Display, Formatter};
use libcrux_kem::{Algorithm, PublicKey};
use crate::error::OutfoxError;
pub const SECURITY_PARAMETER: usize = 16; // k in the Sphinx paper. Measured in bytes; 128 bits.
pub const DESTINATION_ADDRESS_LENGTH: usize = 2 * SECURITY_PARAMETER;
pub const IDENTIFIER_LENGTH: usize = SECURITY_PARAMETER;
pub const NODE_ADDRESS_LENGTH: usize = 2 * SECURITY_PARAMETER;
pub const DEFAULT_PAYLOAD_SIZE: usize = 1024;
// in paper I
pub type SURBIdentifier = [u8; IDENTIFIER_LENGTH];
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Destination {
// address in theory could be changed to a vec<u8> as it does not need to be strictly DESTINATION_ADDRESS_LENGTH long
// but cannot be longer than that (assuming longest possible route)
pub address: DestinationAddressBytes,
pub identifier: SURBIdentifier,
}
impl Destination {
pub fn new(address: DestinationAddressBytes, identifier: SURBIdentifier) -> Self {
Self {
address,
identifier,
}
}
}
// in paper nu
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Hash)]
pub struct NodeAddressBytes([u8; NODE_ADDRESS_LENGTH]);
impl NodeAddressBytes {
pub fn as_base58_string(&self) -> String {
bs58::encode(&self.0).into_string()
}
pub fn try_from_base58_string<S: Into<String>>(val: S) -> Result<Self, OutfoxError> {
let decoded = match bs58::decode(val.into()).into_vec() {
Ok(decoded) => decoded,
Err(e) => {
return Err(OutfoxError::InvalidRouting(format!(
"failed to decode node address from b58 string: {:?}",
e
)))
}
};
if decoded.len() != NODE_ADDRESS_LENGTH {
return Err(OutfoxError::InvalidRouting(
format!("decoded node address has invalid length").into(),
));
}
let mut address_bytes = [0; NODE_ADDRESS_LENGTH];
address_bytes.copy_from_slice(&decoded[..]);
Ok(NodeAddressBytes(address_bytes))
}
pub fn try_from_byte_slice(b: &[u8]) -> Result<Self, OutfoxError> {
if b.len() != NODE_ADDRESS_LENGTH {
return Err(OutfoxError::InvalidRouting(
format!("received bytes got invalid length").into(),
));
}
let mut address_bytes = [0; NODE_ADDRESS_LENGTH];
address_bytes.copy_from_slice(b);
Ok(NodeAddressBytes(address_bytes))
}
pub fn from_bytes(b: [u8; NODE_ADDRESS_LENGTH]) -> Self {
NodeAddressBytes(b)
}
/// View this `NodeAddressBytes` as an array of bytes.
pub fn as_bytes(&self) -> &[u8; NODE_ADDRESS_LENGTH] {
&self.0
}
/// Convert this `NodeAddressBytes` to an array of bytes.
pub fn to_bytes(&self) -> [u8; NODE_ADDRESS_LENGTH] {
self.0
}
}
impl Display for NodeAddressBytes {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.as_base58_string().fmt(f)
}
}
pub struct Node {
pub kem: Algorithm,
pub address: NodeAddressBytes,
pub pub_key: PublicKey,
}
impl Clone for Node {
fn clone(&self) -> Self {
Self {
kem: self.kem,
address: self.address.clone(),
pub_key: PublicKey::decode(self.kem, &self.pub_key.encode()).unwrap(),
}
}
}
impl std::fmt::Debug for Node {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Node")
.field("kem", &self.kem)
.field("address", &self.address)
.field("pub_key", &self.pub_key.encode())
.finish()
}
}
impl Node {
pub fn new(kem: Algorithm, address: NodeAddressBytes, pub_key: PublicKey) -> Self {
Self {
kem,
address,
pub_key,
}
}
}
// in paper delta
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Hash)]
pub struct DestinationAddressBytes([u8; DESTINATION_ADDRESS_LENGTH]);
impl DestinationAddressBytes {
pub fn as_base58_string(&self) -> String {
bs58::encode(&self.0).into_string()
}
pub fn try_from_base58_string<S: Into<String>>(val: S) -> Result<Self, OutfoxError> {
let decoded = match bs58::decode(val.into()).into_vec() {
Ok(decoded) => decoded,
Err(e) => {
return Err(OutfoxError::InvalidRouting(format!(
"failed to decode destination from b58 string: {:?}",
e
))
.into())
}
};
if decoded.len() != DESTINATION_ADDRESS_LENGTH {
return Err(OutfoxError::InvalidRouting(
format!("decoded destination address has invalid length",).into(),
));
}
let mut address_bytes = [0; DESTINATION_ADDRESS_LENGTH];
address_bytes.copy_from_slice(&decoded[..]);
Ok(DestinationAddressBytes(address_bytes))
}
pub fn from_bytes(b: [u8; DESTINATION_ADDRESS_LENGTH]) -> Self {
DestinationAddressBytes(b)
}
pub fn try_from_byte_slice(b: &[u8]) -> Result<Self, OutfoxError> {
if b.len() != DESTINATION_ADDRESS_LENGTH {
return Err(
OutfoxError::InvalidRouting(format!("received bytes got invalid length")).into(),
);
}
let mut address_bytes = [0; DESTINATION_ADDRESS_LENGTH];
address_bytes.copy_from_slice(b);
Ok(DestinationAddressBytes(address_bytes))
}
/// View this `DestinationAddressBytes` as an array of bytes.
pub fn as_bytes_ref(&self) -> &[u8; DESTINATION_ADDRESS_LENGTH] {
&self.0
}
/// Convert this `DestinationAddressBytes` to an array of bytes.
pub fn as_bytes(&self) -> [u8; DESTINATION_ADDRESS_LENGTH] {
self.0
}
}
impl Display for DestinationAddressBytes {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.as_base58_string().fmt(f)
}
}
+193 -136
View File
@@ -9,71 +9,64 @@ mod tests {
repeat_with(|| fastrand::u8(..)).take(n).collect()
}
use libcrux_kem::key_gen;
use nym_outfox::packet::OutfoxPacket;
use sphinx_packet::constants::NODE_ADDRESS_LENGTH;
use sphinx_packet::crypto::{PrivateKey, PublicKey};
use sphinx_packet::route::Destination;
use sphinx_packet::route::DestinationAddressBytes;
use sphinx_packet::route::Node;
use sphinx_packet::route::NodeAddressBytes;
use nym_outfox::route::{
Destination, DestinationAddressBytes, Node, NodeAddressBytes, NODE_ADDRESS_LENGTH,
};
use nym_outfox::format::*;
use nym_outfox::lion::*;
pub fn keygen() -> (PrivateKey, PublicKey) {
let private_key = PrivateKey::random();
let public_key = PublicKey::from(&private_key);
(private_key, public_key)
}
#[test]
fn test_encode_decode() {
let mix_params = MixStageParameters {
routing_information_length_bytes: 32,
remaining_header_length_bytes: (32 + 16 + 32) * 4,
payload_length_bytes: 1024, // 1kb
};
for kem in [
libcrux_kem::Algorithm::X25519,
libcrux_kem::Algorithm::XWingKemDraft06,
libcrux_kem::Algorithm::MlKem768,
] {
let mix_params = MixStageParameters {
kem: kem,
routing_information_length_bytes: 32,
remaining_header_length_bytes: (32 + 16 + 32) * 4,
payload_length_bytes: 1024, // 1kb
};
let user_secret = x25519_dalek::StaticSecret::random();
let mix_secret = x25519_dalek::StaticSecret::random();
let mix_public_key = x25519_dalek::PublicKey::from(&mix_secret);
let mut rng = rand::rng();
let (mix_decapsulation_key, mix_encapsulation_key) = key_gen(kem, &mut rng).unwrap();
let routing = [0; 32];
let destination = [0; 32];
let routing = [0; 32];
let destination = [0; 32];
let buffer = randombytes(mix_params.incoming_packet_length());
let buffer = randombytes(mix_params.incoming_packet_length());
let mut new_buffer = buffer.clone();
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_address_bytes = NodeAddressBytes::from_bytes(routing);
let node = Node::new(node_address_bytes, mix_public_key);
let node = Node::new(kem, node_address_bytes, mix_encapsulation_key);
let _ = mix_params
.encode_mix_layer(
&mut new_buffer[..],
&user_secret,
node.pub_key,
&destination,
)
.unwrap();
let _ = mix_params
.encode_mix_layer(&mut rng, &mut new_buffer[..], &node.pub_key, &destination)
.unwrap();
assert_ne!(
new_buffer[mix_params.payload_range()],
buffer[mix_params.payload_range()]
);
assert_ne!(new_buffer[mix_params.routing_data_range()], routing[..]);
assert_ne!(
new_buffer[mix_params.payload_range()],
buffer[mix_params.payload_range()]
);
assert_ne!(new_buffer[mix_params.routing_data_range()], routing[..]);
let _ = mix_params
.decode_mix_layer(&mut new_buffer[..], &mix_secret)
.unwrap();
let _ = mix_params
.decode_mix_layer(&mut new_buffer[..], &mix_decapsulation_key)
.unwrap();
assert_eq!(
new_buffer[mix_params.payload_range()],
buffer[mix_params.payload_range()]
);
assert_eq!(new_buffer[mix_params.routing_data_range()], routing[..]);
assert_eq!(
new_buffer[mix_params.payload_range()],
buffer[mix_params.payload_range()]
);
assert_eq!(new_buffer[mix_params.routing_data_range()], routing[..]);
}
}
#[test]
@@ -95,113 +88,177 @@ mod tests {
#[test]
fn test_packet_params_short() {
let (node1_pk, node1_pub) = keygen();
let node1 = Node::new(
NodeAddressBytes::from_bytes([0u8; NODE_ADDRESS_LENGTH]),
node1_pub,
);
let (node2_pk, node2_pub) = keygen();
let node2 = Node::new(
NodeAddressBytes::from_bytes([1u8; NODE_ADDRESS_LENGTH]),
node2_pub,
);
let (node3_pk, node3_pub) = keygen();
let node3 = Node::new(
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node3_pub,
);
let mut rng = rand::rng();
for kem in [
libcrux_kem::Algorithm::X25519,
libcrux_kem::Algorithm::XWingKemDraft06,
libcrux_kem::Algorithm::MlKem768,
] {
let (entry_pk, entry_pub) = key_gen(kem, &mut rng).unwrap();
let entry = Node::new(
kem,
NodeAddressBytes::from_bytes([8u8; NODE_ADDRESS_LENGTH]),
entry_pub,
);
let (node1_pk, node1_pub) = key_gen(kem, &mut rng).unwrap();
let node1 = Node::new(
kem,
NodeAddressBytes::from_bytes([0u8; NODE_ADDRESS_LENGTH]),
node1_pub,
);
let (node2_pk, node2_pub) = key_gen(kem, &mut rng).unwrap();
let node2 = Node::new(
kem,
NodeAddressBytes::from_bytes([1u8; NODE_ADDRESS_LENGTH]),
node2_pub,
);
let (node3_pk, node3_pub) = key_gen(kem, &mut rng).unwrap();
let node3 = Node::new(
kem,
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node3_pub,
);
let (gateway_pk, gateway_pub) = keygen();
let gateway = Node::new(
NodeAddressBytes::from_bytes([3u8; NODE_ADDRESS_LENGTH]),
gateway_pub,
);
let (exit_pk, exit_pub) = key_gen(kem, &mut rng).unwrap();
let exit = Node::new(
kem,
NodeAddressBytes::from_bytes([3u8; NODE_ADDRESS_LENGTH]),
exit_pub,
);
let destination = Destination::new(
DestinationAddressBytes::from_bytes([9u8; NODE_ADDRESS_LENGTH]),
[0u8; 16],
);
let destination = Destination::new(
DestinationAddressBytes::from_bytes([9u8; NODE_ADDRESS_LENGTH]),
[0u8; 16],
);
let route = [node1, node2.clone(), node3.clone(), gateway.clone()];
let route = [
entry,
node1.clone(),
node2.clone(),
node3.clone(),
exit.clone(),
];
let payload = vec![0, 0, 1, 1, 1, 0, 0];
let payload = vec![0, 0, 1, 1, 1, 0, 0];
let packet =
OutfoxPacket::build(&payload, &route, &destination, Some(payload.len())).unwrap();
let packet_bytes = packet.to_bytes().unwrap();
println!(
"packet bytes length, {}, declared {}",
packet_bytes.len(),
packet.len()
);
let packet = OutfoxPacket::build(
&mut rng,
kem,
&payload,
&route,
&destination,
Some(payload.len()),
)
.unwrap();
let packet_bytes = packet.to_bytes().unwrap();
println!(
"packet bytes length, {}, declared {}",
packet_bytes.len(),
packet.len()
);
let mut packet = OutfoxPacket::try_from(packet_bytes.as_slice()).unwrap();
let mut packet = OutfoxPacket::try_from((kem, packet_bytes.as_slice())).unwrap();
let next_address = packet.decode_next_layer(&node1_pk).unwrap();
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());
let next_address = packet.decode_next_layer(&node3_pk).unwrap();
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());
let next_address = packet.decode_next_layer(&entry_pk).unwrap();
assert_eq!(&next_address, node1.address.as_bytes());
let next_address = packet.decode_next_layer(&node1_pk).unwrap();
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());
let next_address = packet.decode_next_layer(&node3_pk).unwrap();
assert_eq!(&next_address, exit.address.as_bytes());
let destination_address = packet.decode_next_layer(&exit_pk).unwrap();
assert_eq!(destination_address, destination.address.as_bytes());
assert_eq!(payload, packet.recover_plaintext().unwrap());
assert_eq!(payload, packet.recover_plaintext().unwrap());
}
}
#[test]
fn test_packet_params_long() {
let (node1_pk, node1_pub) = keygen();
let node1 = Node::new(
NodeAddressBytes::from_bytes([0u8; NODE_ADDRESS_LENGTH]),
node1_pub,
);
let (node2_pk, node2_pub) = keygen();
let node2 = Node::new(
NodeAddressBytes::from_bytes([1u8; NODE_ADDRESS_LENGTH]),
node2_pub,
);
let (node3_pk, node3_pub) = keygen();
let node3 = Node::new(
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node3_pub,
);
let mut rng = rand::rng();
for kem in [
libcrux_kem::Algorithm::X25519,
libcrux_kem::Algorithm::XWingKemDraft06,
libcrux_kem::Algorithm::MlKem768,
] {
let (entry_pk, entry_pub) = key_gen(kem, &mut rng).unwrap();
let entry = Node::new(
kem,
NodeAddressBytes::from_bytes([8u8; NODE_ADDRESS_LENGTH]),
entry_pub,
);
let (node1_pk, node1_pub) = key_gen(kem, &mut rng).unwrap();
let node1 = Node::new(
kem,
NodeAddressBytes::from_bytes([0u8; NODE_ADDRESS_LENGTH]),
node1_pub,
);
let (node2_pk, node2_pub) = key_gen(kem, &mut rng).unwrap();
let node2 = Node::new(
kem,
NodeAddressBytes::from_bytes([1u8; NODE_ADDRESS_LENGTH]),
node2_pub,
);
let (node3_pk, node3_pub) = key_gen(kem, &mut rng).unwrap();
let node3 = Node::new(
kem,
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node3_pub,
);
let (gateway_pk, gateway_pub) = keygen();
let gateway = Node::new(
NodeAddressBytes::from_bytes([3u8; NODE_ADDRESS_LENGTH]),
gateway_pub,
);
let (exit_pk, exit_pub) = key_gen(kem, &mut rng).unwrap();
let exit = Node::new(
kem,
NodeAddressBytes::from_bytes([3u8; NODE_ADDRESS_LENGTH]),
exit_pub,
);
let destination = Destination::new(
DestinationAddressBytes::from_bytes([9u8; NODE_ADDRESS_LENGTH]),
[0u8; 16],
);
let destination = Destination::new(
DestinationAddressBytes::from_bytes([9u8; NODE_ADDRESS_LENGTH]),
[0u8; 16],
);
let route = [node1, node2.clone(), node3.clone(), gateway.clone()];
let route = [
entry,
node1.clone(),
node2.clone(),
node3.clone(),
exit.clone(),
];
let payload = randombytes(2048);
let payload = randombytes(2048);
let packet =
OutfoxPacket::build(&payload, &route, &destination, Some(payload.len())).unwrap();
let packet_bytes = packet.to_bytes().unwrap();
println!(
"packet bytes length, {}, declared {}",
packet_bytes.len(),
packet.len()
);
let packet = OutfoxPacket::build(
&mut rng,
kem,
&payload,
&route,
&destination,
Some(payload.len()),
)
.unwrap();
let packet_bytes = packet.to_bytes().unwrap();
println!(
"packet bytes length, {}, declared {}",
packet_bytes.len(),
packet.len()
);
let mut packet = OutfoxPacket::try_from(packet_bytes.as_slice()).unwrap();
let mut packet = OutfoxPacket::try_from((kem, packet_bytes.as_slice())).unwrap();
let next_address = packet.decode_next_layer(&node1_pk).unwrap();
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());
let next_address = packet.decode_next_layer(&node3_pk).unwrap();
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());
let next_address = packet.decode_next_layer(&entry_pk).unwrap();
assert_eq!(&next_address, node1.address.as_bytes());
let next_address = packet.decode_next_layer(&node1_pk).unwrap();
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());
let next_address = packet.decode_next_layer(&node3_pk).unwrap();
assert_eq!(&next_address, exit.address.as_bytes());
let destination_address = packet.decode_next_layer(&exit_pk).unwrap();
assert_eq!(destination_address, destination.address.as_bytes());
assert_eq!(payload, packet.recover_plaintext().unwrap());
assert_eq!(payload, packet.recover_plaintext().unwrap());
}
}
}