From 8019842e89c7503df49464d9826016578b1e2b21 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Wed, 1 Sep 2021 16:48:35 +0000 Subject: [PATCH] XXX: Track SCID aliases from our counterparty and use them in invoices New `funding_locked` messages can include SCID aliases which our counterparty will recognize as "ours" for the purposes of relaying transactions to us. This avoids telling the world about our on-chain transactions every time we want to receive a payment, and will allow for receiving payments before the funding transaction appears on-chain. Here we store the new SCID aliases and use them in invoices instead of he "standard" SCIDs. XXX: Note in spec pr that we need to drop the "MUST NOT retransmit funding_locked" message in reconnection if we are passed initial reconnect. This may imply a feature bit, but will at least imply some kind of "MUST NOT retransmit if it doesn't have evidence the counterparty interpreted the SCID alias previously sent" (to avoid backwards incompat). --- lightning-invoice/src/utils.rs | 8 ++-- lightning/src/ln/channel.rs | 59 +++++++++++++++++++++++++----- lightning/src/ln/channelmanager.rs | 6 +++ lightning/src/routing/router.rs | 1 + 4 files changed, 62 insertions(+), 12 deletions(-) diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index 53fdc13f2..f23faff9e 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -39,9 +39,11 @@ where let our_channels = channelmanager.list_usable_channels(); let mut route_hints = vec![]; for channel in our_channels { - let short_channel_id = match channel.short_channel_id { - Some(id) => id, - None => continue, + let short_channel_id = if let Some(scid) = channel.inbound_scid_alias { scid } else { + match channel.short_channel_id { + Some(id) => id, + None => continue, + } }; let forwarding_info = match channel.counterparty.forwarding_info { Some(info) => info, diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 60e162d5a..c6e365c5d 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -655,6 +655,13 @@ pub(super) struct Channel { /// This channel's type, as negotiated during channel open channel_type: ChannelTypeFeatures, + + // Our counerparty can offer us SCID aliases which they will map to this channel when routing + // outbound payments. These can be used in invoice route hints to provide privacy of which + // on-chain transaction is ours. + // We only bother storing the most recent SCID alias at any time, though our counterparty has + // to store all of them. + latest_inbound_scid_alias: Option, } #[cfg(any(test, feature = "fuzztarget"))] @@ -900,6 +907,8 @@ impl Channel { workaround_lnd_bug_4006: None, + latest_inbound_scid_alias: None, + #[cfg(any(test, feature = "fuzztarget"))] historical_inbound_htlc_fulfills: HashSet::new(), @@ -1200,6 +1209,8 @@ impl Channel { workaround_lnd_bug_4006: None, + latest_inbound_scid_alias: None, + #[cfg(any(test, feature = "fuzztarget"))] historical_inbound_htlc_fulfills: HashSet::new(), @@ -2068,6 +2079,15 @@ impl Channel { return Err(ChannelError::Ignore("Peer sent funding_locked when we needed a channel_reestablish. The peer is likely lnd, see https://github.com/lightningnetwork/lnd/issues/4006".to_owned())); } + if let Some(scid_alias) = msg.short_channel_id_alias { + if Some(scid_alias.0) != self.short_channel_id { + // The scid alias provided can be used to route payments *from* our counterparty, + // i.e. can be used for inbound payments and provided in invoices, but is not used + // whrn routing outbound payments. + self.latest_inbound_scid_alias = Some(scid_alias.0); + } + } + let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS); if non_shutdown_state == ChannelState::FundingSent as u32 { @@ -2075,17 +2095,28 @@ impl Channel { } else if non_shutdown_state == (ChannelState::FundingSent as u32 | ChannelState::OurFundingLocked as u32) { self.channel_state = ChannelState::ChannelFunded as u32 | (self.channel_state & MULTI_STATE_FLAGS); self.update_time_counter += 1; - } else if (self.channel_state & (ChannelState::ChannelFunded as u32) != 0 && - // Note that funding_signed/funding_created will have decremented both by 1! - self.cur_holder_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 1 && - self.cur_counterparty_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 1) || - // If we reconnected before sending our funding locked they may still resend theirs: - (self.channel_state & (ChannelState::FundingSent as u32 | ChannelState::TheirFundingLocked as u32) == - (ChannelState::FundingSent as u32 | ChannelState::TheirFundingLocked as u32)) { - if self.counterparty_cur_commitment_point != Some(msg.next_per_commitment_point) { + } else if self.channel_state & (ChannelState::ChannelFunded as u32) != 0 || + // If we reconnected before sending our funding locked they may still resend theirs: + (self.channel_state & (ChannelState::FundingSent as u32 | ChannelState::TheirFundingLocked as u32) == + (ChannelState::FundingSent as u32 | ChannelState::TheirFundingLocked as u32)) + { + // They probably disconnected/reconnected and re-sent the funding_locked, which is + // required, or we're getting a fresh SCID alias. + let expected_point = + if self.cur_counterparty_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 1 { + // If they haven't ever sent an updated point, the point they send should match + // the current one. + self.counterparty_cur_commitment_point + } else { + // If they have sent updated points, funding_locked is always supposed to match + // their "first" point, which we re-derive here. + self.commitment_secrets.get_secret(INITIAL_COMMITMENT_NUMBER - 1) + .map(|secret| SecretKey::from_slice(&secret).ok()).flatten() + .map(|sk| PublicKey::from_secret_key(&self.secp_ctx, &sk)) + }; + if expected_point != Some(msg.next_per_commitment_point) { return Err(ChannelError::Close("Peer sent a reconnect funding_locked with a different point".to_owned())); } - // They probably disconnected/reconnected and re-sent the funding_locked, which is required return Ok(None); } else { return Err(ChannelError::Close("Peer sent a funding_locked at a strange time".to_owned())); @@ -4102,6 +4133,11 @@ impl Channel { self.short_channel_id } + /// Allowed in any state (including after shutdown) + pub fn get_latest_inbound_scid_alias(&self) -> Option { + self.latest_inbound_scid_alias + } + /// Returns the funding_txo we either got from our peer, or were given by /// get_outbound_funding_created. pub fn get_funding_txo(&self) -> Option { @@ -5632,6 +5668,7 @@ impl Writeable for Channel { (11, self.monitor_pending_finalized_fulfills, vec_type), (13, self.channel_creation_height, required), (15, self.announcement_sigs_state, required), + (17, self.latest_inbound_scid_alias, option), }); Ok(()) @@ -5876,6 +5913,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel // If we read an old Channel, for simplicity we just treat it as "we never sent an // AnnouncementSignatures" which implies we'll re-send it on reconnect, but that's fine. let mut announcement_sigs_state = Some(AnnouncementSigsState::NotSent); + let mut latest_inbound_scid_alias = None; read_tlv_fields!(reader, { (0, announcement_sigs, option), (1, minimum_depth, option), @@ -5889,6 +5927,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel (11, monitor_pending_finalized_fulfills, vec_type), (13, channel_creation_height, option), (15, announcement_sigs_state, option), + (17, latest_inbound_scid_alias, option), }); let chan_features = channel_type.as_ref().unwrap(); @@ -5997,6 +6036,8 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel workaround_lnd_bug_4006: None, + latest_inbound_scid_alias, + #[cfg(any(test, feature = "fuzztarget"))] historical_inbound_htlc_fulfills, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 3aa5c7b2d..b1ea5b2ea 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -1188,6 +1188,11 @@ pub struct ChannelDetails { /// The position of the funding transaction in the chain. None if the funding transaction has /// not yet been confirmed and the channel fully opened. pub short_channel_id: Option, + /// An optional [`short_channel_id`] alias for this channel, randomly generated by our + /// counterparty and usable in place of [`short_channel_id`] in invoice route hints. Our + /// counterparty will recognize the alias provided here in place of the [`short_channel_id`] + /// when they see a payment to be routed to us. + pub inbound_scid_alias: Option, /// The value, in satoshis, of this channel as appears in the funding output pub channel_value_satoshis: u64, /// The value, in satoshis, that must always be held in the channel for us. This value ensures @@ -1812,6 +1817,7 @@ impl ChannelMana }, funding_txo: channel.get_funding_txo(), short_channel_id: channel.get_short_channel_id(), + inbound_scid_alias: channel.get_latest_inbound_scid_alias(), channel_value_satoshis: channel.get_value_satoshis(), unspendable_punishment_reserve: to_self_reserve_satoshis, balance_msat, diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index e49844383..02ea58596 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -1519,6 +1519,7 @@ mod tests { }, funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }), short_channel_id, + inbound_scid_alias: None, channel_value_satoshis: 0, user_channel_id: 0, balance_msat: 0, -- 2.39.5