diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 011b7f595bc..78898eaf966 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -32,6 +32,7 @@ use crate::ln::types::ChannelId; use crate::offers::invoice::Bolt12Invoice; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::static_invoice::StaticInvoice; +use crate::onion_message::dns_resolution::DNSSECProof; use crate::onion_message::messenger::Responder; use crate::routing::gossip::NetworkUpdate; use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters}; @@ -1101,6 +1102,12 @@ pub enum Event { /// /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice bolt12_invoice: Option, + /// The DNSSEC proof for BIP 353 proof of payment, if this payment originated from + /// a Human Readable Name resolution. This proof, combined with the [`Bolt12Invoice`], + /// provides a complete chain of proof from the DNS name to the payment. + /// + /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice + dnssec_proof: Option, }, /// Indicates an outbound payment failed. Individual [`Event::PaymentPathFailed`] events /// provide failure information for each path attempt in the payment, including retries. @@ -1973,6 +1980,7 @@ impl Writeable for Event { ref amount_msat, ref fee_paid_msat, ref bolt12_invoice, + ref dnssec_proof, } => { 2u8.write(writer)?; write_tlv_fields!(writer, { @@ -1982,6 +1990,7 @@ impl Writeable for Event { (5, fee_paid_msat, option), (7, amount_msat, option), (9, bolt12_invoice, option), + (11, dnssec_proof, option), }); }, &Event::PaymentPathFailed { @@ -2474,6 +2483,7 @@ impl MaybeReadable for Event { let mut amount_msat = None; let mut fee_paid_msat = None; let mut bolt12_invoice = None; + let mut dnssec_proof: Option = None; read_tlv_fields!(reader, { (0, payment_preimage, required), (1, payment_hash, option), @@ -2481,6 +2491,7 @@ impl MaybeReadable for Event { (5, fee_paid_msat, option), (7, amount_msat, option), (9, bolt12_invoice, option), + (11, dnssec_proof, option), }); if payment_hash.is_none() { payment_hash = Some(PaymentHash( @@ -2494,6 +2505,7 @@ impl MaybeReadable for Event { amount_msat, fee_paid_msat, bolt12_invoice, + dnssec_proof, })) }; f() diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 8b05d984e30..b15b7d45613 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -16535,6 +16535,7 @@ mod tests { first_hop_htlc_msat: 548, payment_id: PaymentId([42; 32]), bolt12_invoice: None, + dnssec_proof: None, }, skimmed_fee_msat: None, blinding_point: None, @@ -16986,6 +16987,7 @@ mod tests { first_hop_htlc_msat: 0, payment_id: PaymentId([42; 32]), bolt12_invoice: None, + dnssec_proof: None, }; let dummy_outbound_output = OutboundHTLCOutput { htlc_id: 0, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index f9772bb120b..1bba6ff3e61 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -108,7 +108,7 @@ use crate::onion_message::async_payments::{ AsyncPaymentsMessage, AsyncPaymentsMessageHandler, HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ReleaseHeldHtlc, ServeStaticInvoice, StaticInvoicePersisted, }; -use crate::onion_message::dns_resolution::HumanReadableName; +use crate::onion_message::dns_resolution::{DNSSECProof, HumanReadableName}; use crate::onion_message::messenger::{ MessageRouter, MessageSendInstructions, Responder, ResponseInstruction, }; @@ -836,6 +836,10 @@ mod fuzzy_channelmanager { /// we can provide proof-of-payment details in payment claim events even after a restart /// with a stale ChannelManager state. bolt12_invoice: Option, + /// The DNSSEC proof for BIP 353 proof of payment, if this payment originated from + /// a Human Readable Name resolution. Stored here to ensure we can provide it in + /// payment claim events even after a restart with a stale ChannelManager state. + dnssec_proof: Option, }, } @@ -909,6 +913,7 @@ impl core::hash::Hash for HTLCSource { payment_id, first_hop_htlc_msat, bolt12_invoice, + dnssec_proof, } => { 1u8.hash(hasher); path.hash(hasher); @@ -916,6 +921,7 @@ impl core::hash::Hash for HTLCSource { payment_id.hash(hasher); first_hop_htlc_msat.hash(hasher); bolt12_invoice.hash(hasher); + dnssec_proof.hash(hasher); }, HTLCSource::TrampolineForward { previous_hop_data, @@ -943,6 +949,7 @@ impl HTLCSource { first_hop_htlc_msat: 0, payment_id: PaymentId([2; 32]), bolt12_invoice: None, + dnssec_proof: None, } } @@ -5394,6 +5401,7 @@ impl< keysend_preimage, invoice_request: None, bolt12_invoice: None, + dnssec_proof: None, session_priv_bytes, hold_htlc_at_next_hop: false, }) @@ -5409,6 +5417,7 @@ impl< keysend_preimage, invoice_request, bolt12_invoice, + dnssec_proof, session_priv_bytes, hold_htlc_at_next_hop, } = args; @@ -5485,6 +5494,7 @@ impl< first_hop_htlc_msat: htlc_msat, payment_id, bolt12_invoice: bolt12_invoice.cloned(), + dnssec_proof: dnssec_proof.cloned(), }; let send_res = chan.send_htlc_and_commit( htlc_msat, @@ -9614,7 +9624,8 @@ impl< ComplFunc: FnOnce( Option, bool, - ) -> (Option, Option), + ) + -> (Option, Option), >( &self, prev_hop: HTLCPreviousHopData, payment_preimage: PaymentPreimage, payment_info: Option, attribution_data: Option, @@ -9652,7 +9663,8 @@ impl< ComplFunc: FnOnce( Option, bool, - ) -> (Option, Option), + ) + -> (Option, Option), >( &self, prev_hop: HTLCClaimSource, payment_preimage: PaymentPreimage, payment_info: Option, attribution_data: Option, @@ -9952,7 +9964,12 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let htlc_id = SentHTLCId::from_source(&source); match source { HTLCSource::OutboundRoute { - session_priv, payment_id, path, bolt12_invoice, .. + session_priv, + payment_id, + path, + bolt12_invoice, + dnssec_proof, + .. } => { debug_assert!(!startup_replay, "We don't support claim_htlc claims during startup - monitors may not be available yet"); @@ -9984,6 +10001,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ payment_id, payment_preimage, bolt12_invoice, + dnssec_proof, session_priv, path, from_onchain, @@ -17386,6 +17404,7 @@ impl< #[rustfmt::skip] fn handle_dnssec_proof(&self, message: DNSSECProof, context: DNSResolverContext) { + let proof = message.clone(); let offer_opt = self.flow.hrn_resolver.handle_dnssec_proof_for_offer(message, context); #[cfg_attr(not(feature = "_test_utils"), allow(unused_mut))] if let Some((completed_requests, mut offer)) = offer_opt { @@ -17404,7 +17423,12 @@ impl< .received_offer(payment_id, Some(retryable_invoice_request)) .map_err(|_| Bolt12SemanticError::DuplicatePaymentId) }); - if offer_pay_res.is_err() { + if offer_pay_res.is_ok() { + // Store the DNSSEC proof for BIP 353 proof of payment. The proof is + // now attached to the AwaitingInvoice state and will be carried through + // to the PaymentSent event. + self.pending_outbound_payments.set_dnssec_proof(payment_id, proof.clone()); + } else { // The offer we tried to pay is the canonical current offer for the name we // wanted to pay. If we can't pay it, there's no way to recover so fail the // payment. @@ -17767,6 +17791,7 @@ impl Readable for HTLCSource { let mut payment_params: Option = None; let mut blinded_tail: Option = None; let mut bolt12_invoice: Option = None; + let mut dnssec_proof: Option = None; read_tlv_fields!(reader, { (0, session_priv, required), (1, payment_id, option), @@ -17775,6 +17800,7 @@ impl Readable for HTLCSource { (5, payment_params, (option: ReadableArgs, 0)), (6, blinded_tail, option), (7, bolt12_invoice, option), + (9, dnssec_proof, option), }); if payment_id.is_none() { // For backwards compat, if there was no payment_id written, use the session_priv bytes @@ -17798,6 +17824,7 @@ impl Readable for HTLCSource { path, payment_id: payment_id.unwrap(), bolt12_invoice, + dnssec_proof, }) } 1 => Ok(HTLCSource::PreviousHopData(Readable::read(reader)?)), @@ -17817,6 +17844,7 @@ impl Writeable for HTLCSource { ref path, payment_id, bolt12_invoice, + dnssec_proof, } => { 0u8.write(writer)?; let payment_id_opt = Some(payment_id); @@ -17829,6 +17857,7 @@ impl Writeable for HTLCSource { (5, None::, option), // payment_params in LDK versions prior to 0.0.115 (6, path.blinded_tail, option), (7, bolt12_invoice, option), + (9, dnssec_proof, option), }); }, HTLCSource::PreviousHopData(ref field) => { @@ -19687,6 +19716,7 @@ impl< session_priv, path, bolt12_invoice, + dnssec_proof, .. } => { if let Some(preimage) = preimage_opt { @@ -19704,6 +19734,7 @@ impl< payment_id, preimage, bolt12_invoice, + dnssec_proof, session_priv, path, true, diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index e8859494071..fc59ef044a7 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -3025,6 +3025,7 @@ pub fn expect_payment_sent>( ref amount_msat, ref fee_paid_msat, ref bolt12_invoice, + .. } => { assert_eq!(expected_payment_preimage, *payment_preimage); assert_eq!(expected_payment_hash, *payment_hash); diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 9b1b009e93a..68f404e549b 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -3547,6 +3547,7 @@ mod tests { first_hop_htlc_msat: 0, payment_id: PaymentId([1; 32]), bolt12_invoice: None, + dnssec_proof: None, }; process_onion_failure(&ctx_full, &logger, &htlc_source, onion_error) @@ -3733,6 +3734,7 @@ mod tests { first_hop_htlc_msat: dummy_amt_msat, payment_id: PaymentId([1; 32]), bolt12_invoice: None, + dnssec_proof: None, }; { @@ -3921,6 +3923,7 @@ mod tests { first_hop_htlc_msat: 0, payment_id: PaymentId([1; 32]), bolt12_invoice: None, + dnssec_proof: None, }; // Iterate over all possible failure positions and check that the cases that can be attributed are. @@ -4030,6 +4033,7 @@ mod tests { first_hop_htlc_msat: 0, payment_id: PaymentId([1; 32]), bolt12_invoice: None, + dnssec_proof: None, }; let decrypted_failure = process_onion_failure(&ctx_full, &logger, &htlc_source, packet); diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index b08b0f5a886..00e5bd3c841 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -28,6 +28,7 @@ use crate::offers::invoice::{Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder use crate::offers::invoice_request::InvoiceRequest; use crate::offers::nonce::Nonce; use crate::offers::static_invoice::StaticInvoice; +use crate::onion_message::dns_resolution::DNSSECProof; use crate::routing::router::{ BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, RouteParametersConfig, Router, @@ -85,6 +86,9 @@ pub(crate) enum PendingOutboundPayment { retry_strategy: Retry, route_params_config: RouteParametersConfig, retryable_invoice_request: Option, + /// The DNSSEC proof for BIP 353 proof of payment, if this payment originated from + /// a Human Readable Name resolution. + dnssec_proof: Option, }, // Represents the state after the invoice has been received, transitioning from the corresponding // `AwaitingInvoice` state. @@ -127,6 +131,9 @@ pub(crate) enum PendingOutboundPayment { // Storing the BOLT 12 invoice here to allow Proof of Payment after // the payment is made. bolt12_invoice: Option, + /// The DNSSEC proof for BIP 353 proof of payment, if this payment originated from + /// a Human Readable Name resolution. + dnssec_proof: Option, custom_tlvs: Vec<(u64, Vec)>, pending_amt_msat: u64, /// Used to track the fee paid. Present iff the payment was serialized on 0.0.103+. @@ -188,6 +195,13 @@ impl PendingOutboundPayment { } } + fn dnssec_proof(&self) -> Option<&DNSSECProof> { + match self { + PendingOutboundPayment::Retryable { dnssec_proof, .. } => dnssec_proof.as_ref(), + _ => None, + } + } + fn increment_attempts(&mut self) { if let PendingOutboundPayment::Retryable { attempts, .. } = self { attempts.count += 1; @@ -928,6 +942,7 @@ pub(super) struct SendAlongPathArgs<'a> { pub keysend_preimage: &'a Option, pub invoice_request: Option<&'a InvoiceRequest>, pub bolt12_invoice: Option<&'a PaidBolt12Invoice>, + pub dnssec_proof: Option<&'a DNSSECProof>, pub session_priv_bytes: [u8; 32], pub hold_htlc_at_next_hop: bool, } @@ -1098,7 +1113,7 @@ impl OutboundPayments { SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, { - let (payment_hash, retry_strategy, params_config, _) = self + let (payment_hash, retry_strategy, params_config, dnssec_proof, _) = self .mark_invoice_received_and_get_details(invoice, payment_id)?; if invoice.invoice_features().requires_unknown_bits_from(&features) { @@ -1117,7 +1132,7 @@ impl OutboundPayments { } let invoice = PaidBolt12Invoice::Bolt12Invoice(invoice.clone()); self.send_payment_for_bolt12_invoice_internal( - payment_id, payment_hash, None, None, invoice, route_params, retry_strategy, false, router, + payment_id, payment_hash, None, None, invoice, dnssec_proof, route_params, retry_strategy, false, router, first_hops, inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx, best_block_height, pending_events, send_payment_along_path, logger, ) @@ -1129,7 +1144,7 @@ impl OutboundPayments { >( &self, payment_id: PaymentId, payment_hash: PaymentHash, keysend_preimage: Option, invoice_request: Option<&InvoiceRequest>, - bolt12_invoice: PaidBolt12Invoice, + bolt12_invoice: PaidBolt12Invoice, dnssec_proof: Option, mut route_params: RouteParameters, retry_strategy: Retry, hold_htlcs_at_next_hop: bool, router: &R, first_hops: Vec, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1, best_block_height: u32, @@ -1188,7 +1203,8 @@ impl OutboundPayments { hash_map::Entry::Occupied(entry) => match entry.get() { PendingOutboundPayment::InvoiceReceived { .. } => { let (retryable_payment, onion_session_privs) = Self::create_pending_payment( - payment_hash, recipient_onion.clone(), keysend_preimage, None, Some(bolt12_invoice.clone()), &route, + payment_hash, recipient_onion.clone(), keysend_preimage, None, Some(bolt12_invoice.clone()), + dnssec_proof.clone(), &route, Some(retry_strategy), payment_params, entropy_source, best_block_height, ); *entry.into_mut() = retryable_payment; @@ -1199,7 +1215,8 @@ impl OutboundPayments { invoice_request } else { unreachable!() }; let (retryable_payment, onion_session_privs) = Self::create_pending_payment( - payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), Some(bolt12_invoice.clone()), &route, + payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), Some(bolt12_invoice.clone()), + dnssec_proof.clone(), &route, Some(retry_strategy), payment_params, entropy_source, best_block_height ); outbounds.insert(payment_id, retryable_payment); @@ -1212,7 +1229,8 @@ impl OutboundPayments { core::mem::drop(outbounds); let result = self.pay_route_internal( - &route, payment_hash, &recipient_onion, keysend_preimage, invoice_request, Some(&bolt12_invoice), payment_id, + &route, payment_hash, &recipient_onion, keysend_preimage, invoice_request, Some(&bolt12_invoice), + dnssec_proof.as_ref(), payment_id, &onion_session_privs, hold_htlcs_at_next_hop, node_signer, best_block_height, &send_payment_along_path ); @@ -1395,6 +1413,7 @@ impl OutboundPayments { Some(keysend_preimage), Some(&invoice_request), invoice, + None, // Static invoices don't originate from HRN resolution route_params, retry_strategy, hold_htlcs_at_next_hop, @@ -1604,7 +1623,7 @@ impl OutboundPayments { })?; let res = self.pay_route_internal(&route, payment_hash, &recipient_onion, - keysend_preimage, None, None, payment_id, &onion_session_privs, false, node_signer, + keysend_preimage, None, None, None, payment_id, &onion_session_privs, false, node_signer, best_block_height, &send_payment_along_path); log_info!(logger, "Sending payment with id {} and hash {} returned {:?}", payment_id, payment_hash, res); @@ -1671,7 +1690,7 @@ impl OutboundPayments { } } } - let (recipient_onion, keysend_preimage, onion_session_privs, invoice_request, bolt12_invoice) = { + let (recipient_onion, keysend_preimage, onion_session_privs, invoice_request, bolt12_invoice, dnssec_proof) = { let mut outbounds = self.pending_outbound_payments.lock().unwrap(); match outbounds.entry(payment_id) { hash_map::Entry::Occupied(mut payment) => { @@ -1714,8 +1733,9 @@ impl OutboundPayments { payment.get_mut().increment_attempts(); let bolt12_invoice = payment.get().bolt12_invoice(); + let dnssec_proof = payment.get().dnssec_proof(); - (recipient_onion, keysend_preimage, onion_session_privs, invoice_request, bolt12_invoice.cloned()) + (recipient_onion, keysend_preimage, onion_session_privs, invoice_request, bolt12_invoice.cloned(), dnssec_proof.cloned()) }, PendingOutboundPayment::Legacy { .. } => { log_error!(logger, "Unable to retry payments that were initially sent on LDK versions prior to 0.0.102"); @@ -1755,7 +1775,7 @@ impl OutboundPayments { } }; let res = self.pay_route_internal(&route, payment_hash, &recipient_onion, keysend_preimage, - invoice_request.as_ref(), bolt12_invoice.as_ref(), payment_id, + invoice_request.as_ref(), bolt12_invoice.as_ref(), dnssec_proof.as_ref(), payment_id, &onion_session_privs, false, node_signer, best_block_height, &send_payment_along_path); log_info!(logger, "Result retrying payment id {}: {:?}", &payment_id, res); if let Err(e) = res { @@ -1914,7 +1934,7 @@ impl OutboundPayments { })?; match self.pay_route_internal(&route, payment_hash, &recipient_onion_fields, - None, None, None, payment_id, &onion_session_privs, false, node_signer, + None, None, None, None, payment_id, &onion_session_privs, false, node_signer, best_block_height, &send_payment_along_path ) { Ok(()) => Ok((payment_hash, payment_id)), @@ -1977,7 +1997,7 @@ impl OutboundPayments { hash_map::Entry::Occupied(_) => Err(PaymentSendFailure::DuplicatePayment), hash_map::Entry::Vacant(entry) => { let (payment, onion_session_privs) = Self::create_pending_payment( - payment_hash, recipient_onion, keysend_preimage, None, bolt12_invoice, route, retry_strategy, + payment_hash, recipient_onion, keysend_preimage, None, bolt12_invoice, None, route, retry_strategy, payment_params, entropy_source, best_block_height ); entry.insert(payment); @@ -1990,7 +2010,8 @@ impl OutboundPayments { fn create_pending_payment( payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, keysend_preimage: Option, invoice_request: Option, - bolt12_invoice: Option, route: &Route, retry_strategy: Option, + bolt12_invoice: Option, dnssec_proof: Option, + route: &Route, retry_strategy: Option, payment_params: Option, entropy_source: &ES, best_block_height: u32 ) -> (PendingOutboundPayment, Vec<[u8; 32]>) { let mut onion_session_privs = Vec::with_capacity(route.paths.len()); @@ -2011,6 +2032,7 @@ impl OutboundPayments { keysend_preimage, invoice_request, bolt12_invoice, + dnssec_proof, custom_tlvs: recipient_onion.custom_tlvs, starting_block_height: best_block_height, total_msat: route.get_total_amount(), @@ -2075,6 +2097,7 @@ impl OutboundPayments { retry_strategy: *retry_strategy, route_params_config: *route_params_config, retryable_invoice_request, + dnssec_proof: None, }; core::mem::swap(&mut new_val, entry.into_mut()); Ok(()) @@ -2085,6 +2108,19 @@ impl OutboundPayments { } } + /// Sets the DNSSEC proof for a payment that originated from a BIP 353 Human Readable Name + /// resolution. This should be called after `received_offer` to attach the proof to the + /// `AwaitingInvoice` state. + #[cfg(feature = "dnssec")] + pub(super) fn set_dnssec_proof(&self, payment_id: PaymentId, proof: DNSSECProof) { + let mut outbounds = self.pending_outbound_payments.lock().unwrap(); + if let Some(payment) = outbounds.get_mut(&payment_id) { + if let PendingOutboundPayment::AwaitingInvoice { dnssec_proof, .. } = payment { + *dnssec_proof = Some(proof); + } + } + } + pub(super) fn add_new_awaiting_invoice( &self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry, route_params_config: RouteParametersConfig, @@ -2102,6 +2138,7 @@ impl OutboundPayments { retry_strategy, route_params_config, retryable_invoice_request, + dnssec_proof: None, }); Ok(()) @@ -2114,7 +2151,7 @@ impl OutboundPayments { &self, invoice: &Bolt12Invoice, payment_id: PaymentId ) -> Result<(), Bolt12PaymentError> { self.mark_invoice_received_and_get_details(invoice, payment_id) - .and_then(|(_, _, _, is_newly_marked)| { + .and_then(|(_, _, _, _, is_newly_marked)| { is_newly_marked .then_some(()) .ok_or(Bolt12PaymentError::DuplicateInvoice) @@ -2124,7 +2161,7 @@ impl OutboundPayments { #[rustfmt::skip] fn mark_invoice_received_and_get_details( &self, invoice: &Bolt12Invoice, payment_id: PaymentId - ) -> Result<(PaymentHash, Retry, RouteParametersConfig, bool), Bolt12PaymentError> { + ) -> Result<(PaymentHash, Retry, RouteParametersConfig, Option, bool), Bolt12PaymentError> { match self.pending_outbound_payments.lock().unwrap().entry(payment_id) { hash_map::Entry::Occupied(entry) => match entry.get() { PendingOutboundPayment::AwaitingInvoice { @@ -2133,13 +2170,18 @@ impl OutboundPayments { let payment_hash = invoice.payment_hash(); let retry = *retry; let config = *route_params_config; + let dnssec_proof = if let PendingOutboundPayment::AwaitingInvoice { dnssec_proof, .. } = entry.get() { + dnssec_proof.clone() + } else { + None + }; *entry.into_mut() = PendingOutboundPayment::InvoiceReceived { payment_hash, retry_strategy: retry, route_params_config: config, }; - Ok((payment_hash, retry, config, true)) + Ok((payment_hash, retry, config, dnssec_proof, true)) }, // When manual invoice handling is enabled, the corresponding `PendingOutboundPayment` entry // is already updated at the time the invoice is received. This ensures that `InvoiceReceived` @@ -2148,7 +2190,7 @@ impl OutboundPayments { PendingOutboundPayment::InvoiceReceived { retry_strategy, route_params_config, .. } => { - Ok((invoice.payment_hash(), *retry_strategy, *route_params_config, false)) + Ok((invoice.payment_hash(), *retry_strategy, *route_params_config, None, false)) }, _ => Err(Bolt12PaymentError::DuplicateInvoice), }, @@ -2160,6 +2202,7 @@ impl OutboundPayments { fn pay_route_internal( &self, route: &Route, payment_hash: PaymentHash, recipient_onion: &RecipientOnionFields, keysend_preimage: Option, invoice_request: Option<&InvoiceRequest>, bolt12_invoice: Option<&PaidBolt12Invoice>, + dnssec_proof: Option<&DNSSECProof>, payment_id: PaymentId, onion_session_privs: &Vec<[u8; 32]>, hold_htlcs_at_next_hop: bool, node_signer: &NS, best_block_height: u32, send_payment_along_path: &F ) -> Result<(), PaymentSendFailure> @@ -2209,7 +2252,7 @@ impl OutboundPayments { let path_res = send_payment_along_path(SendAlongPathArgs { path: &path, payment_hash: &payment_hash, recipient_onion, cur_height, payment_id, keysend_preimage: &keysend_preimage, invoice_request, - bolt12_invoice, hold_htlc_at_next_hop: hold_htlcs_at_next_hop, + bolt12_invoice, dnssec_proof, hold_htlc_at_next_hop: hold_htlcs_at_next_hop, session_priv_bytes: *session_priv_bytes }); results.push(path_res); @@ -2276,7 +2319,7 @@ impl OutboundPayments { F: Fn(SendAlongPathArgs) -> Result<(), APIError>, { self.pay_route_internal(route, payment_hash, &recipient_onion, - keysend_preimage, None, None, payment_id, &onion_session_privs, + keysend_preimage, None, None, None, payment_id, &onion_session_privs, false, node_signer, best_block_height, &send_payment_along_path) .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e }) } @@ -2300,6 +2343,7 @@ impl OutboundPayments { #[rustfmt::skip] pub(super) fn claim_htlc( &self, payment_id: PaymentId, payment_preimage: PaymentPreimage, bolt12_invoice: Option, + dnssec_proof: Option, session_priv: SecretKey, path: Path, from_onchain: bool, ev_completion_action: &mut Option, pending_events: &Mutex)>>, logger: &WithContext, @@ -2322,7 +2366,8 @@ impl OutboundPayments { payment_hash, amount_msat, fee_paid_msat, - bolt12_invoice: bolt12_invoice, + bolt12_invoice, + dnssec_proof, }, ev_completion_action.take())); payment.get_mut().mark_fulfilled(); } @@ -2718,6 +2763,7 @@ impl OutboundPayments { keysend_preimage: None, // only used for retries, and we'll never retry on startup invoice_request: None, // only used for retries, and we'll never retry on startup bolt12_invoice: None, // only used for retries, and we'll never retry on startup! + dnssec_proof: None, // only used for retries, and we'll never retry on startup custom_tlvs: Vec::new(), // only used for retries, and we'll never retry on startup pending_amt_msat: path_amt, pending_fee_msat: Some(path_fee), @@ -2822,6 +2868,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, })), (13, invoice_request, option), (15, bolt12_invoice, option), + (17, dnssec_proof, option), (not_written, retry_strategy, (static_value, None)), (not_written, attempts, (static_value, PaymentAttempts::new())), }, @@ -2847,6 +2894,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, |fee_msat| RouteParametersConfig::default().with_max_total_routing_fee_msat(fee_msat) ) ))), + (9, dnssec_proof, option), }, (7, InvoiceReceived) => { (0, payment_hash, required), diff --git a/lightning/src/onion_message/dns_resolution.rs b/lightning/src/onion_message/dns_resolution.rs index e857a359c78..29119e0f6ef 100644 --- a/lightning/src/onion_message/dns_resolution.rs +++ b/lightning/src/onion_message/dns_resolution.rs @@ -181,6 +181,23 @@ impl ReadableArgs for DNSResolverMessage { } } +impl Writeable for DNSSECProof { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + (self.name.as_str().len() as u8).write(w)?; + w.write_all(self.name.as_str().as_bytes())?; + self.proof.write(w) + } +} + +impl Readable for DNSSECProof { + fn read(r: &mut R) -> Result { + let s = Hostname::read(r)?; + let name = s.try_into().map_err(|_| DecodeError::InvalidValue)?; + let proof = Readable::read(r)?; + Ok(DNSSECProof { name, proof }) + } +} + impl OnionMessageContents for DNSResolverMessage { #[cfg(c_bindings)] fn msg_type(&self) -> String { diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index f94eb7877f5..a54d6a33eb6 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -526,6 +526,7 @@ impl> MessageRouter for R { pub struct DefaultMessageRouter>, L: Logger, ES: EntropySource> { network_graph: G, entropy_source: ES, + compact_paths: bool, } // Target total length (in hops) for blinded paths used outside of QR codes. @@ -543,8 +544,24 @@ impl>, L: Logger, ES: EntropySource> DefaultMessageRouter { /// Creates a [`DefaultMessageRouter`] using the given [`NetworkGraph`]. + /// + /// Compact blinded paths are enabled by default. Use [`Self::with_compact_paths`] to + /// configure this behavior. pub fn new(network_graph: G, entropy_source: ES) -> Self { - Self { network_graph, entropy_source } + Self { network_graph, entropy_source, compact_paths: true } + } + + /// Creates a [`DefaultMessageRouter`] with configurable compact blinded paths behavior. + /// + /// When `compact_paths` is `true`, blinded paths will use short channel IDs (SCIDs) instead + /// of full node pubkeys when possible, resulting in smaller serialization suitable for + /// space-constrained formats like QR codes. However, compact paths may fail to route if + /// the corresponding channel is closed or modified. + /// + /// When `compact_paths` is `false`, blinded paths will always use full node IDs, which + /// are more stable for long-lived offers but result in larger encoded data. + pub fn with_compact_paths(network_graph: G, entropy_source: ES, compact_paths: bool) -> Self { + Self { network_graph, entropy_source, compact_paths } } pub(crate) fn create_blinded_paths_from_iter< @@ -727,7 +744,7 @@ impl>, L: Logger, ES: EntropySource> MessageRo peers.into_iter(), &self.entropy_source, secp_ctx, - false, + !self.compact_paths, ) } }