use std::{fmt::Debug, marker::PhantomData};
use cryptraits::{
convert::{FromBytes, Len, ToVec},
hash::Hash,
hmac::Hmac,
key::{Blind, SecretKey},
key_exchange::DiffieHellman,
stream_cipher::StreamCipher,
};
use zeroize::Zeroize;
use crate::{
crypto::{
compute_blinding_factor, compute_mac, generate_cipher_stream, generate_encryption_key,
generate_padding, generate_shared_secrets, xor,
},
Address, SfynxError, ENCRYPTION, HASH,
};
#[derive(Clone, Zeroize)]
pub struct Header<A, H, SC, ESK, HASH>
where
A: Address,
H: Hmac,
SC: StreamCipher,
ESK: SecretKey + DiffieHellman,
HASH: Hash,
{
max_relays: u8,
pub routing_info: Vec<u8>,
pub routing_info_mac: Vec<u8>,
pub public_key: <ESK as SecretKey>::PK,
#[zeroize(skip)]
_a: PhantomData<A>,
#[zeroize(skip)]
_h: PhantomData<H>,
#[zeroize(skip)]
_sc: PhantomData<SC>,
#[zeroize(skip)]
_hash: PhantomData<HASH>,
}
impl<A, H, SC, ESK, HASH> Debug for Header<A, H, SC, ESK, HASH>
where
A: Address,
H: Hmac,
SC: StreamCipher,
ESK: SecretKey + DiffieHellman,
HASH: Hash,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Header")
.field("routing_info", &self.routing_info)
.field("routing_info_mac", &self.routing_info_mac)
.field("public_key", &self.public_key)
.finish()
}
}
impl<A, H, SC, ESK, HASH> Drop for Header<A, H, SC, ESK, HASH>
where
A: Address,
H: Hmac,
SC: StreamCipher,
ESK: SecretKey + DiffieHellman,
HASH: Hash,
{
fn drop(&mut self) {
self.zeroize()
}
}
impl<A, H, SC, ESK, HASH> Header<A, H, SC, ESK, HASH>
where
A: Address,
H: Hmac + Len,
SC: StreamCipher,
ESK: SecretKey + DiffieHellman<PK = <ESK as SecretKey>::PK> + Blind + ToVec,
<ESK as DiffieHellman>::SSK: ToVec,
<ESK as SecretKey>::PK: Blind + ToVec + FromBytes,
HASH: Hash,
{
#[allow(clippy::type_complexity)]
pub fn new(
max_relays: u8,
routing_info: &[A],
dest: impl Address,
session_key: ESK,
circuit_pub_keys: Vec<<ESK as DiffieHellman>::PK>,
) -> Result<
(
Vec<<ESK as DiffieHellman>::SSK>,
Header<A, H, SC, ESK, HASH>,
),
SfynxError,
> {
let shared_secrets =
generate_shared_secrets::<ESK, HASH>(&circuit_pub_keys, session_key.clone())?;
Self::with_shared_secrets(max_relays, routing_info, dest, session_key, &shared_secrets)
}
#[allow(clippy::type_complexity)]
pub fn with_shared_secrets(
max_relays: u8,
routing_info: &[A],
dest: impl Address,
session_key: ESK,
shared_secrets: &[<ESK as DiffieHellman>::SSK],
) -> Result<
(
Vec<<ESK as DiffieHellman>::SSK>,
Header<A, H, SC, ESK, HASH>,
),
SfynxError,
> {
Self::validate_header_input(max_relays, routing_info)?;
let relay_data_size: usize = A::LEN + H::LEN;
let routing_info_size: usize = max_relays as usize * relay_data_size;
let stream_size = routing_info_size + relay_data_size;
let padding =
generate_padding::<H, SC>(A::LEN, max_relays.into(), shared_secrets, &[0; 12])
.map_err(|e| SfynxError::StreamCipherError(format!("{e:?}")))?;
let mut routing_info_bytes = vec![0; routing_info_size];
routing_info_bytes[routing_info_size - padding.len()..].copy_from_slice(&padding);
let mut dest = dest.to_vec();
let mut routing_info_mac = vec![0; H::LEN];
for (addr, shared_secret) in routing_info.iter().zip(shared_secrets).rev() {
let enc_key = generate_encryption_key::<H>(&shared_secret.to_vec(), ENCRYPTION);
let mac_key = generate_encryption_key::<H>(&shared_secret.to_vec(), HASH);
if Some(addr) != routing_info.last() {
routing_info_bytes.rotate_right(relay_data_size);
}
routing_info_bytes[..A::LEN].copy_from_slice(&dest);
routing_info_bytes[A::LEN..relay_data_size].copy_from_slice(&routing_info_mac);
let cipher = generate_cipher_stream::<SC>(&enc_key, &[0; 12], stream_size)
.map_err(|e| SfynxError::StreamCipherError(format!("{e:?}")))?;
xor(&mut routing_info_bytes, &cipher[..routing_info_size]);
if Some(addr) == routing_info.last() {
let len = routing_info_bytes.len() - padding.len();
routing_info_bytes[len..].copy_from_slice(&padding);
}
routing_info_mac = compute_mac::<H>(&mac_key, &routing_info_bytes);
dest = addr.to_vec();
}
Ok((
shared_secrets.to_vec(),
Header {
max_relays,
routing_info: routing_info_bytes.to_vec(),
routing_info_mac,
public_key: session_key.to_public(),
_a: Default::default(),
_h: Default::default(),
_sc: Default::default(),
_hash: Default::default(),
},
))
}
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, SfynxError> {
let max_relays = bytes.as_ref()[0] as usize;
let bytes = &bytes.as_ref()[1..];
let routing_info = Vec::from(&bytes[..max_relays * (A::LEN + H::LEN)]);
let routing_info_mac = Vec::from(
&bytes[max_relays * (A::LEN + H::LEN)..max_relays * (A::LEN + H::LEN) + H::LEN],
);
let public_key =
<ESK as SecretKey>::PK::from_bytes(&bytes[max_relays * (A::LEN + H::LEN) + H::LEN..])
.map_err(|e| SfynxError::KeyPairError(format!("{e:?}")))?;
let max_relays = max_relays as u8;
Ok(Self {
max_relays,
routing_info,
routing_info_mac,
public_key,
_a: Default::default(),
_h: Default::default(),
_sc: Default::default(),
_hash: Default::default(),
})
}
pub fn to_vec(&self) -> Result<Vec<u8>, SfynxError> {
let mut bytes = Vec::new();
bytes.extend(&[self.max_relays]);
bytes.extend(&self.routing_info);
bytes.extend(&self.routing_info_mac);
bytes.extend(&self.public_key.to_vec());
Ok(bytes)
}
fn validate_header_input(max_relays: u8, routing_info: &[A]) -> Result<(), SfynxError>
where
A: Address,
{
if routing_info.len() > max_relays as usize {
Err(SfynxError::WrongRoutingInfoLength)
} else {
Ok(())
}
}
pub fn peel(
&self,
shared_secret: &<ESK as DiffieHellman>::SSK,
) -> Result<(A, Self), SfynxError> {
let relay_data_size: usize = A::LEN + H::LEN;
let routing_info_size: usize = self.max_relays as usize * relay_data_size;
let stream_size = routing_info_size + relay_data_size;
let enc_key = generate_encryption_key::<H>(&shared_secret.to_vec(), ENCRYPTION);
let mac_key = generate_encryption_key::<H>(&shared_secret.to_vec(), HASH);
let routing_info_mac = compute_mac::<H>(&mac_key, &self.routing_info.to_vec());
if self.routing_info_mac != routing_info_mac {
return Err(SfynxError::InvalidMac);
}
let mut routing_info = self.routing_info.clone();
routing_info.extend(vec![0; H::LEN + A::LEN]);
let cipher = generate_cipher_stream::<SC>(&enc_key.to_vec(), &[0; 12], stream_size)
.map_err(|e| SfynxError::StreamCipherError(format!("{e:?}")))?;
xor(&mut routing_info[..stream_size], &cipher);
let next_addr = A::from_bytes(&routing_info[..A::LEN]);
let next_routing_info_mac = Vec::from(&routing_info[A::LEN..relay_data_size]);
let next_routing_info = Vec::from(&routing_info[relay_data_size..]);
let blinding_factor = compute_blinding_factor::<ESK, HASH>(&self.public_key, shared_secret);
let new_public_key = self
.public_key
.to_blind(&blinding_factor)
.map_err(|e| SfynxError::KeyPairError(format!("{e:?}")))?;
let next_header = Self {
max_relays: self.max_relays,
routing_info: next_routing_info,
routing_info_mac: next_routing_info_mac,
public_key: new_public_key,
_a: Default::default(),
_h: Default::default(),
_sc: Default::default(),
_hash: Default::default(),
};
Ok((next_addr, next_header))
}
}
#[cfg(test)]
mod tests {
use cryptimitives::{hash::sha256, hmac, key::x25519_ristretto, stream_cipher::chacha20};
use cryptraits::{
convert::{Len, ToVec},
key::{Generate, SecretKey},
};
use crate::{crypto::generate_shared_secrets, header::Header, Address};
#[derive(Debug, PartialEq)]
struct TestAddress(String);
impl Address for TestAddress {
fn from_bytes(bytes: &[u8]) -> Self {
Self(String::from_utf8(bytes.to_vec()).unwrap())
}
fn to_vec(&self) -> Vec<u8> {
Vec::from(self.0.as_bytes())
}
}
impl Len for TestAddress {
const LEN: usize = 46;
}
#[test]
fn test_new_header() {
let num_relays = 4;
let dest = TestAddress(String::from(
"QmZrXVN6xNkXYqFharGfjG6CjdE3X85werKm8AyMdqsQKS",
));
let relay_addrs = vec![
TestAddress(String::from(
"/ip4/127.0.0.1/udp/1234#0000000000000000000000",
)),
TestAddress(String::from(
"QmSFXZRzh6ZdpWXXQQ2mkYtx3ns39ZPtWgQJ7sSqStiHZH",
)),
TestAddress(String::from(
"/ip6/2607:f8b0:4003:c00::6a/udp/5678#000000000",
)),
TestAddress(String::from(
"/ip4/198.162.0.2/tcp/4321#00000000000000000000",
)),
];
let mut circuit_keypairs = Vec::new();
for _ in 0..num_relays {
circuit_keypairs.push(x25519_ristretto::EphemeralSecretKey::generate());
}
let session_key = x25519_ristretto::EphemeralSecretKey::generate();
let shared_secrets =
generate_shared_secrets::<x25519_ristretto::EphemeralSecretKey, sha256::Hash>(
&circuit_keypairs
.iter()
.map(|k| k.to_public())
.collect::<Vec<x25519_ristretto::EphemeralPublicKey>>(),
session_key.clone(),
)
.unwrap();
let (_, header) = Header::<
TestAddress,
hmac::sha256::Hmac,
chacha20::StreamCipher,
x25519_ristretto::EphemeralSecretKey,
sha256::Hash,
>::with_shared_secrets(
num_relays, &relay_addrs, dest, session_key, &shared_secrets
)
.unwrap();
let mut count = 0;
for i in header.routing_info.iter().rev() {
if *i != 0 {
break;
}
count += 1;
}
assert!(
count <= 2,
"Header is revealing number of relays. Suffixed 0s count: {}",
count
);
}
#[test]
fn test_return_shared_secrets() {
let num_relays = 4;
let dest = TestAddress(String::from(
"QmZrXVN6xNkXYqFharGfjG6CjdE3X85werKm8AyMdqsQKS",
));
let routing_info = vec![
TestAddress(String::from(
"/ip4/127.0.0.1/udp/1234#0000000000000000000000",
)),
TestAddress(String::from(
"QmSFXZRzh6ZdpWXXQQ2mkYtx3ns39ZPtWgQJ7sSqStiHZH",
)),
TestAddress(String::from(
"/ip6/2607:f8b0:4003:c00::6a/udp/5678#000000000",
)),
TestAddress(String::from(
"/ip4/198.162.0.2/tcp/4321#00000000000000000000",
)),
];
let mut circuit_keypairs = Vec::new();
for _ in 0..num_relays {
circuit_keypairs.push(x25519_ristretto::EphemeralSecretKey::generate());
}
let session_key = x25519_ristretto::EphemeralSecretKey::generate();
let shared_secrets =
generate_shared_secrets::<x25519_ristretto::EphemeralSecretKey, sha256::Hash>(
&circuit_keypairs
.iter()
.map(|k| k.to_public())
.collect::<Vec<x25519_ristretto::EphemeralPublicKey>>(),
session_key.clone(),
)
.unwrap();
let (secrets, _) = Header::<
TestAddress,
hmac::sha256::Hmac,
chacha20::StreamCipher,
x25519_ristretto::EphemeralSecretKey,
sha256::Hash,
>::with_shared_secrets(
num_relays,
&routing_info,
dest,
session_key,
&shared_secrets,
)
.unwrap();
assert_eq!(
secrets.iter().map(|s| s.to_vec()).collect::<Vec<Vec<u8>>>(),
shared_secrets
.iter()
.map(|s| s.to_vec())
.collect::<Vec<Vec<u8>>>()
);
}
#[test]
fn test_header_from_bytes() {
let num_relays = 4;
let dest = TestAddress(String::from(
"QmZrXVN6xNkXYqFharGfjG6CjdE3X85werKm8AyMdqsQKS",
));
let routing_info = vec![
TestAddress(String::from(
"/ip4/127.0.0.1/udp/1234#0000000000000000000000",
)),
TestAddress(String::from(
"QmSFXZRzh6ZdpWXXQQ2mkYtx3ns39ZPtWgQJ7sSqStiHZH",
)),
TestAddress(String::from(
"/ip6/2607:f8b0:4003:c00::6a/udp/5678#000000000",
)),
TestAddress(String::from(
"/ip4/198.162.0.2/tcp/4321#00000000000000000000",
)),
];
let mut circuit_keypairs = Vec::new();
for _ in 0..num_relays {
circuit_keypairs.push(x25519_ristretto::EphemeralSecretKey::generate());
}
let session_key = x25519_ristretto::EphemeralSecretKey::generate();
let shared_secrets =
generate_shared_secrets::<x25519_ristretto::EphemeralSecretKey, sha256::Hash>(
&circuit_keypairs
.iter()
.map(|k| k.to_public())
.collect::<Vec<x25519_ristretto::EphemeralPublicKey>>(),
session_key.clone(),
)
.unwrap();
let (_, header) = Header::<
TestAddress,
hmac::sha256::Hmac,
chacha20::StreamCipher,
x25519_ristretto::EphemeralSecretKey,
sha256::Hash,
>::with_shared_secrets(
num_relays,
&routing_info,
dest,
session_key,
&shared_secrets,
)
.unwrap();
let bytes = header.to_vec().unwrap();
let header_from_bytes = Header::<
TestAddress,
hmac::sha256::Hmac,
chacha20::StreamCipher,
x25519_ristretto::EphemeralSecretKey,
sha256::Hash,
>::from_bytes(&bytes)
.unwrap();
assert_eq!(header_from_bytes.routing_info, header.routing_info);
assert_eq!(header_from_bytes.routing_info_mac, header.routing_info_mac);
assert_eq!(header_from_bytes.public_key, header.public_key);
}
}