Skip to main content

fiber_sphinx/
lib.rs

1//! A Rust implementation of [Sphinx][] (a.k.a. Onion Message) for [Fiber][].
2//!
3//! [Sphinx]: http://www.cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf
4//! [Fiber]: https://github.com/nervosnetwork/fiber
5//!
6//! See more in the [Specification](https://github.com/cryptape/fiber-sphinx/blob/develop/docs/spec.md).
7//!
8//! ## Example
9//!
10//! ```rust
11//! use secp256k1::{PublicKey, SecretKey, Secp256k1};
12//! use fiber_sphinx::OnionPacket;
13//!
14//! let secp = Secp256k1::new();
15//! let hops_keys = vec![
16//!     SecretKey::from_slice(&[0x20; 32]).expect("32 bytes, within curve order"),
17//!     SecretKey::from_slice(&[0x21; 32]).expect("32 bytes, within curve order"),
18//!     SecretKey::from_slice(&[0x22; 32]).expect("32 bytes, within curve order"),
19//! ];
20//! let hops_path = hops_keys.iter().map(|sk| sk.public_key(&secp)).collect();
21//! let session_key = SecretKey::from_slice(&[0x41; 32]).expect("32 bytes, within curve order");
22//! // Use the first byte to indicate the data len
23//! let hops_data = vec![vec![0], vec![1, 0], vec![5, 0, 1, 2, 3, 4]];
24//! let get_length = |packet_data: &[u8]| Some(packet_data[0] as usize + 1);
25//! let assoc_data = vec![0x42u8; 32];
26//!
27//! let packet = OnionPacket::create(
28//!     session_key,
29//!     hops_path,
30//!     hops_data.clone(),
31//!     Some(assoc_data.clone()),
32//!     1300,
33//!     &secp,
34//! ).expect("new onion packet");
35//!
36//! // Hop 0
37//! # use fiber_sphinx::SphinxError;
38//! # {
39//! #     // error cases
40//! #     let res = packet.clone().peel(&hops_keys[0], None, &secp, get_length);
41//! #     assert_eq!(res, Err(SphinxError::HmacMismatch));
42//! #     let res = packet
43//! #         .clone()
44//! #         .peel(&hops_keys[0], Some(&assoc_data), &secp, |_| None);
45//! #     assert_eq!(res, Err(SphinxError::HopDataLenUnavailable));
46//! # }
47//! let res = packet.peel(&hops_keys[0], Some(&assoc_data), &secp, get_length);
48//! assert!(res.is_ok());
49//! let (data, packet) = res.unwrap();
50//! assert_eq!(data, hops_data[0]);
51//!
52//! // Hop 1
53//! # {
54//! #     // error cases
55//! #     let res = packet.clone().peel(&hops_keys[1], None, &secp, get_length);
56//! #     assert_eq!(res, Err(SphinxError::HmacMismatch));
57//! #     let res = packet
58//! #         .clone()
59//! #         .peel(&hops_keys[1], Some(&assoc_data), &secp, |_| None);
60//! #     assert_eq!(res, Err(SphinxError::HopDataLenUnavailable));
61//! # }
62//! let res = packet.peel(&hops_keys[1], Some(&assoc_data), &secp, get_length);
63//! assert!(res.is_ok());
64//! let (data, packet) = res.unwrap();
65//! assert_eq!(data, hops_data[1]);
66//!
67//! // Hop 2
68//! # {
69//! #     // error cases
70//! #     let res = packet.clone().peel(&hops_keys[2], None, &secp, get_length);
71//! #     assert_eq!(res, Err(SphinxError::HmacMismatch));
72//! #     let res = packet
73//! #         .clone()
74//! #         .peel(&hops_keys[2], Some(&assoc_data), &secp, |_| None);
75//! #     assert_eq!(res, Err(SphinxError::HopDataLenUnavailable));
76//! # }
77//! let res = packet.peel(&hops_keys[2], Some(&assoc_data), &secp, get_length);
78//! assert!(res.is_ok());
79//! let (data, _packet) = res.unwrap();
80//! assert_eq!(data, hops_data[2]);
81//! ```
82use chacha20::{
83    cipher::{KeyIvInit as _, StreamCipher},
84    ChaCha20,
85};
86use hmac::{Hmac, Mac as _};
87use secp256k1::{
88    ecdh::SharedSecret, PublicKey, Scalar, Secp256k1, SecretKey, Signing, Verification,
89};
90use sha2::{Digest as _, Sha256};
91use thiserror::Error;
92
93const HMAC_KEY_RHO: &[u8] = b"rho";
94const HMAC_KEY_MU: &[u8] = b"mu";
95const HMAC_KEY_PAD: &[u8] = b"pad";
96const HMAC_KEY_UM: &[u8] = b"um";
97const HMAC_KEY_AMMAG: &[u8] = b"ammag";
98const CHACHA_NONCE: [u8; 12] = [0u8; 12];
99
100/// Onion packet to send encrypted message via multiple hops.
101#[derive(Debug, Clone, Eq, PartialEq)]
102pub struct OnionPacket {
103    /// Version of the onion packet, currently 0
104    pub version: u8,
105    /// The public key of the next hop. _Alpha_ in the specification.
106    pub public_key: PublicKey,
107    /// Encrypted packet data. _Beta_ in the specification.
108    pub packet_data: Vec<u8>,
109    /// HMAC of the packet data. _Gamma_ in the specification.
110    pub hmac: [u8; 32],
111}
112
113/// Onion error packet to return errors to the origin node.
114///
115/// The nodes must store the shared secrets to forward `OnionPacket` locally and reuse them to obfuscate
116/// the error packet. See the section "Returning Errors" in the specification for details.
117///
118/// ## Example
119///
120/// ```rust
121/// use secp256k1::{PublicKey, SecretKey, Secp256k1};
122/// use std::str::FromStr;
123/// use fiber_sphinx::{OnionErrorPacket, OnionPacket, OnionSharedSecretIter};
124///
125/// let secp = Secp256k1::new();
126/// let hops_path = vec![
127///   PublicKey::from_str("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").expect("valid public key"),
128///   PublicKey::from_str("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").expect("valid public key"),
129///   PublicKey::from_str("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").expect("valid public key"),
130/// ];
131/// let session_key = SecretKey::from_slice(&[0x41; 32]).expect("32 bytes, within curve order");
132/// let hops_ss = OnionSharedSecretIter::new(hops_path.iter(), session_key, &secp).collect::<Vec<_>>();
133///
134/// // The node 0324653...0ab1c generates the error
135/// let shared_secret = hops_ss[1];
136/// let error_packet = OnionErrorPacket::create(&shared_secret, b"error message".to_vec());
137/// ```
138#[derive(Debug, Clone, Eq, PartialEq)]
139pub struct OnionErrorPacket {
140    /// Encrypted error-returning packet data.
141    pub packet_data: Vec<u8>,
142}
143
144impl OnionPacket {
145    /// Creates the new onion packet for the first hop.
146    ///
147    /// - `hops_path`: The public keys for each hop. These are _y_<sub>i</sub> in the specification.
148    /// - `session_key`: The ephemeral secret key for the onion packet. It must be generated securely using a random process.
149    ///     This is _x_ in the specification.
150    /// - `hops_data`: The unencrypted data for each hop. **Attention** that the data for each hop will be concatenated with
151    ///     the remaining encrypted data. To extract the data, the receiver must know the data length. For example, the hops
152    ///     data can include its length at the beginning. These are _m_<sub>i</sub> in the specification.
153    /// - `assoc_data`: The associated data. It will not be included in the packet itself but will be covered by the packet's
154    ///     HMAC. This allows each hop to verify that the associated data has not been tampered with. This is _A_ in the
155    ///     specification.
156    /// - `onion_packet_len`: The length of the onion packet. The packet has the same size for each hop.
157    pub fn create<C: Signing>(
158        session_key: SecretKey,
159        hops_path: Vec<PublicKey>,
160        hops_data: Vec<Vec<u8>>,
161        assoc_data: Option<Vec<u8>>,
162        packet_data_len: usize,
163        secp_ctx: &Secp256k1<C>,
164    ) -> Result<OnionPacket, SphinxError> {
165        if hops_path.len() != hops_data.len() {
166            return Err(SphinxError::HopsLenMismatch);
167        }
168        if hops_path.is_empty() {
169            return Err(SphinxError::HopsIsEmpty);
170        }
171
172        let hops_keys = derive_hops_forward_keys(&hops_path, session_key, secp_ctx);
173        let pad_key = derive_key(HMAC_KEY_PAD, &session_key.secret_bytes());
174        let packet_data = generate_padding_data(packet_data_len, &pad_key);
175        let filler = generate_filler(packet_data_len, &hops_keys, &hops_data)?;
176
177        construct_onion_packet(
178            packet_data,
179            session_key.public_key(secp_ctx),
180            &hops_keys,
181            &hops_data,
182            assoc_data,
183            filler,
184        )
185    }
186
187    /// Converts the onion packet into a byte vector.
188    pub fn into_bytes(self) -> Vec<u8> {
189        let mut bytes = Vec::with_capacity(1 + 33 + self.packet_data.len() + 32);
190        bytes.push(self.version);
191        bytes.extend_from_slice(&self.public_key.serialize());
192        bytes.extend_from_slice(&self.packet_data);
193        bytes.extend_from_slice(&self.hmac);
194        bytes
195    }
196
197    /// Converts back from a byte vector.
198    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, SphinxError> {
199        if bytes.len() < 66 {
200            return Err(SphinxError::PacketDataLenTooSmall);
201        }
202        let version = bytes[0];
203        let public_key =
204            PublicKey::from_slice(&bytes[1..34]).map_err(|_| SphinxError::PublicKeyInvalid)?;
205        let packet_data = (&bytes[34..(bytes.len() - 32)]).into();
206        let mut hmac = [0u8; 32];
207        hmac.copy_from_slice(&bytes[(bytes.len() - 32)..]);
208
209        Ok(Self {
210            version,
211            public_key,
212            packet_data,
213            hmac,
214        })
215    }
216
217    pub fn extract_public_key_from_slice(bytes: &[u8]) -> Result<PublicKey, SphinxError> {
218        if bytes.len() < 66 {
219            return Err(SphinxError::PacketDataLenTooSmall);
220        }
221        PublicKey::from_slice(&bytes[1..34]).map_err(|_| SphinxError::PublicKeyInvalid)
222    }
223
224    /// Derives the shared secret using the node secret key and the ephemeral public key in the onion packet.
225    pub fn shared_secret(&self, secret_key: &SecretKey) -> [u8; 32] {
226        SharedSecret::new(&self.public_key, secret_key).secret_bytes()
227    }
228
229    /// Peels the onion packet at the current hop.
230    ///
231    /// - `secret_key`: the node private key. _x_<sub>i</sub> in the specification.
232    /// - `assoc_data`: The associated data. It was covered by the onion packet's HMAC. _A_ in the specification.
233    /// - `get_hop_data_len`: Tell the hop data len given the decrypted packet data for the current hop.
234    ///
235    /// Returns a tuple (m, p) where m is the hop data for the current hop, and p is remaining onion packet for
236    /// the next hop.
237    pub fn peel<C, F>(
238        self,
239        secret_key: &SecretKey,
240        assoc_data: Option<&[u8]>,
241        secp_ctx: &Secp256k1<C>,
242        get_hop_data_len: F,
243    ) -> Result<(Vec<u8>, Self), SphinxError>
244    where
245        C: Verification,
246        F: FnOnce(&[u8]) -> Option<usize>,
247    {
248        let packet_data_len = self.packet_data.len();
249        let shared_secret = self.shared_secret(secret_key);
250        let rho = derive_key(HMAC_KEY_RHO, shared_secret.as_ref());
251        let mu = derive_key(HMAC_KEY_MU, shared_secret.as_ref());
252
253        let expected_hmac = compute_hmac(&mu, &self.packet_data, assoc_data);
254
255        // TODO: constant time comparison
256        if expected_hmac != self.hmac {
257            return Err(SphinxError::HmacMismatch);
258        }
259
260        let mut chacha = ChaCha20::new(&rho.into(), &CHACHA_NONCE.into());
261        let mut packet_data = self.packet_data;
262        chacha.apply_keystream(&mut packet_data[..]);
263
264        // data | hmac | remaining
265        let data_len = get_hop_data_len(&packet_data).ok_or(SphinxError::HopDataLenUnavailable)?;
266        if data_len > packet_data_len {
267            return Err(SphinxError::HopDataLenTooLarge);
268        }
269        let hop_data = packet_data[0..data_len].to_vec();
270        let mut hmac = [0; 32];
271        hmac.copy_from_slice(&packet_data[data_len..(data_len + 32)]);
272        shift_slice_left(&mut packet_data[..], data_len + 32);
273        // Encrypt 0 bytes until the end
274        chacha.apply_keystream(&mut packet_data[(packet_data_len - data_len - 32)..]);
275
276        let public_key =
277            derive_next_hop_ephemeral_public_key(self.public_key, shared_secret.as_ref(), secp_ctx);
278
279        Ok((
280            hop_data,
281            OnionPacket {
282                version: self.version,
283                public_key,
284                packet_data,
285                hmac,
286            },
287        ))
288    }
289}
290
291impl OnionErrorPacket {
292    /// Creates an onion error packet using the erring node shared secret.
293    ///
294    /// The erring node should store the shared secrets to forward the onion packet locally and reuse them to obfuscate
295    /// the error packet.
296    ///
297    /// The shared secret can be obtained via `OnionPacket::shared_secret`.
298    pub fn create(shared_secret: &[u8; 32], payload: Vec<u8>) -> Self {
299        let ReturnKeys { ammag, um } = ReturnKeys::new(shared_secret);
300        let hmac = compute_hmac(&um, &payload, None);
301        Self::concat(hmac, payload).xor_cipher_stream_with_ammag(ammag)
302    }
303
304    /// Concatenates HMAC and the payload without encryption.
305    pub fn concat(hmac: [u8; 32], mut payload: Vec<u8>) -> Self {
306        let mut packet_data = hmac.to_vec();
307        packet_data.append(&mut payload);
308        OnionErrorPacket { packet_data }
309    }
310
311    fn xor_cipher_stream_with_ammag(self, ammag: [u8; 32]) -> Self {
312        let mut chacha = ChaCha20::new(&ammag.into(), &CHACHA_NONCE.into());
313        let mut packet_data = self.packet_data;
314        chacha.apply_keystream(&mut packet_data[..]);
315
316        Self { packet_data }
317    }
318
319    /// Encrypts or decrypts the packet data with the chacha20 stream.
320    ///
321    /// Apply XOR on the packet data with the keystream generated by the chacha20 stream cipher.
322    pub fn xor_cipher_stream(self, shared_secret: &[u8; 32]) -> Self {
323        let ammag = derive_ammag_key(shared_secret);
324        self.xor_cipher_stream_with_ammag(ammag)
325    }
326
327    /// Decrypts the packet data and parses the error message.
328    ///
329    /// This method is for the origin node to decrypts the packet data node by node and try to parse the message.
330    ///
331    /// - `hops_path`: The public keys for each hop. These are _y_<sub>i</sub> in the specification.
332    /// - `session_key`: The ephemeral secret key for the onion packet. It must be generated securely using a random process.
333    ///     This is _x_ in the specification.
334    /// - `parse_payload`: A function to parse the error payload from the decrypted packet data. It should return `Some(T)` if
335    ///     the given buffer starts with a valid error payload, otherwise `None`.
336    ///
337    /// Returns the parsed error message and the erring node index in `hops_path` if the HMAC is valid and the error message is
338    /// successfully parsed by the function `parse_payload`.
339    pub fn parse<F, T>(
340        self,
341        hops_path: Vec<PublicKey>,
342        session_key: SecretKey,
343        parse_payload: F,
344    ) -> Option<(T, usize)>
345    where
346        F: Fn(&[u8]) -> Option<T>,
347    {
348        // The packet must contain the HMAC so it has to be at least 32 bytes
349        if self.packet_data.len() < 32 {
350            return None;
351        }
352
353        let secp_ctx = Secp256k1::new();
354        let mut packet = self;
355        for (index, shared_secret) in
356            OnionSharedSecretIter::new(hops_path.iter(), session_key, &secp_ctx).enumerate()
357        {
358            let ReturnKeys { ammag, um } = ReturnKeys::new(&shared_secret);
359            packet = packet.xor_cipher_stream_with_ammag(ammag);
360            if let Some(error) = parse_payload(&packet.packet_data[32..]) {
361                let hmac = compute_hmac(&um, &packet.packet_data[32..], None);
362                if hmac == packet.packet_data[..32] {
363                    return Some((error, index));
364                }
365            }
366        }
367
368        None
369    }
370
371    /// Splits into HMAC and payload without decryption.
372    pub fn split(self) -> ([u8; 32], Vec<u8>) {
373        let mut hmac = [0u8; 32];
374        if self.packet_data.len() >= 32 {
375            hmac.copy_from_slice(&self.packet_data[..32]);
376            let payload = self.packet_data[32..].to_vec();
377            (hmac, payload)
378        } else {
379            hmac.copy_from_slice(&self.packet_data[..]);
380            (hmac, Vec::new())
381        }
382    }
383
384    /// Converts the onion packet into a byte vector.
385    pub fn into_bytes(self) -> Vec<u8> {
386        self.packet_data
387    }
388
389    pub fn from_bytes(bytes: Vec<u8>) -> Self {
390        Self { packet_data: bytes }
391    }
392}
393
394#[derive(Error, Debug, Eq, PartialEq)]
395pub enum SphinxError {
396    #[error("The hops path does not match the hops data length")]
397    HopsLenMismatch,
398
399    #[error("The hops path is empty")]
400    HopsIsEmpty,
401
402    #[error("The HMAC does not match the packet data and optional assoc data")]
403    HmacMismatch,
404
405    #[error("Unable to parse the data len for the current hop")]
406    HopDataLenUnavailable,
407
408    #[error("The parsed data len is larger than the onion packet len")]
409    HopDataLenTooLarge,
410
411    #[error("The parsed data len is too small")]
412    PacketDataLenTooSmall,
413
414    #[error("Invalid public key")]
415    PublicKeyInvalid,
416}
417
418/// Keys used to forward the onion packet.
419#[derive(Debug, Clone, Eq, PartialEq)]
420pub struct ForwardKeys {
421    /// Key derived from the shared secret for the hop. It is used to encrypt the packet data.
422    pub rho: [u8; 32],
423    /// Key derived from the shared secret for the hop. It is used to compute the HMAC of the packet data.
424    pub mu: [u8; 32],
425}
426
427impl ForwardKeys {
428    /// Derive keys for forwarding the onion packet from the shared secret.
429    pub fn new(shared_secret: &[u8]) -> ForwardKeys {
430        ForwardKeys {
431            rho: derive_key(HMAC_KEY_RHO, shared_secret),
432            mu: derive_key(HMAC_KEY_MU, shared_secret),
433        }
434    }
435}
436
437/// Keys used to return the error packet.
438#[derive(Debug, Clone, Eq, PartialEq)]
439pub struct ReturnKeys {
440    /// Key derived from the shared secret for the hop. It is used to encrypt the error packet data.
441    pub ammag: [u8; 32],
442    /// Key derived from the shared secret for the hop. It is used to compute the HMAC of the error packet data.
443    pub um: [u8; 32],
444}
445
446impl ReturnKeys {
447    /// Derive keys for returning the error onion packet from the shared secret.
448    pub fn new(shared_secret: &[u8]) -> ReturnKeys {
449        ReturnKeys {
450            ammag: derive_ammag_key(shared_secret),
451            um: derive_key(HMAC_KEY_UM, shared_secret),
452        }
453    }
454}
455
456#[inline]
457pub fn derive_ammag_key(shared_secret: &[u8]) -> [u8; 32] {
458    derive_key(HMAC_KEY_AMMAG, shared_secret)
459}
460
461/// Shared secrets generator.
462///
463/// ## Example
464///
465/// ```rust
466/// use secp256k1::{PublicKey, SecretKey, Secp256k1};
467/// use fiber_sphinx::{OnionSharedSecretIter};
468///
469/// let secp = Secp256k1::new();
470/// let hops_keys = vec![
471///     SecretKey::from_slice(&[0x20; 32]).expect("32 bytes, within curve order"),
472///     SecretKey::from_slice(&[0x21; 32]).expect("32 bytes, within curve order"),
473///     SecretKey::from_slice(&[0x22; 32]).expect("32 bytes, within curve order"),
474/// ];
475/// let hops_path: Vec<_> = hops_keys.iter().map(|sk| sk.public_key(&secp)).collect();
476/// let session_key = SecretKey::from_slice(&[0x41; 32]).expect("32 bytes, within curve order");
477/// // Gets shared secrets for each hop
478/// let hops_ss: Vec<_> = OnionSharedSecretIter::new(hops_path.iter(), session_key, &secp).collect();
479/// ```
480#[derive(Clone)]
481pub struct OnionSharedSecretIter<'s, I, C: Signing> {
482    /// A list of node public keys
483    hops_path_iter: I,
484    ephemeral_secret_key: SecretKey,
485    secp_ctx: &'s Secp256k1<C>,
486}
487
488impl<'s, I, C: Signing> OnionSharedSecretIter<'s, I, C> {
489    /// Creates an iterator to generate shared secrets for each hop.
490    ///
491    /// - `hops_path`: The public keys for each hop. These are _y_<sub>i</sub> in the specification.
492    /// - `session_key`: The ephemeral secret key for the onion packet. It must be generated securely using a random process.
493    ///     This is _x_ in the specification.
494    pub fn new(hops_path_iter: I, session_key: SecretKey, secp_ctx: &'s Secp256k1<C>) -> Self {
495        OnionSharedSecretIter {
496            hops_path_iter,
497            secp_ctx,
498            ephemeral_secret_key: session_key,
499        }
500    }
501}
502
503impl<'s, 'i, I: Iterator<Item = &'i PublicKey>, C: Signing> Iterator
504    for OnionSharedSecretIter<'s, I, C>
505{
506    type Item = [u8; 32];
507
508    fn next(&mut self) -> Option<Self::Item> {
509        self.hops_path_iter.next().map(|pk| {
510            let shared_secret = SharedSecret::new(pk, &self.ephemeral_secret_key);
511
512            let ephemeral_public_key = self.ephemeral_secret_key.public_key(self.secp_ctx);
513            self.ephemeral_secret_key = derive_next_hop_ephemeral_secret_key(
514                self.ephemeral_secret_key,
515                &ephemeral_public_key,
516                shared_secret.as_ref(),
517            );
518
519            shared_secret.secret_bytes()
520        })
521    }
522}
523
524/// Derives keys for forwarding the onion packet.
525fn derive_hops_forward_keys<C: Signing>(
526    hops_path: &[PublicKey],
527    session_key: SecretKey,
528    secp_ctx: &Secp256k1<C>,
529) -> Vec<ForwardKeys> {
530    OnionSharedSecretIter::new(hops_path.iter(), session_key, secp_ctx)
531        .map(|shared_secret| ForwardKeys::new(&shared_secret))
532        .collect()
533}
534
535#[inline]
536fn shift_slice_right(arr: &mut [u8], amt: usize) {
537    for i in (amt..arr.len()).rev() {
538        arr[i] = arr[i - amt];
539    }
540    for item in arr.iter_mut().take(amt) {
541        *item = 0;
542    }
543}
544
545#[inline]
546fn shift_slice_left(arr: &mut [u8], amt: usize) {
547    let pivot = arr.len() - amt;
548    for i in 0..pivot {
549        arr[i] = arr[i + amt];
550    }
551    for item in arr.iter_mut().skip(pivot) {
552        *item = 0;
553    }
554}
555
556/// Computes hmac of packet_data and optional associated data using the key `hmac_key`.
557fn compute_hmac(hmac_key: &[u8; 32], packet_data: &[u8], assoc_data: Option<&[u8]>) -> [u8; 32] {
558    let mut hmac_engine = Hmac::<Sha256>::new_from_slice(hmac_key).expect("valid hmac key");
559    hmac_engine.update(packet_data);
560    if let Some(assoc_data) = assoc_data {
561        hmac_engine.update(assoc_data);
562    }
563    hmac_engine.finalize().into_bytes().into()
564}
565
566/// Forwards the cursor of the stream cipher by `n` bytes.
567fn forward_stream_cipher<S: StreamCipher>(stream: &mut S, n: usize) {
568    for _ in 0..n {
569        let mut dummy = [0; 1];
570        stream.apply_keystream(&mut dummy);
571    }
572}
573
574/// Derives the ephemeral secret key for the next hop.
575///
576/// Assume that the current hop is $n_{i-1}$, and the next hop is $n_i$.
577///
578/// The parameters are:
579///
580/// - `ephemeral_secret_key`: the ephemeral secret key of the current node $n_{i-1}$,
581///     which is x times the blinding factors so far: $x b_0 b_1 \cdots b_{i-2}$
582/// - `ephemeral_public_key`: the corresponding public key of `ephemeral_secret_key`.
583///     This is the _alpha_ in the specification.
584/// - `shared_secret`: the shared secret of the current node $s_{i-1}$
585///
586/// Returns the ephemeral secret key for the mix node $n_i$, which is $x b_0 b_1 \cdots b_{i-1}$.
587fn derive_next_hop_ephemeral_secret_key(
588    ephemeral_secret_key: SecretKey,
589    ephemeral_public_key: &PublicKey,
590    shared_secret: &[u8],
591) -> SecretKey {
592    let blinding_factor: [u8; 32] = {
593        let mut sha = Sha256::new();
594        sha.update(&ephemeral_public_key.serialize()[..]);
595        sha.update(shared_secret);
596        sha.finalize().into()
597    };
598
599    ephemeral_secret_key
600        .mul_tweak(&Scalar::from_be_bytes(blinding_factor).expect("valid scalar"))
601        .expect("valid mul tweak")
602}
603
604/// Derives the ephemeral public key for the next hop.
605///
606/// This is the _alpha_ in the specification.
607fn derive_next_hop_ephemeral_public_key<C: Verification>(
608    ephemeral_public_key: PublicKey,
609    shared_secret: &[u8],
610    secp_ctx: &Secp256k1<C>,
611) -> PublicKey {
612    let blinding_factor: [u8; 32] = {
613        let mut sha = Sha256::new();
614        sha.update(&ephemeral_public_key.serialize()[..]);
615        sha.update(shared_secret.as_ref());
616        sha.finalize().into()
617    };
618
619    ephemeral_public_key
620        .mul_tweak(
621            secp_ctx,
622            &Scalar::from_be_bytes(blinding_factor).expect("valid scalar"),
623        )
624        .expect("valid mul tweak")
625}
626
627/// Derives a key from the shared secret using HMAC.
628fn derive_key(hmac_key: &[u8], shared_secret: &[u8]) -> [u8; 32] {
629    let mut mac = Hmac::<Sha256>::new_from_slice(hmac_key).expect("valid hmac key");
630    mac.update(shared_secret);
631    mac.finalize().into_bytes().into()
632}
633
634/// Generates the initial bytes of onion packet padding data from PRG.
635///
636/// Uses Chacha as the PRG. The key is derived from the session key using HMAC, and the nonce is all zeros.
637fn generate_padding_data(packet_data_len: usize, pad_key: &[u8]) -> Vec<u8> {
638    let mut cipher = ChaCha20::new(pad_key.into(), &CHACHA_NONCE.into());
639    let mut buffer = vec![0u8; packet_data_len];
640    cipher.apply_keystream(&mut buffer);
641    buffer
642}
643
644/// Generates the filler to obfuscate the onion packet.
645fn generate_filler(
646    packet_data_len: usize,
647    hops_keys: &[ForwardKeys],
648    hops_data: &[Vec<u8>],
649) -> Result<Vec<u8>, SphinxError> {
650    let mut filler = Vec::new();
651    let mut pos = 0;
652
653    for (i, (data, keys)) in hops_data.iter().zip(hops_keys.iter()).enumerate() {
654        let mut chacha = ChaCha20::new(&keys.rho.into(), &[0u8; 12].into());
655        forward_stream_cipher(&mut chacha, packet_data_len - pos);
656
657        // 32 for mac
658        pos += data.len() + 32;
659        if pos > packet_data_len {
660            return Err(SphinxError::HopDataLenTooLarge);
661        }
662
663        if i == hops_data.len() - 1 {
664            break;
665        }
666
667        filler.resize(pos, 0u8);
668        chacha.apply_keystream(&mut filler);
669    }
670
671    Ok(filler)
672}
673
674/// Constructs the onion packet internally.
675///
676/// - `packet_data`: The initial 1300 bytes of the onion packet generated by `generate_padding_data`.
677/// - `public_key`: The ephemeral public key for the first hop.
678/// - `hops_keys`: The keys for each hop generated by `derive_hops_forward_keys`.
679/// - `hops_data`: The unencrypted data for each hop.
680/// - `assoc_data`: The associated data. It will not be included in the packet itself but will be covered by the packet's
681///     HMAC. This allows each hop to verify that the associated data has not been tampered with.
682/// - `filler`: The filler to obfuscate the packet data, which is generated by `generate_filler`.
683fn construct_onion_packet(
684    mut packet_data: Vec<u8>,
685    public_key: PublicKey,
686    hops_keys: &[ForwardKeys],
687    hops_data: &[Vec<u8>],
688    assoc_data: Option<Vec<u8>>,
689    filler: Vec<u8>,
690) -> Result<OnionPacket, SphinxError> {
691    let mut hmac = [0; 32];
692
693    for (i, (data, keys)) in hops_data.iter().zip(hops_keys.iter()).rev().enumerate() {
694        let data_len = data.len();
695        shift_slice_right(&mut packet_data, data_len + 32);
696        packet_data[0..data_len].copy_from_slice(data);
697        packet_data[data_len..(data_len + 32)].copy_from_slice(&hmac);
698
699        let mut chacha = ChaCha20::new(&keys.rho.into(), &[0u8; 12].into());
700        chacha.apply_keystream(&mut packet_data);
701
702        if i == 0 {
703            let stop_index = packet_data.len();
704            let start_index = stop_index
705                .checked_sub(filler.len())
706                .ok_or(SphinxError::HopDataLenTooLarge)?;
707            packet_data[start_index..stop_index].copy_from_slice(&filler[..]);
708        }
709
710        hmac = compute_hmac(&keys.mu, &packet_data, assoc_data.as_deref());
711    }
712
713    Ok(OnionPacket {
714        version: 0,
715        public_key,
716        packet_data,
717        hmac,
718    })
719}
720
721#[cfg(test)]
722mod tests;