Skip to content

Commit

Permalink
improve docs
Browse files Browse the repository at this point in the history
  • Loading branch information
doitian committed Sep 11, 2024
1 parent 8d7e79a commit 73c1b83
Showing 1 changed file with 49 additions and 26 deletions.
75 changes: 49 additions & 26 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ const HMAC_KEY_PAD: &[u8] = b"pad";

#[derive(Debug, Clone)]
pub struct OnionPacket {
// Version of the onion packet, currently 0
pub version: u8,
// The public key of the next hop
pub public_key: PublicKey,
// Encrypted packet data
pub packet_data: [u8; ONION_PACKET_DATA_LEN],
// HMAC of the packet data
pub hmac: [u8; 32],
}

impl OnionPacket {
/// Converts the onion packet into a byte vector.
pub fn into_bytes(self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(1 + 33 + ONION_PACKET_DATA_LEN + 32);
bytes.push(self.version);
Expand All @@ -40,7 +45,7 @@ pub enum SphinxError {
#[error("The filler length is too large")]
FillerLenTooLarge,

#[error("The payment path does not match the hops data length")]
#[error("The hops path does not match the hops data length")]
HopsLenMismatch,

#[error("The hops path is empty")]
Expand Down Expand Up @@ -77,18 +82,22 @@ fn derive_next_hop_ephemeral_secret_key(
}

// Keys manager for each hop
pub struct HopKeys {
pub ephemeral_public_key: PublicKey,
pub rho: [u8; 32],
pub mu: [u8; 32],
struct HopKeys {
/// Ephemeral public key for the hop
ephemeral_public_key: PublicKey,
/// Key derived from the shared secret for the hop. It is used to encrypt the packet data.
rho: [u8; 32],
/// Key derived from the shared secret for the hop. It is used to compute the HMAC of the packet data.
mu: [u8; 32],
}

pub fn derive_hops_keys<C: Signing>(
payment_path: &Vec<PublicKey>,
/// Derives HopKeys for each hop.
fn derive_hops_keys<C: Signing>(
hops_path: &Vec<PublicKey>,
session_key: SecretKey,
secp_ctx: &Secp256k1<C>,
) -> Vec<HopKeys> {
payment_path
hops_path
.iter()
.scan(session_key, |ephemeral_secret_key, pk| {
let ephemeral_public_key = ephemeral_secret_key.public_key(secp_ctx);
Expand All @@ -112,7 +121,8 @@ pub fn derive_hops_keys<C: Signing>(
.collect()
}

pub fn derive_key(hmac_key: &[u8], shared_secret: &[u8]) -> [u8; 32] {
/// Derives a key from the shared secret using HMAC.
fn derive_key(hmac_key: &[u8], shared_secret: &[u8]) -> [u8; 32] {
let mut mac = Hmac::<Sha256>::new_from_slice(hmac_key).expect("valid hmac key");
mac.update(shared_secret);
mac.finalize().into_bytes().into()
Expand All @@ -121,22 +131,22 @@ pub fn derive_key(hmac_key: &[u8], shared_secret: &[u8]) -> [u8; 32] {
/// Generates the initial 1300 bytes of onion packet padding data from PRG.
///
/// Uses Chacha as the PRG. The key is derived from the session key using HMAC, and the nonce is all zeros.
pub fn generate_padding_data(pad_key: &[u8]) -> [u8; ONION_PACKET_DATA_LEN] {
fn generate_padding_data(pad_key: &[u8]) -> [u8; ONION_PACKET_DATA_LEN] {
let mut cipher = ChaCha20::new(pad_key.into(), &[0u8; 12].into());
let mut buffer = [0u8; ONION_PACKET_DATA_LEN];
cipher.apply_keystream(&mut buffer);
buffer
}

pub fn generate_filler(
hops_keys: &[HopKeys],
hops_data: &[Vec<u8>],
) -> Result<Vec<u8>, SphinxError> {
/// Generates the filler to obfuscate the onion packet.
fn generate_filler(hops_keys: &[HopKeys], hops_data: &[Vec<u8>]) -> Result<Vec<u8>, SphinxError> {
let mut filler = Vec::new();
let mut pos = 0;

for (i, (data, keys)) in hops_data.iter().zip(hops_keys.iter()).enumerate() {
let mut chacha = ChaCha20::new(&keys.rho.into(), &[0u8; 12].into());

// For each hop, generate 1300 bytes of random data from the cipher stream. Drop the first ONION_PACKET_DATA_LEN bytes and apply the rest to the filler.
for _ in 0..(ONION_PACKET_DATA_LEN - pos) {
let mut dummy = [0; 1];
chacha.apply_keystream(&mut dummy);
Expand Down Expand Up @@ -169,6 +179,13 @@ fn shift_slice_right(arr: &mut [u8], amt: usize) {
}
}

/// Constructs the onion packet internally.
///
/// - `packet_data`: The initial 1300 bytes of the onion packet generated by `generate_padding_data`.
/// - `hops_keys`: The keys for each hop generated by `derive_hops_keys`.
/// - `hops_data`: The unencrypted data for each hop.
/// - `assoc_data`: The associated data. It will be included in the HMAC so each hop can verify that the associate data is not tamperred.
/// - `filler`: The filler to obfuscate the packet data, which is generated by `generate_filler`.
fn construct_onion_packet(
mut packet_data: [u8; ONION_PACKET_DATA_LEN],
hops_keys: &[HopKeys],
Expand Down Expand Up @@ -211,20 +228,27 @@ fn construct_onion_packet(
})
}

/// Creates a new onion packet internally.
///
/// - `hops_path`: The public keys for each hop.
/// - `session_key`: The ephemeral secret key for the onion packet. It must be random generated securely.
/// - `hops_data`: The unencrypted data for each hop.
/// - `assoc_data`: The associated data. It will not be included in the packet. However, it will be
/// covered by the packet HMAC so each hop can verify that the associate data is not tamperred.
pub fn new_onion_packet(
payment_path: Vec<PublicKey>,
hops_path: Vec<PublicKey>,
session_key: SecretKey,
hops_data: Vec<Vec<u8>>,
assoc_data: Option<Vec<u8>>,
) -> Result<OnionPacket, SphinxError> {
if payment_path.len() != hops_data.len() {
if hops_path.len() != hops_data.len() {
return Err(SphinxError::HopsLenMismatch);
}
if payment_path.is_empty() {
if hops_path.is_empty() {
return Err(SphinxError::HopsIsEmpty);
}

let hops_keys = derive_hops_keys(&payment_path, session_key, &Secp256k1::new());
let hops_keys = derive_hops_keys(&hops_path, session_key, &Secp256k1::new());
let pad_key = derive_key(HMAC_KEY_PAD, &session_key.secret_bytes());
let packet_data = generate_padding_data(&pad_key);
let filler = generate_filler(&hops_keys, &hops_data)?;
Expand All @@ -241,7 +265,7 @@ mod tests {
SecretKey::from_slice(&[0x41; 32]).expect("32 bytes, within curve order")
}

fn get_test_payment_path() -> Vec<PublicKey> {
fn get_test_hops_path() -> Vec<PublicKey> {
vec![
Vec::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"),
Vec::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c"),
Expand All @@ -266,9 +290,9 @@ mod tests {

#[test]
fn test_derive_hops_keys() {
let payment_path = get_test_payment_path();
let hops_path = get_test_hops_path();
let session_key = get_test_session_key();
let hops_keys = derive_hops_keys(&payment_path, session_key, &Secp256k1::new());
let hops_keys = derive_hops_keys(&hops_path, session_key, &Secp256k1::new());

assert_eq!(hops_keys.len(), 5);

Expand Down Expand Up @@ -381,9 +405,9 @@ mod tests {

#[test]
fn test_generate_filler() {
let payment_path = get_test_payment_path();
let hops_path = get_test_hops_path();
let session_key = get_test_session_key();
let hops_keys = derive_hops_keys(&payment_path, session_key, &Secp256k1::new());
let hops_keys = derive_hops_keys(&hops_path, session_key, &Secp256k1::new());
let hops_data = get_test_hops_data();

let filler = generate_filler(&hops_keys, &hops_data);
Expand All @@ -394,7 +418,7 @@ mod tests {

#[test]
fn test_new_onion_packet() {
let payment_path = get_test_payment_path();
let hops_path = get_test_hops_path();
let session_key = get_test_session_key();
let hops_data = vec![
Vec::from_hex("1202023a98040205dc06080000000000000001").unwrap(),
Expand All @@ -405,8 +429,7 @@ mod tests {
];
let assoc_data = vec![0x42u8; 32];

let packet =
new_onion_packet(payment_path, session_key, hops_data, Some(assoc_data)).unwrap();
let packet = new_onion_packet(hops_path, session_key, hops_data, Some(assoc_data)).unwrap();
let packet_bytes = packet.into_bytes();
let expected_hex = "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619f7f3416a5aa36dc7eeb3ec6d421e9615471ab870a33ac07fa5d5a51df0a8823aabe3fea3f90d387529d4f72837f9e687230371ccd8d263072206dbed0234f6505e21e282abd8c0e4f5b9ff8042800bbab065036eadd0149b37f27dde664725a49866e052e809d2b0198ab9610faa656bbf4ec516763a59f8f42c171b179166ba38958d4f51b39b3e98706e2d14a2dafd6a5df808093abfca5aeaaca16eded5db7d21fb0294dd1a163edf0fb445d5c8d7d688d6dd9c541762bf5a5123bf9939d957fe648416e88f1b0928bfa034982b22548e1a4d922690eecf546275afb233acf4323974680779f1a964cfe687456035cc0fba8a5428430b390f0057b6d1fe9a8875bfa89693eeb838ce59f09d207a503ee6f6299c92d6361bc335fcbf9b5cd44747aadce2ce6069cfdc3d671daef9f8ae590cf93d957c9e873e9a1bc62d9640dc8fc39c14902d49a1c80239b6c5b7fd91d05878cbf5ffc7db2569f47c43d6c0d27c438abff276e87364deb8858a37e5a62c446af95d8b786eaf0b5fcf78d98b41496794f8dcaac4eef34b2acfb94c7e8c32a9e9866a8fa0b6f2a06f00a1ccde569f97eec05c803ba7500acc96691d8898d73d8e6a47b8f43c3d5de74458d20eda61474c426359677001fbd75a74d7d5db6cb4feb83122f133206203e4e2d293f838bf8c8b3a29acb321315100b87e80e0edb272ee80fda944e3fb6084ed4d7f7c7d21c69d9da43d31a90b70693f9b0cc3eac74c11ab8ff655905688916cfa4ef0bd04135f2e50b7c689a21d04e8e981e74c6058188b9b1f9dfc3eec6838e9ffbcf22ce738d8a177c19318dffef090cee67e12de1a3e2a39f61247547ba5257489cbc11d7d91ed34617fcc42f7a9da2e3cf31a94a210a1018143173913c38f60e62b24bf0d7518f38b5bab3e6a1f8aeb35e31d6442c8abb5178efc892d2e787d79c6ad9e2fc271792983fa9955ac4d1d84a36c024071bc6e431b625519d556af38185601f70e29035ea6a09c8b676c9d88cf7e05e0f17098b584c4168735940263f940033a220f40be4c85344128b14beb9e75696db37014107801a59b13e89cd9d2258c169d523be6d31552c44c82ff4bb18ec9f099f3bf0e5b1bb2ba9a87d7e26f98d294927b600b5529c47e04d98956677cbcee8fa2b60f49776d8b8c367465b7c626da53700684fb6c918ead0eab8360e4f60edd25b4f43816a75ecf70f909301825b512469f8389d79402311d8aecb7b3ef8599e79485a4388d87744d899f7c47ee644361e17040a7958c8911be6f463ab6a9b2afacd688ec55ef517b38f1339efc54487232798bb25522ff4572ff68567fe830f92f7b8113efce3e98c3fffbaedce4fd8b50e41da97c0c08e423a72689cc68e68f752a5e3a9003e64e35c957ca2e1c48bb6f64b05f56b70b575ad2f278d57850a7ad568c24a4d32a3d74b29f03dc125488bc7c637da582357f40b0a52d16b3b40bb2c2315d03360bc24209e20972c200566bcf3bbe5c5b0aedd83132a8a4d5b4242ba370b6d67d9b67eb01052d132c7866b9cb502e44796d9d356e4e3cb47cc527322cd24976fe7c9257a2864151a38e568ef7a79f10d6ef27cc04ce382347a2488b1f404fdbf407fe1ca1c9d0d5649e34800e25e18951c98cae9f43555eef65fee1ea8f15828807366c3b612cd5753bf9fb8fced08855f742cddd6f765f74254f03186683d646e6f09ac2805586c7cf11998357cafc5df3f285329366f475130c928b2dceba4aa383758e7a9d20705c4bb9db619e2992f608a1ba65db254bb389468741d0502e2588aeb54390ac600c19af5c8e61383fc1bebe0029e4474051e4ef908828db9cca13277ef65db3fd47ccc2179126aaefb627719f421e20";
assert_eq!(packet_bytes.len(), expected_hex.len() / 2);
Expand Down

0 comments on commit 73c1b83

Please sign in to comment.