Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "sphinx-packet"
version = "0.5.0"
version = "0.6.0"
authors = ["Ania Piotrowska <ania@nymtech.net>", "Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2018"
edition = "2021"
license = "Apache-2.0"
description = "A Sphinx packet implementation in Rust"
repository = "https://github.com/nymtech/sphinx"
Expand Down Expand Up @@ -43,3 +43,12 @@ rand_chacha = "0.3.1"
name = "benchmarks"
harness = false

[workspace.lints.clippy]
unwrap_used = "deny"
expect_used = "deny"
todo = "deny"
dbg_macro = "deny"
exit = "deny"
panic = "deny"
unimplemented = "deny"
unreachable = "deny"
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ removed processing and creation of packets with undefined operations
- removed `RoutingKeys` in favour of `ExpandedSharedSecret` and added `ReplyTag`
- type adjustments

#### v0.6.0

- new way of deriving `PayloadKey` that uses seed obtained from the `ExpandedSharedSecret` to reduce sizes of `SURB`s
- API changes

### Benchmarks

To run benchmarks, use:
Expand Down
9 changes: 7 additions & 2 deletions benches/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion};
use sphinx_packet::constants::{
DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH,
};

use sphinx_packet::header::delays;
use sphinx_packet::route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes};
use sphinx_packet::test_utils::fixtures::keygen;
use sphinx_packet::SphinxPacket;
use std::time::Duration;
use x25519_dalek::{PublicKey, StaticSecret};

fn keygen() -> (StaticSecret, PublicKey) {
let private_key = StaticSecret::random();
let public_key = PublicKey::from(&private_key);
(private_key, public_key)
}

fn make_packet_copy(packet: &SphinxPacket) -> SphinxPacket {
SphinxPacket::from_bytes(&packet.to_bytes()).unwrap()
Expand Down
3 changes: 3 additions & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
allow-panic-in-tests = true
17 changes: 16 additions & 1 deletion src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ pub const EXPANDED_SHARED_SECRET_LENGTH: usize = crypto::STREAM_CIPHER_KEY_SIZE
+ BLINDING_FACTOR_SIZE
+ REPLAY_TAG_SIZE;

pub const HKDF_INPUT_SEED: &[u8; 97] = b"Dwste mou enan moxlo arketa makru kai ena upomoxlio gia na ton topothetisw kai tha kinisw thn gh.";
pub const STREAM_CIPHER_OUTPUT_LENGTH: usize =
(NODE_META_INFO_SIZE + HEADER_INTEGRITY_MAC_SIZE) * (MAX_PATH_LENGTH + 1);
pub const DESTINATION_ADDRESS_LENGTH: usize = 2 * SECURITY_PARAMETER;
pub const NODE_ADDRESS_LENGTH: usize = 2 * SECURITY_PARAMETER;
pub const IDENTIFIER_LENGTH: usize = SECURITY_PARAMETER;
pub const INTEGRITY_MAC_KEY_SIZE: usize = SECURITY_PARAMETER;
pub const HEADER_INTEGRITY_MAC_SIZE: usize = SECURITY_PARAMETER;
pub const PAYLOAD_KEY_SEED_SIZE: usize = SECURITY_PARAMETER;
pub const PAYLOAD_KEY_SIZE: usize = 192; // must be 192 because of the Lioness implementation we're using
pub const DELAY_LENGTH: usize = 8; // how many bytes we will use to encode the delay
pub const NODE_META_INFO_SIZE: usize =
Expand All @@ -48,6 +48,21 @@ pub const PAYLOAD_SIZE: usize = 1024;
pub const VERSION_LENGTH: usize = 3; // since version is represented as 3 u8 values: major, minor and patch
// we need the single byte to detect padding length

#[deprecated(note = "use EXPANDED_SHARED_SECRET_HKDF_INFO instead")]
pub const HKDF_INPUT_SEED: &[u8] = EXPANDED_SHARED_SECRET_HKDF_INFO;

// content due to legacy reasons
pub const EXPANDED_SHARED_SECRET_HKDF_INFO: &[u8] =
b"Dwste mou enan moxlo arketa makru kai ena upomoxlio gia na ton topothetisw kai tha kinisw thn gh.";

// unfortunately for legacy compatibility reasons, we have to be using an empty salt
// (nodes need to be able to unconditionally recover version information from the header in order to
// decide on further processing. this value is behind the initial hkdf
pub const EXPANDED_SHARED_SECRET_HKDF_SALT: &[u8] = b"";

pub const PAYLOAD_KEY_HKDF_INFO: &[u8] = b"sphinx-payload-key-V01-CS01-HKDF:SHA256-INFO";
pub const PAYLOAD_KEY_HKDF_SALT: &[u8] = b"sphinx-payload-key-V01-CS01-HKDF:SHA256-SALT";

pub type HeaderIntegrityMacSize = U16;

// TODO: to replace with Blake3
Expand Down
29 changes: 14 additions & 15 deletions src/header/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::constants::{INTEGRITY_MAC_KEY_SIZE, PAYLOAD_KEY_SIZE};
use crate::constants::INTEGRITY_MAC_KEY_SIZE;
use crate::crypto::STREAM_CIPHER_KEY_SIZE;
use crate::header::shared_secret::{expand_shared_secret, ExpandedSharedSecret};
use crate::route::Node;
Expand All @@ -21,7 +21,6 @@ use x25519_dalek::{PublicKey, StaticSecret};

pub type StreamCipherKey = [u8; STREAM_CIPHER_KEY_SIZE];
pub type HeaderIntegrityMacKey = [u8; INTEGRITY_MAC_KEY_SIZE];
pub type PayloadKey = [u8; PAYLOAD_KEY_SIZE];

pub struct KeyMaterial {
pub initial_shared_secret: PublicKey,
Expand All @@ -32,24 +31,24 @@ impl KeyMaterial {
// derive shared keys, group elements, blinding factors
pub fn derive(route: &[Node], initial_secret: &StaticSecret) -> Self {
let initial_shared_secret = PublicKey::from(initial_secret);
let mut expanded_shared_secrets = Vec::with_capacity(route.len());

let mut blinding_factors = vec![initial_secret.clone()];
let mut expanded_shared_secrets = Vec::new();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this loop does the same thing as before, I just restructured it to remove clone on the initial_secret

let mut blinding_factors = Vec::new();

for (i, node) in route.iter().enumerate() {
let shared_key = blinding_factors
.iter()
.fold(node.pub_key, |acc, blinding_factor| {
// a nasty hack to convert `SharedSecret` into `PublicKey`,
// so that we could call `diffie_hellman` repeatedly
PublicKey::from(blinding_factor.diffie_hellman(&acc).to_bytes())
});
let expanded_shared_secret = expand_shared_secret(shared_key.as_bytes());
let mut acc = node.pub_key;

// it's not the last iteration
if i != route.len() + 1 {
blinding_factors.push(expanded_shared_secret.blinding_factor());
// avoid having to clone the initial secret by just chaining iterators
for blinding_factor in std::iter::once(initial_secret).chain(&blinding_factors) {
let shared_secret = blinding_factor.diffie_hellman(&acc);
acc = PublicKey::from(shared_secret.to_bytes());
}

let expanded_shared_secret = expand_shared_secret(acc.as_bytes());

if i != route.len() - 1 {
blinding_factors.push(expanded_shared_secret.blinding_factor());
}
expanded_shared_secrets.push(expanded_shared_secret);
}

Expand Down
127 changes: 89 additions & 38 deletions src/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
use crate::constants::HEADER_INTEGRITY_MAC_SIZE;
use crate::header::delays::Delay;
use crate::header::filler::Filler;
use crate::header::keys::{KeyMaterial, PayloadKey};
use crate::header::keys::KeyMaterial;
use crate::header::routing::{EncapsulatedRoutingInformation, ENCRYPTED_ROUTING_INFO_SIZE};
use crate::header::shared_secret::{ExpandSecret, ExpandedSharedSecret};
use crate::packet::ProcessedPacketData;
use crate::payload::key::{derive_payload_key, PayloadKey, PayloadKeySeed};
use crate::payload::Payload;
use crate::route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier};
use crate::version::{Version, CURRENT_VERSION, UPDATED_LEGACY_VERSION};
use crate::version::Version;
use crate::{Error, ErrorKind, ProcessedPacket, Result, SphinxPacket};
use x25519_dalek::{PublicKey, StaticSecret};

Expand Down Expand Up @@ -99,44 +100,50 @@ impl ProcessedHeader {
}

impl SphinxHeader {
// needs client's secret key, how should we inject this?
// needs to deal with SURBs too at some point
pub fn new(
#[cfg(test)]
pub(crate) fn new_current(
initial_secret: &StaticSecret,
route: &[Node],
delays: &[Delay],
destination: &Destination,
) -> (Self, Vec<PayloadKey>) {
) -> BuiltHeader {
let key_material = keys::KeyMaterial::derive(route, initial_secret);
Self::build_header(key_material, route, delays, destination, CURRENT_VERSION)
Self::build_header(
key_material,
route,
delays,
destination,
crate::version::CURRENT_VERSION,
)
}

#[cfg(test)]
#[deprecated]
#[allow(deprecated)]
pub fn new_legacy(
pub(crate) fn new_legacy(
initial_secret: &StaticSecret,
route: &[Node],
delays: &[Delay],
destination: &Destination,
) -> (Self, Vec<PayloadKey>) {
) -> BuiltHeader {
let key_material = keys::KeyMaterial::derive_legacy(route, initial_secret);
Self::build_header(
key_material,
route,
delays,
destination,
UPDATED_LEGACY_VERSION,
crate::version::UPDATED_LEGACY_VERSION,
)
}

#[allow(deprecated)]
pub fn new_versioned(
pub(crate) fn new_versioned(
initial_secret: &StaticSecret,
route: &[Node],
delays: &[Delay],
destination: &Destination,
version: Version,
) -> (Self, Vec<PayloadKey>) {
) -> BuiltHeader {
let key_material = if version.is_legacy() {
keys::KeyMaterial::derive_legacy(route, initial_secret)
} else {
Expand All @@ -151,29 +158,19 @@ impl SphinxHeader {
delays: &[Delay],
destination: &Destination,
version: Version,
) -> (Self, Vec<PayloadKey>) {
) -> BuiltHeader {
let filler_string = Filler::new(&key_material.expanded_shared_secrets[..route.len() - 1]);
let routing_info = Box::new(routing::EncapsulatedRoutingInformation::new(
let routing_info = EncapsulatedRoutingInformation::new(
route,
destination,
delays,
&key_material.expanded_shared_secrets,
filler_string,
version,
));
);

// encapsulate header.routing information, compute MACs
(
SphinxHeader {
shared_secret: key_material.initial_shared_secret,
routing_info,
},
key_material
.expanded_shared_secrets
.iter()
.map(|expanded| *expanded.payload_key())
.collect(),
)
BuiltHeader::new(version, key_material, routing_info)
}

// note: this method is currently removed because there's too many branches to support
Expand Down Expand Up @@ -355,11 +352,7 @@ impl SphinxHeader {
let shared_secret = PublicKey::from(shared_secret_bytes);

// the rest are for the encapsulated routing info
let encapsulated_routing_info_bytes = bytes[32..HEADER_SIZE].to_vec();

let routing_info = Box::new(EncapsulatedRoutingInformation::from_bytes(
&encapsulated_routing_info_bytes,
)?);
let routing_info = Box::new(EncapsulatedRoutingInformation::from_bytes(&bytes[32..])?);

Ok(SphinxHeader {
shared_secret,
Expand Down Expand Up @@ -389,6 +382,60 @@ impl SphinxHeader {
}
}

pub(crate) struct BuiltHeader {
header: SphinxHeader,
version: Version,
expanded_secrets: Vec<ExpandedSharedSecret>,
}

impl BuiltHeader {
fn new(
version: Version,
key_material: KeyMaterial,
routing_information: EncapsulatedRoutingInformation,
) -> Self {
BuiltHeader {
header: SphinxHeader {
shared_secret: key_material.initial_shared_secret,
routing_info: Box::new(routing_information),
},
version,
expanded_secrets: key_material.expanded_shared_secrets,
}
}

// depending on the version either use the initial hkdf output as payload keys
// or extract the seed and run it through another hkdf
pub(crate) fn derive_payload_keys(&self) -> Vec<PayloadKey> {
if self.version.expects_legacy_full_payload_keys() {
self.legacy_full_payload_keys()
} else {
self.expanded_secrets
.iter()
.map(|s| derive_payload_key(s.payload_key_seed()))
.collect()
}
}

pub(crate) fn legacy_full_payload_keys(&self) -> Vec<PayloadKey> {
self.expanded_secrets
.iter()
.map(|s| *s.legacy_payload_key())
.collect()
}

pub(crate) fn payload_key_seeds(&self) -> Vec<PayloadKeySeed> {
self.expanded_secrets
.iter()
.map(|s| *s.payload_key_seed())
.collect()
}

pub(crate) fn into_header(self) -> SphinxHeader {
self.header
}
}

#[cfg(test)]
mod create_and_process_sphinx_packet_header {
use super::*;
Expand Down Expand Up @@ -422,8 +469,9 @@ mod create_and_process_sphinx_packet_header {
let average_delay = 1;
let delays =
delays::generate_from_average_duration(route.len(), Duration::from_secs(average_delay));
let (sphinx_header, _) =
SphinxHeader::new(&initial_secret, &route, &delays, &route_destination);
let sphinx_header =
SphinxHeader::new_current(&initial_secret, &route, &delays, &route_destination)
.into_header();

//let (new_header, next_hop_address, _) = sphinx_header.process(node1_sk).unwrap();
let new_header = match sphinx_header.process(&node1_sk).unwrap().data {
Expand Down Expand Up @@ -521,8 +569,9 @@ mod create_and_process_sphinx_packet_header {
let average_delay = 1;
let delays =
delays::generate_from_average_duration(route.len(), Duration::from_secs(average_delay));
let (sphinx_header, _) =
SphinxHeader::new_legacy(&initial_secret, &route, &delays, &route_destination);
let sphinx_header =
SphinxHeader::new_legacy(&initial_secret, &route, &delays, &route_destination)
.into_header();

//let (new_header, next_hop_address, _) = sphinx_header.process(node1_sk).unwrap();
let new_header = match sphinx_header
Expand Down Expand Up @@ -632,7 +681,7 @@ mod unwrap_routing_information {
} => {
assert_eq!(
routing_info[2..2 + NODE_ADDRESS_LENGTH],
next_hop_address.as_bytes()
next_hop_address.to_bytes()
);
assert_eq!(
routing_info
Expand Down Expand Up @@ -683,7 +732,8 @@ mod unwrapping_using_previously_expanded_shared_secret {
let average_delay = 1;
let delays =
delays::generate_from_average_duration(route.len(), Duration::from_secs(average_delay));
let (sphinx_header, _) = SphinxHeader::new(&initial_secret, &route, &delays, &destination);
let sphinx_header =
SphinxHeader::new_current(&initial_secret, &route, &delays, &destination).into_header();
let initial_secret = sphinx_header.shared_secret;

let normally_unwrapped = match sphinx_header.clone().process(&node1_sk).unwrap().data {
Expand Down Expand Up @@ -727,7 +777,8 @@ mod unwrapping_using_previously_expanded_shared_secret {
let average_delay = 1;
let delays =
delays::generate_from_average_duration(route.len(), Duration::from_secs(average_delay));
let (sphinx_header, _) = SphinxHeader::new(&initial_secret, &route, &delays, &destination);
let sphinx_header =
SphinxHeader::new_current(&initial_secret, &route, &delays, &destination).into_header();
let initial_secret = sphinx_header.shared_secret;

let normally_unwrapped = sphinx_header.clone().process(&node1_sk).unwrap();
Expand Down
Loading
Loading