1use crate::gen::fiber as molecule_fiber;
7use crate::payment::{
8 BasicMppPaymentData, CurrentPaymentHopData, PaymentCustomRecords, PaymentHopData, TlcErr,
9 TlcErrData, TlcErrorCode, USER_CUSTOM_RECORDS_MAX_INDEX,
10};
11use ckb_types::prelude::{Pack, Unpack};
12use fiber_sphinx::OnionErrorPacket;
13use molecule::prelude::{Builder, Entity};
14use serde::{Deserialize, Serialize};
15
16pub const ONION_PACKET_VERSION_V0: u8 = 0;
18pub const ONION_PACKET_VERSION_V1: u8 = 1;
20
21const PACKET_DATA_LEN: usize = 6500;
22
23const HOP_DATA_HEAD_LEN: usize = std::mem::size_of::<u64>();
25
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
31pub struct TlcErrPacket {
32 pub onion_packet: Vec<u8>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
37pub struct DecodedTlcErr {
38 pub error: TlcErr,
39 pub hop_index: usize,
41}
42
43pub const NO_SHARED_SECRET: [u8; 32] = [0u8; 32];
45const NO_ERROR_PACKET_HMAC: [u8; 32] = [0u8; 32];
46
47pub const ONION_SESSION_KEY_MAX_ATTEMPTS: usize = 7;
55
56impl TlcErrPacket {
57 pub fn from_payload(payload: Vec<u8>, shared_secret: &[u8; 32]) -> Self {
60 let onion_packet = OnionErrorPacket::create(shared_secret, payload).into_bytes();
61 TlcErrPacket { onion_packet }
62 }
63
64 pub fn is_plaintext(&self) -> bool {
66 self.onion_packet.len() >= 32 && self.onion_packet[0..32] == NO_ERROR_PACKET_HMAC
67 }
68
69 pub fn backward(self, shared_secret: &[u8; 32]) -> Result<Self, TlcErrPacketError> {
72 if self.is_plaintext() {
73 return Err(TlcErrPacketError::PlaintextForward);
74 }
75
76 let onion_packet = OnionErrorPacket::from_bytes(self.onion_packet)
77 .xor_cipher_stream(shared_secret)
78 .into_bytes();
79 Ok(TlcErrPacket { onion_packet })
80 }
81}
82
83impl From<TlcErrPacket> for molecule_fiber::TlcErrPacket {
84 fn from(tlc_err_packet: TlcErrPacket) -> Self {
85 molecule_fiber::TlcErrPacket::new_builder()
86 .onion_packet(tlc_err_packet.onion_packet.pack())
87 .build()
88 }
89}
90
91impl From<molecule_fiber::TlcErrPacket> for TlcErrPacket {
92 fn from(tlc_err_packet: molecule_fiber::TlcErrPacket) -> Self {
93 TlcErrPacket {
94 onion_packet: tlc_err_packet.onion_packet().unpack(),
95 }
96 }
97}
98
99impl std::fmt::Display for TlcErrPacket {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 write!(f, "TlcErrPacket")
102 }
103}
104
105const ERROR_DECODING_PASSES: usize = 27;
108
109impl TlcErrPacket {
110 pub fn new(tlc_fail: TlcErr, shared_secret: &[u8; 32]) -> Self {
113 let payload = tlc_fail.serialize();
114 Self::from_payload(payload, shared_secret)
115 }
116
117 pub fn decode(
119 &self,
120 session_key: &[u8; 32],
121 hops_public_keys: Vec<crate::Pubkey>,
122 ) -> Option<DecodedTlcErr> {
123 use secp256k1::{PublicKey, SecretKey};
124
125 let hops_public_keys: Vec<PublicKey> = hops_public_keys
126 .iter()
127 .map(|k| PublicKey::from_slice(&k.0).expect("valid pubkey"))
128 .collect();
129 let session_key = SecretKey::from_slice(session_key)
130 .inspect_err(|err| {
131 tracing::error!(
132 target: "fnn::fiber::types::TlcErrPacket",
133 "decode session_key error={} key={}",
134 err,
135 hex::encode(session_key)
136 )
137 })
138 .ok()?;
139 OnionErrorPacket::from_bytes(self.onion_packet.clone())
140 .parse(hops_public_keys, session_key, TlcErr::deserialize)
141 .map(|(error, hop_index)| {
142 for _ in hop_index..ERROR_DECODING_PASSES {
143 OnionErrorPacket::from_bytes(self.onion_packet.clone())
144 .xor_cipher_stream(&NO_SHARED_SECRET);
145 }
146 DecodedTlcErr { error, hop_index }
147 })
148 }
149
150 pub fn new_trampoline_failed(
153 error_code: TlcErrorCode,
154 node_id: crate::Pubkey,
155 inner_error_packet: Vec<u8>,
156 shared_secret: &[u8; 32],
157 ) -> Self {
158 let mut tlc_err = TlcErr::new(error_code);
159 tlc_err.set_extra_data(TlcErrData::TrampolineFailed {
160 node_id,
161 inner_error_packet,
162 });
163 Self::new(tlc_err, shared_secret)
164 }
165}
166
167#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
168pub enum TlcErrPacketError {
169 #[error("Refusing to forward plaintext TLC error packet")]
170 PlaintextForward,
171}
172
173#[derive(thiserror::Error, Debug)]
175pub enum OnionPacketError {
176 #[error("Fail to deserialize the hop data")]
177 InvalidHopData,
178
179 #[error("Unknown onion packet version: {0}")]
180 UnknownVersion(u8),
181
182 #[error("Sphinx protocol error")]
183 Sphinx(#[from] fiber_sphinx::SphinxError),
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
188pub struct PaymentOnionPacket {
189 data: Vec<u8>,
191}
192
193impl PaymentOnionPacket {
194 pub fn new(data: Vec<u8>) -> Self {
196 Self { data }
197 }
198
199 pub fn data(&self) -> &[u8] {
201 &self.data
202 }
203
204 pub fn as_bytes(&self) -> &[u8] {
206 &self.data
207 }
208
209 pub fn into_data(self) -> Vec<u8> {
211 self.data
212 }
213
214 pub fn into_bytes(self) -> Vec<u8> {
216 self.data
217 }
218
219 pub fn into_sphinx_onion_packet(self) -> Result<fiber_sphinx::OnionPacket, OnionPacketError> {
221 fiber_sphinx::OnionPacket::from_bytes_with_packet_data_len(
222 self.data,
223 PaymentSphinxCodec::PACKET_DATA_LEN,
224 )
225 .map_err(OnionPacketError::Sphinx)
226 }
227
228 pub fn peel<C: secp256k1::Verification>(
234 self,
235 peeler: &crate::Privkey,
236 assoc_data: Option<&[u8]>,
237 secp_ctx: &secp256k1::Secp256k1<C>,
238 ) -> Result<PeeledPaymentOnionPacket, OnionPacketError> {
239 let peeled =
240 peel_sphinx_onion::<C, PaymentSphinxCodec>(self.data, peeler, assoc_data, secp_ctx)?;
241 Ok(PeeledPaymentOnionPacket {
242 current: peeled.current,
243 next: peeled.next.map(PaymentOnionPacket::new),
244 shared_secret: peeled.shared_secret,
245 })
246 }
247}
248
249#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
251pub struct PeeledPaymentOnionPacket {
252 pub current: CurrentPaymentHopData,
254 pub shared_secret: [u8; 32],
257 pub next: Option<PaymentOnionPacket>,
259}
260
261impl PeeledPaymentOnionPacket {
262 pub fn create<C: secp256k1::Signing>(
265 session_key: crate::Privkey,
266 mut hops_infos: Vec<PaymentHopData>,
267 assoc_data: Option<Vec<u8>>,
268 secp_ctx: &secp256k1::Secp256k1<C>,
269 ) -> Result<Self, OnionPacketError> {
270 if hops_infos.is_empty() {
271 return Err(OnionPacketError::Sphinx(
272 fiber_sphinx::SphinxError::HopsIsEmpty,
273 ));
274 }
275
276 let hops_path = peeled_payment_hops_path(&hops_infos);
277 let current = hops_infos.remove(0);
278 let payloads = hops_infos;
279
280 let next = if !hops_path.is_empty() {
281 let bytes = create_sphinx_onion::<C, PaymentSphinxCodec>(
282 session_key,
283 hops_path,
284 payloads,
285 assoc_data,
286 secp_ctx,
287 )?;
288 Some(PaymentOnionPacket::new(bytes))
289 } else {
290 None
291 };
292
293 Ok(PeeledPaymentOnionPacket {
294 current: current.into(),
295 next,
296 shared_secret: NO_SHARED_SECRET,
298 })
299 }
300
301 pub fn create_with_session_key_fn<C, F>(
313 gen_session_key: F,
314 mut hops_infos: Vec<PaymentHopData>,
315 assoc_data: Option<Vec<u8>>,
316 secp_ctx: &secp256k1::Secp256k1<C>,
317 ) -> Result<(Self, crate::Privkey), OnionPacketError>
318 where
319 C: secp256k1::Signing,
320 F: FnMut() -> crate::Privkey,
321 {
322 if hops_infos.is_empty() {
323 return Err(OnionPacketError::Sphinx(
324 fiber_sphinx::SphinxError::HopsIsEmpty,
325 ));
326 }
327
328 let hops_path = peeled_payment_hops_path(&hops_infos);
329 if hops_path.is_empty() {
330 return Err(OnionPacketError::Sphinx(
331 fiber_sphinx::SphinxError::HopsIsEmpty,
332 ));
333 }
334 let current = hops_infos.remove(0);
335 let payloads = hops_infos;
336
337 let (bytes, session_key) = create_sphinx_onion_with_session_key_fn::<
338 C,
339 PaymentSphinxCodec,
340 _,
341 >(
342 gen_session_key, hops_path, payloads, assoc_data, secp_ctx
343 )?;
344
345 Ok((
346 PeeledPaymentOnionPacket {
347 current: current.into(),
348 next: Some(PaymentOnionPacket::new(bytes)),
349 shared_secret: NO_SHARED_SECRET,
351 },
352 session_key,
353 ))
354 }
355
356 pub fn is_last(&self) -> bool {
358 self.next.is_none()
359 }
360
361 pub fn mpp_custom_records(&self) -> Option<BasicMppPaymentData> {
363 self.current
364 .custom_records
365 .as_ref()
366 .and_then(BasicMppPaymentData::read)
367 }
368}
369
370fn peeled_payment_hops_path(hops_infos: &[PaymentHopData]) -> Vec<crate::Pubkey> {
371 hops_infos
372 .iter()
373 .map(|h| h.next_hop())
374 .take_while(Option::is_some)
375 .map(|opt| opt.expect("must be some"))
376 .collect()
377}
378
379pub trait SphinxOnionCodec {
385 type Decoded;
386 type Current;
387
388 const PACKET_DATA_LEN: usize;
389 const CURRENT_VERSION: u8;
391
392 fn pack(decoded: &Self::Decoded) -> Vec<u8>;
394 fn unpack(version: u8, buf: &[u8]) -> Option<Self::Decoded>;
396 fn to_current(decoded: Self::Decoded) -> Self::Current;
397 fn is_version_allowed(version: u8) -> bool;
399 fn hop_data_len(version: u8, buf: &[u8]) -> Option<usize>;
401}
402
403pub struct SphinxPeeled<Current> {
405 pub current: Current,
406 pub shared_secret: [u8; 32],
407 pub next: Option<Vec<u8>>,
408}
409
410pub fn peel_sphinx_onion<C: secp256k1::Verification, Codec: SphinxOnionCodec>(
412 packet_bytes: Vec<u8>,
413 peeler: &crate::Privkey,
414 assoc_data: Option<&[u8]>,
415 secp_ctx: &secp256k1::Secp256k1<C>,
416) -> Result<SphinxPeeled<Codec::Current>, OnionPacketError> {
417 let sphinx_packet = fiber_sphinx::OnionPacket::from_bytes_with_packet_data_len(
418 packet_bytes,
419 Codec::PACKET_DATA_LEN,
420 )
421 .map_err(OnionPacketError::Sphinx)?;
422 let version = sphinx_packet.version;
423 if !Codec::is_version_allowed(version) {
424 return Err(OnionPacketError::UnknownVersion(version));
425 }
426 let shared_secret = sphinx_packet.shared_secret(&peeler.0);
427
428 let (new_current, new_next) = sphinx_packet
429 .peel(&peeler.0, assoc_data, secp_ctx, |buf| {
430 Codec::hop_data_len(version, buf)
431 })
432 .map_err(OnionPacketError::Sphinx)?;
433
434 let decoded = Codec::unpack(version, &new_current).ok_or(OnionPacketError::InvalidHopData)?;
435 let current = Codec::to_current(decoded);
436
437 let next = new_next
439 .hmac
440 .iter()
441 .any(|b| *b != 0)
442 .then(|| new_next.into_bytes());
443
444 Ok(SphinxPeeled {
445 current,
446 shared_secret,
447 next,
448 })
449}
450
451pub fn create_sphinx_onion<C: secp256k1::Signing, Codec: SphinxOnionCodec>(
453 session_key: crate::Privkey,
454 hops_path: Vec<crate::Pubkey>,
455 payloads: Vec<Codec::Decoded>,
456 assoc_data: Option<Vec<u8>>,
457 secp_ctx: &secp256k1::Secp256k1<C>,
458) -> Result<Vec<u8>, OnionPacketError> {
459 if hops_path.is_empty() {
460 return Err(OnionPacketError::Sphinx(
461 fiber_sphinx::SphinxError::HopsIsEmpty,
462 ));
463 }
464 if hops_path.len() != payloads.len() {
465 return Err(OnionPacketError::InvalidHopData);
466 }
467
468 let hops_path: Vec<secp256k1::PublicKey> = hops_path
469 .into_iter()
470 .map(|pk| secp256k1::PublicKey::from_slice(&pk.0).expect("valid public key"))
471 .collect();
472 let hops_data: Vec<Vec<u8>> = payloads.iter().map(|p| Codec::pack(p)).collect();
473 let mut packet = fiber_sphinx::OnionPacket::create(
474 session_key.0,
475 hops_path,
476 hops_data,
477 assoc_data,
478 Codec::PACKET_DATA_LEN,
479 secp_ctx,
480 )
481 .map_err(OnionPacketError::Sphinx)?;
482 packet.version = Codec::CURRENT_VERSION;
484 Ok(packet.into_bytes())
485}
486
487pub fn create_sphinx_onion_with_session_key_fn<C, Codec, F>(
496 mut gen_session_key: F,
497 hops_path: Vec<crate::Pubkey>,
498 payloads: Vec<Codec::Decoded>,
499 assoc_data: Option<Vec<u8>>,
500 secp_ctx: &secp256k1::Secp256k1<C>,
501) -> Result<(Vec<u8>, crate::Privkey), OnionPacketError>
502where
503 C: secp256k1::Signing,
504 Codec: SphinxOnionCodec,
505 Codec::Decoded: Clone,
506 F: FnMut() -> crate::Privkey,
507{
508 let mut last_err: Option<fiber_sphinx::SphinxError> = None;
509 for _ in 0..ONION_SESSION_KEY_MAX_ATTEMPTS {
510 let session_key = gen_session_key();
511 match create_sphinx_onion::<C, Codec>(
512 session_key.clone(),
513 hops_path.clone(),
514 payloads.clone(),
515 assoc_data.clone(),
516 secp_ctx,
517 ) {
518 Ok(bytes) => return Ok((bytes, session_key)),
519 Err(OnionPacketError::Sphinx(
520 err @ fiber_sphinx::SphinxError::InvalidBlindingFactor,
521 )) => {
522 last_err = Some(err);
523 continue;
524 }
525 Err(other) => return Err(other),
526 }
527 }
528 Err(OnionPacketError::Sphinx(last_err.expect(
529 "ONION_SESSION_KEY_MAX_ATTEMPTS is non-zero, so at least one attempt ran",
530 )))
531}
532
533pub struct PaymentSphinxCodec;
535
536impl PaymentSphinxCodec {
537 pub fn pack_hop_data(version: u8, hop_data: &PaymentHopData) -> Vec<u8> {
541 match version {
542 ONION_PACKET_VERSION_V0 => pack_len_prefixed(hop_data.serialize()),
543 ONION_PACKET_VERSION_V1 => hop_data.serialize(),
544 other => {
545 debug_assert!(
546 false,
547 "Unknown onion packet version {} passed to pack_hop_data; defaulting to v1",
548 other
549 );
550 hop_data.serialize()
551 }
552 }
553 }
554
555 pub fn unpack_hop_data(version: u8, buf: &[u8]) -> Option<PaymentHopData> {
560 match version {
561 ONION_PACKET_VERSION_V0 => {
562 let payload = unpack_len_prefixed_payload(buf)?;
563 PaymentHopData::deserialize(payload)
564 }
565 ONION_PACKET_VERSION_V1 => {
566 let len = molecule_table_data_len(buf)?;
567 if buf.len() < len {
568 return None;
569 }
570 PaymentHopData::deserialize(&buf[..len])
571 }
572 _ => None,
573 }
574 }
575}
576
577impl SphinxOnionCodec for PaymentSphinxCodec {
578 type Decoded = PaymentHopData;
579 type Current = CurrentPaymentHopData;
580
581 const PACKET_DATA_LEN: usize = PACKET_DATA_LEN;
582 const CURRENT_VERSION: u8 = ONION_PACKET_VERSION_V1;
584
585 fn pack(decoded: &Self::Decoded) -> Vec<u8> {
586 Self::pack_hop_data(Self::CURRENT_VERSION, decoded)
587 }
588
589 fn unpack(version: u8, buf: &[u8]) -> Option<Self::Decoded> {
590 Self::unpack_hop_data(version, buf)
591 }
592
593 fn to_current(decoded: Self::Decoded) -> Self::Current {
594 decoded.into()
595 }
596
597 fn is_version_allowed(version: u8) -> bool {
598 version <= ONION_PACKET_VERSION_V1
600 }
601
602 fn hop_data_len(version: u8, buf: &[u8]) -> Option<usize> {
603 match version {
604 ONION_PACKET_VERSION_V0 => len_with_u64_header(buf),
605 ONION_PACKET_VERSION_V1 => molecule_table_data_len(buf),
606 _ => None,
607 }
608 }
609}
610
611pub fn pack_len_prefixed(mut payload: Vec<u8>) -> Vec<u8> {
614 let mut packed = (payload.len() as u64).to_be_bytes().to_vec();
615 packed.append(&mut payload);
616 packed
617}
618
619pub fn unpack_len_prefixed_payload(buf: &[u8]) -> Option<&[u8]> {
621 let len = len_with_u64_header(buf)?;
622 if buf.len() < len {
623 return None;
624 }
625 buf.get(HOP_DATA_HEAD_LEN..len)
626}
627
628pub fn len_with_u64_header(buf: &[u8]) -> Option<usize> {
631 if buf.len() < HOP_DATA_HEAD_LEN {
632 return None;
633 }
634 let len = u64::from_be_bytes(
635 buf[0..HOP_DATA_HEAD_LEN]
636 .try_into()
637 .expect("u64 from slice"),
638 );
639 usize::try_from(len).ok()?.checked_add(HOP_DATA_HEAD_LEN)
642}
643
644pub fn molecule_table_data_len(buf: &[u8]) -> Option<usize> {
647 if buf.len() < molecule::NUMBER_SIZE {
648 return None;
649 }
650 let len = molecule::unpack_number(buf) as usize;
651 if len < molecule::NUMBER_SIZE {
654 return None;
655 }
656 Some(len)
657}
658
659pub struct TrampolineOnionData;
665
666impl TrampolineOnionData {
667 pub const CUSTOM_RECORD_KEY: u32 = USER_CUSTOM_RECORDS_MAX_INDEX + 2;
670
671 pub fn write(data: Vec<u8>, custom_records: &mut PaymentCustomRecords) {
672 custom_records.data.insert(Self::CUSTOM_RECORD_KEY, data);
673 }
674
675 pub fn read(custom_records: &PaymentCustomRecords) -> Option<Vec<u8>> {
676 custom_records.data.get(&Self::CUSTOM_RECORD_KEY).cloned()
677 }
678}