Skip to content

Commit 33e6995

Browse files
authored
Merge pull request #3192 from jkczyz/2024-07-invoice-error-auth
Authenticate `InvoiceError` messages
2 parents 5ab40b2 + 075a2e3 commit 33e6995

19 files changed

+443
-173
lines changed

lightning-types/src/features.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ mod sealed {
567567

568568
#[cfg(any(test, feature = "_test_utils"))]
569569
define_feature!(
570-
123456789,
570+
12345,
571571
UnknownFeature,
572572
[
573573
NodeContext,
@@ -1117,7 +1117,7 @@ mod tests {
11171117
features.set_unknown_feature_required();
11181118
assert!(features.requires_unknown_bits());
11191119
assert!(features.supports_unknown_bits());
1120-
assert_eq!(features.required_unknown_bits_from(&ChannelFeatures::empty()), vec![123456788]);
1120+
assert_eq!(features.required_unknown_bits_from(&ChannelFeatures::empty()), vec![12344]);
11211121

11221122
let mut features = ChannelFeatures::empty();
11231123
features.set_unknown_feature_optional();
@@ -1127,17 +1127,17 @@ mod tests {
11271127

11281128
let mut features = ChannelFeatures::empty();
11291129
features.set_unknown_feature_required();
1130-
features.set_custom_bit(123456786).unwrap();
1130+
features.set_custom_bit(12346).unwrap();
11311131
assert!(features.requires_unknown_bits());
11321132
assert!(features.supports_unknown_bits());
11331133
assert_eq!(
11341134
features.required_unknown_bits_from(&ChannelFeatures::empty()),
1135-
vec![123456786, 123456788]
1135+
vec![12344, 12346]
11361136
);
11371137

11381138
let mut limiter = ChannelFeatures::empty();
11391139
limiter.set_unknown_feature_optional();
1140-
assert_eq!(features.required_unknown_bits_from(&limiter), vec![123456786]);
1140+
assert_eq!(features.required_unknown_bits_from(&limiter), vec![12346]);
11411141
}
11421142

11431143
#[test]

lightning/src/blinded_path/message.rs

+9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
1616
#[allow(unused_imports)]
1717
use crate::prelude::*;
1818

19+
use bitcoin::hashes::hmac::Hmac;
20+
use bitcoin::hashes::sha256::Hash as Sha256;
1921
use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NextMessageHop, NodeIdLookUp};
2022
use crate::blinded_path::utils;
2123
use crate::io;
@@ -146,6 +148,12 @@ pub enum OffersContext {
146148
/// [`Refund`]: crate::offers::refund::Refund
147149
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
148150
nonce: Nonce,
151+
152+
/// Authentication code for the [`PaymentId`], which should be checked when the context is
153+
/// used with an [`InvoiceError`].
154+
///
155+
/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
156+
hmac: Option<Hmac<Sha256>>,
149157
},
150158
/// Context used by a [`BlindedPath`] as a reply path for a [`Bolt12Invoice`].
151159
///
@@ -173,6 +181,7 @@ impl_writeable_tlv_based_enum!(OffersContext,
173181
(1, OutboundPayment) => {
174182
(0, payment_id, required),
175183
(1, nonce, required),
184+
(2, hmac, option),
176185
},
177186
(2, InboundPayment) => {
178187
(0, payment_hash, required),

lightning/src/events/mod.rs

+67-30
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,12 @@ impl_writeable_tlv_based_enum!(InterceptNextHop,
502502
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
503503
pub enum PaymentFailureReason {
504504
/// The intended recipient rejected our payment.
505+
///
506+
/// Also used for [`UnknownRequiredFeatures`] and [`InvoiceRequestRejected`] when downgrading to
507+
/// version prior to 0.0.124.
508+
///
509+
/// [`UnknownRequiredFeatures`]: Self::UnknownRequiredFeatures
510+
/// [`InvoiceRequestRejected`]: Self::InvoiceRequestRejected
505511
RecipientRejected,
506512
/// The user chose to abandon this payment by calling [`ChannelManager::abandon_payment`].
507513
///
@@ -517,7 +523,10 @@ pub enum PaymentFailureReason {
517523
/// The payment expired while retrying, based on the provided
518524
/// [`PaymentParameters::expiry_time`].
519525
///
526+
/// Also used for [`InvoiceRequestExpired`] when downgrading to version prior to 0.0.124.
527+
///
520528
/// [`PaymentParameters::expiry_time`]: crate::routing::router::PaymentParameters::expiry_time
529+
/// [`InvoiceRequestExpired`]: Self::InvoiceRequestExpired
521530
PaymentExpired,
522531
/// We failed to find a route while retrying the payment.
523532
///
@@ -528,12 +537,23 @@ pub enum PaymentFailureReason {
528537
/// This error should generally never happen. This likely means that there is a problem with
529538
/// your router.
530539
UnexpectedError,
540+
/// An invoice was received that required unknown features.
541+
UnknownRequiredFeatures,
542+
/// A [`Bolt12Invoice`] was not received in a reasonable amount of time.
543+
InvoiceRequestExpired,
544+
/// An [`InvoiceRequest`] for the payment was rejected by the recipient.
545+
///
546+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
547+
InvoiceRequestRejected,
531548
}
532549

533-
impl_writeable_tlv_based_enum!(PaymentFailureReason,
550+
impl_writeable_tlv_based_enum_upgradable!(PaymentFailureReason,
534551
(0, RecipientRejected) => {},
552+
(1, UnknownRequiredFeatures) => {},
535553
(2, UserAbandoned) => {},
554+
(3, InvoiceRequestExpired) => {},
536555
(4, RetriesExhausted) => {},
556+
(5, InvoiceRequestRejected) => {},
537557
(6, PaymentExpired) => {},
538558
(8, RouteNotFound) => {},
539559
(10, UnexpectedError) => {},
@@ -769,22 +789,6 @@ pub enum Event {
769789
/// Sockets for connecting to the node.
770790
addresses: Vec<msgs::SocketAddress>,
771791
},
772-
/// Indicates a request for an invoice failed to yield a response in a reasonable amount of time
773-
/// or was explicitly abandoned by [`ChannelManager::abandon_payment`]. This may be for an
774-
/// [`InvoiceRequest`] sent for an [`Offer`] or for a [`Refund`] that hasn't been redeemed.
775-
///
776-
/// # Failure Behavior and Persistence
777-
/// This event will eventually be replayed after failures-to-handle (i.e., the event handler
778-
/// returning `Err(ReplayEvent ())`) and will be persisted across restarts.
779-
///
780-
/// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment
781-
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
782-
/// [`Offer`]: crate::offers::offer::Offer
783-
/// [`Refund`]: crate::offers::refund::Refund
784-
InvoiceRequestFailed {
785-
/// The `payment_id` to have been associated with payment for the requested invoice.
786-
payment_id: PaymentId,
787-
},
788792
/// Indicates a [`Bolt12Invoice`] in response to an [`InvoiceRequest`] or a [`Refund`] was
789793
/// received.
790794
///
@@ -876,12 +880,15 @@ pub enum Event {
876880
///
877881
/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
878882
payment_id: PaymentId,
879-
/// The hash that was given to [`ChannelManager::send_payment`].
883+
/// The hash that was given to [`ChannelManager::send_payment`]. `None` if the payment failed
884+
/// before receiving an invoice when paying a BOLT12 [`Offer`].
880885
///
881886
/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
882-
payment_hash: PaymentHash,
887+
/// [`Offer`]: crate::offers::offer::Offer
888+
payment_hash: Option<PaymentHash>,
883889
/// The reason the payment failed. This is only `None` for events generated or serialized
884-
/// by versions prior to 0.0.115.
890+
/// by versions prior to 0.0.115, or when downgrading to a version with a reason that was
891+
/// added after.
885892
reason: Option<PaymentFailureReason>,
886893
},
887894
/// Indicates that a path for an outbound payment was successful.
@@ -1552,10 +1559,34 @@ impl Writeable for Event {
15521559
},
15531560
&Event::PaymentFailed { ref payment_id, ref payment_hash, ref reason } => {
15541561
15u8.write(writer)?;
1562+
let (payment_hash, invoice_received) = match payment_hash {
1563+
Some(payment_hash) => (payment_hash, true),
1564+
None => (&PaymentHash([0; 32]), false),
1565+
};
1566+
let legacy_reason = match reason {
1567+
None => &None,
1568+
// Variants available prior to version 0.0.124.
1569+
Some(PaymentFailureReason::RecipientRejected)
1570+
| Some(PaymentFailureReason::UserAbandoned)
1571+
| Some(PaymentFailureReason::RetriesExhausted)
1572+
| Some(PaymentFailureReason::PaymentExpired)
1573+
| Some(PaymentFailureReason::RouteNotFound)
1574+
| Some(PaymentFailureReason::UnexpectedError) => reason,
1575+
// Variants introduced at version 0.0.124 or later. Prior versions fail to parse
1576+
// unknown variants, while versions 0.0.124 or later will use None.
1577+
Some(PaymentFailureReason::UnknownRequiredFeatures) =>
1578+
&Some(PaymentFailureReason::RecipientRejected),
1579+
Some(PaymentFailureReason::InvoiceRequestExpired) =>
1580+
&Some(PaymentFailureReason::RetriesExhausted),
1581+
Some(PaymentFailureReason::InvoiceRequestRejected) =>
1582+
&Some(PaymentFailureReason::RecipientRejected),
1583+
};
15551584
write_tlv_fields!(writer, {
15561585
(0, payment_id, required),
1557-
(1, reason, option),
1586+
(1, legacy_reason, option),
15581587
(2, payment_hash, required),
1588+
(3, invoice_received, required),
1589+
(5, reason, option),
15591590
})
15601591
},
15611592
&Event::OpenChannelRequest { .. } => {
@@ -1634,12 +1665,6 @@ impl Writeable for Event {
16341665
(8, funding_txo, required),
16351666
});
16361667
},
1637-
&Event::InvoiceRequestFailed { ref payment_id } => {
1638-
33u8.write(writer)?;
1639-
write_tlv_fields!(writer, {
1640-
(0, payment_id, required),
1641-
})
1642-
},
16431668
&Event::ConnectionNeeded { .. } => {
16441669
35u8.write(writer)?;
16451670
// Never write ConnectionNeeded events as buffered onion messages aren't serialized.
@@ -1929,15 +1954,24 @@ impl MaybeReadable for Event {
19291954
let mut payment_hash = PaymentHash([0; 32]);
19301955
let mut payment_id = PaymentId([0; 32]);
19311956
let mut reason = None;
1957+
let mut legacy_reason = None;
1958+
let mut invoice_received: Option<bool> = None;
19321959
read_tlv_fields!(reader, {
19331960
(0, payment_id, required),
1934-
(1, reason, upgradable_option),
1961+
(1, legacy_reason, upgradable_option),
19351962
(2, payment_hash, required),
1963+
(3, invoice_received, option),
1964+
(5, reason, upgradable_option),
19361965
});
1966+
let payment_hash = match invoice_received {
1967+
Some(invoice_received) => invoice_received.then(|| payment_hash),
1968+
None => (payment_hash != PaymentHash([0; 32])).then(|| payment_hash),
1969+
};
1970+
let reason = reason.or(legacy_reason);
19371971
Ok(Some(Event::PaymentFailed {
19381972
payment_id,
19391973
payment_hash,
1940-
reason,
1974+
reason: _init_tlv_based_struct_field!(reason, upgradable_option),
19411975
}))
19421976
};
19431977
f()
@@ -2076,13 +2110,16 @@ impl MaybeReadable for Event {
20762110
};
20772111
f()
20782112
},
2113+
// This was Event::InvoiceRequestFailed prior to version 0.0.124.
20792114
33u8 => {
20802115
let mut f = || {
20812116
_init_and_read_len_prefixed_tlv_fields!(reader, {
20822117
(0, payment_id, required),
20832118
});
2084-
Ok(Some(Event::InvoiceRequestFailed {
2119+
Ok(Some(Event::PaymentFailed {
20852120
payment_id: payment_id.0.unwrap(),
2121+
payment_hash: None,
2122+
reason: Some(PaymentFailureReason::InvoiceRequestExpired),
20862123
}))
20872124
};
20882125
f()

lightning/src/ln/blinded_payment_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,7 @@ fn blinded_path_retries() {
10681068
assert_eq!(evs.len(), 1);
10691069
match evs[0] {
10701070
Event::PaymentFailed { payment_hash: ev_payment_hash, reason, .. } => {
1071-
assert_eq!(ev_payment_hash, payment_hash);
1071+
assert_eq!(ev_payment_hash, Some(payment_hash));
10721072
// We have 1 retry attempt remaining, but we're out of blinded paths to try.
10731073
assert_eq!(reason, Some(PaymentFailureReason::RouteNotFound));
10741074
},

lightning/src/ln/chanmon_update_fail_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1776,7 +1776,7 @@ fn test_monitor_update_on_pending_forwards() {
17761776
} else { panic!("Unexpected event!"); }
17771777
match events[2] {
17781778
Event::PaymentFailed { payment_hash, .. } => {
1779-
assert_eq!(payment_hash, payment_hash_1);
1779+
assert_eq!(payment_hash, Some(payment_hash_1));
17801780
},
17811781
_ => panic!("Unexpected event"),
17821782
}

0 commit comments

Comments
 (0)