Merge pull request #1989 from jkczyz/2023-01-stateless-offers
[rust-lightning] / lightning / src / offers / offer.rs
index 6a8f956ae635eb8f3278ee85f48893a9fb8f5aaf..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;
@@ -79,7 +79,7 @@ use crate::io;
 use crate::ln::features::OfferFeatures;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
 use crate::ln::msgs::MAX_VALUE_MSAT;
-use crate::offers::invoice_request::InvoiceRequestBuilder;
+use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder};
 use crate::offers::merkle::TlvStream;
 use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
 use crate::offers::signer::{Metadata, MetadataMaterial, self};
@@ -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.
 ///
@@ -439,8 +439,53 @@ impl Offer {
                self.contents.signing_pubkey()
        }
 
-       /// Creates an [`InvoiceRequest`] for the offer with the given `metadata` and `payer_id`, which
-       /// will be reflected in the `Invoice` response.
+       /// Similar to [`Offer::request_invoice`] except it:
+       /// - derives the [`InvoiceRequest::payer_id`] such that a different key can be used for each
+       ///   request, and
+       /// - sets the [`InvoiceRequest::metadata`] when [`InvoiceRequestBuilder::build`] is called such
+       ///   that it can be used by [`Invoice::verify`] to determine if the invoice was requested using
+       ///   a base [`ExpandedKey`] from which the payer id was derived.
+       ///
+       /// Useful to protect the sender's privacy.
+       ///
+       /// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
+       /// [`InvoiceRequest::metadata`]: crate::offers::invoice_request::InvoiceRequest::metadata
+       /// [`Invoice::verify`]: crate::offers::invoice::Invoice::verify
+       /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
+       pub fn request_invoice_deriving_payer_id<'a, 'b, ES: Deref, T: secp256k1::Signing>(
+               &'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>
+       ) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, SemanticError>
+       where
+               ES::Target: EntropySource,
+       {
+               if self.features().requires_unknown_bits() {
+                       return Err(SemanticError::UnknownRequiredFeatures);
+               }
+
+               Ok(InvoiceRequestBuilder::deriving_payer_id(self, expanded_key, entropy_source, secp_ctx))
+       }
+
+       /// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the
+       /// [`InvoiceRequest::payer_id`] instead of deriving a different key for each request.
+       ///
+       /// Useful for recurring payments using the same `payer_id` with different invoices.
+       ///
+       /// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
+       pub fn request_invoice_deriving_metadata<ES: Deref>(
+               &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES
+       ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, SemanticError>
+       where
+               ES::Target: EntropySource,
+       {
+               if self.features().requires_unknown_bits() {
+                       return Err(SemanticError::UnknownRequiredFeatures);
+               }
+
+               Ok(InvoiceRequestBuilder::deriving_metadata(self, payer_id, expanded_key, entropy_source))
+       }
+
+       /// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
+       /// which will be reflected in the `Invoice` response.
        ///
        /// The `metadata` is useful for including information about the derivation of `payer_id` such
        /// that invoice response handling can be stateless. Also serves as payer-provided entropy while
@@ -454,7 +499,7 @@ impl Offer {
        /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
        pub fn request_invoice(
                &self, metadata: Vec<u8>, payer_id: PublicKey
-       ) -> Result<InvoiceRequestBuilder, SemanticError> {
+       ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, SemanticError> {
                if self.features().requires_unknown_bits() {
                        return Err(SemanticError::UnknownRequiredFeatures);
                }
@@ -570,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(),
@@ -585,7 +630,7 @@ impl OfferContents {
                                        metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
                                )
                        },
-                       None => false,
+                       None => Err(()),
                }
        }
 
@@ -677,7 +722,7 @@ impl Quantity {
 }
 
 /// Valid type range for offer TLV records.
-const OFFER_TYPES: core::ops::Range<u64> = 1..80;
+pub(super) const OFFER_TYPES: core::ops::Range<u64> = 1..80;
 
 /// TLV record type for [`Offer::metadata`].
 const OFFER_METADATA_TYPE: u64 = 4;
@@ -917,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();
@@ -930,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();
@@ -944,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]
@@ -974,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();
@@ -987,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();
@@ -1001,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]