Skip to content

Commit eeb5d24

Browse files
committed
Introduce VerifiedInvoiceRequest<S: SigningPubkeyStrategy>
This commit reintroduces `VerifiedInvoiceRequest`, now parameterized by `SigningPubkeyStrategy`. The key motivation is to restrict which functions can be called on a `VerifiedInvoiceRequest` based on its strategy type. This enables compile-time guarantees — ensuring that an incorrect `InvoiceBuilder` cannot be constructed for a given request, and misuses are caught early.
1 parent 2a5f168 commit eeb5d24

File tree

6 files changed

+186
-107
lines changed

6 files changed

+186
-107
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7735,7 +7735,7 @@ where
77357735
};
77367736
let payment_purpose_context =
77377737
PaymentContext::Bolt12Offer(Bolt12OfferContext {
7738-
offer_id: verified_invreq.offer_id,
7738+
offer_id: verified_invreq.offer_id(),
77397739
invoice_request: verified_invreq.fields(),
77407740
});
77417741
let from_parts_res = events::PaymentPurpose::from_parts(
@@ -14876,7 +14876,7 @@ where
1487614876
};
1487714877

1487814878
let amount_msats = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
14879-
&invoice_request.inner
14879+
&invoice_request.inner()
1488014880
) {
1488114881
Ok(amount_msats) => amount_msats,
1488214882
Err(error) => return Some((OffersMessage::InvoiceError(error.into()), responder.respond())),

lightning/src/ln/offers_tests.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, Init, NodeAnnou
5757
use crate::ln::outbound_payment::IDEMPOTENCY_TIMEOUT_TICKS;
5858
use crate::offers::invoice::Bolt12Invoice;
5959
use crate::offers::invoice_error::InvoiceError;
60-
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields};
60+
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields, InvoiceRequestVerifiedFromOffer};
6161
use crate::offers::nonce::Nonce;
6262
use crate::offers::parse::Bolt12SemanticError;
6363
use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageSendInstructions, NodeIdMessageRouter, NullMessageRouter, PeeledOnion, PADDED_PATH_LENGTH};
@@ -2326,11 +2326,19 @@ fn fails_paying_invoice_with_unknown_required_features() {
23262326
let secp_ctx = Secp256k1::new();
23272327

23282328
let created_at = alice.node.duration_since_epoch();
2329-
let invoice = invoice_request
2330-
.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap()
2331-
.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at).unwrap()
2332-
.features_unchecked(Bolt12InvoiceFeatures::unknown())
2333-
.build_and_sign(&secp_ctx).unwrap();
2329+
let verified_invoice_request = invoice_request
2330+
.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap();
2331+
2332+
let invoice = match verified_invoice_request {
2333+
InvoiceRequestVerifiedFromOffer::DerivedKeys(request) => {
2334+
request.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at).unwrap()
2335+
.features_unchecked(Bolt12InvoiceFeatures::unknown())
2336+
.build_and_sign(&secp_ctx).unwrap()
2337+
},
2338+
InvoiceRequestVerifiedFromOffer::ExplicitKeys(_) => {
2339+
panic!("Expected invoice request with keys");
2340+
},
2341+
};
23342342

23352343
// Enqueue an onion message containing the new invoice.
23362344
let instructions = MessageSendInstructions::WithoutReplyPath {

lightning/src/offers/flow.rs

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use crate::offers::invoice::{
4141
};
4242
use crate::offers::invoice_error::InvoiceError;
4343
use crate::offers::invoice_request::{
44-
InvoiceRequest, InvoiceRequestBuilder, VerifiedInvoiceRequest,
44+
InvoiceRequest, InvoiceRequestBuilder, InvoiceRequestVerifiedFromOffer,
4545
};
4646
use crate::offers::nonce::Nonce;
4747
use crate::offers::offer::{Amount, DerivedMetadata, Offer, OfferBuilder};
@@ -403,7 +403,7 @@ fn enqueue_onion_message_with_reply_paths<T: OnionMessageContents + Clone>(
403403
pub enum InvreqResponseInstructions {
404404
/// We are the recipient of this payment, and a [`Bolt12Invoice`] should be sent in response to
405405
/// the invoice request since it is now verified.
406-
SendInvoice(VerifiedInvoiceRequest),
406+
SendInvoice(InvoiceRequestVerifiedFromOffer),
407407
/// We are a static invoice server and should respond to this invoice request by retrieving the
408408
/// [`StaticInvoice`] corresponding to the `recipient_id` and `invoice_slot` and calling
409409
/// [`OffersMessageFlow::enqueue_static_invoice`].
@@ -939,7 +939,7 @@ where
939939
Ok(builder.into())
940940
}
941941

942-
/// Creates a response for the provided [`VerifiedInvoiceRequest`].
942+
/// Creates a response for the provided [`InvoiceRequestVerifiedFromOffer`].
943943
///
944944
/// A response can be either an [`OffersMessage::Invoice`] with additional [`MessageContext`],
945945
/// or an [`OffersMessage::InvoiceError`], depending on the [`InvoiceRequest`].
@@ -949,8 +949,9 @@ where
949949
/// - We fail to generate a valid signed [`Bolt12Invoice`] for the [`InvoiceRequest`].
950950
pub fn create_response_for_invoice_request<ES: Deref, NS: Deref, R: Deref>(
951951
&self, signer: &NS, router: &R, entropy_source: ES,
952-
invoice_request: VerifiedInvoiceRequest, amount_msats: u64, payment_hash: PaymentHash,
953-
payment_secret: PaymentSecret, usable_channels: Vec<ChannelDetails>,
952+
invoice_request: InvoiceRequestVerifiedFromOffer, amount_msats: u64,
953+
payment_hash: PaymentHash, payment_secret: PaymentSecret,
954+
usable_channels: Vec<ChannelDetails>,
954955
) -> (OffersMessage, Option<MessageContext>)
955956
where
956957
ES::Target: EntropySource,
@@ -963,7 +964,7 @@ where
963964
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
964965

965966
let context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
966-
offer_id: invoice_request.offer_id,
967+
offer_id: invoice_request.offer_id(),
967968
invoice_request: invoice_request.fields(),
968969
});
969970

@@ -986,35 +987,36 @@ where
986987
#[cfg(not(feature = "std"))]
987988
let created_at = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
988989

989-
let response = if invoice_request.keys.is_some() {
990-
#[cfg(feature = "std")]
991-
let builder = invoice_request.respond_using_derived_keys(payment_paths, payment_hash);
992-
#[cfg(not(feature = "std"))]
993-
let builder = invoice_request.respond_using_derived_keys_no_std(
994-
payment_paths,
995-
payment_hash,
996-
created_at,
997-
);
998-
builder
999-
.map(InvoiceBuilder::<DerivedSigningPubkey>::from)
1000-
.and_then(|builder| builder.allow_mpp().build_and_sign(secp_ctx))
1001-
.map_err(InvoiceError::from)
1002-
} else {
1003-
#[cfg(feature = "std")]
1004-
let builder = invoice_request.respond_with(payment_paths, payment_hash);
1005-
#[cfg(not(feature = "std"))]
1006-
let builder = invoice_request.respond_with_no_std(payment_paths, payment_hash, created_at);
1007-
builder
1008-
.map(InvoiceBuilder::<ExplicitSigningPubkey>::from)
1009-
.and_then(|builder| builder.allow_mpp().build())
1010-
.map_err(InvoiceError::from)
1011-
.and_then(|invoice| {
1012-
#[cfg(c_bindings)]
1013-
let mut invoice = invoice;
1014-
invoice
1015-
.sign(|invoice: &UnsignedBolt12Invoice| signer.sign_bolt12_invoice(invoice))
1016-
.map_err(InvoiceError::from)
1017-
})
990+
let response = match invoice_request {
991+
InvoiceRequestVerifiedFromOffer::DerivedKeys(request) => {
992+
#[cfg(feature = "std")]
993+
let builder = request.respond_using_derived_keys(payment_paths, payment_hash);
994+
#[cfg(not(feature = "std"))]
995+
let builder = request.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at);
996+
builder
997+
.map(InvoiceBuilder::<DerivedSigningPubkey>::from)
998+
.and_then(|builder| builder.allow_mpp().build_and_sign(secp_ctx))
999+
.map_err(InvoiceError::from)
1000+
},
1001+
InvoiceRequestVerifiedFromOffer::ExplicitKeys(request) => {
1002+
#[cfg(feature = "std")]
1003+
let builder = request.respond_with(payment_paths, payment_hash);
1004+
#[cfg(not(feature = "std"))]
1005+
let builder = request.respond_with_no_std(payment_paths, payment_hash, created_at);
1006+
builder
1007+
.map(InvoiceBuilder::<ExplicitSigningPubkey>::from)
1008+
.and_then(|builder| builder.allow_mpp().build())
1009+
.map_err(InvoiceError::from)
1010+
.and_then(|invoice| {
1011+
#[cfg(c_bindings)]
1012+
let mut invoice = invoice;
1013+
invoice
1014+
.sign(|invoice: &UnsignedBolt12Invoice| {
1015+
signer.sign_bolt12_invoice(invoice)
1016+
})
1017+
.map_err(InvoiceError::from)
1018+
})
1019+
},
10181020
};
10191021

10201022
match response {

lightning/src/offers/invoice.rs

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,6 +1819,7 @@ mod tests {
18191819
use crate::ln::msgs::DecodeError;
18201820
use crate::offers::invoice_request::{
18211821
ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStreamRef,
1822+
InvoiceRequestVerifiedFromOffer,
18221823
};
18231824
use crate::offers::merkle::{self, SignError, SignatureTlvStreamRef, TaggedHash, TlvStream};
18241825
use crate::offers::nonce::Nonce;
@@ -2235,42 +2236,31 @@ mod tests {
22352236
.build_and_sign()
22362237
.unwrap();
22372238

2238-
if let Err(e) = invoice_request
2239+
let verified_request = invoice_request
22392240
.clone()
22402241
.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx)
2241-
.unwrap()
2242-
.respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now())
2243-
.unwrap()
2244-
.build_and_sign(&secp_ctx)
2245-
{
2246-
panic!("error building invoice: {:?}", e);
2242+
.unwrap();
2243+
2244+
match verified_request {
2245+
InvoiceRequestVerifiedFromOffer::DerivedKeys(req) => {
2246+
let invoice = req
2247+
.respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now())
2248+
.unwrap()
2249+
.build_and_sign(&secp_ctx);
2250+
2251+
if let Err(e) = invoice {
2252+
panic!("error building invoice: {:?}", e);
2253+
}
2254+
},
2255+
InvoiceRequestVerifiedFromOffer::ExplicitKeys(_) => {
2256+
panic!("expected invoice request with keys");
2257+
},
22472258
}
22482259

22492260
let expanded_key = ExpandedKey::new([41; 32]);
22502261
assert!(invoice_request
22512262
.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx)
22522263
.is_err());
2253-
2254-
let invoice_request =
2255-
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
2256-
.amount_msats(1000)
2257-
// Omit the path so that node_id is used for the signing pubkey instead of deriving it
2258-
.experimental_foo(42)
2259-
.build()
2260-
.unwrap()
2261-
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
2262-
.unwrap()
2263-
.build_and_sign()
2264-
.unwrap();
2265-
2266-
match invoice_request
2267-
.verify_using_metadata(&expanded_key, &secp_ctx)
2268-
.unwrap()
2269-
.respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now())
2270-
{
2271-
Ok(_) => panic!("expected error"),
2272-
Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
2273-
}
22742264
}
22752265

22762266
#[test]

0 commit comments

Comments
 (0)