Merge pull request #1989 from jkczyz/2023-01-stateless-offers
[rust-lightning] / lightning / src / offers / invoice_request.rs
index f617383fdcfa364c38bbc521b91a43554d6a35f4..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};
@@ -78,7 +78,7 @@ use crate::prelude::*;
 
 const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice_request", "signature");
 
-const IV_BYTES: &[u8; IV_LEN] = b"LDK Invreq ~~~~~";
+pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Invreq ~~~~~";
 
 /// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow.
 ///
@@ -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)]
@@ -528,10 +577,18 @@ impl InvoiceRequestContents {
                self.inner.metadata()
        }
 
+       pub(super) fn derives_keys(&self) -> bool {
+               self.inner.payer.0.derives_keys()
+       }
+
        pub(super) fn chain(&self) -> ChainHash {
                self.inner.chain()
        }
 
+       pub(super) fn payer_id(&self) -> PublicKey {
+               self.payer_id
+       }
+
        pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
                let (payer, offer, mut invoice_request) = self.inner.as_tlv_stream();
                invoice_request.payer_id = Some(&self.payer_id);
@@ -585,12 +642,20 @@ impl Writeable for InvoiceRequestContents {
        }
 }
 
-tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
+/// Valid type range for invoice_request TLV records.
+pub(super) const INVOICE_REQUEST_TYPES: core::ops::Range<u64> = 80..160;
+
+/// TLV record type for [`InvoiceRequest::payer_id`] and [`Refund::payer_id`].
+///
+/// [`Refund::payer_id`]: crate::offers::refund::Refund::payer_id
+pub(super) const INVOICE_REQUEST_PAYER_ID_TYPE: u64 = 88;
+
+tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST_TYPES, {
        (80, chain: ChainHash),
        (82, amount: (u64, HighZeroBytesDroppedBigSize)),
        (84, features: (InvoiceRequestFeatures, WithoutLength)),
        (86, quantity: (u64, HighZeroBytesDroppedBigSize)),
-       (88, payer_id: PublicKey),
+       (INVOICE_REQUEST_PAYER_ID_TYPE, payer_id: PublicKey),
        (89, payer_note: (String, WithoutLength)),
 });
 
@@ -702,8 +767,11 @@ mod tests {
        use core::num::NonZeroU64;
        #[cfg(feature = "std")]
        use core::time::Duration;
+       use crate::chain::keysinterface::KeyMaterial;
        use crate::ln::features::InvoiceRequestFeatures;
+       use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
+       use crate::offers::invoice::{Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG};
        use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
        use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
        use crate::offers::parse::{ParseError, SemanticError};
@@ -800,6 +868,148 @@ mod tests {
                }
        }
 
+       #[test]
+       fn builds_invoice_request_with_derived_metadata() {
+               let payer_id = payer_pubkey();
+               let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
+               let entropy = FixedEntropy {};
+               let secp_ctx = Secp256k1::new();
+
+               let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
+                       .amount_msats(1000)
+                       .build().unwrap();
+               let invoice_request = offer
+                       .request_invoice_deriving_metadata(payer_id, &expanded_key, &entropy)
+                       .unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap();
+               assert_eq!(invoice_request.payer_id(), payer_pubkey());
+
+               let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
+                       .unwrap()
+                       .build().unwrap()
+                       .sign(recipient_sign).unwrap();
+               assert!(invoice.verify(&expanded_key, &secp_ctx));
+
+               // Fails verification with altered fields
+               let (
+                       payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
+                       mut invoice_tlv_stream, mut signature_tlv_stream
+               ) = invoice.as_tlv_stream();
+               invoice_request_tlv_stream.amount = Some(2000);
+               invoice_tlv_stream.amount = Some(2000);
+
+               let tlv_stream =
+                       (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
+               let mut bytes = Vec::new();
+               tlv_stream.write(&mut bytes).unwrap();
+
+               let signature = merkle::sign_message(
+                       recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
+               ).unwrap();
+               signature_tlv_stream.signature = Some(&signature);
+
+               let mut encoded_invoice = bytes;
+               signature_tlv_stream.write(&mut encoded_invoice).unwrap();
+
+               let invoice = Invoice::try_from(encoded_invoice).unwrap();
+               assert!(!invoice.verify(&expanded_key, &secp_ctx));
+
+               // Fails verification with altered metadata
+               let (
+                       mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
+                       mut signature_tlv_stream
+               ) = invoice.as_tlv_stream();
+               let metadata = payer_tlv_stream.metadata.unwrap().iter().copied().rev().collect();
+               payer_tlv_stream.metadata = Some(&metadata);
+
+               let tlv_stream =
+                       (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
+               let mut bytes = Vec::new();
+               tlv_stream.write(&mut bytes).unwrap();
+
+               let signature = merkle::sign_message(
+                       recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
+               ).unwrap();
+               signature_tlv_stream.signature = Some(&signature);
+
+               let mut encoded_invoice = bytes;
+               signature_tlv_stream.write(&mut encoded_invoice).unwrap();
+
+               let invoice = Invoice::try_from(encoded_invoice).unwrap();
+               assert!(!invoice.verify(&expanded_key, &secp_ctx));
+       }
+
+       #[test]
+       fn builds_invoice_request_with_derived_payer_id() {
+               let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
+               let entropy = FixedEntropy {};
+               let secp_ctx = Secp256k1::new();
+
+               let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
+                       .amount_msats(1000)
+                       .build().unwrap();
+               let invoice_request = offer
+                       .request_invoice_deriving_payer_id(&expanded_key, &entropy, &secp_ctx)
+                       .unwrap()
+                       .build_and_sign()
+                       .unwrap();
+
+               let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
+                       .unwrap()
+                       .build().unwrap()
+                       .sign(recipient_sign).unwrap();
+               assert!(invoice.verify(&expanded_key, &secp_ctx));
+
+               // Fails verification with altered fields
+               let (
+                       payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
+                       mut invoice_tlv_stream, mut signature_tlv_stream
+               ) = invoice.as_tlv_stream();
+               invoice_request_tlv_stream.amount = Some(2000);
+               invoice_tlv_stream.amount = Some(2000);
+
+               let tlv_stream =
+                       (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
+               let mut bytes = Vec::new();
+               tlv_stream.write(&mut bytes).unwrap();
+
+               let signature = merkle::sign_message(
+                       recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
+               ).unwrap();
+               signature_tlv_stream.signature = Some(&signature);
+
+               let mut encoded_invoice = bytes;
+               signature_tlv_stream.write(&mut encoded_invoice).unwrap();
+
+               let invoice = Invoice::try_from(encoded_invoice).unwrap();
+               assert!(!invoice.verify(&expanded_key, &secp_ctx));
+
+               // Fails verification with altered payer id
+               let (
+                       payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, invoice_tlv_stream,
+                       mut signature_tlv_stream
+               ) = invoice.as_tlv_stream();
+               let payer_id = pubkey(1);
+               invoice_request_tlv_stream.payer_id = Some(&payer_id);
+
+               let tlv_stream =
+                       (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
+               let mut bytes = Vec::new();
+               tlv_stream.write(&mut bytes).unwrap();
+
+               let signature = merkle::sign_message(
+                       recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
+               ).unwrap();
+               signature_tlv_stream.signature = Some(&signature);
+
+               let mut encoded_invoice = bytes;
+               signature_tlv_stream.write(&mut encoded_invoice).unwrap();
+
+               let invoice = Invoice::try_from(encoded_invoice).unwrap();
+               assert!(!invoice.verify(&expanded_key, &secp_ctx));
+       }
+
        #[test]
        fn builds_invoice_request_with_chain() {
                let mainnet = ChainHash::using_genesis_block(Network::Bitcoin);