Skip to content

Commit 1173ad8

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 5126bff commit 1173ad8

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
@@ -1250,36 +1250,31 @@ mod tests {
12501250
.build().unwrap()
12511251
.sign(payer_sign).unwrap();
12521252

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

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

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

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

lightning/src/offers/invoice_request.rs

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

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

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

544-
self.verify_and_respond_using_derived_keys_no_std(
545-
payment_paths, payment_hash, created_at, expanded_key, secp_ctx
546-
)
613+
self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at)
547614
}
548615

549616
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
@@ -555,42 +622,22 @@ impl InvoiceRequest {
555622
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
556623
///
557624
/// [`Invoice`]: crate::offers::invoice::Invoice
558-
pub fn verify_and_respond_using_derived_keys_no_std<T: secp256k1::Signing>(
625+
pub fn respond_using_derived_keys_no_std(
559626
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
560-
created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
627+
created_at: core::time::Duration
561628
) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
562-
if self.features().requires_unknown_bits() {
629+
if self.inner.features().requires_unknown_bits() {
563630
return Err(SemanticError::UnknownRequiredFeatures);
564631
}
565632

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

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

0 commit comments

Comments
 (0)