Stateless verification of Invoice for Offer
[rust-lightning] / lightning / src / offers / invoice_request.rs
index f617383fdcfa364c38bbc521b91a43554d6a35f4..a7cdbfc0f155157cfa7b7f8e1976953ee750d080 100644 (file)
@@ -66,10 +66,10 @@ 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::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
+use crate::offers::offer::{OFFER_TYPES, Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
 use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
-use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
-use crate::offers::signer::{Metadata, MetadataMaterial};
+use crate::offers::payer::{PAYER_METADATA_TYPE, PayerContents, PayerTlvStream, PayerTlvStreamRef};
+use crate::offers::signer::{Metadata, MetadataMaterial, self};
 use crate::onion_message::BlindedPath;
 use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
 use crate::util::string::PrintableString;
@@ -532,6 +532,22 @@ impl InvoiceRequestContents {
                self.inner.chain()
        }
 
+       /// Verifies that the payer metadata was produced from the invoice request in the TLV stream.
+       pub(super) fn verify<T: secp256k1::Signing>(
+               &self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+       ) -> bool {
+               let offer_records = tlv_stream.clone().range(OFFER_TYPES);
+               let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| {
+                       match record.r#type {
+                               PAYER_METADATA_TYPE => false, // Should be outside range
+                               INVOICE_REQUEST_PAYER_ID_TYPE => !self.inner.payer.0.derives_keys(),
+                               _ => true,
+                       }
+               });
+               let tlv_stream = offer_records.chain(invreq_records);
+               signer::verify_metadata(self.metadata(), key, IV_BYTES, self.payer_id, tlv_stream, secp_ctx)
+       }
+
        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 +601,20 @@ impl Writeable for InvoiceRequestContents {
        }
 }
 
-tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
+/// Valid type range for invoice_request TLV records.
+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
+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 +726,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 +827,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);