Skip to content

Commit 6c87fa1

Browse files
committed
Split InvoiceRequest::verify_and_respond_using_derived_keys
InvoiceRequest::verify_and_respond_using_derived_keys takes a payment hash. To avoid generating one for invoice requests that ultimately cannot be verified, split the method into one for verifying and another for responding.
1 parent 8bfc896 commit 6c87fa1

File tree

2 files changed

+91
-49
lines changed

2 files changed

+91
-49
lines changed

lightning/src/offers/invoice.rs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,36 +1249,31 @@ mod tests {
12491249
.build().unwrap()
12501250
.sign(payer_sign).unwrap();
12511251

1252-
if let Err(e) = invoice_request
1253-
.verify_and_respond_using_derived_keys_no_std(
1254-
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
1255-
)
1256-
.unwrap()
1252+
if let Err(e) = invoice_request.clone()
1253+
.verify(&expanded_key, &secp_ctx).unwrap()
1254+
.respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now()).unwrap()
12571255
.build_and_sign(&secp_ctx)
12581256
{
12591257
panic!("error building invoice: {:?}", e);
12601258
}
12611259

12621260
let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32]));
1263-
match invoice_request.verify_and_respond_using_derived_keys_no_std(
1264-
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
1265-
) {
1266-
Ok(_) => panic!("expected error"),
1267-
Err(e) => assert_eq!(e, SemanticError::InvalidMetadata),
1268-
}
1261+
assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
12691262

12701263
let desc = "foo".to_string();
12711264
let offer = OfferBuilder
12721265
::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
12731266
.amount_msats(1000)
1267+
// Omit the path so that node_id is used for the signing pubkey instead of deriving
12741268
.build().unwrap();
12751269
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
12761270
.build().unwrap()
12771271
.sign(payer_sign).unwrap();
12781272

1279-
match invoice_request.verify_and_respond_using_derived_keys_no_std(
1280-
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
1281-
) {
1273+
match invoice_request
1274+
.verify(&expanded_key, &secp_ctx).unwrap()
1275+
.respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now())
1276+
{
12821277
Ok(_) => panic!("expected error"),
12831278
Err(e) => assert_eq!(e, SemanticError::InvalidMetadata),
12841279
}

lightning/src/offers/invoice_request.rs

Lines changed: 82 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,22 @@ pub struct InvoiceRequest {
399399
signature: Signature,
400400
}
401401

402+
/// An [`InvoiceRequest`] that has been verified by [`InvoiceRequest::verify`] and exposes different
403+
/// ways to respond depending on whether the signing keys were derived.
404+
#[derive(Clone, Debug)]
405+
pub struct VerifiedInvoiceRequest {
406+
/// The verified request.
407+
pub inner: InvoiceRequest,
408+
409+
/// Keys used for signing an [`Invoice`] if they can be derived.
410+
///
411+
/// If `Some`, then must respond with methods that use derived keys. Otherwise, should respond
412+
/// with an invoice signed explicitly.
413+
///
414+
/// [`Invoice`]: crate::offers::invoice::Invoice
415+
pub keys: Option<KeyPair>,
416+
}
417+
402418
/// The contents of an [`InvoiceRequest`], which may be shared with an [`Invoice`].
403419
///
404420
/// [`Invoice`]: crate::offers::invoice::Invoice
@@ -522,6 +538,60 @@ impl InvoiceRequest {
522538
InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash)
523539
}
524540

541+
/// Verifies that the request was for an offer created using the given key. Returns the verified
542+
/// request along with the derived keys needed to sign an [`Invoice`] for the request if they
543+
/// could be extracted from the metadata.
544+
///
545+
/// [`Invoice`]: crate::offers::invoice::Invoice
546+
pub fn verify<T: secp256k1::Signing>(
547+
self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
548+
) -> Result<VerifiedInvoiceRequest, ()> {
549+
let keys = self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)?;
550+
Ok(VerifiedInvoiceRequest {
551+
inner: self,
552+
keys,
553+
})
554+
}
555+
556+
#[cfg(test)]
557+
fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
558+
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
559+
self.contents.as_tlv_stream();
560+
let signature_tlv_stream = SignatureTlvStreamRef {
561+
signature: Some(&self.signature),
562+
};
563+
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
564+
}
565+
}
566+
567+
impl VerifiedInvoiceRequest {
568+
/// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the
569+
/// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
570+
///
571+
/// See [`InvoiceRequest::respond_with_no_std`] for further details.
572+
///
573+
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
574+
///
575+
/// [`Duration`]: core::time::Duration
576+
#[cfg(feature = "std")]
577+
pub fn respond_with(
578+
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
579+
) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
580+
self.inner.respond_with(payment_paths, payment_hash)
581+
}
582+
583+
/// Creates an [`InvoiceBuilder`] for the request with the given required fields.
584+
///
585+
/// See [`InvoiceRequest::respond_with_no_std`] for further details.
586+
///
587+
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
588+
pub fn respond_with_no_std(
589+
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
590+
created_at: core::time::Duration
591+
) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
592+
self.inner.respond_with_no_std(payment_paths, payment_hash, created_at)
593+
}
594+
525595
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
526596
/// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the
527597
/// same [`ExpandedKey`] as the one used to create the offer.
@@ -532,17 +602,14 @@ impl InvoiceRequest {
532602
///
533603
/// [`Invoice`]: crate::offers::invoice::Invoice
534604
#[cfg(feature = "std")]
535-
pub fn verify_and_respond_using_derived_keys<T: secp256k1::Signing>(
536-
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
537-
expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
605+
pub fn respond_using_derived_keys(
606+
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
538607
) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
539608
let created_at = std::time::SystemTime::now()
540609
.duration_since(std::time::SystemTime::UNIX_EPOCH)
541610
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
542611

543-
self.verify_and_respond_using_derived_keys_no_std(
544-
payment_paths, payment_hash, created_at, expanded_key, secp_ctx
545-
)
612+
self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at)
546613
}
547614

548615
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
@@ -554,42 +621,22 @@ impl InvoiceRequest {
554621
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
555622
///
556623
/// [`Invoice`]: crate::offers::invoice::Invoice
557-
pub fn verify_and_respond_using_derived_keys_no_std<T: secp256k1::Signing>(
624+
pub fn respond_using_derived_keys_no_std(
558625
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
559-
created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
626+
created_at: core::time::Duration
560627
) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
561-
if self.features().requires_unknown_bits() {
628+
if self.inner.features().requires_unknown_bits() {
562629
return Err(SemanticError::UnknownRequiredFeatures);
563630
}
564631

565-
let keys = match self.verify(expanded_key, secp_ctx) {
566-
Err(()) => return Err(SemanticError::InvalidMetadata),
567-
Ok(None) => return Err(SemanticError::InvalidMetadata),
568-
Ok(Some(keys)) => keys,
632+
let keys = match self.keys {
633+
None => return Err(SemanticError::InvalidMetadata),
634+
Some(keys) => keys,
569635
};
570636

571-
InvoiceBuilder::for_offer_using_keys(self, payment_paths, created_at, payment_hash, keys)
572-
}
573-
574-
/// Verifies that the request was for an offer created using the given key. Returns the derived
575-
/// keys need to sign an [`Invoice`] for the request if they could be extracted from the
576-
/// metadata.
577-
///
578-
/// [`Invoice`]: crate::offers::invoice::Invoice
579-
pub fn verify<T: secp256k1::Signing>(
580-
&self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
581-
) -> Result<Option<KeyPair>, ()> {
582-
self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)
583-
}
584-
585-
#[cfg(test)]
586-
fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
587-
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
588-
self.contents.as_tlv_stream();
589-
let signature_tlv_stream = SignatureTlvStreamRef {
590-
signature: Some(&self.signature),
591-
};
592-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
637+
InvoiceBuilder::for_offer_using_keys(
638+
&self.inner, payment_paths, created_at, payment_hash, keys
639+
)
593640
}
594641
}
595642

0 commit comments

Comments
 (0)