use bitcoin::hashes::hmac::{Hmac, HmacEngine};
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)]
}
}
- 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
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();
- bytes.extend_from_slice(&Hmac::from_engine(self.hmac).into_inner());
+ 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).as_byte_array());
bytes
}
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 privkey = SecretKey::from_slice(hmac.as_byte_array()).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)
+ },
+ }
}
}
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();
+ let privkey = SecretKey::from_slice(hmac.as_byte_array()).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 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()
+ 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))
Err(())
}
} else if metadata[Nonce::LENGTH..].len() == Sha256::LEN {
- if fixed_time_eq(&metadata[Nonce::LENGTH..], &hmac.into_inner()) {
+ if fixed_time_eq(&metadata[Nonce::LENGTH..], &hmac.to_byte_array()) {
Ok(None)
} 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>, ()> {
+) -> Result<HmacEngine<Sha256>, ()> {
if metadata.len() < Nonce::LENGTH {
return Err(());
}
hmac.input(DERIVED_METADATA_HMAC_INPUT);
}
- Ok(Hmac::from_engine(hmac))
+ Ok(hmac)
}