Skip to content

Commit 8596137

Browse files
Include DNSSECProof in PaymentSent for BIP 353 proof of payment
Store the DNSSEC proof received during BIP 353 Human Readable Name resolution and carry it through the payment lifecycle alongside the Bolt12Invoice. The proof is stored in PendingOutboundPayment::Retryable and HTLCSource::OutboundRoute (for restart persistence), and emitted in the PaymentSent event. This allows users who pay via HRN to have a complete chain of proof: DNS name -> DNSSEC proof -> Offer -> Invoice -> Payment preimage. Addresses the DNSSECProof part of #3344. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 547d166 commit 8596137

7 files changed

Lines changed: 131 additions & 23 deletions

File tree

lightning/src/events/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use crate::ln::types::ChannelId;
3232
use crate::offers::invoice::Bolt12Invoice;
3333
use crate::offers::invoice_request::InvoiceRequest;
3434
use crate::offers::static_invoice::StaticInvoice;
35+
use crate::onion_message::dns_resolution::DNSSECProof;
3536
use crate::onion_message::messenger::Responder;
3637
use crate::routing::gossip::NetworkUpdate;
3738
use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters};
@@ -1101,6 +1102,12 @@ pub enum Event {
11011102
///
11021103
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
11031104
bolt12_invoice: Option<PaidBolt12Invoice>,
1105+
/// The DNSSEC proof for BIP 353 proof of payment, if this payment originated from
1106+
/// a Human Readable Name resolution. This proof, combined with the [`Bolt12Invoice`],
1107+
/// provides a complete chain of proof from the DNS name to the payment.
1108+
///
1109+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
1110+
dnssec_proof: Option<DNSSECProof>,
11041111
},
11051112
/// Indicates an outbound payment failed. Individual [`Event::PaymentPathFailed`] events
11061113
/// provide failure information for each path attempt in the payment, including retries.
@@ -1973,6 +1980,7 @@ impl Writeable for Event {
19731980
ref amount_msat,
19741981
ref fee_paid_msat,
19751982
ref bolt12_invoice,
1983+
ref dnssec_proof,
19761984
} => {
19771985
2u8.write(writer)?;
19781986
write_tlv_fields!(writer, {
@@ -1982,6 +1990,7 @@ impl Writeable for Event {
19821990
(5, fee_paid_msat, option),
19831991
(7, amount_msat, option),
19841992
(9, bolt12_invoice, option),
1993+
(11, dnssec_proof, option),
19851994
});
19861995
},
19871996
&Event::PaymentPathFailed {
@@ -2474,13 +2483,15 @@ impl MaybeReadable for Event {
24742483
let mut amount_msat = None;
24752484
let mut fee_paid_msat = None;
24762485
let mut bolt12_invoice = None;
2486+
let mut dnssec_proof: Option<DNSSECProof> = None;
24772487
read_tlv_fields!(reader, {
24782488
(0, payment_preimage, required),
24792489
(1, payment_hash, option),
24802490
(3, payment_id, option),
24812491
(5, fee_paid_msat, option),
24822492
(7, amount_msat, option),
24832493
(9, bolt12_invoice, option),
2494+
(11, dnssec_proof, option),
24842495
});
24852496
if payment_hash.is_none() {
24862497
payment_hash = Some(PaymentHash(
@@ -2494,6 +2505,7 @@ impl MaybeReadable for Event {
24942505
amount_msat,
24952506
fee_paid_msat,
24962507
bolt12_invoice,
2508+
dnssec_proof,
24972509
}))
24982510
};
24992511
f()

lightning/src/ln/channel.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16535,6 +16535,7 @@ mod tests {
1653516535
first_hop_htlc_msat: 548,
1653616536
payment_id: PaymentId([42; 32]),
1653716537
bolt12_invoice: None,
16538+
dnssec_proof: None,
1653816539
},
1653916540
skimmed_fee_msat: None,
1654016541
blinding_point: None,
@@ -16986,6 +16987,7 @@ mod tests {
1698616987
first_hop_htlc_msat: 0,
1698716988
payment_id: PaymentId([42; 32]),
1698816989
bolt12_invoice: None,
16990+
dnssec_proof: None,
1698916991
};
1699016992
let dummy_outbound_output = OutboundHTLCOutput {
1699116993
htlc_id: 0,

lightning/src/ln/channelmanager.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ use crate::onion_message::async_payments::{
108108
AsyncPaymentsMessage, AsyncPaymentsMessageHandler, HeldHtlcAvailable, OfferPaths,
109109
OfferPathsRequest, ReleaseHeldHtlc, ServeStaticInvoice, StaticInvoicePersisted,
110110
};
111-
use crate::onion_message::dns_resolution::HumanReadableName;
111+
use crate::onion_message::dns_resolution::{DNSSECProof, HumanReadableName};
112112
use crate::onion_message::messenger::{
113113
MessageRouter, MessageSendInstructions, Responder, ResponseInstruction,
114114
};
@@ -836,6 +836,10 @@ mod fuzzy_channelmanager {
836836
/// we can provide proof-of-payment details in payment claim events even after a restart
837837
/// with a stale ChannelManager state.
838838
bolt12_invoice: Option<PaidBolt12Invoice>,
839+
/// The DNSSEC proof for BIP 353 proof of payment, if this payment originated from
840+
/// a Human Readable Name resolution. Stored here to ensure we can provide it in
841+
/// payment claim events even after a restart with a stale ChannelManager state.
842+
dnssec_proof: Option<DNSSECProof>,
839843
},
840844
}
841845

@@ -909,13 +913,15 @@ impl core::hash::Hash for HTLCSource {
909913
payment_id,
910914
first_hop_htlc_msat,
911915
bolt12_invoice,
916+
dnssec_proof,
912917
} => {
913918
1u8.hash(hasher);
914919
path.hash(hasher);
915920
session_priv[..].hash(hasher);
916921
payment_id.hash(hasher);
917922
first_hop_htlc_msat.hash(hasher);
918923
bolt12_invoice.hash(hasher);
924+
dnssec_proof.hash(hasher);
919925
},
920926
HTLCSource::TrampolineForward {
921927
previous_hop_data,
@@ -943,6 +949,7 @@ impl HTLCSource {
943949
first_hop_htlc_msat: 0,
944950
payment_id: PaymentId([2; 32]),
945951
bolt12_invoice: None,
952+
dnssec_proof: None,
946953
}
947954
}
948955

@@ -5394,6 +5401,7 @@ impl<
53945401
keysend_preimage,
53955402
invoice_request: None,
53965403
bolt12_invoice: None,
5404+
dnssec_proof: None,
53975405
session_priv_bytes,
53985406
hold_htlc_at_next_hop: false,
53995407
})
@@ -5409,6 +5417,7 @@ impl<
54095417
keysend_preimage,
54105418
invoice_request,
54115419
bolt12_invoice,
5420+
dnssec_proof,
54125421
session_priv_bytes,
54135422
hold_htlc_at_next_hop,
54145423
} = args;
@@ -5485,6 +5494,7 @@ impl<
54855494
first_hop_htlc_msat: htlc_msat,
54865495
payment_id,
54875496
bolt12_invoice: bolt12_invoice.cloned(),
5497+
dnssec_proof: dnssec_proof.cloned(),
54885498
};
54895499
let send_res = chan.send_htlc_and_commit(
54905500
htlc_msat,
@@ -9952,7 +9962,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
99529962
let htlc_id = SentHTLCId::from_source(&source);
99539963
match source {
99549964
HTLCSource::OutboundRoute {
9955-
session_priv, payment_id, path, bolt12_invoice, ..
9965+
session_priv, payment_id, path, bolt12_invoice, dnssec_proof, ..
99569966
} => {
99579967
debug_assert!(!startup_replay,
99589968
"We don't support claim_htlc claims during startup - monitors may not be available yet");
@@ -9984,6 +9994,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
99849994
payment_id,
99859995
payment_preimage,
99869996
bolt12_invoice,
9997+
dnssec_proof,
99879998
session_priv,
99889999
path,
998910000
from_onchain,
@@ -17386,6 +17397,7 @@ impl<
1738617397

1738717398
#[rustfmt::skip]
1738817399
fn handle_dnssec_proof(&self, message: DNSSECProof, context: DNSResolverContext) {
17400+
let proof = message.clone();
1738917401
let offer_opt = self.flow.hrn_resolver.handle_dnssec_proof_for_offer(message, context);
1739017402
#[cfg_attr(not(feature = "_test_utils"), allow(unused_mut))]
1739117403
if let Some((completed_requests, mut offer)) = offer_opt {
@@ -17404,7 +17416,12 @@ impl<
1740417416
.received_offer(payment_id, Some(retryable_invoice_request))
1740517417
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)
1740617418
});
17407-
if offer_pay_res.is_err() {
17419+
if offer_pay_res.is_ok() {
17420+
// Store the DNSSEC proof for BIP 353 proof of payment. The proof is
17421+
// now attached to the AwaitingInvoice state and will be carried through
17422+
// to the PaymentSent event.
17423+
self.pending_outbound_payments.set_dnssec_proof(payment_id, proof.clone());
17424+
} else {
1740817425
// The offer we tried to pay is the canonical current offer for the name we
1740917426
// wanted to pay. If we can't pay it, there's no way to recover so fail the
1741017427
// payment.
@@ -17767,6 +17784,7 @@ impl Readable for HTLCSource {
1776717784
let mut payment_params: Option<PaymentParameters> = None;
1776817785
let mut blinded_tail: Option<BlindedTail> = None;
1776917786
let mut bolt12_invoice: Option<PaidBolt12Invoice> = None;
17787+
let mut dnssec_proof: Option<DNSSECProof> = None;
1777017788
read_tlv_fields!(reader, {
1777117789
(0, session_priv, required),
1777217790
(1, payment_id, option),
@@ -17775,6 +17793,7 @@ impl Readable for HTLCSource {
1777517793
(5, payment_params, (option: ReadableArgs, 0)),
1777617794
(6, blinded_tail, option),
1777717795
(7, bolt12_invoice, option),
17796+
(9, dnssec_proof, option),
1777817797
});
1777917798
if payment_id.is_none() {
1778017799
// For backwards compat, if there was no payment_id written, use the session_priv bytes
@@ -17798,6 +17817,7 @@ impl Readable for HTLCSource {
1779817817
path,
1779917818
payment_id: payment_id.unwrap(),
1780017819
bolt12_invoice,
17820+
dnssec_proof,
1780117821
})
1780217822
}
1780317823
1 => Ok(HTLCSource::PreviousHopData(Readable::read(reader)?)),
@@ -17817,6 +17837,7 @@ impl Writeable for HTLCSource {
1781717837
ref path,
1781817838
payment_id,
1781917839
bolt12_invoice,
17840+
dnssec_proof,
1782017841
} => {
1782117842
0u8.write(writer)?;
1782217843
let payment_id_opt = Some(payment_id);
@@ -17829,6 +17850,7 @@ impl Writeable for HTLCSource {
1782917850
(5, None::<PaymentParameters>, option), // payment_params in LDK versions prior to 0.0.115
1783017851
(6, path.blinded_tail, option),
1783117852
(7, bolt12_invoice, option),
17853+
(9, dnssec_proof, option),
1783217854
});
1783317855
},
1783417856
HTLCSource::PreviousHopData(ref field) => {
@@ -19687,6 +19709,7 @@ impl<
1968719709
session_priv,
1968819710
path,
1968919711
bolt12_invoice,
19712+
dnssec_proof,
1969019713
..
1969119714
} => {
1969219715
if let Some(preimage) = preimage_opt {
@@ -19704,6 +19727,7 @@ impl<
1970419727
payment_id,
1970519728
preimage,
1970619729
bolt12_invoice,
19730+
dnssec_proof,
1970719731
session_priv,
1970819732
path,
1970919733
true,

lightning/src/ln/functional_test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3025,6 +3025,7 @@ pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM = CM>>(
30253025
ref amount_msat,
30263026
ref fee_paid_msat,
30273027
ref bolt12_invoice,
3028+
..
30283029
} => {
30293030
assert_eq!(expected_payment_preimage, *payment_preimage);
30303031
assert_eq!(expected_payment_hash, *payment_hash);

lightning/src/ln/onion_utils.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3547,6 +3547,7 @@ mod tests {
35473547
first_hop_htlc_msat: 0,
35483548
payment_id: PaymentId([1; 32]),
35493549
bolt12_invoice: None,
3550+
dnssec_proof: None,
35503551
};
35513552

35523553
process_onion_failure(&ctx_full, &logger, &htlc_source, onion_error)
@@ -3733,6 +3734,7 @@ mod tests {
37333734
first_hop_htlc_msat: dummy_amt_msat,
37343735
payment_id: PaymentId([1; 32]),
37353736
bolt12_invoice: None,
3737+
dnssec_proof: None,
37363738
};
37373739

37383740
{
@@ -3921,6 +3923,7 @@ mod tests {
39213923
first_hop_htlc_msat: 0,
39223924
payment_id: PaymentId([1; 32]),
39233925
bolt12_invoice: None,
3926+
dnssec_proof: None,
39243927
};
39253928

39263929
// Iterate over all possible failure positions and check that the cases that can be attributed are.
@@ -4030,6 +4033,7 @@ mod tests {
40304033
first_hop_htlc_msat: 0,
40314034
payment_id: PaymentId([1; 32]),
40324035
bolt12_invoice: None,
4036+
dnssec_proof: None,
40334037
};
40344038

40354039
let decrypted_failure = process_onion_failure(&ctx_full, &logger, &htlc_source, packet);

0 commit comments

Comments
 (0)