]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Extract keys from Offer::metadata to sign Invoice
authorJeffrey Czyz <jkczyz@gmail.com>
Wed, 5 Apr 2023 05:04:41 +0000 (00:04 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Thu, 20 Apr 2023 02:31:07 +0000 (21:31 -0500)
For offers where the signing pubkey is derived, the keys need to be
extracted from the Offer::metadata in order to sign an invoice.
Parameterize InvoiceBuilder such that a build_and_sign method is
available for this situation.

lightning/src/offers/invoice.rs
lightning/src/offers/invoice_request.rs
lightning/src/offers/offer.rs
lightning/src/offers/parse.rs
lightning/src/offers/refund.rs
lightning/src/offers/signer.rs

index 17908391a5b4b9ae3f23b6cceb869fdeb9b1b7e6..1635d956cbe88d0b55f5726836eae926cac942bf 100644 (file)
@@ -97,11 +97,11 @@ use bitcoin::blockdata::constants::ChainHash;
 use bitcoin::hash_types::{WPubkeyHash, WScriptHash};
 use bitcoin::hashes::Hash;
 use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
+use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, self};
 use bitcoin::secp256k1::schnorr::Signature;
 use bitcoin::util::address::{Address, Payload, WitnessVersion};
 use bitcoin::util::schnorr::TweakedPublicKey;
-use core::convert::TryFrom;
+use core::convert::{Infallible, TryFrom};
 use core::time::Duration;
 use crate::io;
 use crate::ln::PaymentHash;
@@ -136,28 +136,31 @@ pub(super) const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
 /// [`Refund`]: crate::offers::refund::Refund
 /// [module-level documentation]: self
-pub struct InvoiceBuilder<'a> {
+pub struct InvoiceBuilder<'a, S: SigningPubkeyStrategy> {
        invreq_bytes: &'a Vec<u8>,
        invoice: InvoiceContents,
+       keys: Option<KeyPair>,
+       signing_pubkey_strategy: core::marker::PhantomData<S>,
 }
 
-impl<'a> InvoiceBuilder<'a> {
+/// Indicates how [`Invoice::signing_pubkey`] was set.
+pub trait SigningPubkeyStrategy {}
+
+/// [`Invoice::signing_pubkey`] was explicitly set.
+pub struct ExplicitSigningPubkey {}
+
+/// [`Invoice::signing_pubkey`] was derived.
+pub struct DerivedSigningPubkey {}
+
+impl SigningPubkeyStrategy for ExplicitSigningPubkey {}
+impl SigningPubkeyStrategy for DerivedSigningPubkey {}
+
+impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
        pub(super) fn for_offer(
                invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
                created_at: Duration, payment_hash: PaymentHash
        ) -> Result<Self, SemanticError> {
-               let amount_msats = match invoice_request.amount_msats() {
-                       Some(amount_msats) => amount_msats,
-                       None => match invoice_request.contents.inner.offer.amount() {
-                               Some(Amount::Bitcoin { amount_msats }) => {
-                                       amount_msats.checked_mul(invoice_request.quantity().unwrap_or(1))
-                                               .ok_or(SemanticError::InvalidAmount)?
-                               },
-                               Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
-                               None => return Err(SemanticError::MissingAmount),
-                       },
-               };
-
+               let amount_msats = Self::check_amount_msats(invoice_request)?;
                let contents = InvoiceContents::ForOffer {
                        invoice_request: invoice_request.contents.clone(),
                        fields: InvoiceFields {
@@ -167,7 +170,7 @@ impl<'a> InvoiceBuilder<'a> {
                        },
                };
 
-               Self::new(&invoice_request.bytes, contents)
+               Self::new(&invoice_request.bytes, contents, None)
        }
 
        pub(super) fn for_refund(
@@ -183,15 +186,57 @@ impl<'a> InvoiceBuilder<'a> {
                        },
                };
 
-               Self::new(&refund.bytes, contents)
+               Self::new(&refund.bytes, contents, None)
        }
+}
 
-       fn new(invreq_bytes: &'a Vec<u8>, contents: InvoiceContents) -> Result<Self, SemanticError> {
+impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
+       pub(super) fn for_offer_using_keys(
+               invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+               created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
+       ) -> Result<Self, SemanticError> {
+               let amount_msats = Self::check_amount_msats(invoice_request)?;
+               let contents = InvoiceContents::ForOffer {
+                       invoice_request: invoice_request.contents.clone(),
+                       fields: InvoiceFields {
+                               payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
+                               fallbacks: None, features: Bolt12InvoiceFeatures::empty(),
+                               signing_pubkey: invoice_request.contents.inner.offer.signing_pubkey(),
+                       },
+               };
+
+               Self::new(&invoice_request.bytes, contents, Some(keys))
+       }
+}
+
+impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
+       fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, SemanticError> {
+               match invoice_request.amount_msats() {
+                       Some(amount_msats) => Ok(amount_msats),
+                       None => match invoice_request.contents.inner.offer.amount() {
+                               Some(Amount::Bitcoin { amount_msats }) => {
+                                       amount_msats.checked_mul(invoice_request.quantity().unwrap_or(1))
+                                               .ok_or(SemanticError::InvalidAmount)
+                               },
+                               Some(Amount::Currency { .. }) => Err(SemanticError::UnsupportedCurrency),
+                               None => Err(SemanticError::MissingAmount),
+                       },
+               }
+       }
+
+       fn new(
+               invreq_bytes: &'a Vec<u8>, contents: InvoiceContents, keys: Option<KeyPair>
+       ) -> Result<Self, SemanticError> {
                if contents.fields().payment_paths.is_empty() {
                        return Err(SemanticError::MissingPaths);
                }
 
-               Ok(Self { invreq_bytes, invoice: contents })
+               Ok(Self {
+                       invreq_bytes,
+                       invoice: contents,
+                       keys,
+                       signing_pubkey_strategy: core::marker::PhantomData,
+               })
        }
 
        /// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry
@@ -248,7 +293,9 @@ impl<'a> InvoiceBuilder<'a> {
                self.invoice.fields_mut().features.set_basic_mpp_optional();
                self
        }
+}
 
+impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
        /// Builds an unsigned [`Invoice`] after checking for valid semantics. It can be signed by
        /// [`UnsignedInvoice::sign`].
        pub fn build(self) -> Result<UnsignedInvoice<'a>, SemanticError> {
@@ -258,11 +305,36 @@ impl<'a> InvoiceBuilder<'a> {
                        }
                }
 
-               let InvoiceBuilder { invreq_bytes, invoice } = self;
+               let InvoiceBuilder { invreq_bytes, invoice, .. } = self;
                Ok(UnsignedInvoice { invreq_bytes, invoice })
        }
 }
 
+impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
+       /// Builds a signed [`Invoice`] after checking for valid semantics.
+       pub fn build_and_sign<T: secp256k1::Signing>(
+               self, secp_ctx: &Secp256k1<T>
+       ) -> Result<Invoice, SemanticError> {
+               #[cfg(feature = "std")] {
+                       if self.invoice.is_offer_or_refund_expired() {
+                               return Err(SemanticError::AlreadyExpired);
+                       }
+               }
+
+               let InvoiceBuilder { invreq_bytes, invoice, keys, .. } = self;
+               let keys = match &invoice {
+                       InvoiceContents::ForOffer { .. } => keys.unwrap(),
+                       InvoiceContents::ForRefund { .. } => unreachable!(),
+               };
+
+               let unsigned_invoice = UnsignedInvoice { invreq_bytes, invoice };
+               let invoice = unsigned_invoice
+                       .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+                       .unwrap();
+               Ok(invoice)
+       }
+}
+
 /// A semantically valid [`Invoice`] that hasn't been signed.
 pub struct UnsignedInvoice<'a> {
        invreq_bytes: &'a Vec<u8>,
@@ -551,7 +623,10 @@ impl InvoiceContents {
                        },
                };
 
-               signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx)
+               match signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) {
+                       Ok(_) => true,
+                       Err(()) => false,
+               }
        }
 
        fn derives_keys(&self) -> bool {
@@ -831,8 +906,10 @@ mod tests {
        use bitcoin::util::schnorr::TweakedPublicKey;
        use core::convert::TryFrom;
        use core::time::Duration;
-       use crate::ln::msgs::DecodeError;
+       use crate::chain::keysinterface::KeyMaterial;
        use crate::ln::features::Bolt12InvoiceFeatures;
+       use crate::ln::inbound_payment::ExpandedKey;
+       use crate::ln::msgs::DecodeError;
        use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
        use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
        use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity};
@@ -840,6 +917,7 @@ mod tests {
        use crate::offers::payer::PayerTlvStreamRef;
        use crate::offers::refund::RefundBuilder;
        use crate::offers::test_utils::*;
+       use crate::onion_message::{BlindedHop, BlindedPath};
        use crate::util::ser::{BigSize, Iterable, Writeable};
 
        trait ToBytes {
@@ -1084,6 +1162,67 @@ mod tests {
                }
        }
 
+       #[test]
+       fn builds_invoice_from_offer_using_derived_keys() {
+               let desc = "foo".to_string();
+               let node_id = recipient_pubkey();
+               let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
+               let entropy = FixedEntropy {};
+               let secp_ctx = Secp256k1::new();
+
+               let blinded_path = BlindedPath {
+                       introduction_node_id: pubkey(40),
+                       blinding_point: pubkey(41),
+                       blinded_hops: vec![
+                               BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
+                               BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![0; 44] },
+                       ],
+               };
+
+               let offer = OfferBuilder
+                       ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
+                       .amount_msats(1000)
+                       .path(blinded_path)
+                       .build().unwrap();
+               let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap();
+
+               if let Err(e) = invoice_request
+                       .verify_and_respond_using_derived_keys_no_std(
+                               payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
+                       )
+                       .unwrap()
+                       .build_and_sign(&secp_ctx)
+               {
+                       panic!("error building invoice: {:?}", e);
+               }
+
+               let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32]));
+               match invoice_request.verify_and_respond_using_derived_keys_no_std(
+                       payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
+               ) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, SemanticError::InvalidMetadata),
+               }
+
+               let desc = "foo".to_string();
+               let offer = OfferBuilder
+                       ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
+                       .amount_msats(1000)
+                       .build().unwrap();
+               let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap();
+
+               match invoice_request.verify_and_respond_using_derived_keys_no_std(
+                       payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
+               ) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, SemanticError::InvalidMetadata),
+               }
+       }
+
        #[test]
        fn builds_invoice_with_relative_expiry() {
                let now = now();
index 6b5c7786220e04ee6c06fb9903370af53d6ebab3..92fabd6fdf0c7ab5f49c46cfd0793e02a8886774 100644 (file)
@@ -64,8 +64,8 @@ use crate::ln::PaymentHash;
 use crate::ln::features::InvoiceRequestFeatures;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
 use crate::ln::msgs::DecodeError;
-use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
-use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, self};
+use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder};
+use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, self};
 use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
 use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
 use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
@@ -469,7 +469,7 @@ impl InvoiceRequest {
        #[cfg(feature = "std")]
        pub fn respond_with(
                &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash
-       ) -> Result<InvoiceBuilder, SemanticError> {
+       ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
                let created_at = std::time::SystemTime::now()
                        .duration_since(std::time::SystemTime::UNIX_EPOCH)
                        .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
@@ -497,7 +497,7 @@ impl InvoiceRequest {
        pub fn respond_with_no_std(
                &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
                created_at: core::time::Duration
-       ) -> Result<InvoiceBuilder, SemanticError> {
+       ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
                if self.features().requires_unknown_bits() {
                        return Err(SemanticError::UnknownRequiredFeatures);
                }
@@ -505,11 +505,60 @@ impl InvoiceRequest {
                InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash)
        }
 
-       /// Verifies that the request was for an offer created using the given key.
+       /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
+       /// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the
+       /// same [`ExpandedKey`] as the one used to create the offer.
+       ///
+       /// See [`InvoiceRequest::respond_with`] for further details.
+       ///
+       /// [`Invoice`]: crate::offers::invoice::Invoice
+       #[cfg(feature = "std")]
+       pub fn verify_and_respond_using_derived_keys<T: secp256k1::Signing>(
+               &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+               expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+       ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
+               let created_at = std::time::SystemTime::now()
+                       .duration_since(std::time::SystemTime::UNIX_EPOCH)
+                       .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
+
+               self.verify_and_respond_using_derived_keys_no_std(
+                       payment_paths, payment_hash, created_at, expanded_key, secp_ctx
+               )
+       }
+
+       /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
+       /// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the
+       /// same [`ExpandedKey`] as the one used to create the offer.
+       ///
+       /// See [`InvoiceRequest::respond_with_no_std`] for further details.
+       ///
+       /// [`Invoice`]: crate::offers::invoice::Invoice
+       pub fn verify_and_respond_using_derived_keys_no_std<T: secp256k1::Signing>(
+               &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+               created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+       ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
+               if self.features().requires_unknown_bits() {
+                       return Err(SemanticError::UnknownRequiredFeatures);
+               }
+
+               let keys = match self.verify(expanded_key, secp_ctx) {
+                       Err(()) => return Err(SemanticError::InvalidMetadata),
+                       Ok(None) => return Err(SemanticError::InvalidMetadata),
+                       Ok(Some(keys)) => keys,
+               };
+
+               InvoiceBuilder::for_offer_using_keys(self, payment_paths, created_at, payment_hash, keys)
+       }
+
+       /// Verifies that the request was for an offer created using the given key. Returns the derived
+       /// keys need to sign an [`Invoice`] for the request if they could be extracted from the
+       /// metadata.
+       ///
+       /// [`Invoice`]: crate::offers::invoice::Invoice
        pub fn verify<T: secp256k1::Signing>(
                &self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
-       ) -> bool {
-               self.contents.inner.offer.verify(TlvStream::new(&self.bytes), key, secp_ctx)
+       ) -> Result<Option<KeyPair>, ()> {
+               self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)
        }
 
        #[cfg(test)]
index 9f22e9af184a887ae9654de48f58e4db4e7d04c1..d2918e809429d8f5db858b52930e8e19634e869e 100644 (file)
@@ -68,7 +68,7 @@
 
 use bitcoin::blockdata::constants::ChainHash;
 use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::{PublicKey, Secp256k1, self};
+use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
 use core::convert::TryFrom;
 use core::num::NonZeroU64;
 use core::ops::Deref;
@@ -92,7 +92,7 @@ use crate::prelude::*;
 #[cfg(feature = "std")]
 use std::time::SystemTime;
 
-const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~";
+pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~";
 
 /// Builds an [`Offer`] for the "offer to be paid" flow.
 ///
@@ -615,11 +615,11 @@ impl OfferContents {
 
        /// Verifies that the offer metadata was produced from the offer in the TLV stream.
        pub(super) fn verify<T: secp256k1::Signing>(
-               &self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
-       ) -> bool {
+               &self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+       ) -> Result<Option<KeyPair>, ()> {
                match self.metadata() {
                        Some(metadata) => {
-                               let tlv_stream = tlv_stream.range(OFFER_TYPES).filter(|record| {
+                               let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
                                        match record.r#type {
                                                OFFER_METADATA_TYPE => false,
                                                OFFER_NODE_ID_TYPE => !self.metadata.as_ref().unwrap().derives_keys(),
@@ -630,7 +630,7 @@ impl OfferContents {
                                        metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
                                )
                        },
-                       None => false,
+                       None => Err(()),
                }
        }
 
@@ -962,7 +962,7 @@ mod tests {
                let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap()
                        .sign(payer_sign).unwrap();
-               assert!(invoice_request.verify(&expanded_key, &secp_ctx));
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
 
                // Fails verification with altered offer field
                let mut tlv_stream = offer.as_tlv_stream();
@@ -975,7 +975,7 @@ mod tests {
                        .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap()
                        .sign(payer_sign).unwrap();
-               assert!(!invoice_request.verify(&expanded_key, &secp_ctx));
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
 
                // Fails verification with altered metadata
                let mut tlv_stream = offer.as_tlv_stream();
@@ -989,7 +989,7 @@ mod tests {
                        .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap()
                        .sign(payer_sign).unwrap();
-               assert!(!invoice_request.verify(&expanded_key, &secp_ctx));
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
        }
 
        #[test]
@@ -1019,7 +1019,7 @@ mod tests {
                let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap()
                        .sign(payer_sign).unwrap();
-               assert!(invoice_request.verify(&expanded_key, &secp_ctx));
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
 
                // Fails verification with altered offer field
                let mut tlv_stream = offer.as_tlv_stream();
@@ -1032,7 +1032,7 @@ mod tests {
                        .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap()
                        .sign(payer_sign).unwrap();
-               assert!(!invoice_request.verify(&expanded_key, &secp_ctx));
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
 
                // Fails verification with altered signing pubkey
                let mut tlv_stream = offer.as_tlv_stream();
@@ -1046,7 +1046,7 @@ mod tests {
                        .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap()
                        .sign(payer_sign).unwrap();
-               assert!(!invoice_request.verify(&expanded_key, &secp_ctx));
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
        }
 
        #[test]
index 6afd4d68fef385bb5b6b77b8375891e53878f6e7..3f1a9c887ec002315d56d766910b026e6d7efedc 100644 (file)
@@ -171,6 +171,8 @@ pub enum SemanticError {
        InvalidQuantity,
        /// A quantity or quantity bounds was provided but was not expected.
        UnexpectedQuantity,
+       /// Metadata could not be used to verify the offers message.
+       InvalidMetadata,
        /// Metadata was provided but was not expected.
        UnexpectedMetadata,
        /// Payer metadata was expected but was missing.
index 582c5b7eb187560d02b1ee0f0c6af3fdef1e8263..899586ba29d6b3875c225e91d40956c611a98793 100644 (file)
@@ -84,7 +84,7 @@ use crate::ln::PaymentHash;
 use crate::ln::features::InvoiceRequestFeatures;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
 use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
-use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
+use crate::offers::invoice::{BlindedPayInfo, ExplicitSigningPubkey, InvoiceBuilder};
 use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
 use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
 use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
@@ -392,7 +392,7 @@ impl Refund {
        pub fn respond_with(
                &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
                signing_pubkey: PublicKey,
-       ) -> Result<InvoiceBuilder, SemanticError> {
+       ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
                let created_at = std::time::SystemTime::now()
                        .duration_since(std::time::SystemTime::UNIX_EPOCH)
                        .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
@@ -423,7 +423,7 @@ impl Refund {
        pub fn respond_with_no_std(
                &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
                signing_pubkey: PublicKey, created_at: Duration
-       ) -> Result<InvoiceBuilder, SemanticError> {
+       ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
                if self.features().requires_unknown_bits() {
                        return Err(SemanticError::UnknownRequiredFeatures);
                }
index f6141e59699addb391ff6bfb523f75767e81aa7f..7229775aa0b3b959bd83df3e70269f172a5bc153 100644 (file)
@@ -172,14 +172,40 @@ pub(super) fn verify_metadata<'a, T: secp256k1::Signing>(
        metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
        signing_pubkey: PublicKey, tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>,
        secp_ctx: &Secp256k1<T>
-) -> bool {
+) -> Result<Option<KeyPair>, ()> {
+       let hmac = hmac_for_message(metadata, expanded_key, iv_bytes, tlv_stream)?;
+
+       if metadata.len() == Nonce::LENGTH {
+               let derived_keys = KeyPair::from_secret_key(
+                       secp_ctx, &SecretKey::from_slice(hmac.as_inner()).unwrap()
+               );
+               if fixed_time_eq(&signing_pubkey.serialize(), &derived_keys.public_key().serialize()) {
+                       Ok(Some(derived_keys))
+               } else {
+                       Err(())
+               }
+       } else if metadata[Nonce::LENGTH..].len() == Sha256::LEN {
+               if fixed_time_eq(&metadata[Nonce::LENGTH..], &hmac.into_inner()) {
+                       Ok(None)
+               } else {
+                       Err(())
+               }
+       } else {
+               Err(())
+       }
+}
+
+fn hmac_for_message<'a>(
+       metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
+       tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>
+) -> Result<Hmac<Sha256>, ()> {
        if metadata.len() < Nonce::LENGTH {
-               return false;
+               return Err(());
        }
 
        let nonce = match Nonce::try_from(&metadata[..Nonce::LENGTH]) {
                Ok(nonce) => nonce,
-               Err(_) => return false,
+               Err(_) => return Err(()),
        };
        let mut hmac = expanded_key.hmac_for_offer(nonce, iv_bytes);
 
@@ -189,13 +215,9 @@ pub(super) fn verify_metadata<'a, T: secp256k1::Signing>(
 
        if metadata.len() == Nonce::LENGTH {
                hmac.input(DERIVED_METADATA_AND_KEYS_HMAC_INPUT);
-               let hmac = Hmac::from_engine(hmac);
-               let derived_pubkey = SecretKey::from_slice(hmac.as_inner()).unwrap().public_key(&secp_ctx);
-               fixed_time_eq(&signing_pubkey.serialize(), &derived_pubkey.serialize())
-       } else if metadata[Nonce::LENGTH..].len() == Sha256::LEN {
-               hmac.input(DERIVED_METADATA_HMAC_INPUT);
-               fixed_time_eq(&metadata[Nonce::LENGTH..], &Hmac::from_engine(hmac).into_inner())
        } else {
-               false
+               hmac.input(DERIVED_METADATA_HMAC_INPUT);
        }
+
+       Ok(Hmac::from_engine(hmac))
 }