Track SCID aliases from our counterparty and use them in invoices
authorMatt Corallo <git@bluematt.me>
Tue, 1 Feb 2022 17:37:16 +0000 (17:37 +0000)
committerMatt Corallo <git@bluematt.me>
Wed, 9 Mar 2022 19:14:38 +0000 (19:14 +0000)
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.

fuzz/src/router.rs
lightning-invoice/src/utils.rs
lightning/src/ln/channel.rs
lightning/src/ln/channelmanager.rs
lightning/src/routing/router.rs

index 095059c5183676232c48b36093a244a9fe69ba3b..20ca399449c85c6d5233d1010c9ff13964de36bc 100644 (file)
@@ -216,6 +216,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
                                                                },
                                                                funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
                                                                short_channel_id: Some(scid),
+                                                               inbound_scid_alias: None,
                                                                channel_value_satoshis: slice_to_be64(get_slice!(8)),
                                                                user_channel_id: 0, inbound_capacity_msat: 0,
                                                                unspendable_punishment_reserve: None,
index fc82541a564e8cbd82c64f75fd37d4fa87ecd8af..73f0138824d446c5defca01a0880853822e08ff8 100644 (file)
@@ -65,7 +65,7 @@ pub fn create_phantom_invoice<Signer: Sign, K: Deref>(
 
        for hint in phantom_route_hints {
                for channel in &hint.channels {
-                       let short_channel_id = match channel.short_channel_id {
+                       let short_channel_id = match channel.get_inbound_payment_scid() {
                                Some(id) => id,
                                None => continue,
                        };
@@ -162,7 +162,7 @@ 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 {
+               let short_channel_id = match channel.get_inbound_payment_scid() {
                        Some(id) => id,
                        None => continue,
                };
@@ -313,6 +313,13 @@ mod test {
                assert_eq!(invoice.min_final_cltv_expiry(), MIN_FINAL_CLTV_EXPIRY as u64);
                assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
 
+               // Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is
+               // available.
+               assert_eq!(invoice.route_hints().len(), 1);
+               assert_eq!(invoice.route_hints()[0].0.len(), 1);
+               assert_eq!(invoice.route_hints()[0].0[0].short_channel_id,
+                       nodes[1].node.list_usable_channels()[0].inbound_scid_alias.unwrap());
+
                let payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key())
                        .with_features(invoice.features().unwrap().clone())
                        .with_route_hints(invoice.route_hints());
index 9935d3c019cbd3747394963186726b1e30fc3b40..99fb345be7d66e96f4ca8cbe9ac13244dbcadaa1 100644 (file)
@@ -695,6 +695,13 @@ pub(super) struct Channel<Signer: Sign> {
 
        /// This channel's type, as negotiated during channel open
        channel_type: ChannelTypeFeatures,
+
+       // Our counterparty 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 avoid explicitly revealing
+       // the channel's funding UTXO.
+       // 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<u64>,
 }
 
 #[cfg(any(test, fuzzing))]
@@ -947,6 +954,8 @@ impl<Signer: Sign> Channel<Signer> {
 
                        workaround_lnd_bug_4006: None,
 
+                       latest_inbound_scid_alias: None,
+
                        #[cfg(any(test, fuzzing))]
                        historical_inbound_htlc_fulfills: HashSet::new(),
 
@@ -1252,6 +1261,8 @@ impl<Signer: Sign> Channel<Signer> {
 
                        workaround_lnd_bug_4006: None,
 
+                       latest_inbound_scid_alias: None,
+
                        #[cfg(any(test, fuzzing))]
                        historical_inbound_htlc_fulfills: HashSet::new(),
 
@@ -2151,6 +2162,15 @@ impl<Signer: Sign> Channel<Signer> {
                        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) != 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
+                               // when routing outbound payments.
+                               self.latest_inbound_scid_alias = Some(scid_alias);
+                       }
+               }
+
                let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
 
                if non_shutdown_state == ChannelState::FundingSent as u32 {
@@ -4198,6 +4218,11 @@ impl<Signer: Sign> Channel<Signer> {
                self.short_channel_id
        }
 
+       /// Allowed in any state (including after shutdown)
+       pub fn latest_inbound_scid_alias(&self) -> Option<u64> {
+               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<OutPoint> {
@@ -5769,6 +5794,7 @@ impl<Signer: Sign> Writeable for Channel<Signer> {
                        (13, self.channel_creation_height, required),
                        (15, preimages, vec_type),
                        (17, self.announcement_sigs_state, required),
+                       (19, self.latest_inbound_scid_alias, option),
                });
 
                Ok(())
@@ -6024,6 +6050,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel<Signer>
                // 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),
@@ -6039,6 +6066,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel<Signer>
                        (13, channel_creation_height, option),
                        (15, preimages_opt, vec_type),
                        (17, announcement_sigs_state, option),
+                       (19, latest_inbound_scid_alias, option),
                });
 
                if let Some(preimages) = preimages_opt {
@@ -6173,6 +6201,8 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel<Signer>
 
                        workaround_lnd_bug_4006: None,
 
+                       latest_inbound_scid_alias,
+
                        #[cfg(any(test, fuzzing))]
                        historical_inbound_htlc_fulfills,
 
index 6d067c24a1edf40333e5f4800d77c793444be709..46bad1f56566cb0eb802fc60fe88592d66b5a3d8 100644 (file)
@@ -1188,7 +1188,20 @@ pub struct ChannelDetails {
        pub funding_txo: Option<OutPoint>,
        /// The position of the funding transaction in the chain. None if the funding transaction has
        /// not yet been confirmed and the channel fully opened.
+       ///
+       /// Note that if [`inbound_scid_alias`] is set, it must be used for invoices and inbound
+       /// payments instead of this. See [`get_inbound_payment_scid`].
+       ///
+       /// [`inbound_scid_alias`]: Self::inbound_scid_alias
+       /// [`get_inbound_payment_scid`]: Self::get_inbound_payment_scid
        pub short_channel_id: Option<u64>,
+       /// 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.
+       ///
+       /// [`short_channel_id`]: Self::short_channel_id
+       pub inbound_scid_alias: Option<u64>,
        /// 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
@@ -1274,6 +1287,15 @@ pub struct ChannelDetails {
        pub is_public: bool,
 }
 
+impl ChannelDetails {
+       /// Gets the SCID which should be used to identify this channel for inbound payments. This
+       /// should be used for providing invoice hints or in any other context where our counterparty
+       /// will forward a payment to us.
+       pub fn get_inbound_payment_scid(&self) -> Option<u64> {
+               self.inbound_scid_alias.or(self.short_channel_id)
+       }
+}
+
 /// If a payment fails to send, it can be in one of several states. This enum is returned as the
 /// Err() type describing which state the payment is in, see the description of individual enum
 /// states for more.
@@ -1833,6 +1855,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                                        },
                                        funding_txo: channel.get_funding_txo(),
                                        short_channel_id: channel.get_short_channel_id(),
+                                       inbound_scid_alias: channel.latest_inbound_scid_alias(),
                                        channel_value_satoshis: channel.get_value_satoshis(),
                                        unspendable_punishment_reserve: to_self_reserve_satoshis,
                                        balance_msat,
@@ -5973,6 +5996,7 @@ impl_writeable_tlv_based!(ChannelCounterparty, {
 });
 
 impl_writeable_tlv_based!(ChannelDetails, {
+       (1, inbound_scid_alias, option),
        (2, channel_id, required),
        (4, counterparty, required),
        (6, funding_txo, option),
index c45ce59ac13581a3312c535f5e143747fd52220e..870b1e1261fd93a47467fe85e3e098934e388c11 100644 (file)
@@ -1578,6 +1578,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,
@@ -5101,6 +5102,7 @@ mod benches {
                                txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0
                        }),
                        short_channel_id: Some(1),
+                       inbound_scid_alias: None,
                        channel_value_satoshis: 10_000_000,
                        user_channel_id: 0,
                        balance_msat: 10_000_000,