+
+ 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.
+///
+/// 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 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_byte_array()).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.to_byte_array()) {
+ 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<HmacEngine<Sha256>, ()> {
+ if metadata.len() < Nonce::LENGTH {
+ return Err(());
+ }
+
+ let nonce = match Nonce::try_from(&metadata[..Nonce::LENGTH]) {
+ Ok(nonce) => nonce,
+ Err(_) => return Err(()),
+ };
+ let mut hmac = expanded_key.hmac_for_offer(nonce, iv_bytes);
+
+ for record in tlv_stream {
+ hmac.input(record.record_bytes);
+ }
+
+ if metadata.len() == Nonce::LENGTH {
+ hmac.input(DERIVED_METADATA_AND_KEYS_HMAC_INPUT);
+ } else {
+ hmac.input(DERIVED_METADATA_HMAC_INPUT);
+ }
+
+ Ok(hmac)