1use crate::gen::fiber as molecule_fiber;
7use crate::payment::{
8 BasicMppPaymentData, CurrentPaymentHopData, PaymentCustomRecords, PaymentHopData, TlcErr,
9 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
35pub const NO_SHARED_SECRET: [u8; 32] = [0u8; 32];
37const NO_ERROR_PACKET_HMAC: [u8; 32] = [0u8; 32];
38
39impl TlcErrPacket {
40 pub fn from_payload(payload: Vec<u8>, shared_secret: &[u8; 32]) -> Self {
44 let onion_packet = if shared_secret != &NO_SHARED_SECRET {
45 OnionErrorPacket::create(shared_secret, payload)
46 } else {
47 OnionErrorPacket::concat(NO_ERROR_PACKET_HMAC, payload)
48 }
49 .into_bytes();
50 TlcErrPacket { onion_packet }
51 }
52
53 pub fn is_plaintext(&self) -> bool {
55 self.onion_packet.len() >= 32 && self.onion_packet[0..32] == NO_ERROR_PACKET_HMAC
56 }
57
58 pub fn backward(self, shared_secret: &[u8; 32]) -> Self {
61 if !self.is_plaintext() {
62 let onion_packet = OnionErrorPacket::from_bytes(self.onion_packet)
63 .xor_cipher_stream(shared_secret)
64 .into_bytes();
65 TlcErrPacket { onion_packet }
66 } else {
67 self
69 }
70 }
71}
72
73impl From<TlcErrPacket> for molecule_fiber::TlcErrPacket {
74 fn from(tlc_err_packet: TlcErrPacket) -> Self {
75 molecule_fiber::TlcErrPacket::new_builder()
76 .onion_packet(tlc_err_packet.onion_packet.pack())
77 .build()
78 }
79}
80
81impl From<molecule_fiber::TlcErrPacket> for TlcErrPacket {
82 fn from(tlc_err_packet: molecule_fiber::TlcErrPacket) -> Self {
83 TlcErrPacket {
84 onion_packet: tlc_err_packet.onion_packet().unpack(),
85 }
86 }
87}
88
89impl std::fmt::Display for TlcErrPacket {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 write!(f, "TlcErrPacket")
92 }
93}
94
95const ERROR_DECODING_PASSES: usize = 27;
98
99impl TlcErrPacket {
100 pub fn new(tlc_fail: TlcErr, shared_secret: &[u8; 32]) -> Self {
103 let payload = tlc_fail.serialize();
104 Self::from_payload(payload, shared_secret)
105 }
106
107 pub fn decode(
109 &self,
110 session_key: &[u8; 32],
111 hops_public_keys: Vec<crate::Pubkey>,
112 ) -> Option<TlcErr> {
113 use secp256k1::{PublicKey, SecretKey};
114
115 if self.is_plaintext() {
116 let error = TlcErr::deserialize(&self.onion_packet[32..]);
117 if error.is_some() {
118 return error;
119 }
120 }
121
122 let hops_public_keys: Vec<PublicKey> = hops_public_keys
123 .iter()
124 .map(|k| PublicKey::from_slice(&k.0).expect("valid pubkey"))
125 .collect();
126 let session_key = SecretKey::from_slice(session_key)
127 .inspect_err(|err| {
128 tracing::error!(
129 target: "fnn::fiber::types::TlcErrPacket",
130 "decode session_key error={} key={}",
131 err,
132 hex::encode(session_key)
133 )
134 })
135 .ok()?;
136 OnionErrorPacket::from_bytes(self.onion_packet.clone())
137 .parse(hops_public_keys, session_key, TlcErr::deserialize)
138 .map(|(error, hop_index)| {
139 for _ in hop_index..ERROR_DECODING_PASSES {
140 OnionErrorPacket::from_bytes(self.onion_packet.clone())
141 .xor_cipher_stream(&NO_SHARED_SECRET);
142 }
143 error
144 })
145 }
146}
147
148#[derive(thiserror::Error, Debug)]
150pub enum OnionPacketError {
151 #[error("Fail to deserialize the hop data")]
152 InvalidHopData,
153
154 #[error("Unknown onion packet version: {0}")]
155 UnknownVersion(u8),
156
157 #[error("Sphinx protocol error")]
158 Sphinx(#[from] fiber_sphinx::SphinxError),
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
163pub struct PaymentOnionPacket {
164 data: Vec<u8>,
166}
167
168impl PaymentOnionPacket {
169 pub fn new(data: Vec<u8>) -> Self {
171 Self { data }
172 }
173
174 pub fn data(&self) -> &[u8] {
176 &self.data
177 }
178
179 pub fn as_bytes(&self) -> &[u8] {
181 &self.data
182 }
183
184 pub fn into_data(self) -> Vec<u8> {
186 self.data
187 }
188
189 pub fn into_bytes(self) -> Vec<u8> {
191 self.data
192 }
193
194 pub fn into_sphinx_onion_packet(self) -> Result<fiber_sphinx::OnionPacket, OnionPacketError> {
196 fiber_sphinx::OnionPacket::from_bytes(self.data).map_err(OnionPacketError::Sphinx)
197 }
198
199 pub fn peel<C: secp256k1::Verification>(
205 self,
206 peeler: &crate::Privkey,
207 assoc_data: Option<&[u8]>,
208 secp_ctx: &secp256k1::Secp256k1<C>,
209 ) -> Result<PeeledPaymentOnionPacket, OnionPacketError> {
210 let peeled =
211 peel_sphinx_onion::<C, PaymentSphinxCodec>(self.data, peeler, assoc_data, secp_ctx)?;
212 Ok(PeeledPaymentOnionPacket {
213 current: peeled.current,
214 next: peeled.next.map(PaymentOnionPacket::new),
215 shared_secret: peeled.shared_secret,
216 })
217 }
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
222pub struct PeeledPaymentOnionPacket {
223 pub current: CurrentPaymentHopData,
225 pub shared_secret: [u8; 32],
228 pub next: Option<PaymentOnionPacket>,
230}
231
232impl PeeledPaymentOnionPacket {
233 pub fn create<C: secp256k1::Signing>(
236 session_key: crate::Privkey,
237 mut hops_infos: Vec<PaymentHopData>,
238 assoc_data: Option<Vec<u8>>,
239 secp_ctx: &secp256k1::Secp256k1<C>,
240 ) -> Result<Self, OnionPacketError> {
241 if hops_infos.is_empty() {
242 return Err(OnionPacketError::Sphinx(
243 fiber_sphinx::SphinxError::HopsIsEmpty,
244 ));
245 }
246
247 let hops_path: Vec<crate::Pubkey> = hops_infos
248 .iter()
249 .map(|h| h.next_hop())
250 .take_while(Option::is_some)
251 .map(|opt| opt.expect("must be some"))
252 .collect();
253
254 let current = hops_infos.remove(0);
256 let payloads = hops_infos;
257
258 let next = if !hops_path.is_empty() {
259 Some(PaymentOnionPacket::new(create_sphinx_onion::<
260 C,
261 PaymentSphinxCodec,
262 >(
263 session_key,
264 hops_path,
265 payloads,
266 assoc_data,
267 secp_ctx,
268 )?))
269 } else {
270 None
271 };
272
273 Ok(PeeledPaymentOnionPacket {
274 current: current.into(),
275 next,
276 shared_secret: NO_SHARED_SECRET,
278 })
279 }
280
281 pub fn is_last(&self) -> bool {
283 self.next.is_none()
284 }
285
286 pub fn mpp_custom_records(&self) -> Option<BasicMppPaymentData> {
288 self.current
289 .custom_records
290 .as_ref()
291 .and_then(BasicMppPaymentData::read)
292 }
293}
294
295pub trait SphinxOnionCodec {
301 type Decoded;
302 type Current;
303
304 const PACKET_DATA_LEN: usize;
305 const CURRENT_VERSION: u8;
307
308 fn pack(decoded: &Self::Decoded) -> Vec<u8>;
310 fn unpack(version: u8, buf: &[u8]) -> Option<Self::Decoded>;
312 fn to_current(decoded: Self::Decoded) -> Self::Current;
313 fn is_version_allowed(version: u8) -> bool;
315 fn hop_data_len(version: u8, buf: &[u8]) -> Option<usize>;
317}
318
319pub struct SphinxPeeled<Current> {
321 pub current: Current,
322 pub shared_secret: [u8; 32],
323 pub next: Option<Vec<u8>>,
324}
325
326pub fn peel_sphinx_onion<C: secp256k1::Verification, Codec: SphinxOnionCodec>(
328 packet_bytes: Vec<u8>,
329 peeler: &crate::Privkey,
330 assoc_data: Option<&[u8]>,
331 secp_ctx: &secp256k1::Secp256k1<C>,
332) -> Result<SphinxPeeled<Codec::Current>, OnionPacketError> {
333 let sphinx_packet =
334 fiber_sphinx::OnionPacket::from_bytes(packet_bytes).map_err(OnionPacketError::Sphinx)?;
335 let version = sphinx_packet.version;
336 if !Codec::is_version_allowed(version) {
337 return Err(OnionPacketError::UnknownVersion(version));
338 }
339 let shared_secret = sphinx_packet.shared_secret(&peeler.0);
340
341 let (new_current, new_next) = sphinx_packet
342 .peel(&peeler.0, assoc_data, secp_ctx, |buf| {
343 Codec::hop_data_len(version, buf)
344 })
345 .map_err(OnionPacketError::Sphinx)?;
346
347 let decoded = Codec::unpack(version, &new_current).ok_or(OnionPacketError::InvalidHopData)?;
348 let current = Codec::to_current(decoded);
349
350 let next = new_next
352 .hmac
353 .iter()
354 .any(|b| *b != 0)
355 .then(|| new_next.into_bytes());
356
357 Ok(SphinxPeeled {
358 current,
359 shared_secret,
360 next,
361 })
362}
363
364pub fn create_sphinx_onion<C: secp256k1::Signing, Codec: SphinxOnionCodec>(
366 session_key: crate::Privkey,
367 hops_path: Vec<crate::Pubkey>,
368 payloads: Vec<Codec::Decoded>,
369 assoc_data: Option<Vec<u8>>,
370 secp_ctx: &secp256k1::Secp256k1<C>,
371) -> Result<Vec<u8>, OnionPacketError> {
372 let hops_path: Vec<secp256k1::PublicKey> = hops_path
373 .into_iter()
374 .map(|pk| secp256k1::PublicKey::from_slice(&pk.0).expect("valid public key"))
375 .collect();
376 let hops_data: Vec<Vec<u8>> = payloads.iter().map(|p| Codec::pack(p)).collect();
377 let mut packet = fiber_sphinx::OnionPacket::create(
378 session_key.0,
379 hops_path,
380 hops_data,
381 assoc_data,
382 Codec::PACKET_DATA_LEN,
383 secp_ctx,
384 )
385 .map_err(OnionPacketError::Sphinx)?;
386 packet.version = Codec::CURRENT_VERSION;
388 Ok(packet.into_bytes())
389}
390
391pub struct PaymentSphinxCodec;
393
394impl PaymentSphinxCodec {
395 pub fn pack_hop_data(version: u8, hop_data: &PaymentHopData) -> Vec<u8> {
399 match version {
400 ONION_PACKET_VERSION_V0 => pack_len_prefixed(hop_data.serialize()),
401 ONION_PACKET_VERSION_V1 => hop_data.serialize(),
402 other => {
403 debug_assert!(
404 false,
405 "Unknown onion packet version {} passed to pack_hop_data; defaulting to v1",
406 other
407 );
408 hop_data.serialize()
409 }
410 }
411 }
412
413 pub fn unpack_hop_data(version: u8, buf: &[u8]) -> Option<PaymentHopData> {
418 match version {
419 ONION_PACKET_VERSION_V0 => {
420 let payload = unpack_len_prefixed_payload(buf)?;
421 PaymentHopData::deserialize(payload)
422 }
423 ONION_PACKET_VERSION_V1 => {
424 let len = molecule_table_data_len(buf)?;
425 if buf.len() < len {
426 return None;
427 }
428 PaymentHopData::deserialize(&buf[..len])
429 }
430 _ => None,
431 }
432 }
433}
434
435impl SphinxOnionCodec for PaymentSphinxCodec {
436 type Decoded = PaymentHopData;
437 type Current = CurrentPaymentHopData;
438
439 const PACKET_DATA_LEN: usize = PACKET_DATA_LEN;
440 const CURRENT_VERSION: u8 = ONION_PACKET_VERSION_V1;
442
443 fn pack(decoded: &Self::Decoded) -> Vec<u8> {
444 Self::pack_hop_data(Self::CURRENT_VERSION, decoded)
445 }
446
447 fn unpack(version: u8, buf: &[u8]) -> Option<Self::Decoded> {
448 Self::unpack_hop_data(version, buf)
449 }
450
451 fn to_current(decoded: Self::Decoded) -> Self::Current {
452 decoded.into()
453 }
454
455 fn is_version_allowed(version: u8) -> bool {
456 version <= ONION_PACKET_VERSION_V1
458 }
459
460 fn hop_data_len(version: u8, buf: &[u8]) -> Option<usize> {
461 match version {
462 ONION_PACKET_VERSION_V0 => len_with_u64_header(buf),
463 ONION_PACKET_VERSION_V1 => molecule_table_data_len(buf),
464 _ => None,
465 }
466 }
467}
468
469pub fn pack_len_prefixed(mut payload: Vec<u8>) -> Vec<u8> {
472 let mut packed = (payload.len() as u64).to_be_bytes().to_vec();
473 packed.append(&mut payload);
474 packed
475}
476
477pub fn unpack_len_prefixed_payload(buf: &[u8]) -> Option<&[u8]> {
479 let len = len_with_u64_header(buf)?;
480 if buf.len() < len {
481 return None;
482 }
483 buf.get(HOP_DATA_HEAD_LEN..len)
484}
485
486pub fn len_with_u64_header(buf: &[u8]) -> Option<usize> {
489 if buf.len() < HOP_DATA_HEAD_LEN {
490 return None;
491 }
492 let len = u64::from_be_bytes(
493 buf[0..HOP_DATA_HEAD_LEN]
494 .try_into()
495 .expect("u64 from slice"),
496 );
497 usize::try_from(len).ok()?.checked_add(HOP_DATA_HEAD_LEN)
500}
501
502pub fn molecule_table_data_len(buf: &[u8]) -> Option<usize> {
505 if buf.len() < molecule::NUMBER_SIZE {
506 return None;
507 }
508 let len = molecule::unpack_number(buf) as usize;
509 if len < molecule::NUMBER_SIZE {
512 return None;
513 }
514 Some(len)
515}
516
517pub struct TrampolineOnionData;
523
524impl TrampolineOnionData {
525 pub const CUSTOM_RECORD_KEY: u32 = USER_CUSTOM_RECORDS_MAX_INDEX + 2;
528
529 pub fn write(data: Vec<u8>, custom_records: &mut PaymentCustomRecords) {
530 custom_records.data.insert(Self::CUSTOM_RECORD_KEY, data);
531 }
532
533 pub fn read(custom_records: &PaymentCustomRecords) -> Option<Vec<u8>> {
534 custom_records.data.get(&Self::CUSTOM_RECORD_KEY).cloned()
535 }
536}