Implement receiving keysend payments
authorValentine Wallace <vwallace@protonmail.com>
Sun, 4 Jul 2021 21:42:26 +0000 (17:42 -0400)
committerValentine Wallace <vwallace@protonmail.com>
Tue, 27 Jul 2021 19:15:24 +0000 (15:15 -0400)
lightning/src/ln/channelmanager.rs

index 3c8330811bc567ce0fd9717f6605ff0a4a0b1c09..b31e691c861e1f311b4ba9b9f35250555b857f31 100644 (file)
@@ -157,14 +157,20 @@ pub(crate) struct HTLCPreviousHopData {
        outpoint: OutPoint,
 }
 
-struct ClaimableHTLC {
-       prev_hop: HTLCPreviousHopData,
-       value: u64,
+enum OnionPayload {
        /// Contains a total_msat (which may differ from value if this is a Multi-Path Payment) and a
        /// payment_secret which prevents path-probing attacks and can associate different HTLCs which
        /// are part of the same payment.
-       payment_data: msgs::FinalOnionHopData,
+       Invoice(msgs::FinalOnionHopData),
+       /// Contains the payer-provided preimage.
+       Spontaneous(PaymentPreimage),
+}
+
+struct ClaimableHTLC {
+       prev_hop: HTLCPreviousHopData,
        cltv_expiry: u32,
+       value: u64,
+       onion_payload: OnionPayload,
 }
 
 /// Tracks the inbound corresponding to an outbound HTLC
@@ -1475,15 +1481,37 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                                return_err!("Upstream node set CLTV to the wrong value", 18, &byte_utils::be32_to_array(msg.cltv_expiry));
                        }
 
-                       let payment_data = match next_hop_data.format {
-                               msgs::OnionHopDataFormat::Legacy { .. } => None,
+                       let routing = match next_hop_data.format {
+                               msgs::OnionHopDataFormat::Legacy { .. } => return_err!("We require payment_secrets", 0x4000|0x2000|3, &[0;0]),
                                msgs::OnionHopDataFormat::NonFinalNode { .. } => return_err!("Got non final data with an HMAC of 0", 0x4000 | 22, &[0;0]),
-                               msgs::OnionHopDataFormat::FinalNode { payment_data, .. } => payment_data,
-                       };
+                               msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage } => {
+                                       if payment_data.is_some() && keysend_preimage.is_some() {
+                                               return_err!("We don't support MPP keysend payments", 0x4000|22, &[0;0]);
+                                       } else if let Some(data) = payment_data {
+                                               PendingHTLCRouting::Receive {
+                                                       payment_data: data,
+                                                       incoming_cltv_expiry: msg.cltv_expiry,
+                                               }
+                                       } else if let Some(payment_preimage) = keysend_preimage {
+                                               // We need to check that the sender knows the keysend preimage before processing this
+                                               // payment further. Otherwise, an intermediary routing hop forwarding non-keysend-HTLC X
+                                               // could discover the final destination of X, by probing the adjacent nodes on the route
+                                               // with a keysend payment of identical payment hash to X and observing the processing
+                                               // time discrepancies due to a hash collision with X.
+                                               let hashed_preimage = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
+                                               if hashed_preimage != msg.payment_hash {
+                                                       return_err!("Payment preimage didn't match payment hash", 0x4000|22, &[0;0]);
+                                               }
 
-                       if payment_data.is_none() {
-                               return_err!("We require payment_secrets", 0x4000|0x2000|3, &[0;0]);
-                       }
+                                               PendingHTLCRouting::ReceiveKeysend {
+                                                       payment_preimage,
+                                                       incoming_cltv_expiry: msg.cltv_expiry,
+                                               }
+                                       } else {
+                                               return_err!("We require payment_secrets", 0x4000|0x2000|3, &[0;0]);
+                                       }
+                               },
+                       };
 
                        // Note that we could obviously respond immediately with an update_fulfill_htlc
                        // message, however that would leak that we are the recipient of this payment, so
@@ -1491,10 +1519,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                        // delay) once they've send us a commitment_signed!
 
                        PendingHTLCStatus::Forward(PendingHTLCInfo {
-                               routing: PendingHTLCRouting::Receive {
-                                       payment_data: payment_data.unwrap(),
-                                       incoming_cltv_expiry: msg.cltv_expiry,
-                               },
+                               routing,
                                payment_hash: msg.payment_hash.clone(),
                                incoming_shared_secret: shared_secret,
                                amt_to_forward: next_hop_data.amt_to_forward,
@@ -2252,9 +2277,17 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                                        for forward_info in pending_forwards.drain(..) {
                                                match forward_info {
                                                        HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo {
-                                                                       routing: PendingHTLCRouting::Receive { payment_data, incoming_cltv_expiry },
-                                                                       incoming_shared_secret, payment_hash, amt_to_forward, .. },
+                                                                       routing, incoming_shared_secret, payment_hash, amt_to_forward, .. },
                                                                        prev_funding_outpoint } => {
+                                                               let (cltv_expiry, onion_payload) = match routing {
+                                                                       PendingHTLCRouting::Receive { payment_data, incoming_cltv_expiry } =>
+                                                                               (incoming_cltv_expiry, OnionPayload::Invoice(payment_data)),
+                                                                       PendingHTLCRouting::ReceiveKeysend { payment_preimage, incoming_cltv_expiry } =>
+                                                                               (incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage)),
+                                                                       _ => {
+                                                                               panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
+                                                                       }
+                                                               };
                                                                let claimable_htlc = ClaimableHTLC {
                                                                        prev_hop: HTLCPreviousHopData {
                                                                                short_channel_id: prev_short_channel_id,
@@ -2263,8 +2296,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                                                                                incoming_packet_shared_secret: incoming_shared_secret,
                                                                        },
                                                                        value: amt_to_forward,
-                                                                       payment_data: payment_data.clone(),
-                                                                       cltv_expiry: incoming_cltv_expiry,
+                                                                       cltv_expiry,
+                                                                       onion_payload,
                                                                };
 
                                                                macro_rules! fail_htlc {
@@ -2293,10 +2326,38 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                                                                let mut payment_secrets = self.pending_inbound_payments.lock().unwrap();
                                                                match payment_secrets.entry(payment_hash) {
                                                                        hash_map::Entry::Vacant(_) => {
-                                                                               log_trace!(self.logger, "Failing new HTLC with payment_hash {} as we didn't have a corresponding inbound payment.", log_bytes!(payment_hash.0));
-                                                                               fail_htlc!(claimable_htlc);
+                                                                               match claimable_htlc.onion_payload {
+                                                                                       OnionPayload::Invoice(_) => {
+                                                                                               log_trace!(self.logger, "Failing new HTLC with payment_hash {} as we didn't have a corresponding inbound payment.", log_bytes!(payment_hash.0));
+                                                                                               fail_htlc!(claimable_htlc);
+                                                                                       },
+                                                                                       OnionPayload::Spontaneous(preimage) => {
+                                                                                               match channel_state.claimable_htlcs.entry(payment_hash) {
+                                                                                                       hash_map::Entry::Vacant(e) => {
+                                                                                                               e.insert(vec![claimable_htlc]);
+                                                                                                               new_events.push(events::Event::PaymentReceived {
+                                                                                                                       payment_hash,
+                                                                                                                       amt: amt_to_forward,
+                                                                                                                       purpose: events::PaymentPurpose::SpontaneousPayment(preimage),
+                                                                                                               });
+                                                                                                       },
+                                                                                                       hash_map::Entry::Occupied(_) => {
+                                                                                                               log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} for a duplicative payment hash", log_bytes!(payment_hash.0));
+                                                                                                               fail_htlc!(claimable_htlc);
+                                                                                                       }
+                                                                                               }
+                                                                                       }
+                                                                               }
                                                                        },
                                                                        hash_map::Entry::Occupied(inbound_payment) => {
+                                                                               let payment_data =
+                                                                                       if let OnionPayload::Invoice(ref data) = claimable_htlc.onion_payload {
+                                                                                               data.clone()
+                                                                                       } else {
+                                                                                               log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} because we already have an inbound payment with the same payment hash", log_bytes!(payment_hash.0));
+                                                                                               fail_htlc!(claimable_htlc);
+                                                                                               continue
+                                                                                       };
                                                                                if inbound_payment.get().payment_secret != payment_data.payment_secret {
                                                                                        log_trace!(self.logger, "Failing new HTLC with payment_hash {} as it didn't match our expected payment secret.", log_bytes!(payment_hash.0));
                                                                                        fail_htlc!(claimable_htlc);
@@ -2308,15 +2369,27 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                                                                                        let mut total_value = 0;
                                                                                        let htlcs = channel_state.claimable_htlcs.entry(payment_hash)
                                                                                                .or_insert(Vec::new());
+                                                                                       if htlcs.len() == 1 {
+                                                                                               if let OnionPayload::Spontaneous(_) = htlcs[0].onion_payload {
+                                                                                                       log_trace!(self.logger, "Failing new HTLC with payment_hash {} as we already had an existing keysend HTLC with the same payment hash", log_bytes!(payment_hash.0));
+                                                                                                       fail_htlc!(claimable_htlc);
+                                                                                                       continue
+                                                                                               }
+                                                                                       }
                                                                                        htlcs.push(claimable_htlc);
                                                                                        for htlc in htlcs.iter() {
                                                                                                total_value += htlc.value;
-                                                                                               if htlc.payment_data.total_msat != payment_data.total_msat {
-                                                                                                       log_trace!(self.logger, "Failing HTLCs with payment_hash {} as the HTLCs had inconsistent total values (eg {} and {})",
-                                                                                                               log_bytes!(payment_hash.0), payment_data.total_msat, htlc.payment_data.total_msat);
-                                                                                                       total_value = msgs::MAX_VALUE_MSAT;
+                                                                                               match &htlc.onion_payload {
+                                                                                                       OnionPayload::Invoice(htlc_payment_data) => {
+                                                                                                               if htlc_payment_data.total_msat != payment_data.total_msat {
+                                                                                                                       log_trace!(self.logger, "Failing HTLCs with payment_hash {} as the HTLCs had inconsistent total values (eg {} and {})",
+                                                                                                                                                                log_bytes!(payment_hash.0), payment_data.total_msat, htlc_payment_data.total_msat);
+                                                                                                                       total_value = msgs::MAX_VALUE_MSAT;
+                                                                                                               }
+                                                                                                               if total_value >= msgs::MAX_VALUE_MSAT { break; }
+                                                                                                       },
+                                                                                                       _ => unreachable!(),
                                                                                                }
-                                                                                               if total_value >= msgs::MAX_VALUE_MSAT { break; }
                                                                                        }
                                                                                        if total_value >= msgs::MAX_VALUE_MSAT || total_value > payment_data.total_msat {
                                                                                                log_trace!(self.logger, "Failing HTLCs with payment_hash {} as the total value {} ran over expected value {} (or HTLCs were inconsistent)",
@@ -2347,9 +2420,6 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                                                                        },
                                                                };
                                                        },
-                                                       HTLCForwardInfo::AddHTLC { .. } => {
-                                                               panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
-                                                       },
                                                        HTLCForwardInfo::FailHTLC { .. } => {
                                                                panic!("Got pending fail of our own HTLC");
                                                        }
@@ -4509,12 +4579,63 @@ impl_writeable_tlv_based!(HTLCPreviousHopData, {
        (6, incoming_packet_shared_secret, required)
 });
 
-impl_writeable_tlv_based!(ClaimableHTLC, {
-       (0, prev_hop, required),
-       (2, value, required),
-       (4, payment_data, required),
-       (6, cltv_expiry, required),
-});
+impl Writeable for ClaimableHTLC {
+       fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
+               let payment_data = match &self.onion_payload {
+                       OnionPayload::Invoice(data) => Some(data.clone()),
+                       _ => None,
+               };
+               let keysend_preimage = match self.onion_payload {
+                       OnionPayload::Invoice(_) => None,
+                       OnionPayload::Spontaneous(preimage) => Some(preimage.clone()),
+               };
+               write_tlv_fields!
+               (writer,
+                {
+                  (0, self.prev_hop, required), (2, self.value, required),
+                  (4, payment_data, option), (6, self.cltv_expiry, required),
+                        (8, keysend_preimage, option),
+                });
+               Ok(())
+       }
+}
+
+impl Readable for ClaimableHTLC {
+       fn read<R: Read>(reader: &mut R) -> Result<Self, DecodeError> {
+               let mut prev_hop = ::util::ser::OptionDeserWrapper(None);
+               let mut value = 0;
+               let mut payment_data: Option<msgs::FinalOnionHopData> = None;
+               let mut cltv_expiry = 0;
+               let mut keysend_preimage: Option<PaymentPreimage> = None;
+               read_tlv_fields!
+               (reader,
+                {
+                  (0, prev_hop, required), (2, value, required),
+                  (4, payment_data, option), (6, cltv_expiry, required),
+                        (8, keysend_preimage, option)
+                });
+               let onion_payload = match keysend_preimage {
+                       Some(p) => {
+                               if payment_data.is_some() {
+                                       return Err(DecodeError::InvalidValue)
+                               }
+                               OnionPayload::Spontaneous(p)
+                       },
+                       None => {
+                               if payment_data.is_none() {
+                                       return Err(DecodeError::InvalidValue)
+                               }
+                               OnionPayload::Invoice(payment_data.unwrap())
+                       },
+               };
+               Ok(Self {
+                       prev_hop: prev_hop.0.unwrap(),
+                       value,
+                       onion_payload,
+                       cltv_expiry,
+               })
+       }
+}
 
 impl_writeable_tlv_based_enum!(HTLCSource,
        (0, OutboundRoute) => {