Skip to content

Commit 21fa551

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 32d6e91 commit 21fa551

File tree

2 files changed

+102
-49
lines changed

2 files changed

+102
-49
lines changed

lightning/src/offers/invoice.rs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1642,36 +1642,31 @@ mod tests {
16421642
.build().unwrap()
16431643
.sign(payer_sign).unwrap();
16441644

1645-
if let Err(e) = invoice_request
1646-
.verify_and_respond_using_derived_keys_no_std(
1647-
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
1648-
)
1649-
.unwrap()
1645+
if let Err(e) = invoice_request.clone()
1646+
.verify(&expanded_key, &secp_ctx).unwrap()
1647+
.respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now()).unwrap()
16501648
.build_and_sign(&secp_ctx)
16511649
{
16521650
panic!("error building invoice: {:?}", e);
16531651
}
16541652

16551653
let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32]));
1656-
match invoice_request.verify_and_respond_using_derived_keys_no_std(
1657-
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
1658-
) {
1659-
Ok(_) => panic!("expected error"),
1660-
Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
1661-
}
1654+
assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
16621655

16631656
let desc = "foo".to_string();
16641657
let offer = OfferBuilder
16651658
::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
16661659
.amount_msats(1000)
1660+
// Omit the path so that node_id is used for the signing pubkey instead of deriving
16671661
.build().unwrap();
16681662
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
16691663
.build().unwrap()
16701664
.sign(payer_sign).unwrap();
16711665

1672-
match invoice_request.verify_and_respond_using_derived_keys_no_std(
1673-
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
1674-
) {
1666+
match invoice_request
1667+
.verify(&expanded_key, &secp_ctx).unwrap()
1668+
.respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now())
1669+
{
16751670
Ok(_) => panic!("expected error"),
16761671
Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
16771672
}

lightning/src/offers/invoice_request.rs

Lines changed: 93 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,24 @@ pub struct InvoiceRequest {
424424
signature: Signature,
425425
}
426426

427+
/// An [`InvoiceRequest`] that has been verified by [`InvoiceRequest::verify`] and exposes different
428+
/// ways to respond depending on whether the signing keys were derived.
429+
#[derive(Clone, Debug)]
430+
pub struct VerifiedInvoiceRequest {
431+
/// The verified request.
432+
inner: InvoiceRequest,
433+
434+
/// Keys used for signing a [`Bolt12Invoice`] if they can be derived.
435+
///
436+
/// If `Some`, must call [`respond_using_derived_keys`] when responding. Otherwise, call
437+
/// [`respond_with`].
438+
///
439+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
440+
/// [`respond_using_derived_keys`]: Self::respond_using_derived_keys
441+
/// [`respond_with`]: Self::respond_with
442+
pub keys: Option<KeyPair>,
443+
}
444+
427445
/// The contents of an [`InvoiceRequest`], which may be shared with an [`Bolt12Invoice`].
428446
///
429447
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
@@ -542,9 +560,15 @@ impl InvoiceRequest {
542560
///
543561
/// Errors if the request contains unknown required features.
544562
///
563+
/// # Note
564+
///
565+
/// If the originating [`Offer`] was created using [`OfferBuilder::deriving_signing_pubkey`],
566+
/// then use [`InvoiceRequest::verify`] and [`VerifiedInvoiceRequest`] methods instead.
567+
///
545568
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
546569
///
547570
/// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at
571+
/// [`OfferBuilder::deriving_signing_pubkey`]: crate::offers::offer::OfferBuilder::deriving_signing_pubkey
548572
pub fn respond_with_no_std(
549573
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
550574
created_at: core::time::Duration
@@ -556,6 +580,63 @@ impl InvoiceRequest {
556580
InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash)
557581
}
558582

583+
/// Verifies that the request was for an offer created using the given key. Returns the verified
584+
/// request which contains the derived keys needed to sign a [`Bolt12Invoice`] for the request
585+
/// if they could be extracted from the metadata.
586+
///
587+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
588+
pub fn verify<T: secp256k1::Signing>(
589+
self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
590+
) -> Result<VerifiedInvoiceRequest, ()> {
591+
let keys = self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)?;
592+
Ok(VerifiedInvoiceRequest {
593+
inner: self,
594+
keys,
595+
})
596+
}
597+
598+
#[cfg(test)]
599+
fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
600+
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
601+
self.contents.as_tlv_stream();
602+
let signature_tlv_stream = SignatureTlvStreamRef {
603+
signature: Some(&self.signature),
604+
};
605+
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
606+
}
607+
}
608+
609+
impl VerifiedInvoiceRequest {
610+
offer_accessors!(self, self.inner.contents.inner.offer);
611+
invoice_request_accessors!(self, self.inner.contents);
612+
613+
/// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the
614+
/// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
615+
///
616+
/// See [`InvoiceRequest::respond_with_no_std`] for further details.
617+
///
618+
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
619+
///
620+
/// [`Duration`]: core::time::Duration
621+
#[cfg(feature = "std")]
622+
pub fn respond_with(
623+
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
624+
) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
625+
self.inner.respond_with(payment_paths, payment_hash)
626+
}
627+
628+
/// Creates an [`InvoiceBuilder`] for the request with the given required fields.
629+
///
630+
/// See [`InvoiceRequest::respond_with_no_std`] for further details.
631+
///
632+
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
633+
pub fn respond_with_no_std(
634+
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
635+
created_at: core::time::Duration
636+
) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
637+
self.inner.respond_with_no_std(payment_paths, payment_hash, created_at)
638+
}
639+
559640
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
560641
/// derived signing keys from the originating [`Offer`] to sign the [`Bolt12Invoice`]. Must use
561642
/// the same [`ExpandedKey`] as the one used to create the offer.
@@ -566,17 +647,14 @@ impl InvoiceRequest {
566647
///
567648
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
568649
#[cfg(feature = "std")]
569-
pub fn verify_and_respond_using_derived_keys<T: secp256k1::Signing>(
570-
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
571-
expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
650+
pub fn respond_using_derived_keys(
651+
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
572652
) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
573653
let created_at = std::time::SystemTime::now()
574654
.duration_since(std::time::SystemTime::UNIX_EPOCH)
575655
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
576656

577-
self.verify_and_respond_using_derived_keys_no_std(
578-
payment_paths, payment_hash, created_at, expanded_key, secp_ctx
579-
)
657+
self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at)
580658
}
581659

582660
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
@@ -588,42 +666,22 @@ impl InvoiceRequest {
588666
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
589667
///
590668
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
591-
pub fn verify_and_respond_using_derived_keys_no_std<T: secp256k1::Signing>(
669+
pub fn respond_using_derived_keys_no_std(
592670
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
593-
created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
671+
created_at: core::time::Duration
594672
) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
595-
if self.invoice_request_features().requires_unknown_bits() {
673+
if self.inner.invoice_request_features().requires_unknown_bits() {
596674
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
597675
}
598676

599-
let keys = match self.verify(expanded_key, secp_ctx) {
600-
Err(()) => return Err(Bolt12SemanticError::InvalidMetadata),
601-
Ok(None) => return Err(Bolt12SemanticError::InvalidMetadata),
602-
Ok(Some(keys)) => keys,
677+
let keys = match self.keys {
678+
None => return Err(Bolt12SemanticError::InvalidMetadata),
679+
Some(keys) => keys,
603680
};
604681

605-
InvoiceBuilder::for_offer_using_keys(self, payment_paths, created_at, payment_hash, keys)
606-
}
607-
608-
/// Verifies that the request was for an offer created using the given key. Returns the derived
609-
/// keys need to sign an [`Bolt12Invoice`] for the request if they could be extracted from the
610-
/// metadata.
611-
///
612-
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
613-
pub fn verify<T: secp256k1::Signing>(
614-
&self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
615-
) -> Result<Option<KeyPair>, ()> {
616-
self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)
617-
}
618-
619-
#[cfg(test)]
620-
fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
621-
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
622-
self.contents.as_tlv_stream();
623-
let signature_tlv_stream = SignatureTlvStreamRef {
624-
signature: Some(&self.signature),
625-
};
626-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
682+
InvoiceBuilder::for_offer_using_keys(
683+
&self.inner, payment_paths, created_at, payment_hash, keys
684+
)
627685
}
628686
}
629687

0 commit comments

Comments
 (0)