1use 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#[derive(Debug, Clone, Eq, PartialEq)]
106pub struct OnionPacket {
107 pub version: u8,
109 pub public_key: PublicKey,
111 pub packet_data: Vec<u8>,
113 pub hmac: [u8; PACKET_HMAC_LEN],
115}
116
117#[derive(Debug, Clone, Eq, PartialEq)]
145pub struct OnionErrorPacket {
146 pub packet_data: Vec<u8>,
148}
149
150impl OnionPacket {
151 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 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 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 #[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 pub fn shared_secret(&self, secret_key: &SecretKey) -> [u8; 32] {
270 SharedSecret::new(&self.public_key, secret_key).secret_bytes()
271 }
272
273 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 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 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 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 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 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 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 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 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#[derive(Debug, Clone, Eq, PartialEq)]
473pub struct ForwardKeys {
474 pub rho: [u8; 32],
476 pub mu: [u8; 32],
478}
479
480impl ForwardKeys {
481 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#[derive(Debug, Clone, Eq, PartialEq)]
492pub struct ReturnKeys {
493 pub ammag: [u8; 32],
495 pub um: [u8; 32],
497}
498
499impl ReturnKeys {
500 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#[derive(Clone)]
536pub struct OnionSharedSecretIter<'s, I, C: Signing> {
537 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 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
579fn 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
611fn 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
639fn 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
647fn 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
681fn 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
717fn 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
738fn 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
745fn 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
755fn 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
784fn 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;