X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Fsigner.rs;h=8d5f98e6f6b050993474bbedbcc9a0f25c409980;hb=4b241357387bcbb5cb51e00c83fab2dbeaebd0b3;hp=e1a1a4dfd6cf0ac07ac4523bc4296c2fbe327cbf;hpb=1cad430e14108710c826adebbfab2a5ea64a6a5a;p=rust-lightning diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index e1a1a4df..8d5f98e6 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -10,12 +10,14 @@ //! 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>, + secp_ctx: &Secp256k1 +) -> Result, ()> { + 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> +) -> Result, ()> { + 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)) +}