use crate::gen::fiber as molecule_fiber;
use crate::payment::{
BasicMppPaymentData, CurrentPaymentHopData, PaymentCustomRecords, PaymentHopData, TlcErr,
USER_CUSTOM_RECORDS_MAX_INDEX,
};
use ckb_types::prelude::{Pack, Unpack};
use fiber_sphinx::OnionErrorPacket;
use molecule::prelude::{Builder, Entity};
use serde::{Deserialize, Serialize};
pub const ONION_PACKET_VERSION_V0: u8 = 0;
pub const ONION_PACKET_VERSION_V1: u8 = 1;
const PACKET_DATA_LEN: usize = 6500;
const HOP_DATA_HEAD_LEN: usize = std::mem::size_of::<u64>();
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct TlcErrPacket {
pub onion_packet: Vec<u8>,
}
pub const NO_SHARED_SECRET: [u8; 32] = [0u8; 32];
const NO_ERROR_PACKET_HMAC: [u8; 32] = [0u8; 32];
impl TlcErrPacket {
pub fn from_payload(payload: Vec<u8>, shared_secret: &[u8; 32]) -> Self {
let onion_packet = if shared_secret != &NO_SHARED_SECRET {
OnionErrorPacket::create(shared_secret, payload)
} else {
OnionErrorPacket::concat(NO_ERROR_PACKET_HMAC, payload)
}
.into_bytes();
TlcErrPacket { onion_packet }
}
pub fn is_plaintext(&self) -> bool {
self.onion_packet.len() >= 32 && self.onion_packet[0..32] == NO_ERROR_PACKET_HMAC
}
pub fn backward(self, shared_secret: &[u8; 32]) -> Self {
if !self.is_plaintext() {
let onion_packet = OnionErrorPacket::from_bytes(self.onion_packet)
.xor_cipher_stream(shared_secret)
.into_bytes();
TlcErrPacket { onion_packet }
} else {
self
}
}
}
impl From<TlcErrPacket> for molecule_fiber::TlcErrPacket {
fn from(tlc_err_packet: TlcErrPacket) -> Self {
molecule_fiber::TlcErrPacket::new_builder()
.onion_packet(tlc_err_packet.onion_packet.pack())
.build()
}
}
impl From<molecule_fiber::TlcErrPacket> for TlcErrPacket {
fn from(tlc_err_packet: molecule_fiber::TlcErrPacket) -> Self {
TlcErrPacket {
onion_packet: tlc_err_packet.onion_packet().unpack(),
}
}
}
impl std::fmt::Display for TlcErrPacket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TlcErrPacket")
}
}
const ERROR_DECODING_PASSES: usize = 27;
impl TlcErrPacket {
pub fn new(tlc_fail: TlcErr, shared_secret: &[u8; 32]) -> Self {
let payload = tlc_fail.serialize();
Self::from_payload(payload, shared_secret)
}
pub fn decode(
&self,
session_key: &[u8; 32],
hops_public_keys: Vec<crate::Pubkey>,
) -> Option<TlcErr> {
use secp256k1::{PublicKey, SecretKey};
if self.is_plaintext() {
let error = TlcErr::deserialize(&self.onion_packet[32..]);
if error.is_some() {
return error;
}
}
let hops_public_keys: Vec<PublicKey> = hops_public_keys
.iter()
.map(|k| PublicKey::from_slice(&k.0).expect("valid pubkey"))
.collect();
let session_key = SecretKey::from_slice(session_key)
.inspect_err(|err| {
tracing::error!(
target: "fnn::fiber::types::TlcErrPacket",
"decode session_key error={} key={}",
err,
hex::encode(session_key)
)
})
.ok()?;
OnionErrorPacket::from_bytes(self.onion_packet.clone())
.parse(hops_public_keys, session_key, TlcErr::deserialize)
.map(|(error, hop_index)| {
for _ in hop_index..ERROR_DECODING_PASSES {
OnionErrorPacket::from_bytes(self.onion_packet.clone())
.xor_cipher_stream(&NO_SHARED_SECRET);
}
error
})
}
}
#[derive(thiserror::Error, Debug)]
pub enum OnionPacketError {
#[error("Fail to deserialize the hop data")]
InvalidHopData,
#[error("Unknown onion packet version: {0}")]
UnknownVersion(u8),
#[error("Sphinx protocol error")]
Sphinx(#[from] fiber_sphinx::SphinxError),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PaymentOnionPacket {
data: Vec<u8>,
}
impl PaymentOnionPacket {
pub fn new(data: Vec<u8>) -> Self {
Self { data }
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn into_data(self) -> Vec<u8> {
self.data
}
pub fn into_bytes(self) -> Vec<u8> {
self.data
}
pub fn into_sphinx_onion_packet(self) -> Result<fiber_sphinx::OnionPacket, OnionPacketError> {
fiber_sphinx::OnionPacket::from_bytes(self.data).map_err(OnionPacketError::Sphinx)
}
pub fn peel<C: secp256k1::Verification>(
self,
peeler: &crate::Privkey,
assoc_data: Option<&[u8]>,
secp_ctx: &secp256k1::Secp256k1<C>,
) -> Result<PeeledPaymentOnionPacket, OnionPacketError> {
let peeled =
peel_sphinx_onion::<C, PaymentSphinxCodec>(self.data, peeler, assoc_data, secp_ctx)?;
Ok(PeeledPaymentOnionPacket {
current: peeled.current,
next: peeled.next.map(PaymentOnionPacket::new),
shared_secret: peeled.shared_secret,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PeeledPaymentOnionPacket {
pub current: CurrentPaymentHopData,
pub shared_secret: [u8; 32],
pub next: Option<PaymentOnionPacket>,
}
impl PeeledPaymentOnionPacket {
pub fn create<C: secp256k1::Signing>(
session_key: crate::Privkey,
mut hops_infos: Vec<PaymentHopData>,
assoc_data: Option<Vec<u8>>,
secp_ctx: &secp256k1::Secp256k1<C>,
) -> Result<Self, OnionPacketError> {
if hops_infos.is_empty() {
return Err(OnionPacketError::Sphinx(
fiber_sphinx::SphinxError::HopsIsEmpty,
));
}
let hops_path: Vec<crate::Pubkey> = hops_infos
.iter()
.map(|h| h.next_hop())
.take_while(Option::is_some)
.map(|opt| opt.expect("must be some"))
.collect();
let current = hops_infos.remove(0);
let payloads = hops_infos;
let next = if !hops_path.is_empty() {
Some(PaymentOnionPacket::new(create_sphinx_onion::<
C,
PaymentSphinxCodec,
>(
session_key,
hops_path,
payloads,
assoc_data,
secp_ctx,
)?))
} else {
None
};
Ok(PeeledPaymentOnionPacket {
current: current.into(),
next,
shared_secret: NO_SHARED_SECRET,
})
}
pub fn is_last(&self) -> bool {
self.next.is_none()
}
pub fn mpp_custom_records(&self) -> Option<BasicMppPaymentData> {
self.current
.custom_records
.as_ref()
.and_then(BasicMppPaymentData::read)
}
}
pub trait SphinxOnionCodec {
type Decoded;
type Current;
const PACKET_DATA_LEN: usize;
const CURRENT_VERSION: u8;
fn pack(decoded: &Self::Decoded) -> Vec<u8>;
fn unpack(version: u8, buf: &[u8]) -> Option<Self::Decoded>;
fn to_current(decoded: Self::Decoded) -> Self::Current;
fn is_version_allowed(version: u8) -> bool;
fn hop_data_len(version: u8, buf: &[u8]) -> Option<usize>;
}
pub struct SphinxPeeled<Current> {
pub current: Current,
pub shared_secret: [u8; 32],
pub next: Option<Vec<u8>>,
}
pub fn peel_sphinx_onion<C: secp256k1::Verification, Codec: SphinxOnionCodec>(
packet_bytes: Vec<u8>,
peeler: &crate::Privkey,
assoc_data: Option<&[u8]>,
secp_ctx: &secp256k1::Secp256k1<C>,
) -> Result<SphinxPeeled<Codec::Current>, OnionPacketError> {
let sphinx_packet =
fiber_sphinx::OnionPacket::from_bytes(packet_bytes).map_err(OnionPacketError::Sphinx)?;
let version = sphinx_packet.version;
if !Codec::is_version_allowed(version) {
return Err(OnionPacketError::UnknownVersion(version));
}
let shared_secret = sphinx_packet.shared_secret(&peeler.0);
let (new_current, new_next) = sphinx_packet
.peel(&peeler.0, assoc_data, secp_ctx, |buf| {
Codec::hop_data_len(version, buf)
})
.map_err(OnionPacketError::Sphinx)?;
let decoded = Codec::unpack(version, &new_current).ok_or(OnionPacketError::InvalidHopData)?;
let current = Codec::to_current(decoded);
let next = new_next
.hmac
.iter()
.any(|b| *b != 0)
.then(|| new_next.into_bytes());
Ok(SphinxPeeled {
current,
shared_secret,
next,
})
}
pub fn create_sphinx_onion<C: secp256k1::Signing, Codec: SphinxOnionCodec>(
session_key: crate::Privkey,
hops_path: Vec<crate::Pubkey>,
payloads: Vec<Codec::Decoded>,
assoc_data: Option<Vec<u8>>,
secp_ctx: &secp256k1::Secp256k1<C>,
) -> Result<Vec<u8>, OnionPacketError> {
let hops_path: Vec<secp256k1::PublicKey> = hops_path
.into_iter()
.map(|pk| secp256k1::PublicKey::from_slice(&pk.0).expect("valid public key"))
.collect();
let hops_data: Vec<Vec<u8>> = payloads.iter().map(|p| Codec::pack(p)).collect();
let mut packet = fiber_sphinx::OnionPacket::create(
session_key.0,
hops_path,
hops_data,
assoc_data,
Codec::PACKET_DATA_LEN,
secp_ctx,
)
.map_err(OnionPacketError::Sphinx)?;
packet.version = Codec::CURRENT_VERSION;
Ok(packet.into_bytes())
}
pub struct PaymentSphinxCodec;
impl PaymentSphinxCodec {
pub fn pack_hop_data(version: u8, hop_data: &PaymentHopData) -> Vec<u8> {
match version {
ONION_PACKET_VERSION_V0 => pack_len_prefixed(hop_data.serialize()),
ONION_PACKET_VERSION_V1 => hop_data.serialize(),
other => {
debug_assert!(
false,
"Unknown onion packet version {} passed to pack_hop_data; defaulting to v1",
other
);
hop_data.serialize()
}
}
}
pub fn unpack_hop_data(version: u8, buf: &[u8]) -> Option<PaymentHopData> {
match version {
ONION_PACKET_VERSION_V0 => {
let payload = unpack_len_prefixed_payload(buf)?;
PaymentHopData::deserialize(payload)
}
ONION_PACKET_VERSION_V1 => {
let len = molecule_table_data_len(buf)?;
if buf.len() < len {
return None;
}
PaymentHopData::deserialize(&buf[..len])
}
_ => None,
}
}
}
impl SphinxOnionCodec for PaymentSphinxCodec {
type Decoded = PaymentHopData;
type Current = CurrentPaymentHopData;
const PACKET_DATA_LEN: usize = PACKET_DATA_LEN;
const CURRENT_VERSION: u8 = ONION_PACKET_VERSION_V1;
fn pack(decoded: &Self::Decoded) -> Vec<u8> {
Self::pack_hop_data(Self::CURRENT_VERSION, decoded)
}
fn unpack(version: u8, buf: &[u8]) -> Option<Self::Decoded> {
Self::unpack_hop_data(version, buf)
}
fn to_current(decoded: Self::Decoded) -> Self::Current {
decoded.into()
}
fn is_version_allowed(version: u8) -> bool {
version <= ONION_PACKET_VERSION_V1
}
fn hop_data_len(version: u8, buf: &[u8]) -> Option<usize> {
match version {
ONION_PACKET_VERSION_V0 => len_with_u64_header(buf),
ONION_PACKET_VERSION_V1 => molecule_table_data_len(buf),
_ => None,
}
}
}
pub fn pack_len_prefixed(mut payload: Vec<u8>) -> Vec<u8> {
let mut packed = (payload.len() as u64).to_be_bytes().to_vec();
packed.append(&mut payload);
packed
}
pub fn unpack_len_prefixed_payload(buf: &[u8]) -> Option<&[u8]> {
let len = len_with_u64_header(buf)?;
if buf.len() < len {
return None;
}
buf.get(HOP_DATA_HEAD_LEN..len)
}
pub fn len_with_u64_header(buf: &[u8]) -> Option<usize> {
if buf.len() < HOP_DATA_HEAD_LEN {
return None;
}
let len = u64::from_be_bytes(
buf[0..HOP_DATA_HEAD_LEN]
.try_into()
.expect("u64 from slice"),
);
usize::try_from(len).ok()?.checked_add(HOP_DATA_HEAD_LEN)
}
pub fn molecule_table_data_len(buf: &[u8]) -> Option<usize> {
if buf.len() < molecule::NUMBER_SIZE {
return None;
}
let len = molecule::unpack_number(buf) as usize;
if len < molecule::NUMBER_SIZE {
return None;
}
Some(len)
}
pub struct TrampolineOnionData;
impl TrampolineOnionData {
pub const CUSTOM_RECORD_KEY: u32 = USER_CUSTOM_RECORDS_MAX_INDEX + 2;
pub fn write(data: Vec<u8>, custom_records: &mut PaymentCustomRecords) {
custom_records.data.insert(Self::CUSTOM_RECORD_KEY, data);
}
pub fn read(custom_records: &PaymentCustomRecords) -> Option<Vec<u8>> {
custom_records.data.get(&Self::CUSTOM_RECORD_KEY).cloned()
}
}