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;