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/nervosnetwork/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    constants, 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];
99const PACKET_VERSION_LEN: usize = 1;
100const PACKET_PUBLIC_KEY_LEN: usize = 33;
101const PACKET_HMAC_LEN: usize = 32;
102const MIN_ONION_PACKET_LEN: usize = PACKET_VERSION_LEN + PACKET_PUBLIC_KEY_LEN + PACKET_HMAC_LEN;
103
104/// Onion packet to send encrypted message via multiple hops.
105#[derive(Debug, Clone, Eq, PartialEq)]
106pub struct OnionPacket {
107    /// Version of the onion packet, currently 0
108    pub version: u8,
109    /// The public key of the next hop. _Alpha_ in the specification.
110    pub public_key: PublicKey,
111    /// Encrypted packet data. _Beta_ in the specification.
112    pub packet_data: Vec<u8>,
113    /// HMAC of the packet data. _Gamma_ in the specification.
114    pub hmac: [u8; PACKET_HMAC_LEN],
115}
116
117/// Onion error packet to return errors to the origin node.
118///
119/// The nodes must store the shared secrets to forward `OnionPacket` locally and reuse them to obfuscate
120/// the error packet. See the section "Returning Errors" in the specification for details.
121///
122/// ## Example
123///
124/// ```rust
125/// use secp256k1::{PublicKey, SecretKey, Secp256k1};
126/// use std::str::FromStr;
127/// use fiber_sphinx::{OnionErrorPacket, OnionPacket, OnionSharedSecretIter};
128///
129/// let secp = Secp256k1::new();
130/// let hops_path = vec![
131///   PublicKey::from_str("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").expect("valid public key"),
132///   PublicKey::from_str("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").expect("valid public key"),
133///   PublicKey::from_str("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").expect("valid public key"),
134/// ];
135/// let session_key = SecretKey::from_slice(&[0x41; 32]).expect("32 bytes, within curve order");
136/// let hops_ss = OnionSharedSecretIter::new(hops_path.iter(), session_key, &secp)
137///     .collect::<Result<Vec<_>, _>>()
138///     .expect("shared secrets");
139///
140/// // The node 0324653...0ab1c generates the error
141/// let shared_secret = hops_ss[1];
142/// let error_packet = OnionErrorPacket::create(&shared_secret, b"error message".to_vec());
143/// ```
144#[derive(Debug, Clone, Eq, PartialEq)]
145pub struct OnionErrorPacket {
146    /// Encrypted error-returning packet data.
147    pub packet_data: Vec<u8>,
148}
149
150impl OnionPacket {
151    /// Creates the new onion packet for the first hop.
152    ///
153    /// - `hops_path`: The public keys for each hop. These are _y_<sub>i</sub> in the specification.
154    /// - `session_key`: The ephemeral secret key for the onion packet. It must be generated securely using a random process.
155    ///   This is _x_ in the specification.
156    /// - `hops_data`: The unencrypted data for each hop. **Attention** that the data for each hop will be concatenated with
157    ///   the remaining encrypted data. To extract the data, the receiver must know the data length. For example, the hops
158    ///   data can include its length at the beginning. These are _m_<sub>i</sub> in the specification.
159    /// - `assoc_data`: The associated data. It will not be included in the packet itself but will be covered by the packet's
160    ///   HMAC. This allows each hop to verify that the associated data has not been tampered with. This is _A_ in the
161    ///   specification.
162    /// - `onion_packet_len`: The length of the onion packet. The packet has the same size for each hop.
163    ///
164    /// If this returns [`SphinxError::InvalidBlindingFactor`], retry with a different session key.
165    pub fn create<C: Signing>(
166        session_key: SecretKey,
167        hops_path: Vec<PublicKey>,
168        hops_data: Vec<Vec<u8>>,
169        assoc_data: Option<Vec<u8>>,
170        packet_data_len: usize,
171        secp_ctx: &Secp256k1<C>,
172    ) -> Result<OnionPacket, SphinxError> {
173        if hops_path.len() != hops_data.len() {
174            return Err(SphinxError::HopsLenMismatch);
175        }
176        if hops_path.is_empty() {
177            return Err(SphinxError::HopsIsEmpty);
178        }
179
180        let hops_keys = derive_hops_forward_keys(&hops_path, session_key, secp_ctx)?;
181        let pad_key = derive_key(HMAC_KEY_PAD, &session_key.secret_bytes());
182        let packet_data = generate_padding_data(packet_data_len, &pad_key);
183        let filler = generate_filler(packet_data_len, &hops_keys, &hops_data)?;
184
185        construct_onion_packet(
186            packet_data,
187            session_key.public_key(secp_ctx),
188            &hops_keys,
189            &hops_data,
190            assoc_data,
191            filler,
192        )
193    }
194
195    /// Converts the onion packet into a byte vector.
196    pub fn into_bytes(self) -> Vec<u8> {
197        let mut bytes = Vec::with_capacity(MIN_ONION_PACKET_LEN + self.packet_data.len());
198        bytes.push(self.version);
199        bytes.extend_from_slice(&self.public_key.serialize());
200        bytes.extend_from_slice(&self.packet_data);
201        bytes.extend_from_slice(&self.hmac);
202        bytes
203    }
204
205    /// Converts back from a byte vector with the expected packet data length.
206    ///
207    /// An onion packet has the layout `version || public_key || packet_data || hmac`.
208    /// This function verifies that `bytes` is exactly
209    /// `PACKET_VERSION_LEN + PACKET_PUBLIC_KEY_LEN + packet_data_len + PACKET_HMAC_LEN`
210    /// bytes before copying the HMAC and packet data.
211    pub fn from_bytes_with_packet_data_len(
212        bytes: Vec<u8>,
213        packet_data_len: usize,
214    ) -> Result<Self, SphinxError> {
215        let expected_len = MIN_ONION_PACKET_LEN
216            .checked_add(packet_data_len)
217            .ok_or(SphinxError::PacketDataLenMismatch)?;
218        if bytes.len() != expected_len {
219            return Err(SphinxError::PacketDataLenMismatch);
220        }
221
222        let version = bytes[0];
223        let public_key = PublicKey::from_slice(
224            &bytes[PACKET_VERSION_LEN..PACKET_VERSION_LEN + PACKET_PUBLIC_KEY_LEN],
225        )
226        .map_err(|_| SphinxError::PublicKeyInvalid)?;
227        let packet_data_start = PACKET_VERSION_LEN + PACKET_PUBLIC_KEY_LEN;
228        let packet_data_end = packet_data_start + packet_data_len;
229        let packet_data = bytes[packet_data_start..packet_data_end].to_vec();
230        let mut hmac = [0u8; PACKET_HMAC_LEN];
231        hmac.copy_from_slice(&bytes[packet_data_end..]);
232
233        Ok(Self {
234            version,
235            public_key,
236            packet_data,
237            hmac,
238        })
239    }
240
241    /// Converts back from a byte vector.
242    ///
243    /// Deprecated because this function accepts any packet data length that can be
244    /// inferred from `bytes`. Use [`OnionPacket::from_bytes_with_packet_data_len`]
245    /// when the expected packet data length is known.
246    #[deprecated(
247        since = "2.4.0",
248        note = "use OnionPacket::from_bytes_with_packet_data_len to verify the packet data length"
249    )]
250    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, SphinxError> {
251        if bytes.len() < MIN_ONION_PACKET_LEN {
252            return Err(SphinxError::PacketDataLenTooSmall);
253        }
254        let packet_data_len = bytes.len() - MIN_ONION_PACKET_LEN;
255        Self::from_bytes_with_packet_data_len(bytes, packet_data_len)
256    }
257
258    pub fn extract_public_key_from_slice(bytes: &[u8]) -> Result<PublicKey, SphinxError> {
259        if bytes.len() < MIN_ONION_PACKET_LEN {
260            return Err(SphinxError::PacketDataLenTooSmall);
261        }
262        PublicKey::from_slice(
263            &bytes[PACKET_VERSION_LEN..PACKET_VERSION_LEN + PACKET_PUBLIC_KEY_LEN],
264        )
265        .map_err(|_| SphinxError::PublicKeyInvalid)
266    }
267
268    /// Derives the shared secret using the node secret key and the ephemeral public key in the onion packet.
269    pub fn shared_secret(&self, secret_key: &SecretKey) -> [u8; 32] {
270        SharedSecret::new(&self.public_key, secret_key).secret_bytes()
271    }
272
273    /// Peels the onion packet at the current hop.
274    ///
275    /// - `secret_key`: the node private key. _x_<sub>i</sub> in the specification.
276    /// - `assoc_data`: The associated data. It was covered by the onion packet's HMAC. _A_ in the specification.
277    /// - `get_hop_data_len`: Tell the hop data len given the decrypted packet data for the current hop.
278    ///
279    /// Returns a tuple (m, p) where m is the hop data for the current hop, and p is remaining onion packet for
280    /// the next hop.
281    pub fn peel<C, F>(
282        self,
283        secret_key: &SecretKey,
284        assoc_data: Option<&[u8]>,
285        secp_ctx: &Secp256k1<C>,
286        get_hop_data_len: F,
287    ) -> Result<(Vec<u8>, Self), SphinxError>
288    where
289        C: Verification,
290        F: FnOnce(&[u8]) -> Option<usize>,
291    {
292        let packet_data_len = self.packet_data.len();
293        let shared_secret = self.shared_secret(secret_key);
294        let rho = derive_key(HMAC_KEY_RHO, shared_secret.as_ref());
295        let mu = derive_key(HMAC_KEY_MU, shared_secret.as_ref());
296
297        if !verify_hmac(&mu, &self.packet_data, assoc_data, &self.hmac) {
298            return Err(SphinxError::HmacMismatch);
299        }
300
301        let mut chacha = ChaCha20::new(&rho.into(), &CHACHA_NONCE.into());
302        let mut packet_data = self.packet_data;
303        chacha.apply_keystream(&mut packet_data[..]);
304
305        // data | hmac | remaining
306        let data_len = get_hop_data_len(&packet_data).ok_or(SphinxError::HopDataLenUnavailable)?;
307        let hmac_end = data_len
308            .checked_add(PACKET_HMAC_LEN)
309            .ok_or(SphinxError::HopDataLenTooLarge)?;
310        if hmac_end > packet_data_len {
311            return Err(SphinxError::HopDataLenTooLarge);
312        }
313        let hop_data = packet_data[0..data_len].to_vec();
314        let mut hmac = [0; PACKET_HMAC_LEN];
315        hmac.copy_from_slice(&packet_data[data_len..hmac_end]);
316        shift_slice_left(&mut packet_data[..], hmac_end);
317        // Encrypt 0 bytes until the end
318        chacha.apply_keystream(&mut packet_data[(packet_data_len - hmac_end)..]);
319
320        let public_key = derive_next_hop_ephemeral_public_key(
321            self.public_key,
322            shared_secret.as_ref(),
323            secp_ctx,
324        )?;
325
326        Ok((
327            hop_data,
328            OnionPacket {
329                version: self.version,
330                public_key,
331                packet_data,
332                hmac,
333            },
334        ))
335    }
336}
337
338impl OnionErrorPacket {
339    /// Creates an onion error packet using the erring node shared secret.
340    ///
341    /// The erring node should store the shared secrets to forward the onion packet locally and reuse them to obfuscate
342    /// the error packet.
343    ///
344    /// The shared secret can be obtained via `OnionPacket::shared_secret`.
345    pub fn create(shared_secret: &[u8; 32], payload: Vec<u8>) -> Self {
346        let ReturnKeys { ammag, um } = ReturnKeys::new(shared_secret);
347        let hmac = compute_hmac(&um, &payload, None);
348        Self::concat(hmac, payload).xor_cipher_stream_with_ammag(ammag)
349    }
350
351    /// Concatenates HMAC and the payload without encryption.
352    pub fn concat(hmac: [u8; PACKET_HMAC_LEN], mut payload: Vec<u8>) -> Self {
353        let mut packet_data = hmac.to_vec();
354        packet_data.append(&mut payload);
355        OnionErrorPacket { packet_data }
356    }
357
358    fn xor_cipher_stream_with_ammag(self, ammag: [u8; 32]) -> Self {
359        let mut chacha = ChaCha20::new(&ammag.into(), &CHACHA_NONCE.into());
360        let mut packet_data = self.packet_data;
361        chacha.apply_keystream(&mut packet_data[..]);
362
363        Self { packet_data }
364    }
365
366    /// Encrypts or decrypts the packet data with the chacha20 stream.
367    ///
368    /// Apply XOR on the packet data with the keystream generated by the chacha20 stream cipher.
369    pub fn xor_cipher_stream(self, shared_secret: &[u8; 32]) -> Self {
370        let ammag = derive_ammag_key(shared_secret);
371        self.xor_cipher_stream_with_ammag(ammag)
372    }
373
374    /// Decrypts the packet data and parses the error message.
375    ///
376    /// This method is for the origin node to decrypts the packet data node by node and try to parse the message.
377    ///
378    /// - `hops_path`: The public keys for each hop. These are _y_<sub>i</sub> in the specification.
379    /// - `session_key`: The ephemeral secret key for the onion packet. It must be generated securely using a random process.
380    ///   This is _x_ in the specification.
381    /// - `parse_payload`: A function to parse the error payload from the decrypted packet data. It should return `Some(T)` if
382    ///   the given buffer starts with a valid error payload, otherwise `None`.
383    ///
384    /// Returns the parsed error message and the erring node index in `hops_path` if the HMAC is valid and the error message is
385    /// successfully parsed by the function `parse_payload`.
386    pub fn parse<F, T>(
387        self,
388        hops_path: Vec<PublicKey>,
389        session_key: SecretKey,
390        parse_payload: F,
391    ) -> Option<(T, usize)>
392    where
393        F: Fn(&[u8]) -> Option<T>,
394    {
395        if self.packet_data.len() < PACKET_HMAC_LEN {
396            return None;
397        }
398
399        let secp_ctx = Secp256k1::new();
400        let mut packet = self;
401        for (index, shared_secret) in
402            OnionSharedSecretIter::new(hops_path.iter(), session_key, &secp_ctx).enumerate()
403        {
404            let shared_secret = shared_secret.ok()?;
405            let ReturnKeys { ammag, um } = ReturnKeys::new(&shared_secret);
406            packet = packet.xor_cipher_stream_with_ammag(ammag);
407            let payload = &packet.packet_data[PACKET_HMAC_LEN..];
408            if verify_hmac(&um, payload, None, &packet.packet_data[..PACKET_HMAC_LEN]) {
409                if let Some(error) = parse_payload(payload) {
410                    return Some((error, index));
411                }
412            }
413        }
414
415        None
416    }
417
418    /// Splits into HMAC and payload without decryption.
419    pub fn split(self) -> ([u8; PACKET_HMAC_LEN], Vec<u8>) {
420        let mut hmac = [0u8; PACKET_HMAC_LEN];
421        if self.packet_data.len() >= PACKET_HMAC_LEN {
422            hmac.copy_from_slice(&self.packet_data[..PACKET_HMAC_LEN]);
423            let payload = self.packet_data[PACKET_HMAC_LEN..].to_vec();
424            (hmac, payload)
425        } else {
426            hmac.copy_from_slice(&self.packet_data[..]);
427            (hmac, Vec::new())
428        }
429    }
430
431    /// Converts the onion packet into a byte vector.
432    pub fn into_bytes(self) -> Vec<u8> {
433        self.packet_data
434    }
435
436    pub fn from_bytes(bytes: Vec<u8>) -> Self {
437        Self { packet_data: bytes }
438    }
439}
440
441#[derive(Error, Debug, Eq, PartialEq)]
442pub enum SphinxError {
443    #[error("The hops path does not match the hops data length")]
444    HopsLenMismatch,
445
446    #[error("The hops path is empty")]
447    HopsIsEmpty,
448
449    #[error("The HMAC does not match the packet data and optional assoc data")]
450    HmacMismatch,
451
452    #[error("Unable to parse the data len for the current hop")]
453    HopDataLenUnavailable,
454
455    #[error("The parsed data len is larger than the onion packet len")]
456    HopDataLenTooLarge,
457
458    #[error("The parsed data len is too small")]
459    PacketDataLenTooSmall,
460
461    #[error("The packet data length does not match the bytes length")]
462    PacketDataLenMismatch,
463
464    #[error("Invalid public key")]
465    PublicKeyInvalid,
466
467    #[error("Invalid blinding factor")]
468    InvalidBlindingFactor,
469}
470
471/// Keys used to forward the onion packet.
472#[derive(Debug, Clone, Eq, PartialEq)]
473pub struct ForwardKeys {
474    /// Key derived from the shared secret for the hop. It is used to encrypt the packet data.
475    pub rho: [u8; 32],
476    /// Key derived from the shared secret for the hop. It is used to compute the HMAC of the packet data.
477    pub mu: [u8; 32],
478}
479
480impl ForwardKeys {
481    /// Derive keys for forwarding the onion packet from the shared secret.
482    pub fn new(shared_secret: &[u8]) -> ForwardKeys {
483        ForwardKeys {
484            rho: derive_key(HMAC_KEY_RHO, shared_secret),
485            mu: derive_key(HMAC_KEY_MU, shared_secret),
486        }
487    }
488}
489
490/// Keys used to return the error packet.
491#[derive(Debug, Clone, Eq, PartialEq)]
492pub struct ReturnKeys {
493    /// Key derived from the shared secret for the hop. It is used to encrypt the error packet data.
494    pub ammag: [u8; 32],
495    /// Key derived from the shared secret for the hop. It is used to compute the HMAC of the error packet data.
496    pub um: [u8; 32],
497}
498
499impl ReturnKeys {
500    /// Derive keys for returning the error onion packet from the shared secret.
501    pub fn new(shared_secret: &[u8]) -> ReturnKeys {
502        ReturnKeys {
503            ammag: derive_ammag_key(shared_secret),
504            um: derive_key(HMAC_KEY_UM, shared_secret),
505        }
506    }
507}
508
509#[inline]
510pub fn derive_ammag_key(shared_secret: &[u8]) -> [u8; 32] {
511    derive_key(HMAC_KEY_AMMAG, shared_secret)
512}
513
514/// Shared secrets generator.
515///
516/// ## Example
517///
518/// ```rust
519/// use secp256k1::{PublicKey, SecretKey, Secp256k1};
520/// use fiber_sphinx::{OnionSharedSecretIter};
521///
522/// let secp = Secp256k1::new();
523/// let hops_keys = vec![
524///     SecretKey::from_slice(&[0x20; 32]).expect("32 bytes, within curve order"),
525///     SecretKey::from_slice(&[0x21; 32]).expect("32 bytes, within curve order"),
526///     SecretKey::from_slice(&[0x22; 32]).expect("32 bytes, within curve order"),
527/// ];
528/// let hops_path: Vec<_> = hops_keys.iter().map(|sk| sk.public_key(&secp)).collect();
529/// let session_key = SecretKey::from_slice(&[0x41; 32]).expect("32 bytes, within curve order");
530/// // Gets shared secrets for each hop
531/// let hops_ss: Vec<_> = OnionSharedSecretIter::new(hops_path.iter(), session_key, &secp)
532///     .collect::<Result<Vec<_>, _>>()
533///     .expect("shared secrets");
534/// ```
535#[derive(Clone)]
536pub struct OnionSharedSecretIter<'s, I, C: Signing> {
537    /// A list of node public keys
538    hops_path_iter: I,
539    ephemeral_secret_key: SecretKey,
540    secp_ctx: &'s Secp256k1<C>,
541}
542
543impl<'s, I, C: Signing> OnionSharedSecretIter<'s, I, C> {
544    /// Creates an iterator to generate shared secrets for each hop.
545    ///
546    /// - `hops_path`: The public keys for each hop. These are _y_<sub>i</sub> in the specification.
547    /// - `session_key`: The ephemeral secret key for the onion packet. It must be generated securely using a random process.
548    ///   This is _x_ in the specification.
549    pub fn new(hops_path_iter: I, session_key: SecretKey, secp_ctx: &'s Secp256k1<C>) -> Self {
550        OnionSharedSecretIter {
551            hops_path_iter,
552            secp_ctx,
553            ephemeral_secret_key: session_key,
554        }
555    }
556}
557
558impl<'s, 'i, I: Iterator<Item = &'i PublicKey>, C: Signing> Iterator
559    for OnionSharedSecretIter<'s, I, C>
560{
561    type Item = Result<[u8; 32], SphinxError>;
562
563    fn next(&mut self) -> Option<Self::Item> {
564        self.hops_path_iter.next().map(|pk| {
565            let shared_secret = SharedSecret::new(pk, &self.ephemeral_secret_key);
566
567            let ephemeral_public_key = self.ephemeral_secret_key.public_key(self.secp_ctx);
568            self.ephemeral_secret_key = derive_next_hop_ephemeral_secret_key(
569                self.ephemeral_secret_key,
570                &ephemeral_public_key,
571                shared_secret.as_ref(),
572            )?;
573
574            Ok(shared_secret.secret_bytes())
575        })
576    }
577}
578
579/// Derives keys for forwarding the onion packet.
580fn derive_hops_forward_keys<C: Signing>(
581    hops_path: &[PublicKey],
582    session_key: SecretKey,
583    secp_ctx: &Secp256k1<C>,
584) -> Result<Vec<ForwardKeys>, SphinxError> {
585    OnionSharedSecretIter::new(hops_path.iter(), session_key, secp_ctx)
586        .map(|shared_secret| shared_secret.map(|shared_secret| ForwardKeys::new(&shared_secret)))
587        .collect()
588}
589
590#[inline]
591fn shift_slice_right(arr: &mut [u8], amt: usize) {
592    for i in (amt..arr.len()).rev() {
593        arr[i] = arr[i - amt];
594    }
595    for item in arr.iter_mut().take(amt) {
596        *item = 0;
597    }
598}
599
600#[inline]
601fn shift_slice_left(arr: &mut [u8], amt: usize) {
602    let pivot = arr.len() - amt;
603    for i in 0..pivot {
604        arr[i] = arr[i + amt];
605    }
606    for item in arr.iter_mut().skip(pivot) {
607        *item = 0;
608    }
609}
610
611/// Computes hmac of packet_data and optional associated data using the key `hmac_key`.
612fn compute_hmac(
613    hmac_key: &[u8; 32],
614    packet_data: &[u8],
615    assoc_data: Option<&[u8]>,
616) -> [u8; PACKET_HMAC_LEN] {
617    let mut hmac_engine = Hmac::<Sha256>::new_from_slice(hmac_key).expect("valid hmac key");
618    hmac_engine.update(packet_data);
619    if let Some(assoc_data) = assoc_data {
620        hmac_engine.update(assoc_data);
621    }
622    hmac_engine.finalize().into_bytes().into()
623}
624
625fn verify_hmac(
626    hmac_key: &[u8; 32],
627    packet_data: &[u8],
628    assoc_data: Option<&[u8]>,
629    expected_hmac: &[u8],
630) -> bool {
631    let mut hmac_engine = Hmac::<Sha256>::new_from_slice(hmac_key).expect("valid hmac key");
632    hmac_engine.update(packet_data);
633    if let Some(assoc_data) = assoc_data {
634        hmac_engine.update(assoc_data);
635    }
636    hmac_engine.verify_slice(expected_hmac).is_ok()
637}
638
639/// Forwards the cursor of the stream cipher by `n` bytes.
640fn forward_stream_cipher<S: StreamCipher>(stream: &mut S, n: usize) {
641    for _ in 0..n {
642        let mut dummy = [0; 1];
643        stream.apply_keystream(&mut dummy);
644    }
645}
646
647/// Derives the ephemeral secret key for the next hop.
648///
649/// Assume that the current hop is $n_{i-1}$, and the next hop is $n_i$.
650///
651/// The parameters are:
652///
653/// - `ephemeral_secret_key`: the ephemeral secret key of the current node $n_{i-1}$,
654///   which is x times the blinding factors so far: $x b_0 b_1 \cdots b_{i-2}$
655/// - `ephemeral_public_key`: the corresponding public key of `ephemeral_secret_key`.
656///   This is the _alpha_ in the specification.
657/// - `shared_secret`: the shared secret of the current node $s_{i-1}$
658///
659/// Returns the ephemeral secret key for the mix node $n_i$, which is $x b_0 b_1 \cdots b_{i-1}$.
660///
661/// Returns [`SphinxError::InvalidBlindingFactor`] when the SHA256 output reduced modulo the
662/// secp256k1 curve order is zero. In that case, `mul_tweak` would produce an invalid zero
663/// secret key.
664fn derive_next_hop_ephemeral_secret_key(
665    ephemeral_secret_key: SecretKey,
666    ephemeral_public_key: &PublicKey,
667    shared_secret: &[u8],
668) -> Result<SecretKey, SphinxError> {
669    let blinding_factor = {
670        let mut sha = Sha256::new();
671        sha.update(&ephemeral_public_key.serialize()[..]);
672        sha.update(shared_secret);
673        scalar_from_blinding_factor(sha.finalize().into())?
674    };
675
676    ephemeral_secret_key
677        .mul_tweak(&blinding_factor)
678        .map_err(|_| SphinxError::InvalidBlindingFactor)
679}
680
681/// Derives the ephemeral public key for the next hop.
682///
683/// This is the _alpha_ in the specification.
684///
685/// Returns [`SphinxError::InvalidBlindingFactor`] when the SHA256 output reduced modulo the
686/// secp256k1 curve order is zero. In that case, `mul_tweak` would produce the invalid point at
687/// infinity.
688fn derive_next_hop_ephemeral_public_key<C: Verification>(
689    ephemeral_public_key: PublicKey,
690    shared_secret: &[u8],
691    secp_ctx: &Secp256k1<C>,
692) -> Result<PublicKey, SphinxError> {
693    let blinding_factor = {
694        let mut sha = Sha256::new();
695        sha.update(&ephemeral_public_key.serialize()[..]);
696        sha.update(shared_secret.as_ref());
697        scalar_from_blinding_factor(sha.finalize().into())?
698    };
699
700    ephemeral_public_key
701        .mul_tweak(secp_ctx, &blinding_factor)
702        .map_err(|_| SphinxError::InvalidBlindingFactor)
703}
704
705fn scalar_from_blinding_factor(mut blinding_factor: [u8; 32]) -> Result<Scalar, SphinxError> {
706    if blinding_factor >= constants::CURVE_ORDER {
707        subtract_secp256k1_order(&mut blinding_factor);
708    }
709
710    if blinding_factor == constants::ZERO {
711        return Err(SphinxError::InvalidBlindingFactor);
712    }
713
714    Scalar::from_be_bytes(blinding_factor).map_err(|_| SphinxError::InvalidBlindingFactor)
715}
716
717// The secp256k1 crate exposes the curve order but not a modulo-reducing Scalar
718// constructor. A SHA256 output is less than 2^256, so one subtraction is enough.
719// A big-integer dependency would add audit surface for this fixed-size operation
720// without simplifying it.
721fn subtract_secp256k1_order(value: &mut [u8; 32]) {
722    let mut borrow = 0u16;
723
724    for (byte, order_byte) in value.iter_mut().zip(constants::CURVE_ORDER.iter()).rev() {
725        let subtrahend = *order_byte as u16 + borrow;
726        let minuend = *byte as u16;
727
728        if minuend >= subtrahend {
729            *byte = (minuend - subtrahend) as u8;
730            borrow = 0;
731        } else {
732            *byte = (minuend + 256 - subtrahend) as u8;
733            borrow = 1;
734        }
735    }
736}
737
738/// Derives a key from the shared secret using HMAC.
739fn derive_key(hmac_key: &[u8], shared_secret: &[u8]) -> [u8; 32] {
740    let mut mac = Hmac::<Sha256>::new_from_slice(hmac_key).expect("valid hmac key");
741    mac.update(shared_secret);
742    mac.finalize().into_bytes().into()
743}
744
745/// Generates the initial bytes of onion packet padding data from PRG.
746///
747/// Uses Chacha as the PRG. The key is derived from the session key using HMAC, and the nonce is all zeros.
748fn generate_padding_data(packet_data_len: usize, pad_key: &[u8]) -> Vec<u8> {
749    let mut cipher = ChaCha20::new(pad_key.into(), &CHACHA_NONCE.into());
750    let mut buffer = vec![0u8; packet_data_len];
751    cipher.apply_keystream(&mut buffer);
752    buffer
753}
754
755/// Generates the filler to obfuscate the onion packet.
756fn generate_filler(
757    packet_data_len: usize,
758    hops_keys: &[ForwardKeys],
759    hops_data: &[Vec<u8>],
760) -> Result<Vec<u8>, SphinxError> {
761    let mut filler = Vec::new();
762    let mut pos = 0;
763
764    for (i, (data, keys)) in hops_data.iter().zip(hops_keys.iter()).enumerate() {
765        let mut chacha = ChaCha20::new(&keys.rho.into(), &[0u8; 12].into());
766        forward_stream_cipher(&mut chacha, packet_data_len - pos);
767
768        pos += data.len() + PACKET_HMAC_LEN;
769        if pos > packet_data_len {
770            return Err(SphinxError::HopDataLenTooLarge);
771        }
772
773        if i == hops_data.len() - 1 {
774            break;
775        }
776
777        filler.resize(pos, 0u8);
778        chacha.apply_keystream(&mut filler);
779    }
780
781    Ok(filler)
782}
783
784/// Constructs the onion packet internally.
785///
786/// - `packet_data`: The initial 1300 bytes of the onion packet generated by `generate_padding_data`.
787/// - `public_key`: The ephemeral public key for the first hop.
788/// - `hops_keys`: The keys for each hop generated by `derive_hops_forward_keys`.
789/// - `hops_data`: The unencrypted data for each hop.
790/// - `assoc_data`: The associated data. It will not be included in the packet itself but will be covered by the packet's
791///   HMAC. This allows each hop to verify that the associated data has not been tampered with.
792/// - `filler`: The filler to obfuscate the packet data, which is generated by `generate_filler`.
793fn construct_onion_packet(
794    mut packet_data: Vec<u8>,
795    public_key: PublicKey,
796    hops_keys: &[ForwardKeys],
797    hops_data: &[Vec<u8>],
798    assoc_data: Option<Vec<u8>>,
799    filler: Vec<u8>,
800) -> Result<OnionPacket, SphinxError> {
801    let mut hmac = [0; PACKET_HMAC_LEN];
802
803    for (i, (data, keys)) in hops_data.iter().zip(hops_keys.iter()).rev().enumerate() {
804        let data_len = data.len();
805        shift_slice_right(&mut packet_data, data_len + PACKET_HMAC_LEN);
806        packet_data[0..data_len].copy_from_slice(data);
807        packet_data[data_len..(data_len + PACKET_HMAC_LEN)].copy_from_slice(&hmac);
808
809        let mut chacha = ChaCha20::new(&keys.rho.into(), &[0u8; 12].into());
810        chacha.apply_keystream(&mut packet_data);
811
812        if i == 0 {
813            let stop_index = packet_data.len();
814            let start_index = stop_index
815                .checked_sub(filler.len())
816                .ok_or(SphinxError::HopDataLenTooLarge)?;
817            packet_data[start_index..stop_index].copy_from_slice(&filler[..]);
818        }
819
820        hmac = compute_hmac(&keys.mu, &packet_data, assoc_data.as_deref());
821    }
822
823    Ok(OnionPacket {
824        version: 0,
825        public_key,
826        packet_data,
827        hmac,
828    })
829}
830
831#[cfg(test)]
832mod tests;