use util::config::UserConfig;
use util::events::{EventHandler, EventsProvider, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
use util::{byte_utils, events};
+use util::scid_utils::fake_scid;
use util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, MaybeReadable, Writeable, Writer};
use util::logger::{Level, Logger};
use util::errors::APIError;
inbound_payment_key: inbound_payment::ExpandedKey,
+ /// LDK puts the [fake scids] that it generates into namespaces, to identify the type of an
+ /// incoming payment. To make it harder for a third-party to identify the type of a payment,
+ /// we encrypt the namespace identifier using these bytes.
+ ///
+ /// [fake scids]: crate::util::scid_utils::fake_scid
+ fake_scid_rand_bytes: [u8; 32],
+
/// Used to track the last value sent in a node_announcement "timestamp" field. We ensure this
/// value increases strictly since we don't assume access to a time source.
last_node_announcement_serial: AtomicUsize,
},
}
+/// Route hints used in constructing invoices for [phantom node payents].
+///
+/// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
+pub struct PhantomRouteHints {
+ /// The list of channels to be included in the invoice route hints.
+ pub channels: Vec<ChannelDetails>,
+ /// A fake scid used for representing the phantom node's fake channel in generating the invoice
+ /// route hints.
+ pub phantom_scid: u64,
+ /// The pubkey of the real backing node that would ultimately receive the payment.
+ pub real_node_pubkey: PublicKey,
+}
+
macro_rules! handle_error {
($self: ident, $internal: expr, $counterparty_node_id: expr) => {
match $internal {
secp_ctx,
inbound_payment_key: expanded_inbound_key,
+ fake_scid_rand_bytes: keys_manager.get_secure_random_bytes(),
last_node_announcement_serial: AtomicUsize::new(0),
highest_seen_timestamp: AtomicUsize::new(0),
if let &PendingHTLCRouting::Forward { ref short_channel_id, .. } = routing {
let id_option = channel_state.as_ref().unwrap().short_to_id.get(&short_channel_id).cloned();
if let Some((err, code, chan_update)) = loop {
- let forwarding_id = match id_option {
+ let forwarding_id_opt = match id_option {
None => { // unknown_next_peer
- break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
+ // Note that this is likely a timing oracle for detecting whether an scid is a
+ // phantom.
+ if fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, *short_channel_id) {
+ None
+ } else {
+ break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
+ }
},
- Some(id) => id.clone(),
+ Some(id) => Some(id.clone()),
};
+ let (chan_update_opt, forwardee_cltv_expiry_delta) = if let Some(forwarding_id) = forwarding_id_opt {
+ let chan = channel_state.as_mut().unwrap().by_id.get_mut(&forwarding_id).unwrap();
+ // Leave channel updates as None for private channels.
+ let chan_update_opt = if chan.should_announce() {
+ Some(self.get_channel_update_for_unicast(chan).unwrap()) } else { None };
+ if !chan.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels {
+ // Note that the behavior here should be identical to the above block - we
+ // should NOT reveal the existence or non-existence of a private channel if
+ // we don't allow forwards outbound over them.
+ break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
+ }
- let chan = channel_state.as_mut().unwrap().by_id.get_mut(&forwarding_id).unwrap();
-
- if !chan.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels {
- // Note that the behavior here should be identical to the above block - we
- // should NOT reveal the existence or non-existence of a private channel if
- // we don't allow forwards outbound over them.
- break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
- }
+ // Note that we could technically not return an error yet here and just hope
+ // that the connection is reestablished or monitor updated by the time we get
+ // around to doing the actual forward, but better to fail early if we can and
+ // hopefully an attacker trying to path-trace payments cannot make this occur
+ // on a small/per-node/per-channel scale.
+ if !chan.is_live() { // channel_disabled
+ break Some(("Forwarding channel is not in a ready state.", 0x1000 | 20, chan_update_opt));
+ }
+ if *amt_to_forward < chan.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
+ break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, chan_update_opt));
+ }
+ let fee = amt_to_forward.checked_mul(chan.get_fee_proportional_millionths() as u64)
+ .and_then(|prop_fee| { (prop_fee / 1000000)
+ .checked_add(chan.get_outbound_forwarding_fee_base_msat() as u64) });
+ if fee.is_none() || msg.amount_msat < fee.unwrap() || (msg.amount_msat - fee.unwrap()) < *amt_to_forward { // fee_insufficient
+ break Some(("Prior hop has deviated from specified fees parameters or origin node has obsolete ones", 0x1000 | 12, chan_update_opt));
+ }
+ (chan_update_opt, chan.get_cltv_expiry_delta())
+ } else { (None, MIN_CLTV_EXPIRY_DELTA) };
- // Note that we could technically not return an error yet here and just hope
- // that the connection is reestablished or monitor updated by the time we get
- // around to doing the actual forward, but better to fail early if we can and
- // hopefully an attacker trying to path-trace payments cannot make this occur
- // on a small/per-node/per-channel scale.
- if !chan.is_live() { // channel_disabled
- break Some(("Forwarding channel is not in a ready state.", 0x1000 | 20, Some(self.get_channel_update_for_unicast(chan).unwrap())));
- }
- if *amt_to_forward < chan.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
- break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, Some(self.get_channel_update_for_unicast(chan).unwrap())));
- }
- let fee = amt_to_forward.checked_mul(chan.get_fee_proportional_millionths() as u64)
- .and_then(|prop_fee| { (prop_fee / 1000000)
- .checked_add(chan.get_outbound_forwarding_fee_base_msat() as u64) });
- if fee.is_none() || msg.amount_msat < fee.unwrap() || (msg.amount_msat - fee.unwrap()) < *amt_to_forward { // fee_insufficient
- break Some(("Prior hop has deviated from specified fees parameters or origin node has obsolete ones", 0x1000 | 12, Some(self.get_channel_update_for_unicast(chan).unwrap())));
- }
- if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + chan.get_cltv_expiry_delta() as u64 { // incorrect_cltv_expiry
- break Some(("Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta", 0x1000 | 13, Some(self.get_channel_update_for_unicast(chan).unwrap())));
+ if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + forwardee_cltv_expiry_delta as u64 { // incorrect_cltv_expiry
+ break Some(("Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta", 0x1000 | 13, chan_update_opt));
}
let cur_height = self.best_block.read().unwrap().height() + 1;
// Theoretically, channel counterparty shouldn't send us a HTLC expiring now,
// but we want to be robust wrt to counterparty packet sanitization (see
// HTLC_FAIL_BACK_BUFFER rationale).
if msg.cltv_expiry <= cur_height + HTLC_FAIL_BACK_BUFFER as u32 { // expiry_too_soon
- break Some(("CLTV expiry is too close", 0x1000 | 14, Some(self.get_channel_update_for_unicast(chan).unwrap())));
+ break Some(("CLTV expiry is too close", 0x1000 | 14, chan_update_opt));
}
if msg.cltv_expiry > cur_height + CLTV_FAR_FAR_AWAY as u32 { // expiry_too_far
break Some(("CLTV expiry is too far in the future", 21, None));
// but there is no need to do that, and since we're a bit conservative with our
// risk threshold it just results in failing to forward payments.
if (*outgoing_cltv_value) as u64 <= (cur_height + LATENCY_GRACE_PERIOD_BLOCKS) as u64 {
- break Some(("Outgoing CLTV value is too soon", 0x1000 | 14, Some(self.get_channel_update_for_unicast(chan).unwrap())));
+ break Some(("Outgoing CLTV value is too soon", 0x1000 | 14, chan_update_opt));
}
break None;
let mut new_events = Vec::new();
let mut failed_forwards = Vec::new();
+ let mut phantom_receives: Vec<(u64, OutPoint, Vec<(PendingHTLCInfo, u64)>)> = Vec::new();
let mut handle_errors = Vec::new();
{
let mut channel_state_lock = self.channel_state.lock().unwrap();
let forward_chan_id = match channel_state.short_to_id.get(&short_chan_id) {
Some(chan_id) => chan_id.clone(),
None => {
- failed_forwards.reserve(pending_forwards.len());
for forward_info in pending_forwards.drain(..) {
match forward_info {
- HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info,
- prev_funding_outpoint } => {
- let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData {
- short_channel_id: prev_short_channel_id,
- outpoint: prev_funding_outpoint,
- htlc_id: prev_htlc_id,
- incoming_packet_shared_secret: forward_info.incoming_shared_secret,
- });
- failed_forwards.push((htlc_source, forward_info.payment_hash,
- HTLCFailReason::Reason { failure_code: 0x4000 | 10, data: Vec::new() }
- ));
- },
+ HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo {
+ routing, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value },
+ prev_funding_outpoint } => {
+ macro_rules! fail_forward {
+ ($msg: expr, $err_code: expr, $err_data: expr) => {
+ {
+ log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg);
+ let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData {
+ short_channel_id: short_chan_id,
+ outpoint: prev_funding_outpoint,
+ htlc_id: prev_htlc_id,
+ incoming_packet_shared_secret: incoming_shared_secret,
+ });
+ failed_forwards.push((htlc_source, payment_hash,
+ HTLCFailReason::Reason { failure_code: $err_code, data: $err_data }
+ ));
+ continue;
+ }
+ }
+ }
+ if let PendingHTLCRouting::Forward { onion_packet, .. } = routing {
+ let phantom_secret_res = self.keys_manager.get_node_secret(Recipient::PhantomNode);
+ if phantom_secret_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id) {
+ let shared_secret = {
+ let mut arr = [0; 32];
+ arr.copy_from_slice(&SharedSecret::new(&onion_packet.public_key.unwrap(), &phantom_secret_res.unwrap())[..]);
+ arr
+ };
+ let next_hop = match onion_utils::decode_next_hop(shared_secret, &onion_packet.hop_data, onion_packet.hmac, payment_hash) {
+ Ok(res) => res,
+ Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
+ fail_forward!(err_msg, err_code, Vec::new());
+ },
+ Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code }) => {
+ fail_forward!(err_msg, err_code, Vec::new());
+ },
+ };
+ match next_hop {
+ onion_utils::Hop::Receive(hop_data) => {
+ match self.construct_recv_pending_htlc_info(hop_data, shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value) {
+ Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, vec![(info, prev_htlc_id)])),
+ Err(ReceiveError { err_code, err_data, msg }) => fail_forward!(msg, err_code, err_data)
+ }
+ },
+ _ => panic!(),
+ }
+ } else {
+ fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new());
+ }
+ } else {
+ fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new());
+ }
+ },
HTLCForwardInfo::FailHTLC { .. } => {
// Channel went away before we could fail it. This implies
// the channel is now on chain and our counterparty is
// trying to broadcast the HTLC-Timeout, but that's their
// problem, not ours.
+ //
+ // `fail_htlc_backwards_internal` is never called for
+ // phantom payments, so this is unreachable for them.
}
}
}
for (htlc_source, payment_hash, failure_reason) in failed_forwards.drain(..) {
self.fail_htlc_backwards_internal(self.channel_state.lock().unwrap(), htlc_source, &payment_hash, failure_reason);
}
+ self.forward_htlcs(&mut phantom_receives);
for (counterparty_node_id, err) in handle_errors.drain(..) {
let _ = handle_error!(self, err, counterparty_node_id);
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
}
+ /// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids
+ /// are used when constructing the phantom invoice's route hints.
+ ///
+ /// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
+ pub fn get_phantom_scid(&self) -> u64 {
+ let mut channel_state = self.channel_state.lock().unwrap();
+ let best_block = self.best_block.read().unwrap();
+ loop {
+ let scid_candidate = fake_scid::get_phantom_scid(&self.fake_scid_rand_bytes, best_block.height(), &self.genesis_hash, &self.keys_manager);
+ // Ensure the generated scid doesn't conflict with a real channel.
+ match channel_state.short_to_id.entry(scid_candidate) {
+ hash_map::Entry::Occupied(_) => continue,
+ hash_map::Entry::Vacant(_) => return scid_candidate
+ }
+ }
+ }
+
+ /// Gets route hints for use in receiving [phantom node payments].
+ ///
+ /// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
+ pub fn get_phantom_route_hints(&self) -> PhantomRouteHints {
+ PhantomRouteHints {
+ channels: self.list_usable_channels(),
+ phantom_scid: self.get_phantom_scid(),
+ real_node_pubkey: self.get_our_node_id(),
+ }
+ }
+
#[cfg(any(test, feature = "fuzztarget", feature = "_test_utils"))]
pub fn get_and_clear_pending_events(&self) -> Vec<events::Event> {
let events = core::cell::RefCell::new(Vec::new());
(32, is_public, required),
});
+impl_writeable_tlv_based!(PhantomRouteHints, {
+ (2, channels, vec_type),
+ (4, phantom_scid, required),
+ (6, real_node_pubkey, required),
+});
+
impl_writeable_tlv_based_enum!(PendingHTLCRouting,
(0, Forward) => {
(0, onion_packet, required),
write_tlv_fields!(writer, {
(1, pending_outbound_payments_no_retry, required),
(3, pending_outbound_payments, required),
- (5, self.our_network_pubkey, required)
+ (5, self.our_network_pubkey, required),
+ (7, self.fake_scid_rand_bytes, required),
});
Ok(())
let mut pending_outbound_payments_no_retry: Option<HashMap<PaymentId, HashSet<[u8; 32]>>> = None;
let mut pending_outbound_payments = None;
let mut received_network_pubkey: Option<PublicKey> = None;
+ let mut fake_scid_rand_bytes: Option<[u8; 32]> = None;
read_tlv_fields!(reader, {
(1, pending_outbound_payments_no_retry, option),
(3, pending_outbound_payments, option),
- (5, received_network_pubkey, option)
+ (5, received_network_pubkey, option),
+ (7, fake_scid_rand_bytes, option),
});
+ if fake_scid_rand_bytes.is_none() {
+ fake_scid_rand_bytes = Some(args.keys_manager.get_secure_random_bytes());
+ }
if pending_outbound_payments.is_none() && pending_outbound_payments_no_retry.is_none() {
pending_outbound_payments = Some(pending_outbound_payments_compat);
inbound_payment_key: expanded_inbound_key,
pending_inbound_payments: Mutex::new(pending_inbound_payments),
pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()),
+ fake_scid_rand_bytes: fake_scid_rand_bytes.unwrap(),
our_network_key,
our_network_pubkey,