Merge pull request #2492 from optout21/payment-hash-display
[rust-lightning] / lightning / src / offers / signer.rs
index e1a1a4dfd6cf0ac07ac4523bc4296c2fbe327cbf..8d5f98e6f6b050993474bbedbcc9a0f25c409980 100644 (file)
 //! Utilities for signing offer messages and verifying metadata.
 
 use bitcoin::hashes::{Hash, HashEngine};
+use bitcoin::hashes::cmp::fixed_time_eq;
 use bitcoin::hashes::hmac::{Hmac, HmacEngine};
 use bitcoin::hashes::sha256::Hash as Sha256;
-use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey, self};
-use core::convert::TryInto;
+use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self};
+use core::convert::TryFrom;
 use core::fmt;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
+use crate::offers::merkle::TlvRecord;
 use crate::util::ser::Writeable;
 
 use crate::prelude::*;
@@ -56,7 +58,12 @@ impl Metadata {
 
        pub fn derives_keys(&self) -> bool {
                match self {
-                       Metadata::Bytes(_) => false,
+                       // 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 Nonce::LENGTH had
+                       // been set explicitly.
+                       Metadata::Bytes(bytes) => bytes.len() == Nonce::LENGTH,
                        Metadata::Derived(_) => false,
                        Metadata::DerivedSigningPubkey(_) => true,
                }
@@ -89,6 +96,12 @@ impl Metadata {
        }
 }
 
+impl Default for Metadata {
+       fn default() -> Self {
+               Metadata::Bytes(vec![])
+       }
+}
+
 impl fmt::Debug for Metadata {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                match self {
@@ -148,3 +161,71 @@ impl MetadataMaterial {
                (self.nonce.as_slice().to_vec(), keys)
        }
 }
+
+pub(super) fn derive_keys(nonce: Nonce, expanded_key: &ExpandedKey) -> KeyPair {
+       const IV_BYTES: &[u8; IV_LEN] = b"LDK Invoice ~~~~";
+       let secp_ctx = Secp256k1::new();
+       let hmac = Hmac::from_engine(expanded_key.hmac_for_offer(nonce, IV_BYTES));
+       let privkey = SecretKey::from_slice(hmac.as_inner()).unwrap();
+       KeyPair::from_secret_key(&secp_ctx, &privkey)
+}
+
+/// 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>(
+       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)?;
+
+       if metadata.len() == Nonce::LENGTH {
+               let derived_keys = KeyPair::from_secret_key(
+                       secp_ctx, &SecretKey::from_slice(hmac.as_inner()).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.into_inner()) {
+                       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<Hmac<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::from_engine(hmac))
+}