Skip to content

Commit 865df15

Browse files
vincenzopalazzojkczyzclaude
committed
refactor(offers): bundle paid invoice data for payer proofs
Encapsulate the paid invoice, preimage, and payer nonce in the PaidBolt12Invoice struct and surface it through Event::PaymentSent::bolt12_invoice. To support the nonce round-trip, plumb payment_nonce through HTLCSource::OutboundRoute, SendAlongPathArgs, PendingOutboundPayment::Retryable and the outbound payment internals, and extract it from the OffersContext variants so payers can later re-derive the payer signing key from the same nonce used for the invoice request. Update expect_payment_sent, claim_payment, claim_payment_along_route and the async-payments test assertions to surface and consume the PaidBolt12Invoice. Also add Writeable/Readable impls for sha256::Hash in util::ser so PaidBolt12Invoice serialization compiles. Co-Authored-By: Jeffrey Czyz <jkczyz@gmail.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9455562 commit 865df15

9 files changed

Lines changed: 187 additions & 109 deletions

File tree

fuzz/src/process_onion_failure.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
122122
first_hop_htlc_msat: 0,
123123
payment_id,
124124
bolt12_invoice: None,
125+
payment_nonce: None,
125126
};
126127

127128
let failure_len = get_u16!();

lightning/src/events/mod.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ use crate::ln::outbound_payment::RecipientOnionFields;
3131
use crate::ln::types::ChannelId;
3232
use crate::offers::invoice::Bolt12Invoice;
3333
use crate::offers::invoice_request::InvoiceRequest;
34+
use crate::offers::nonce::Nonce;
3435
use crate::offers::payer_proof::Bolt12InvoiceType;
36+
pub use crate::offers::payer_proof::PaidBolt12Invoice;
3537
use crate::offers::static_invoice::StaticInvoice;
3638
use crate::onion_message::messenger::Responder;
3739
use crate::routing::gossip::NetworkUpdate;
@@ -1090,19 +1092,14 @@ pub enum Event {
10901092
///
10911093
/// [`Route::get_total_fees`]: crate::routing::router::Route::get_total_fees
10921094
fee_paid_msat: Option<u64>,
1093-
/// The BOLT 12 invoice that was paid. `None` if the payment was a non BOLT 12 payment.
1095+
/// The paid BOLT 12 invoice bundled with the data needed to construct a
1096+
/// [`PayerProof`], which selectively discloses invoice fields to prove payment to a
1097+
/// third party.
10941098
///
1095-
/// The BOLT 12 invoice is useful for proof of payment because it contains the
1096-
/// payment hash. A third party can verify that the payment was made by
1097-
/// showing the invoice and confirming that the payment hash matches
1098-
/// the hash of the payment preimage.
1099+
/// `None` for non-BOLT 12 payments.
10991100
///
1100-
/// However, the [`Bolt12InvoiceType`] can also be of type [`StaticInvoice`], which
1101-
/// is a special [`Bolt12Invoice`] where proof of payment is not possible.
1102-
///
1103-
/// [`Bolt12InvoiceType`]: crate::offers::payer_proof::Bolt12InvoiceType
1104-
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
1105-
bolt12_invoice: Option<Bolt12InvoiceType>,
1101+
/// [`PayerProof`]: crate::offers::payer_proof::PayerProof
1102+
bolt12_invoice: Option<PaidBolt12Invoice>,
11061103
},
11071104
/// Indicates an outbound payment failed. Individual [`Event::PaymentPathFailed`] events
11081105
/// provide failure information for each path attempt in the payment, including retries.
@@ -1977,13 +1974,16 @@ impl Writeable for Event {
19771974
ref bolt12_invoice,
19781975
} => {
19791976
2u8.write(writer)?;
1977+
let invoice_type = bolt12_invoice.as_ref().map(|paid| paid.invoice_type());
1978+
let payment_nonce = bolt12_invoice.as_ref().and_then(|paid| paid.nonce());
19801979
write_tlv_fields!(writer, {
19811980
(0, payment_preimage, required),
19821981
(1, payment_hash, required),
19831982
(3, payment_id, option),
19841983
(5, fee_paid_msat, option),
19851984
(7, amount_msat, option),
1986-
(9, bolt12_invoice, option),
1985+
(9, invoice_type, option),
1986+
(11, payment_nonce, option),
19871987
});
19881988
},
19891989
&Event::PaymentPathFailed {
@@ -2475,20 +2475,25 @@ impl MaybeReadable for Event {
24752475
let mut payment_id = None;
24762476
let mut amount_msat = None;
24772477
let mut fee_paid_msat = None;
2478-
let mut bolt12_invoice = None;
2478+
let mut invoice_type: Option<Bolt12InvoiceType> = None;
2479+
let mut payment_nonce: Option<Nonce> = None;
24792480
read_tlv_fields!(reader, {
24802481
(0, payment_preimage, required),
24812482
(1, payment_hash, option),
24822483
(3, payment_id, option),
24832484
(5, fee_paid_msat, option),
24842485
(7, amount_msat, option),
2485-
(9, bolt12_invoice, option),
2486+
(9, invoice_type, option),
2487+
(11, payment_nonce, option),
24862488
});
24872489
if payment_hash.is_none() {
24882490
payment_hash = Some(PaymentHash(
24892491
Sha256::hash(&payment_preimage.0[..]).to_byte_array(),
24902492
));
24912493
}
2494+
let bolt12_invoice = invoice_type.map(|invoice| {
2495+
PaidBolt12Invoice::new(invoice, payment_preimage, payment_nonce)
2496+
});
24922497
Ok(Some(Event::PaymentSent {
24932498
payment_id,
24942499
payment_preimage,

lightning/src/ln/async_payments_tests.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ use crate::offers::flow::{
4242
use crate::offers::invoice_request::InvoiceRequest;
4343
use crate::offers::nonce::Nonce;
4444
use crate::offers::offer::{Amount, Offer};
45-
use crate::offers::payer_proof::Bolt12InvoiceType;
4645
use crate::offers::static_invoice::{
4746
StaticInvoice, StaticInvoiceBuilder,
4847
DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY,
@@ -992,7 +991,7 @@ fn ignore_duplicate_invoice() {
992991
let keysend_preimage = extract_payment_preimage(&claimable_ev);
993992
let (res, _) =
994993
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
995-
assert_eq!(res, Some(Bolt12InvoiceType::StaticInvoice(static_invoice.clone())));
994+
assert_eq!(res.as_ref().and_then(|paid| paid.static_invoice()), Some(&static_invoice));
996995

997996
// After paying the static invoice, check that regular invoice received from async recipient is ignored.
998997
match sender.onion_messenger.peel_onion_message(&invoice_om) {
@@ -1077,7 +1076,7 @@ fn ignore_duplicate_invoice() {
10771076

10781077
// After paying invoice, check that static invoice is ignored.
10791078
let res = claim_payment(sender, route[0], payment_preimage);
1080-
assert_eq!(res, Some(Bolt12InvoiceType::Bolt12Invoice(invoice)));
1079+
assert_eq!(res.as_ref().and_then(|paid| paid.bolt12_invoice()), Some(&invoice));
10811080

10821081
sender.onion_messenger.handle_onion_message(always_online_node_id, &static_invoice_om);
10831082
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(sender.node);
@@ -1148,7 +1147,7 @@ fn async_receive_flow_success() {
11481147
let keysend_preimage = extract_payment_preimage(&claimable_ev);
11491148
let (res, _) =
11501149
claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], route, keysend_preimage));
1151-
assert_eq!(res, Some(Bolt12InvoiceType::StaticInvoice(static_invoice)));
1150+
assert_eq!(res.as_ref().and_then(|paid| paid.static_invoice()), Some(&static_invoice));
11521151
}
11531152

11541153
#[cfg_attr(feature = "std", ignore)]
@@ -2392,7 +2391,7 @@ fn refresh_static_invoices_for_used_offers() {
23922391
let claimable_ev = do_pass_along_path(args).unwrap();
23932392
let keysend_preimage = extract_payment_preimage(&claimable_ev);
23942393
let res = claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
2395-
assert_eq!(res.0, Some(Bolt12InvoiceType::StaticInvoice(updated_invoice)));
2394+
assert_eq!(res.0.as_ref().and_then(|paid| paid.static_invoice()), Some(&updated_invoice));
23962395
}
23972396

23982397
#[cfg_attr(feature = "std", ignore)]
@@ -2727,7 +2726,7 @@ fn invoice_server_is_not_channel_peer() {
27272726
let claimable_ev = do_pass_along_path(args).unwrap();
27282727
let keysend_preimage = extract_payment_preimage(&claimable_ev);
27292728
let res = claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
2730-
assert_eq!(res.0, Some(Bolt12InvoiceType::StaticInvoice(invoice)));
2729+
assert_eq!(res.0.as_ref().and_then(|paid| paid.static_invoice()), Some(&invoice));
27312730
}
27322731

27332732
#[test]
@@ -2970,7 +2969,7 @@ fn async_payment_e2e() {
29702969
let keysend_preimage = extract_payment_preimage(&claimable_ev);
29712970
let (res, _) =
29722971
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
2973-
assert_eq!(res, Some(Bolt12InvoiceType::StaticInvoice(static_invoice)));
2972+
assert_eq!(res.as_ref().and_then(|paid| paid.static_invoice()), Some(&static_invoice));
29742973
}
29752974

29762975
#[test]
@@ -3207,7 +3206,7 @@ fn intercepted_hold_htlc() {
32073206
let keysend_preimage = extract_payment_preimage(&claimable_ev);
32083207
let (res, _) =
32093208
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
3210-
assert_eq!(res, Some(Bolt12InvoiceType::StaticInvoice(static_invoice)));
3209+
assert_eq!(res.as_ref().and_then(|paid| paid.static_invoice()), Some(&static_invoice));
32113210
}
32123211

32133212
#[test]
@@ -3457,7 +3456,7 @@ fn release_htlc_races_htlc_onion_decode() {
34573456
let keysend_preimage = extract_payment_preimage(&claimable_ev);
34583457
let (res, _) =
34593458
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
3460-
assert_eq!(res, Some(Bolt12InvoiceType::StaticInvoice(static_invoice)));
3459+
assert_eq!(res.as_ref().and_then(|paid| paid.static_invoice()), Some(&static_invoice));
34613460
}
34623461

34633462
#[test]
@@ -3621,5 +3620,5 @@ fn async_payment_e2e_release_before_hold_registered() {
36213620
let keysend_preimage = extract_payment_preimage(&claimable_ev);
36223621
let (res, _) =
36233622
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
3624-
assert_eq!(res, Some(Bolt12InvoiceType::StaticInvoice(static_invoice)));
3623+
assert_eq!(res.as_ref().and_then(|paid| paid.static_invoice()), Some(&static_invoice));
36253624
}

lightning/src/ln/channel.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17060,6 +17060,7 @@ mod tests {
1706017060
first_hop_htlc_msat: 548,
1706117061
payment_id: PaymentId([42; 32]),
1706217062
bolt12_invoice: None,
17063+
payment_nonce: None,
1706317064
},
1706417065
skimmed_fee_msat: None,
1706517066
blinding_point: None,
@@ -17553,6 +17554,7 @@ mod tests {
1755317554
first_hop_htlc_msat: 0,
1755417555
payment_id: PaymentId([42; 32]),
1755517556
bolt12_invoice: None,
17557+
payment_nonce: None,
1755617558
};
1755717559
let dummy_outbound_output = OutboundHTLCOutput {
1755817560
htlc_id: 0,

lightning/src/ln/channelmanager.rs

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -819,10 +819,20 @@ mod fuzzy_channelmanager {
819819
/// doing a double-pass on route when we get a failure back
820820
first_hop_htlc_msat: u64,
821821
payment_id: PaymentId,
822-
/// The BOLT12 invoice associated with this payment, if any. This is stored here to ensure
823-
/// we can provide proof-of-payment details in payment claim events even after a restart
824-
/// with a stale ChannelManager state.
822+
/// The BOLT 12 invoice associated with this payment, if any. Stored here so it can
823+
/// be bundled into [`PaidBolt12Invoice`] in [`Event::PaymentSent`] even after a
824+
/// restart with a stale `ChannelManager` state.
825+
///
826+
/// [`PaidBolt12Invoice`]: crate::offers::payer_proof::PaidBolt12Invoice
827+
/// [`Event::PaymentSent`]: crate::events::Event::PaymentSent
825828
bolt12_invoice: Option<Bolt12InvoiceType>,
829+
/// The [`Nonce`] used when the BOLT 12 [`InvoiceRequest`] was created. Stored here so
830+
/// it can be bundled into [`PaidBolt12Invoice`] for building payer proofs, even after
831+
/// a restart with a stale `ChannelManager` state.
832+
///
833+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
834+
/// [`PaidBolt12Invoice`]: crate::offers::payer_proof::PaidBolt12Invoice
835+
payment_nonce: Option<Nonce>,
826836
},
827837
}
828838

@@ -896,6 +906,7 @@ impl core::hash::Hash for HTLCSource {
896906
payment_id,
897907
first_hop_htlc_msat,
898908
bolt12_invoice,
909+
..
899910
} => {
900911
1u8.hash(hasher);
901912
path.hash(hasher);
@@ -930,6 +941,7 @@ impl HTLCSource {
930941
first_hop_htlc_msat: 0,
931942
payment_id: PaymentId([2; 32]),
932943
bolt12_invoice: None,
944+
payment_nonce: None,
933945
}
934946
}
935947

@@ -958,9 +970,9 @@ impl HTLCSource {
958970
pub(crate) fn static_invoice(&self) -> Option<StaticInvoice> {
959971
match self {
960972
Self::OutboundRoute {
961-
bolt12_invoice: Some(Bolt12InvoiceType::StaticInvoice(inv)),
973+
bolt12_invoice: Some(Bolt12InvoiceType::StaticInvoice(invoice)),
962974
..
963-
} => Some(inv.clone()),
975+
} => Some(invoice.clone()),
964976
_ => None,
965977
}
966978
}
@@ -5348,6 +5360,7 @@ impl<
53485360
keysend_preimage,
53495361
invoice_request: None,
53505362
bolt12_invoice: None,
5363+
payment_nonce: None,
53515364
session_priv_bytes,
53525365
hold_htlc_at_next_hop: false,
53535366
})
@@ -5363,6 +5376,7 @@ impl<
53635376
keysend_preimage,
53645377
invoice_request,
53655378
bolt12_invoice,
5379+
payment_nonce,
53665380
session_priv_bytes,
53675381
hold_htlc_at_next_hop,
53685382
} = args;
@@ -5439,6 +5453,7 @@ impl<
54395453
first_hop_htlc_msat: htlc_msat,
54405454
payment_id,
54415455
bolt12_invoice: bolt12_invoice.cloned(),
5456+
payment_nonce,
54425457
};
54435458
let send_res = chan.send_htlc_and_commit(
54445459
htlc_msat,
@@ -5732,21 +5747,29 @@ impl<
57325747
pub fn send_payment_for_bolt12_invoice(
57335748
&self, invoice: &Bolt12Invoice, context: Option<&OffersContext>,
57345749
) -> Result<(), Bolt12PaymentError> {
5750+
let nonce = context.and_then(|ctx| match ctx {
5751+
OffersContext::OutboundPaymentForOffer { nonce, .. }
5752+
| OffersContext::OutboundPaymentForRefund { nonce, .. } => Some(*nonce),
5753+
_ => None,
5754+
});
57355755
match self.flow.verify_bolt12_invoice(invoice, context) {
5736-
Ok(payment_id) => self.send_payment_for_verified_bolt12_invoice(invoice, payment_id),
5756+
Ok(payment_id) => {
5757+
self.send_payment_for_verified_bolt12_invoice(invoice, payment_id, nonce)
5758+
},
57375759
Err(()) => Err(Bolt12PaymentError::UnexpectedInvoice),
57385760
}
57395761
}
57405762

57415763
fn send_payment_for_verified_bolt12_invoice(
5742-
&self, invoice: &Bolt12Invoice, payment_id: PaymentId,
5764+
&self, invoice: &Bolt12Invoice, payment_id: PaymentId, payment_nonce: Option<Nonce>,
57435765
) -> Result<(), Bolt12PaymentError> {
57445766
let best_block_height = self.best_block.read().unwrap().height;
57455767
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
57465768
let features = self.bolt12_invoice_features();
57475769
self.pending_outbound_payments.send_payment_for_bolt12_invoice(
57485770
invoice,
57495771
payment_id,
5772+
payment_nonce,
57505773
&self.router,
57515774
self.list_usable_channels(),
57525775
features,
@@ -9964,7 +9987,12 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
99649987
let htlc_id = SentHTLCId::from_source(&source);
99659988
match source {
99669989
HTLCSource::OutboundRoute {
9967-
session_priv, payment_id, path, bolt12_invoice, ..
9990+
session_priv,
9991+
payment_id,
9992+
path,
9993+
bolt12_invoice,
9994+
payment_nonce,
9995+
..
99689996
} => {
99699997
debug_assert!(!startup_replay,
99709998
"We don't support claim_htlc claims during startup - monitors may not be available yet");
@@ -9996,6 +10024,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
999610024
payment_id,
999710025
payment_preimage,
999810026
bolt12_invoice,
10027+
payment_nonce,
999910028
session_priv,
1000010029
path,
1000110030
from_onchain,
@@ -17036,7 +17065,12 @@ impl<
1703617065
return None;
1703717066
}
1703817067

17039-
let res = self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id);
17068+
let payment_nonce = context.as_ref().and_then(|ctx| match ctx {
17069+
OffersContext::OutboundPaymentForOffer { nonce, .. }
17070+
| OffersContext::OutboundPaymentForRefund { nonce, .. } => Some(*nonce),
17071+
_ => None,
17072+
});
17073+
let res = self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id, payment_nonce);
1704017074
handle_pay_invoice_res!(res, invoice, logger);
1704117075
},
1704217076
OffersMessage::StaticInvoice(invoice) => {
@@ -17677,6 +17711,7 @@ impl Readable for HTLCSource {
1767717711
let mut payment_params: Option<PaymentParameters> = None;
1767817712
let mut blinded_tail: Option<BlindedTail> = None;
1767917713
let mut bolt12_invoice: Option<Bolt12InvoiceType> = None;
17714+
let mut payment_nonce: Option<Nonce> = None;
1768017715
read_tlv_fields!(reader, {
1768117716
(0, session_priv, required),
1768217717
(1, payment_id, option),
@@ -17685,6 +17720,7 @@ impl Readable for HTLCSource {
1768517720
(5, payment_params, (option: ReadableArgs, 0)),
1768617721
(6, blinded_tail, option),
1768717722
(7, bolt12_invoice, option),
17723+
(9, payment_nonce, option),
1768817724
});
1768917725
if payment_id.is_none() {
1769017726
// For backwards compat, if there was no payment_id written, use the session_priv bytes
@@ -17708,6 +17744,7 @@ impl Readable for HTLCSource {
1770817744
path,
1770917745
payment_id: payment_id.unwrap(),
1771017746
bolt12_invoice,
17747+
payment_nonce,
1771117748
})
1771217749
}
1771317750
1 => Ok(HTLCSource::PreviousHopData(Readable::read(reader)?)),
@@ -17727,6 +17764,7 @@ impl Writeable for HTLCSource {
1772717764
ref path,
1772817765
payment_id,
1772917766
bolt12_invoice,
17767+
payment_nonce,
1773017768
} => {
1773117769
0u8.write(writer)?;
1773217770
let payment_id_opt = Some(payment_id);
@@ -17739,6 +17777,7 @@ impl Writeable for HTLCSource {
1773917777
(5, None::<PaymentParameters>, option), // payment_params in LDK versions prior to 0.0.115
1774017778
(6, path.blinded_tail, option),
1774117779
(7, bolt12_invoice, option),
17780+
(9, payment_nonce, option),
1774217781
});
1774317782
},
1774417783
HTLCSource::PreviousHopData(ref field) => {
@@ -19603,6 +19642,7 @@ impl<
1960319642
session_priv,
1960419643
path,
1960519644
bolt12_invoice,
19645+
payment_nonce,
1960619646
..
1960719647
} => {
1960819648
if let Some(preimage) = preimage_opt {
@@ -19620,6 +19660,7 @@ impl<
1962019660
payment_id,
1962119661
preimage,
1962219662
bolt12_invoice,
19663+
payment_nonce,
1962319664
session_priv,
1962419665
path,
1962519666
true,

0 commit comments

Comments
 (0)