Merge pull request #2468 from jkczyz/2023-08-offer-payment-id
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Tue, 29 Aug 2023 19:29:21 +0000 (19:29 +0000)
committerGitHub <noreply@github.com>
Tue, 29 Aug 2023 19:29:21 +0000 (19:29 +0000)
Offer outbound payments

lightning/src/ln/channelmanager.rs
lightning/src/ln/inbound_payment.rs
lightning/src/offers/invoice.rs
lightning/src/offers/invoice_request.rs
lightning/src/offers/mod.rs
lightning/src/offers/offer.rs
lightning/src/offers/refund.rs
lightning/src/offers/signer.rs
lightning/src/util/chacha20.rs
lightning/src/util/crypto.rs

index 64d5521dcb95f78ba3317398e19438f074b19dbc..213a2882fbc3f899e5dd43ebc09a92a47f35c39a 100644 (file)
@@ -237,7 +237,12 @@ impl From<&ClaimableHTLC> for events::ClaimedHTLC {
 ///
 /// This is not exported to bindings users as we just use [u8; 32] directly
 #[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
-pub struct PaymentId(pub [u8; 32]);
+pub struct PaymentId(pub [u8; Self::LENGTH]);
+
+impl PaymentId {
+       /// Number of bytes in the id.
+       pub const LENGTH: usize = 32;
+}
 
 impl Writeable for PaymentId {
        fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
index dda7cc2b29a125d214e022b18511e4cfc78fd031..f9e10880afbe5f5eae122d48220f5b8e5dcd9757 100644 (file)
@@ -19,7 +19,7 @@ use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
 use crate::ln::msgs;
 use crate::ln::msgs::MAX_VALUE_MSAT;
 use crate::util::chacha20::ChaCha20;
-use crate::util::crypto::hkdf_extract_expand_4x;
+use crate::util::crypto::hkdf_extract_expand_5x;
 use crate::util::errors::APIError;
 use crate::util::logger::Logger;
 
@@ -50,6 +50,8 @@ pub struct ExpandedKey {
        user_pmt_hash_key: [u8; 32],
        /// The base key used to derive signing keys and authenticate messages for BOLT 12 Offers.
        offers_base_key: [u8; 32],
+       /// The key used to encrypt message metadata for BOLT 12 Offers.
+       offers_encryption_key: [u8; 32],
 }
 
 impl ExpandedKey {
@@ -57,20 +59,25 @@ impl ExpandedKey {
        ///
        /// It is recommended to cache this value and not regenerate it for each new inbound payment.
        pub fn new(key_material: &KeyMaterial) -> ExpandedKey {
-               let (metadata_key, ldk_pmt_hash_key, user_pmt_hash_key, offers_base_key) =
-                       hkdf_extract_expand_4x(b"LDK Inbound Payment Key Expansion", &key_material.0);
+               let (
+                       metadata_key,
+                       ldk_pmt_hash_key,
+                       user_pmt_hash_key,
+                       offers_base_key,
+                       offers_encryption_key,
+               ) = hkdf_extract_expand_5x(b"LDK Inbound Payment Key Expansion", &key_material.0);
                Self {
                        metadata_key,
                        ldk_pmt_hash_key,
                        user_pmt_hash_key,
                        offers_base_key,
+                       offers_encryption_key,
                }
        }
 
        /// Returns an [`HmacEngine`] used to construct [`Offer::metadata`].
        ///
        /// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
-       #[allow(unused)]
        pub(crate) fn hmac_for_offer(
                &self, nonce: Nonce, iv_bytes: &[u8; IV_LEN]
        ) -> HmacEngine<Sha256> {
@@ -79,6 +86,13 @@ impl ExpandedKey {
                hmac.input(&nonce.0);
                hmac
        }
+
+       /// Encrypts or decrypts the given `bytes`. Used for data included in an offer message's
+       /// metadata (e.g., payment id).
+       pub(crate) fn crypt_for_offer(&self, mut bytes: [u8; 32], nonce: Nonce) -> [u8; 32] {
+               ChaCha20::encrypt_single_block_in_place(&self.offers_encryption_key, &nonce.0, &mut bytes);
+               bytes
+       }
 }
 
 /// A 128-bit number used only once.
@@ -88,7 +102,6 @@ impl ExpandedKey {
 ///
 /// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
 /// [`Offer::signing_pubkey`]: crate::offers::offer::Offer::signing_pubkey
-#[allow(unused)]
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub(crate) struct Nonce(pub(crate) [u8; Self::LENGTH]);
 
@@ -271,10 +284,9 @@ fn construct_payment_secret(iv_bytes: &[u8; IV_LEN], metadata_bytes: &[u8; METAD
        let (iv_slice, encrypted_metadata_slice) = payment_secret_bytes.split_at_mut(IV_LEN);
        iv_slice.copy_from_slice(iv_bytes);
 
-       let chacha_block = ChaCha20::get_single_block(metadata_key, iv_bytes);
-       for i in 0..METADATA_LEN {
-               encrypted_metadata_slice[i] = chacha_block[i] ^ metadata_bytes[i];
-       }
+       ChaCha20::encrypt_single_block(
+               metadata_key, iv_bytes, encrypted_metadata_slice, metadata_bytes
+       );
        PaymentSecret(payment_secret_bytes)
 }
 
@@ -406,11 +418,10 @@ fn decrypt_metadata(payment_secret: PaymentSecret, keys: &ExpandedKey) -> ([u8;
        let (iv_slice, encrypted_metadata_bytes) = payment_secret.0.split_at(IV_LEN);
        iv_bytes.copy_from_slice(iv_slice);
 
-       let chacha_block = ChaCha20::get_single_block(&keys.metadata_key, &iv_bytes);
        let mut metadata_bytes: [u8; METADATA_LEN] = [0; METADATA_LEN];
-       for i in 0..METADATA_LEN {
-               metadata_bytes[i] = chacha_block[i] ^ encrypted_metadata_bytes[i];
-       }
+       ChaCha20::encrypt_single_block(
+               &keys.metadata_key, &iv_bytes, &mut metadata_bytes, encrypted_metadata_bytes
+       );
 
        (iv_bytes, metadata_bytes)
 }
index 75a844cd117abe5d956ddcbdf7efde5bff44a769..06215e2d48615ce9784c6e95f534a8eeb7739be9 100644 (file)
@@ -110,6 +110,7 @@ use core::time::Duration;
 use crate::io;
 use crate::blinded_path::BlindedPath;
 use crate::ln::PaymentHash;
+use crate::ln::channelmanager::PaymentId;
 use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
 use crate::ln::inbound_payment::ExpandedKey;
 use crate::ln::msgs::DecodeError;
@@ -695,10 +696,11 @@ impl Bolt12Invoice {
                merkle::message_digest(SIGNATURE_TAG, &self.bytes).as_ref().clone()
        }
 
-       /// Verifies that the invoice was for a request or refund created using the given key.
+       /// Verifies that the invoice was for a request or refund created using the given key. Returns
+       /// the associated [`PaymentId`] to use when sending the payment.
        pub fn verify<T: secp256k1::Signing>(
                &self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
-       ) -> bool {
+       ) -> Result<PaymentId, ()> {
                self.contents.verify(TlvStream::new(&self.bytes), key, secp_ctx)
        }
 
@@ -947,7 +949,7 @@ impl InvoiceContents {
 
        fn verify<T: secp256k1::Signing>(
                &self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
-       ) -> bool {
+       ) -> Result<PaymentId, ()> {
                let offer_records = tlv_stream.clone().range(OFFER_TYPES);
                let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| {
                        match record.r#type {
@@ -967,10 +969,7 @@ impl InvoiceContents {
                        },
                };
 
-               match signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) {
-                       Ok(_) => true,
-                       Err(()) => false,
-               }
+               signer::verify_payer_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx)
        }
 
        fn derives_keys(&self) -> bool {
@@ -1642,36 +1641,31 @@ mod tests {
                        .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()
+               if let Err(e) = invoice_request.clone()
+                       .verify(&expanded_key, &secp_ctx).unwrap()
+                       .respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now()).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, Bolt12SemanticError::InvalidMetadata),
-               }
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
 
                let desc = "foo".to_string();
                let offer = OfferBuilder
                        ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
                        .amount_msats(1000)
+                       // Omit the path so that node_id is used for the signing pubkey instead of deriving
                        .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
-               ) {
+               match invoice_request
+                       .verify(&expanded_key, &secp_ctx).unwrap()
+                       .respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now())
+               {
                        Ok(_) => panic!("expected error"),
                        Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
                }
index 03af068d1d61912738f23ac71ff3c91079161ffe..fb0b0205bd689e1356b65ae5f7c79690479a55f5 100644 (file)
@@ -64,6 +64,7 @@ use crate::sign::EntropySource;
 use crate::io;
 use crate::blinded_path::BlindedPath;
 use crate::ln::PaymentHash;
+use crate::ln::channelmanager::PaymentId;
 use crate::ln::features::InvoiceRequestFeatures;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
 use crate::ln::msgs::DecodeError;
@@ -128,10 +129,12 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI
        }
 
        pub(super) fn deriving_metadata<ES: Deref>(
-               offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES
+               offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
+               payment_id: PaymentId,
        ) -> Self where ES::Target: EntropySource {
                let nonce = Nonce::from_entropy_source(entropy_source);
-               let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+               let payment_id = Some(payment_id);
+               let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id);
                let metadata = Metadata::Derived(derivation_material);
                Self {
                        offer,
@@ -145,10 +148,12 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI
 
 impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> {
        pub(super) fn deriving_payer_id<ES: Deref>(
-               offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>
+               offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES,
+               secp_ctx: &'b Secp256k1<T>, payment_id: PaymentId
        ) -> Self where ES::Target: EntropySource {
                let nonce = Nonce::from_entropy_source(entropy_source);
-               let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+               let payment_id = Some(payment_id);
+               let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id);
                let metadata = Metadata::DerivedSigningPubkey(derivation_material);
                Self {
                        offer,
@@ -259,7 +264,7 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a
                        let mut tlv_stream = self.invoice_request.as_tlv_stream();
                        debug_assert!(tlv_stream.2.payer_id.is_none());
                        tlv_stream.0.metadata = None;
-                       if !metadata.derives_keys() {
+                       if !metadata.derives_payer_keys() {
                                tlv_stream.2.payer_id = self.payer_id.as_ref();
                        }
 
@@ -424,6 +429,24 @@ pub struct InvoiceRequest {
        signature: Signature,
 }
 
+/// An [`InvoiceRequest`] that has been verified by [`InvoiceRequest::verify`] and exposes different
+/// ways to respond depending on whether the signing keys were derived.
+#[derive(Clone, Debug)]
+pub struct VerifiedInvoiceRequest {
+       /// The verified request.
+       inner: InvoiceRequest,
+
+       /// Keys used for signing a [`Bolt12Invoice`] if they can be derived.
+       ///
+       /// If `Some`, must call [`respond_using_derived_keys`] when responding. Otherwise, call
+       /// [`respond_with`].
+       ///
+       /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+       /// [`respond_using_derived_keys`]: Self::respond_using_derived_keys
+       /// [`respond_with`]: Self::respond_with
+       pub keys: Option<KeyPair>,
+}
+
 /// The contents of an [`InvoiceRequest`], which may be shared with an [`Bolt12Invoice`].
 ///
 /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
@@ -542,9 +565,15 @@ impl InvoiceRequest {
        ///
        /// Errors if the request contains unknown required features.
        ///
+       /// # Note
+       ///
+       /// If the originating [`Offer`] was created using [`OfferBuilder::deriving_signing_pubkey`],
+       /// then use [`InvoiceRequest::verify`] and [`VerifiedInvoiceRequest`] methods instead.
+       ///
        /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
        ///
        /// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at
+       /// [`OfferBuilder::deriving_signing_pubkey`]: crate::offers::offer::OfferBuilder::deriving_signing_pubkey
        pub fn respond_with_no_std(
                &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
                created_at: core::time::Duration
@@ -556,6 +585,63 @@ 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. Returns the verified
+       /// request which contains the derived keys needed to sign a [`Bolt12Invoice`] for the request
+       /// if they could be extracted from the metadata.
+       ///
+       /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+       pub fn verify<T: secp256k1::Signing>(
+               self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+       ) -> Result<VerifiedInvoiceRequest, ()> {
+               let keys = self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)?;
+               Ok(VerifiedInvoiceRequest {
+                       inner: self,
+                       keys,
+               })
+       }
+
+       #[cfg(test)]
+       fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
+               let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
+                       self.contents.as_tlv_stream();
+               let signature_tlv_stream = SignatureTlvStreamRef {
+                       signature: Some(&self.signature),
+               };
+               (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
+       }
+}
+
+impl VerifiedInvoiceRequest {
+       offer_accessors!(self, self.inner.contents.inner.offer);
+       invoice_request_accessors!(self, self.inner.contents);
+
+       /// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the
+       /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
+       ///
+       /// See [`InvoiceRequest::respond_with_no_std`] for further details.
+       ///
+       /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+       ///
+       /// [`Duration`]: core::time::Duration
+       #[cfg(feature = "std")]
+       pub fn respond_with(
+               &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
+       ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
+               self.inner.respond_with(payment_paths, payment_hash)
+       }
+
+       /// Creates an [`InvoiceBuilder`] for the request with the given required fields.
+       ///
+       /// See [`InvoiceRequest::respond_with_no_std`] for further details.
+       ///
+       /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+       pub fn respond_with_no_std(
+               &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
+               created_at: core::time::Duration
+       ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
+               self.inner.respond_with_no_std(payment_paths, payment_hash, created_at)
+       }
+
        /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
        /// derived signing keys from the originating [`Offer`] to sign the [`Bolt12Invoice`]. Must use
        /// the same [`ExpandedKey`] as the one used to create the offer.
@@ -566,17 +652,14 @@ impl InvoiceRequest {
        ///
        /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
        #[cfg(feature = "std")]
-       pub fn verify_and_respond_using_derived_keys<T: secp256k1::Signing>(
-               &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
-               expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+       pub fn respond_using_derived_keys(
+               &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
        ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
                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
-               )
+               self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at)
        }
 
        /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
@@ -588,42 +671,22 @@ impl InvoiceRequest {
        /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
        ///
        /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
-       pub fn verify_and_respond_using_derived_keys_no_std<T: secp256k1::Signing>(
+       pub fn respond_using_derived_keys_no_std(
                &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
-               created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+               created_at: core::time::Duration
        ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
-               if self.invoice_request_features().requires_unknown_bits() {
+               if self.inner.invoice_request_features().requires_unknown_bits() {
                        return Err(Bolt12SemanticError::UnknownRequiredFeatures);
                }
 
-               let keys = match self.verify(expanded_key, secp_ctx) {
-                       Err(()) => return Err(Bolt12SemanticError::InvalidMetadata),
-                       Ok(None) => return Err(Bolt12SemanticError::InvalidMetadata),
-                       Ok(Some(keys)) => keys,
+               let keys = match self.keys {
+                       None => return Err(Bolt12SemanticError::InvalidMetadata),
+                       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 [`Bolt12Invoice`] for the request if they could be extracted from the
-       /// metadata.
-       ///
-       /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
-       pub fn verify<T: secp256k1::Signing>(
-               &self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
-       ) -> Result<Option<KeyPair>, ()> {
-               self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)
-       }
-
-       #[cfg(test)]
-       fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
-               let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
-                       self.contents.as_tlv_stream();
-               let signature_tlv_stream = SignatureTlvStreamRef {
-                       signature: Some(&self.signature),
-               };
-               (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
+               InvoiceBuilder::for_offer_using_keys(
+                       &self.inner, payment_paths, created_at, payment_hash, keys
+               )
        }
 }
 
@@ -633,7 +696,7 @@ impl InvoiceRequestContents {
        }
 
        pub(super) fn derives_keys(&self) -> bool {
-               self.inner.payer.0.derives_keys()
+               self.inner.payer.0.derives_payer_keys()
        }
 
        pub(super) fn chain(&self) -> ChainHash {
@@ -866,6 +929,7 @@ mod tests {
        #[cfg(feature = "std")]
        use core::time::Duration;
        use crate::sign::KeyMaterial;
+       use crate::ln::channelmanager::PaymentId;
        use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
        use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
@@ -1011,12 +1075,13 @@ mod tests {
                let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
                let entropy = FixedEntropy {};
                let secp_ctx = Secp256k1::new();
+               let payment_id = PaymentId([1; 32]);
 
                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)
+                       .request_invoice_deriving_metadata(payer_id, &expanded_key, &entropy, payment_id)
                        .unwrap()
                        .build().unwrap()
                        .sign(payer_sign).unwrap();
@@ -1026,7 +1091,10 @@ mod tests {
                        .unwrap()
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
-               assert!(invoice.verify(&expanded_key, &secp_ctx));
+               match invoice.verify(&expanded_key, &secp_ctx) {
+                       Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
+                       Err(()) => panic!("verification failed"),
+               }
 
                // Fails verification with altered fields
                let (
@@ -1049,7 +1117,7 @@ mod tests {
                signature_tlv_stream.write(&mut encoded_invoice).unwrap();
 
                let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
-               assert!(!invoice.verify(&expanded_key, &secp_ctx));
+               assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
 
                // Fails verification with altered metadata
                let (
@@ -1072,7 +1140,7 @@ mod tests {
                signature_tlv_stream.write(&mut encoded_invoice).unwrap();
 
                let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
-               assert!(!invoice.verify(&expanded_key, &secp_ctx));
+               assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
        }
 
        #[test]
@@ -1080,12 +1148,13 @@ mod tests {
                let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
                let entropy = FixedEntropy {};
                let secp_ctx = Secp256k1::new();
+               let payment_id = PaymentId([1; 32]);
 
                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)
+                       .request_invoice_deriving_payer_id(&expanded_key, &entropy, &secp_ctx, payment_id)
                        .unwrap()
                        .build_and_sign()
                        .unwrap();
@@ -1094,7 +1163,10 @@ mod tests {
                        .unwrap()
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
-               assert!(invoice.verify(&expanded_key, &secp_ctx));
+               match invoice.verify(&expanded_key, &secp_ctx) {
+                       Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
+                       Err(()) => panic!("verification failed"),
+               }
 
                // Fails verification with altered fields
                let (
@@ -1117,7 +1189,7 @@ mod tests {
                signature_tlv_stream.write(&mut encoded_invoice).unwrap();
 
                let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
-               assert!(!invoice.verify(&expanded_key, &secp_ctx));
+               assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
 
                // Fails verification with altered payer id
                let (
@@ -1140,7 +1212,7 @@ mod tests {
                signature_tlv_stream.write(&mut encoded_invoice).unwrap();
 
                let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
-               assert!(!invoice.verify(&expanded_key, &secp_ctx));
+               assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
        }
 
        #[test]
index c62702711c604d79047334c3f79b7217d7883b52..c6883abca34d827093a7e55de1d87a950ae4d28c 100644 (file)
@@ -22,7 +22,6 @@ pub mod merkle;
 pub mod parse;
 mod payer;
 pub mod refund;
-#[allow(unused)]
 pub(crate) mod signer;
 #[cfg(test)]
 mod test_utils;
index f6aa354b9e4f3d6c9efd2aee4ac47a04c8741dc0..e0bc63e8b2b8109e0b9243a1a32ecfb0a5a3d073 100644 (file)
@@ -77,6 +77,7 @@ use core::time::Duration;
 use crate::sign::EntropySource;
 use crate::io;
 use crate::blinded_path::BlindedPath;
+use crate::ln::channelmanager::PaymentId;
 use crate::ln::features::OfferFeatures;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
 use crate::ln::msgs::MAX_VALUE_MSAT;
@@ -169,7 +170,7 @@ impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> {
                secp_ctx: &'a Secp256k1<T>
        ) -> Self where ES::Target: EntropySource {
                let nonce = Nonce::from_entropy_source(entropy_source);
-               let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+               let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, None);
                let metadata = Metadata::DerivedSigningPubkey(derivation_material);
                OfferBuilder {
                        offer: OfferContents {
@@ -283,7 +284,7 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
                                let mut tlv_stream = self.offer.as_tlv_stream();
                                debug_assert_eq!(tlv_stream.metadata, None);
                                tlv_stream.metadata = None;
-                               if metadata.derives_keys() {
+                               if metadata.derives_recipient_keys() {
                                        tlv_stream.node_id = None;
                                }
 
@@ -454,10 +455,12 @@ impl Offer {
 
        /// 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::payer_metadata`] when [`InvoiceRequestBuilder::build`] is
-       ///   called such that it can be used by [`Bolt12Invoice::verify`] to determine if the invoice
-       ///   was requested using a base [`ExpandedKey`] from which the payer id was derived.
+       ///   request,
+       /// - sets [`InvoiceRequest::payer_metadata`] when [`InvoiceRequestBuilder::build`] is called
+       ///   such that it can be used by [`Bolt12Invoice::verify`] to determine if the invoice was
+       ///   requested using a base [`ExpandedKey`] from which the payer id was derived, and
+       /// - includes the [`PaymentId`] encrypted in [`InvoiceRequest::payer_metadata`] so that it can
+       ///   be used when sending the payment for the requested invoice.
        ///
        /// Useful to protect the sender's privacy.
        ///
@@ -468,7 +471,8 @@ impl Offer {
        /// [`Bolt12Invoice::verify`]: crate::offers::invoice::Bolt12Invoice::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>
+               &'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>,
+               payment_id: PaymentId
        ) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, Bolt12SemanticError>
        where
                ES::Target: EntropySource,
@@ -477,7 +481,9 @@ impl Offer {
                        return Err(Bolt12SemanticError::UnknownRequiredFeatures);
                }
 
-               Ok(InvoiceRequestBuilder::deriving_payer_id(self, expanded_key, entropy_source, secp_ctx))
+               Ok(InvoiceRequestBuilder::deriving_payer_id(
+                       self, expanded_key, entropy_source, secp_ctx, payment_id
+               ))
        }
 
        /// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the
@@ -489,7 +495,8 @@ impl Offer {
        ///
        /// [`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
+               &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
+               payment_id: PaymentId
        ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError>
        where
                ES::Target: EntropySource,
@@ -498,7 +505,9 @@ impl Offer {
                        return Err(Bolt12SemanticError::UnknownRequiredFeatures);
                }
 
-               Ok(InvoiceRequestBuilder::deriving_metadata(self, payer_id, expanded_key, entropy_source))
+               Ok(InvoiceRequestBuilder::deriving_metadata(
+                       self, payer_id, expanded_key, entropy_source, payment_id
+               ))
        }
 
        /// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
@@ -661,11 +670,13 @@ impl OfferContents {
                                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(),
+                                               OFFER_NODE_ID_TYPE => {
+                                                       !self.metadata.as_ref().unwrap().derives_recipient_keys()
+                                               },
                                                _ => true,
                                        }
                                });
-                               signer::verify_metadata(
+                               signer::verify_recipient_metadata(
                                        metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
                                )
                        },
index d419e8fe0d2b41e06c8b44f5f6215d8d07a221a9..4b4572b4df9c85fd8970e9758b727d9b6214a067 100644 (file)
@@ -82,6 +82,7 @@ use crate::sign::EntropySource;
 use crate::io;
 use crate::blinded_path::BlindedPath;
 use crate::ln::PaymentHash;
+use crate::ln::channelmanager::PaymentId;
 use crate::ln::features::InvoiceRequestFeatures;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
 use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
@@ -147,18 +148,22 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> {
        /// Also, sets the metadata when [`RefundBuilder::build`] is called such that it can be used to
        /// verify that an [`InvoiceRequest`] was produced for the refund given an [`ExpandedKey`].
        ///
+       /// The `payment_id` is encrypted in the metadata and should be unique. This ensures that only
+       /// one invoice will be paid for the refund and that payments can be uniquely identified.
+       ///
        /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
        /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
        pub fn deriving_payer_id<ES: Deref>(
                description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
-               secp_ctx: &'a Secp256k1<T>, amount_msats: u64
+               secp_ctx: &'a Secp256k1<T>, amount_msats: u64, payment_id: PaymentId
        ) -> Result<Self, Bolt12SemanticError> where ES::Target: EntropySource {
                if amount_msats > MAX_VALUE_MSAT {
                        return Err(Bolt12SemanticError::InvalidAmount);
                }
 
                let nonce = Nonce::from_entropy_source(entropy_source);
-               let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+               let payment_id = Some(payment_id);
+               let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id);
                let metadata = Metadata::DerivedSigningPubkey(derivation_material);
                Ok(Self {
                        refund: RefundContents {
@@ -244,7 +249,7 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> {
 
                        let mut tlv_stream = self.refund.as_tlv_stream();
                        tlv_stream.0.metadata = None;
-                       if metadata.derives_keys() {
+                       if metadata.derives_payer_keys() {
                                tlv_stream.2.payer_id = None;
                        }
 
@@ -566,7 +571,7 @@ impl RefundContents {
        }
 
        pub(super) fn derives_keys(&self) -> bool {
-               self.payer.0.derives_keys()
+               self.payer.0.derives_payer_keys()
        }
 
        pub(super) fn as_tlv_stream(&self) -> RefundTlvStreamRef {
@@ -748,6 +753,7 @@ mod tests {
        use core::time::Duration;
        use crate::blinded_path::{BlindedHop, BlindedPath};
        use crate::sign::KeyMaterial;
+       use crate::ln::channelmanager::PaymentId;
        use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
        use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
@@ -841,9 +847,10 @@ mod tests {
                let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
                let entropy = FixedEntropy {};
                let secp_ctx = Secp256k1::new();
+               let payment_id = PaymentId([1; 32]);
 
                let refund = RefundBuilder
-                       ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000)
+                       ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000, payment_id)
                        .unwrap()
                        .build().unwrap();
                assert_eq!(refund.payer_id(), node_id);
@@ -854,7 +861,10 @@ mod tests {
                        .unwrap()
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
-               assert!(invoice.verify(&expanded_key, &secp_ctx));
+               match invoice.verify(&expanded_key, &secp_ctx) {
+                       Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
+                       Err(()) => panic!("verification failed"),
+               }
 
                let mut tlv_stream = refund.as_tlv_stream();
                tlv_stream.2.amount = Some(2000);
@@ -867,7 +877,7 @@ mod tests {
                        .unwrap()
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
-               assert!(!invoice.verify(&expanded_key, &secp_ctx));
+               assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
 
                // Fails verification with altered metadata
                let mut tlv_stream = refund.as_tlv_stream();
@@ -882,7 +892,7 @@ mod tests {
                        .unwrap()
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
-               assert!(!invoice.verify(&expanded_key, &secp_ctx));
+               assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
        }
 
        #[test]
@@ -892,6 +902,7 @@ mod tests {
                let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
                let entropy = FixedEntropy {};
                let secp_ctx = Secp256k1::new();
+               let payment_id = PaymentId([1; 32]);
 
                let blinded_path = BlindedPath {
                        introduction_node_id: pubkey(40),
@@ -903,7 +914,7 @@ mod tests {
                };
 
                let refund = RefundBuilder
-                       ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000)
+                       ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000, payment_id)
                        .unwrap()
                        .path(blinded_path)
                        .build().unwrap();
@@ -914,7 +925,10 @@ mod tests {
                        .unwrap()
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
-               assert!(invoice.verify(&expanded_key, &secp_ctx));
+               match invoice.verify(&expanded_key, &secp_ctx) {
+                       Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
+                       Err(()) => panic!("verification failed"),
+               }
 
                // Fails verification with altered fields
                let mut tlv_stream = refund.as_tlv_stream();
@@ -928,7 +942,7 @@ mod tests {
                        .unwrap()
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
-               assert!(!invoice.verify(&expanded_key, &secp_ctx));
+               assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
 
                // Fails verification with altered payer_id
                let mut tlv_stream = refund.as_tlv_stream();
@@ -943,7 +957,7 @@ mod tests {
                        .unwrap()
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
-               assert!(!invoice.verify(&expanded_key, &secp_ctx));
+               assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
        }
 
        #[test]
index 8d5f98e6f6b050993474bbedbcc9a0f25c409980..4d5d4662bd62b806cb78543e41653c266a02146a 100644 (file)
@@ -16,15 +16,26 @@ use bitcoin::hashes::sha256::Hash as Sha256;
 use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self};
 use core::convert::TryFrom;
 use core::fmt;
+use crate::ln::channelmanager::PaymentId;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
 use crate::offers::merkle::TlvRecord;
 use crate::util::ser::Writeable;
 
 use crate::prelude::*;
 
+// Use a different HMAC input for each derivation. Otherwise, an attacker could:
+// - take an Offer that has metadata consisting of a nonce and HMAC
+// - strip off the HMAC and replace the signing_pubkey where the privkey is the HMAC,
+// - generate and sign an invoice using the new signing_pubkey, and
+// - claim they paid it since they would know the preimage of the invoice's payment_hash
 const DERIVED_METADATA_HMAC_INPUT: &[u8; 16] = &[1; 16];
 const DERIVED_METADATA_AND_KEYS_HMAC_INPUT: &[u8; 16] = &[2; 16];
 
+// Additional HMAC inputs to distinguish use cases, either Offer or Refund/InvoiceRequest, where
+// metadata for the latter contain an encrypted PaymentId.
+const WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[3; 16];
+const WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[4; 16];
+
 /// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be
 /// verified.
 #[derive(Clone)]
@@ -56,7 +67,20 @@ impl Metadata {
                }
        }
 
-       pub fn derives_keys(&self) -> bool {
+       pub fn derives_payer_keys(&self) -> bool {
+               match self {
+                       // Infer whether Metadata::derived_from was called on Metadata::DerivedSigningPubkey to
+                       // produce Metadata::Bytes. This is merely to determine which fields should be included
+                       // when verifying a message. It doesn't necessarily indicate that keys were in fact
+                       // derived, as wouldn't be the case if a Metadata::Bytes with length PaymentId::LENGTH +
+                       // Nonce::LENGTH had been set explicitly.
+                       Metadata::Bytes(bytes) => bytes.len() == PaymentId::LENGTH + Nonce::LENGTH,
+                       Metadata::Derived(_) => false,
+                       Metadata::DerivedSigningPubkey(_) => true,
+               }
+       }
+
+       pub fn derives_recipient_keys(&self) -> bool {
                match self {
                        // Infer whether Metadata::derived_from was called on Metadata::DerivedSigningPubkey to
                        // produce Metadata::Bytes. This is merely to determine which fields should be included
@@ -132,20 +156,33 @@ impl PartialEq for Metadata {
 pub(super) struct MetadataMaterial {
        nonce: Nonce,
        hmac: HmacEngine<Sha256>,
+       // Some for payer metadata and None for offer metadata
+       encrypted_payment_id: Option<[u8; PaymentId::LENGTH]>,
 }
 
 impl MetadataMaterial {
-       pub fn new(nonce: Nonce, expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN]) -> Self {
+       pub fn new(
+               nonce: Nonce, expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
+               payment_id: Option<PaymentId>
+       ) -> Self {
+               // Encrypt payment_id
+               let encrypted_payment_id = payment_id.map(|payment_id| {
+                       expanded_key.crypt_for_offer(payment_id.0, nonce)
+               });
+
                Self {
                        nonce,
                        hmac: expanded_key.hmac_for_offer(nonce, iv_bytes),
+                       encrypted_payment_id,
                }
        }
 
        fn derive_metadata(mut self) -> Vec<u8> {
                self.hmac.input(DERIVED_METADATA_HMAC_INPUT);
+               self.maybe_include_encrypted_payment_id();
 
-               let mut bytes = self.nonce.as_slice().to_vec();
+               let mut bytes = self.encrypted_payment_id.map(|id| id.to_vec()).unwrap_or(vec![]);
+               bytes.extend_from_slice(self.nonce.as_slice());
                bytes.extend_from_slice(&Hmac::from_engine(self.hmac).into_inner());
                bytes
        }
@@ -154,11 +191,26 @@ impl MetadataMaterial {
                mut self, secp_ctx: &Secp256k1<T>
        ) -> (Vec<u8>, KeyPair) {
                self.hmac.input(DERIVED_METADATA_AND_KEYS_HMAC_INPUT);
+               self.maybe_include_encrypted_payment_id();
+
+               let mut bytes = self.encrypted_payment_id.map(|id| id.to_vec()).unwrap_or(vec![]);
+               bytes.extend_from_slice(self.nonce.as_slice());
 
                let hmac = Hmac::from_engine(self.hmac);
                let privkey = SecretKey::from_slice(hmac.as_inner()).unwrap();
                let keys = KeyPair::from_secret_key(secp_ctx, &privkey);
-               (self.nonce.as_slice().to_vec(), keys)
+
+               (bytes, keys)
+       }
+
+       fn maybe_include_encrypted_payment_id(&mut self) {
+               match self.encrypted_payment_id {
+                       None => self.hmac.input(WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT),
+                       Some(encrypted_payment_id) => {
+                               self.hmac.input(WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT);
+                               self.hmac.input(&encrypted_payment_id)
+                       },
+               }
        }
 }
 
@@ -170,19 +222,65 @@ pub(super) fn derive_keys(nonce: Nonce, expanded_key: &ExpandedKey) -> KeyPair {
        KeyPair::from_secret_key(&secp_ctx, &privkey)
 }
 
+/// Verifies data given in a TLV stream was used to produce the given metadata, consisting of:
+/// - a 256-bit [`PaymentId`],
+/// - a 128-bit [`Nonce`], and possibly
+/// - a [`Sha256`] hash of the nonce and the TLV records using the [`ExpandedKey`].
+///
+/// If the latter is not included in the metadata, the TLV stream is used to check if the given
+/// `signing_pubkey` can be derived from it.
+///
+/// Returns the [`PaymentId`] that should be used for sending the payment.
+pub(super) fn verify_payer_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>
+) -> Result<PaymentId, ()> {
+       if metadata.len() < PaymentId::LENGTH {
+               return Err(());
+       }
+
+       let mut encrypted_payment_id = [0u8; PaymentId::LENGTH];
+       encrypted_payment_id.copy_from_slice(&metadata[..PaymentId::LENGTH]);
+
+       let mut hmac = hmac_for_message(
+               &metadata[PaymentId::LENGTH..], expanded_key, iv_bytes, tlv_stream
+       )?;
+       hmac.input(WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT);
+       hmac.input(&encrypted_payment_id);
+
+       verify_metadata(
+               &metadata[PaymentId::LENGTH..], Hmac::from_engine(hmac), signing_pubkey, secp_ctx
+       )?;
+
+       let nonce = Nonce::try_from(&metadata[PaymentId::LENGTH..][..Nonce::LENGTH]).unwrap();
+       let payment_id = expanded_key.crypt_for_offer(encrypted_payment_id, nonce);
+
+       Ok(PaymentId(payment_id))
+}
+
 /// Verifies data given in a TLV stream was used to produce the given metadata, consisting of:
 /// - a 128-bit [`Nonce`] and possibly
 /// - a [`Sha256`] hash of the nonce and the TLV records using the [`ExpandedKey`].
 ///
 /// If the latter is not included in the metadata, the TLV stream is used to check if the given
 /// `signing_pubkey` can be derived from it.
-pub(super) fn verify_metadata<'a, T: secp256k1::Signing>(
+///
+/// Returns the [`KeyPair`] for signing the invoice, if it can be derived from the metadata.
+pub(super) fn verify_recipient_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>
 ) -> Result<Option<KeyPair>, ()> {
-       let hmac = hmac_for_message(metadata, expanded_key, iv_bytes, tlv_stream)?;
+       let mut hmac = hmac_for_message(metadata, expanded_key, iv_bytes, tlv_stream)?;
+       hmac.input(WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT);
+
+       verify_metadata(metadata, Hmac::from_engine(hmac), signing_pubkey, secp_ctx)
+}
 
+fn verify_metadata<T: secp256k1::Signing>(
+       metadata: &[u8], hmac: Hmac<Sha256>, signing_pubkey: PublicKey, secp_ctx: &Secp256k1<T>
+) -> Result<Option<KeyPair>, ()> {
        if metadata.len() == Nonce::LENGTH {
                let derived_keys = KeyPair::from_secret_key(
                        secp_ctx, &SecretKey::from_slice(hmac.as_inner()).unwrap()
@@ -206,7 +304,7 @@ pub(super) fn verify_metadata<'a, T: secp256k1::Signing>(
 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>, ()> {
+) -> Result<HmacEngine<Sha256>, ()> {
        if metadata.len() < Nonce::LENGTH {
                return Err(());
        }
@@ -227,5 +325,5 @@ fn hmac_for_message<'a>(
                hmac.input(DERIVED_METADATA_HMAC_INPUT);
        }
 
-       Ok(Hmac::from_engine(hmac))
+       Ok(hmac)
 }
index c729e6847470e024858911793b9273592b0f7fb6..f46b344f2ce144c235e40e041743e707570e43a5 100644 (file)
@@ -159,6 +159,30 @@ mod real_chacha {
                        chacha_bytes
                }
 
+               /// Encrypts `src` into `dest` using a single block from a ChaCha stream. Passing `dest` as
+               /// `src` in a second call will decrypt it.
+               pub fn encrypt_single_block(
+                       key: &[u8; 32], nonce: &[u8; 16], dest: &mut [u8], src: &[u8]
+               ) {
+                       debug_assert_eq!(dest.len(), src.len());
+                       debug_assert!(dest.len() <= 32);
+
+                       let block = ChaCha20::get_single_block(key, nonce);
+                       for i in 0..dest.len() {
+                               dest[i] = block[i] ^ src[i];
+                       }
+               }
+
+               /// Same as `encrypt_single_block` only operates on a fixed-size input in-place.
+               pub fn encrypt_single_block_in_place(
+                       key: &[u8; 32], nonce: &[u8; 16], bytes: &mut [u8; 32]
+               ) {
+                       let block = ChaCha20::get_single_block(key, nonce);
+                       for i in 0..bytes.len() {
+                               bytes[i] = block[i] ^ bytes[i];
+                       }
+               }
+
                fn expand(key: &[u8], nonce: &[u8]) -> ChaChaState {
                        let constant = match key.len() {
                                16 => b"expand 16-byte k",
@@ -290,6 +314,17 @@ mod fuzzy_chacha {
                        [0; 32]
                }
 
+               pub fn encrypt_single_block(
+                       _key: &[u8; 32], _nonce: &[u8; 16], dest: &mut [u8], src: &[u8]
+               ) {
+                       debug_assert_eq!(dest.len(), src.len());
+                       debug_assert!(dest.len() <= 32);
+               }
+
+               pub fn encrypt_single_block_in_place(
+                       _key: &[u8; 32], _nonce: &[u8; 16], _bytes: &mut [u8; 32]
+               ) {}
+
                pub fn process(&mut self, input: &[u8], output: &mut [u8]) {
                        output.copy_from_slice(input);
                }
@@ -618,4 +653,49 @@ mod test {
 
                assert_eq!(ChaCha20::get_single_block(&key, &nonce_16bytes), block_bytes);
        }
+
+       #[test]
+       fn encrypt_single_block() {
+               let key = [
+                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                       0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+                       0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+                       0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+               ];
+               let nonce = [
+                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+                       0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+               ];
+               let bytes = [1; 32];
+
+               let mut encrypted_bytes = [0; 32];
+               ChaCha20::encrypt_single_block(&key, &nonce, &mut encrypted_bytes, &bytes);
+
+               let mut decrypted_bytes = [0; 32];
+               ChaCha20::encrypt_single_block(&key, &nonce, &mut decrypted_bytes, &encrypted_bytes);
+
+               assert_eq!(bytes, decrypted_bytes);
+       }
+
+       #[test]
+       fn encrypt_single_block_in_place() {
+               let key = [
+                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                       0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+                       0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+                       0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+               ];
+               let nonce = [
+                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+                       0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+               ];
+               let unencrypted_bytes = [1; 32];
+               let mut bytes = unencrypted_bytes;
+
+               ChaCha20::encrypt_single_block_in_place(&key, &nonce, &mut bytes);
+               assert_ne!(bytes, unencrypted_bytes);
+
+               ChaCha20::encrypt_single_block_in_place(&key, &nonce, &mut bytes);
+               assert_eq!(bytes, unencrypted_bytes);
+       }
 }
index 617f71e42c6854cb5d106e7a20d7543b14332e3b..cdd00d92af9c5f3cd0a2fe3a61c3447310a0bba2 100644 (file)
@@ -24,7 +24,7 @@ macro_rules! hkdf_extract_expand {
                let (k1, k2, _) = hkdf_extract_expand!($salt, $ikm);
                (k1, k2)
        }};
-       ($salt: expr, $ikm: expr, 4) => {{
+       ($salt: expr, $ikm: expr, 5) => {{
                let (k1, k2, prk) = hkdf_extract_expand!($salt, $ikm);
 
                let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
@@ -35,7 +35,14 @@ macro_rules! hkdf_extract_expand {
                let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
                hmac.input(&k3);
                hmac.input(&[4; 1]);
-               (k1, k2, k3, Hmac::from_engine(hmac).into_inner())
+               let k4 = Hmac::from_engine(hmac).into_inner();
+
+               let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
+               hmac.input(&k4);
+               hmac.input(&[5; 1]);
+               let k5 = Hmac::from_engine(hmac).into_inner();
+
+               (k1, k2, k3, k4, k5)
        }}
 }
 
@@ -43,8 +50,8 @@ pub fn hkdf_extract_expand_twice(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32]
        hkdf_extract_expand!(salt, ikm, 2)
 }
 
-pub fn hkdf_extract_expand_4x(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32], [u8; 32], [u8; 32]) {
-       hkdf_extract_expand!(salt, ikm, 4)
+pub fn hkdf_extract_expand_5x(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32], [u8; 32], [u8; 32], [u8; 32]) {
+       hkdf_extract_expand!(salt, ikm, 5)
 }
 
 #[inline]