use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
#[allow(unused_imports)]
use crate::prelude::*;
use crate::blinded_path::utils::{self, BlindedPathWithPadding};
use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode, NodeIdLookUp};
use crate::crypto::streams::ChaChaPolyReadAdapter;
use crate::io;
use crate::io::Cursor;
use crate::ln::channelmanager::{InterceptId, PaymentId};
use crate::ln::msgs::DecodeError;
use crate::ln::onion_utils;
use crate::offers::nonce::Nonce;
use crate::offers::offer::OfferId;
use crate::onion_message::packet::ControlTlvs;
use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph};
use crate::sign::{EntropySource, NodeSigner, ReceiveAuthKey, Recipient};
use crate::types::payment::PaymentHash;
use crate::util::scid_utils;
use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, Writeable, Writer};
use core::ops::Deref;
use core::time::Duration;
use core::{cmp, mem};
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BlindedMessagePath(pub(super) BlindedPath);
impl Writeable for BlindedMessagePath {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.0.write(w)
}
}
impl Readable for BlindedMessagePath {
fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
Ok(Self(BlindedPath::read(r)?))
}
}
impl BlindedMessagePath {
pub fn one_hop<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
recipient_node_id: PublicKey, local_node_receive_key: ReceiveAuthKey,
context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1<T>,
) -> Self
where
ES::Target: EntropySource,
{
Self::new(&[], recipient_node_id, local_node_receive_key, context, entropy_source, secp_ctx)
}
pub fn new<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey,
local_node_receive_key: ReceiveAuthKey, context: MessageContext, entropy_source: ES,
secp_ctx: &Secp256k1<T>,
) -> Self
where
ES::Target: EntropySource,
{
BlindedMessagePath::new_with_dummy_hops(
intermediate_nodes,
recipient_node_id,
0,
local_node_receive_key,
context,
entropy_source,
secp_ctx,
)
}
pub fn new_with_dummy_hops<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey,
dummy_hop_count: usize, local_node_receive_key: ReceiveAuthKey, context: MessageContext,
entropy_source: ES, secp_ctx: &Secp256k1<T>,
) -> Self
where
ES::Target: EntropySource,
{
let introduction_node = IntroductionNode::NodeId(
intermediate_nodes.first().map_or(recipient_node_id, |n| n.node_id),
);
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
let blinding_secret =
SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
Self(BlindedPath {
introduction_node,
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
blinded_hops: blinded_hops(
secp_ctx,
intermediate_nodes,
recipient_node_id,
dummy_hop_count,
context,
&blinding_secret,
local_node_receive_key,
),
})
}
pub fn use_compact_introduction_node(&mut self, network_graph: &ReadOnlyNetworkGraph) {
if let IntroductionNode::NodeId(pubkey) = &self.0.introduction_node {
let node_id = NodeId::from_pubkey(pubkey);
if let Some(node_info) = network_graph.node(&node_id) {
if let Some((scid, channel_info)) = node_info
.channels
.iter()
.filter_map(|scid| network_graph.channel(*scid).map(|info| (*scid, info)))
.min_by_key(|(scid, _)| scid_utils::block_from_scid(*scid))
{
let direction = if node_id == channel_info.node_one {
Direction::NodeOne
} else {
debug_assert_eq!(node_id, channel_info.node_two);
Direction::NodeTwo
};
self.0.introduction_node =
IntroductionNode::DirectedShortChannelId(direction, scid);
}
}
}
}
pub fn public_introduction_node_id<'a>(
&self, network_graph: &'a ReadOnlyNetworkGraph,
) -> Option<&'a NodeId> {
self.0.public_introduction_node_id(network_graph)
}
pub fn introduction_node(&self) -> &IntroductionNode {
&self.0.introduction_node
}
pub fn blinding_point(&self) -> PublicKey {
self.0.blinding_point
}
pub fn blinded_hops(&self) -> &[BlindedHop] {
&self.0.blinded_hops
}
pub fn advance_path_by_one<NS: Deref, NL: Deref, T>(
&mut self, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1<T>,
) -> Result<(), ()>
where
NS::Target: NodeSigner,
NL::Target: NodeIdLookUp,
T: secp256k1::Signing + secp256k1::Verification,
{
let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.0.blinding_point, None)?;
let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
let encrypted_control_tlvs = &self.0.blinded_hops.get(0).ok_or(())?.encrypted_payload;
let mut s = Cursor::new(encrypted_control_tlvs);
let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
match ChaChaPolyReadAdapter::read(&mut reader, rho) {
Ok(ChaChaPolyReadAdapter {
readable: ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override }),
}) => {
let next_node_id = match next_hop {
NextMessageHop::NodeId(pubkey) => pubkey,
NextMessageHop::ShortChannelId(scid) => match node_id_lookup.next_node_id(scid)
{
Some(pubkey) => pubkey,
None => return Err(()),
},
};
let mut new_blinding_point = match next_blinding_override {
Some(blinding_point) => blinding_point,
None => onion_utils::next_hop_pubkey(
secp_ctx,
self.0.blinding_point,
control_tlvs_ss.as_ref(),
)
.map_err(|_| ())?,
};
mem::swap(&mut self.0.blinding_point, &mut new_blinding_point);
self.0.introduction_node = IntroductionNode::NodeId(next_node_id);
self.0.blinded_hops.remove(0);
Ok(())
},
_ => Err(()),
}
}
pub(crate) fn introduction_node_mut(&mut self) -> &mut IntroductionNode {
&mut self.0.introduction_node
}
pub fn from_blinded_path(
introduction_node_id: PublicKey, blinding_point: PublicKey, blinded_hops: Vec<BlindedHop>,
) -> Self {
Self(BlindedPath {
introduction_node: IntroductionNode::NodeId(introduction_node_id),
blinding_point,
blinded_hops,
})
}
#[cfg(test)]
pub fn clear_blinded_hops(&mut self) {
self.0.blinded_hops.clear()
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum NextMessageHop {
NodeId(PublicKey),
ShortChannelId(u64),
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct MessageForwardNode {
pub node_id: PublicKey,
pub short_channel_id: Option<u64>,
}
pub(crate) struct ForwardTlvs {
pub(crate) next_hop: NextMessageHop,
pub(crate) next_blinding_override: Option<PublicKey>,
}
pub(crate) struct DummyTlv;
impl Writeable for DummyTlv {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
encode_tlv_stream!(writer, {
(65539, (), required),
});
Ok(())
}
}
pub(crate) struct ReceiveTlvs {
pub context: Option<MessageContext>,
}
impl Writeable for ForwardTlvs {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
let (next_node_id, short_channel_id) = match self.next_hop {
NextMessageHop::NodeId(pubkey) => (Some(pubkey), None),
NextMessageHop::ShortChannelId(scid) => (None, Some(scid)),
};
encode_tlv_stream!(writer, {
(2, short_channel_id, option),
(4, next_node_id, option),
(8, self.next_blinding_override, option)
});
Ok(())
}
}
impl Writeable for ReceiveTlvs {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
encode_tlv_stream!(writer, {
(65537, self.context, option),
});
Ok(())
}
}
#[derive(Clone, Debug)]
pub enum MessageContext {
Offers(OffersContext),
AsyncPayments(AsyncPaymentsContext),
DNSResolver(DNSResolverContext),
Custom(Vec<u8>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum OffersContext {
InvoiceRequest {
nonce: Nonce,
},
StaticInvoiceRequested {
recipient_id: Vec<u8>,
invoice_slot: u16,
path_absolute_expiry: Duration,
},
OutboundPayment {
payment_id: PaymentId,
nonce: Nonce,
},
InboundPayment {
payment_hash: PaymentHash,
},
}
#[derive(Clone, Debug)]
pub enum AsyncPaymentsContext {
OfferPathsRequest {
recipient_id: Vec<u8>,
path_absolute_expiry: Option<core::time::Duration>,
},
OfferPaths {
invoice_slot: u16,
path_absolute_expiry: core::time::Duration,
},
ServeStaticInvoice {
recipient_id: Vec<u8>,
invoice_slot: u16,
path_absolute_expiry: core::time::Duration,
},
StaticInvoicePersisted {
offer_id: OfferId,
invoice_created_at: core::time::Duration,
},
OutboundPayment {
payment_id: PaymentId,
},
InboundPayment {
path_absolute_expiry: core::time::Duration,
},
ReleaseHeldHtlc {
intercept_id: InterceptId,
prev_outbound_scid_alias: u64,
htlc_id: u64,
},
}
impl_writeable_tlv_based_enum!(MessageContext,
{0, Offers} => (),
{1, Custom} => (),
{2, AsyncPayments} => (),
{3, DNSResolver} => (),
);
impl_writeable_tlv_based_enum!(OffersContext,
(0, InvoiceRequest) => {
(0, nonce, required),
},
(1, OutboundPayment) => {
(0, payment_id, required),
(1, nonce, required),
},
(2, InboundPayment) => {
(0, payment_hash, required),
},
(3, StaticInvoiceRequested) => {
(0, recipient_id, required),
(2, invoice_slot, required),
(4, path_absolute_expiry, required),
},
);
impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
(0, OutboundPayment) => {
(0, payment_id, required),
},
(1, InboundPayment) => {
(4, path_absolute_expiry, required),
},
(2, OfferPaths) => {
(0, path_absolute_expiry, required),
(2, invoice_slot, required),
},
(3, StaticInvoicePersisted) => {
(0, offer_id, required),
(2, invoice_created_at, required),
},
(4, OfferPathsRequest) => {
(0, recipient_id, required),
(2, path_absolute_expiry, option),
},
(5, ServeStaticInvoice) => {
(0, recipient_id, required),
(2, invoice_slot, required),
(4, path_absolute_expiry, required),
},
(6, ReleaseHeldHtlc) => {
(0, intercept_id, required),
(2, prev_outbound_scid_alias, required),
(4, htlc_id, required),
},
);
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct DNSResolverContext {
pub nonce: [u8; 16],
}
impl_writeable_tlv_based!(DNSResolverContext, {
(0, nonce, required),
});
pub(crate) const MESSAGE_PADDING_ROUND_OFF: usize = 100;
pub const MAX_DUMMY_HOPS_COUNT: usize = 10;
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[MessageForwardNode],
recipient_node_id: PublicKey, dummy_hop_count: usize, context: MessageContext,
session_priv: &SecretKey, local_node_receive_key: ReceiveAuthKey,
) -> Vec<BlindedHop> {
let dummy_count = cmp::min(dummy_hop_count, MAX_DUMMY_HOPS_COUNT);
let pks = intermediate_nodes
.iter()
.map(|node| (node.node_id, None))
.chain(
core::iter::repeat((recipient_node_id, Some(local_node_receive_key))).take(dummy_count),
)
.chain(core::iter::once((recipient_node_id, Some(local_node_receive_key))));
let is_compact = intermediate_nodes.iter().any(|node| node.short_channel_id.is_some());
let tlvs = pks
.clone()
.skip(1) .zip(intermediate_nodes.iter().map(|node| node.short_channel_id))
.map(|((pubkey, _), scid)| match scid {
Some(scid) => NextMessageHop::ShortChannelId(scid),
None => NextMessageHop::NodeId(pubkey),
})
.map(|next_hop| {
ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None })
})
.chain((0..dummy_count).map(|_| ControlTlvs::Dummy))
.chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { context: Some(context) })));
if is_compact {
let path = pks.zip(tlvs);
utils::construct_blinded_hops(secp_ctx, path, session_priv)
} else {
let path =
pks.zip(tlvs.map(|tlv| BlindedPathWithPadding {
tlvs: tlv,
round_off: MESSAGE_PADDING_ROUND_OFF,
}));
utils::construct_blinded_hops(secp_ctx, path, session_priv)
}
}