Skip to content

Commit 615eefb

Browse files
Verify inbound ReleaseHeldHtlc messages via hmac.
See AsyncPaymentsContext::hmac, but this prevents the recipient from deanonymizing us. Without this, if they are able to guess the correct payment id, then they could create a blinded path to us and confirm our identity. We also move the PAYMENT_HASH_HMAC_INPUT const to use &[7; 16], which is safe because this const was added since the last release. This ordering reads more smoothly.
1 parent 5a7f523 commit 615eefb

File tree

3 files changed

+71
-12
lines changed

3 files changed

+71
-12
lines changed

lightning/src/blinded_path/message.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,18 @@ pub enum AsyncPaymentsContext {
383383
/// which of our pending outbound payments should be released to its often-offline payee.
384384
///
385385
/// [`Offer`]: crate::offers::offer::Offer
386-
payment_id: PaymentId
386+
payment_id: PaymentId,
387+
/// A nonce used for authenticating that a [`ReleaseHeldHtlc`] message is valid for a preceding
388+
/// [`HeldHtlcAvailable`] message.
389+
///
390+
/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
391+
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
392+
nonce: Nonce,
393+
/// Authentication code for the [`PaymentId`].
394+
///
395+
/// Prevents the recipient from being able to deanonymize us by creating a blinded path to us
396+
/// containing the expected [`PaymentId`].
397+
hmac: Hmac<Sha256>,
387398
},
388399
}
389400

@@ -412,6 +423,8 @@ impl_writeable_tlv_based_enum!(OffersContext,
412423
impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
413424
(0, OutboundPayment) => {
414425
(0, payment_id, required),
426+
(2, nonce, required),
427+
(4, hmac, required),
415428
},
416429
);
417430

lightning/src/ln/channelmanager.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,24 @@ pub struct PaymentId(pub [u8; Self::LENGTH]);
453453
impl PaymentId {
454454
/// Number of bytes in the id.
455455
pub const LENGTH: usize = 32;
456+
457+
/// Constructs an HMAC to include in [`AsyncPaymentsContext::OutboundPayment`] for the payment id
458+
/// along with the given [`Nonce`].
459+
#[cfg(async_payments)]
460+
pub fn hmac_for_async_payment(
461+
&self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey,
462+
) -> Hmac<Sha256> {
463+
signer::hmac_for_async_payment_id(*self, nonce, expanded_key)
464+
}
465+
466+
/// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an
467+
/// [`AsyncPaymentsContext::OutboundPayment`].
468+
#[cfg(async_payments)]
469+
pub fn verify_for_async_payment(
470+
&self, hmac: Hmac<Sha256>, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey,
471+
) -> Result<(), ()> {
472+
signer::verify_async_payment_id(*self, hmac, nonce, expanded_key)
473+
}
456474
}
457475

458476
impl Verification for PaymentId {
@@ -4353,8 +4371,12 @@ where
43534371
}
43544372
};
43554373

4374+
let nonce = Nonce::from_entropy_source(&*self.entropy_source);
4375+
let hmac = payment_id.hmac_for_async_payment(nonce, &self.inbound_payment_key);
43564376
let reply_paths = match self.create_blinded_paths(
4357-
MessageContext::AsyncPayments(AsyncPaymentsContext::OutboundPayment { payment_id })
4377+
MessageContext::AsyncPayments(
4378+
AsyncPaymentsContext::OutboundPayment { payment_id, nonce, hmac }
4379+
)
43584380
) {
43594381
Ok(paths) => paths,
43604382
Err(()) => {
@@ -11209,7 +11231,8 @@ where
1120911231

1121011232
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {
1121111233
#[cfg(async_payments)] {
11212-
let AsyncPaymentsContext::OutboundPayment { payment_id } = _context;
11234+
let AsyncPaymentsContext::OutboundPayment { payment_id, hmac, nonce } = _context;
11235+
if payment_id.verify_for_async_payment(hmac, nonce, &self.inbound_payment_key).is_err() { return }
1121311236
if let Err(e) = self.send_payment_for_static_invoice(payment_id, _message.payment_release_secret) {
1121411237
log_trace!(
1121511238
self.logger, "Failed to release held HTLC with payment id {} and release secret {:02x?}: {:?}",

lightning/src/offers/signer.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ const WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[4; 16];
3939

4040
// HMAC input for a `PaymentId`. The HMAC is used in `OffersContext::OutboundPayment`.
4141
const OFFER_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[5; 16];
42+
// HMAC input for a `PaymentId`. The HMAC is used in `AsyncPaymentsContext::OutboundPayment`.
43+
#[cfg(async_payments)]
44+
const ASYNC_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[6; 16];
4245

4346
// HMAC input for a `PaymentHash`. The HMAC is used in `OffersContext::InboundPayment`.
44-
const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[6; 16];
47+
const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[7; 16];
4548

4649
/// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be
4750
/// verified.
@@ -402,14 +405,7 @@ fn hmac_for_message<'a>(
402405
pub(crate) fn hmac_for_offer_payment_id(
403406
payment_id: PaymentId, nonce: Nonce, expanded_key: &ExpandedKey,
404407
) -> Hmac<Sha256> {
405-
const IV_BYTES: &[u8; IV_LEN] = b"LDK Payment ID ~";
406-
let mut hmac = expanded_key.hmac_for_offer();
407-
hmac.input(IV_BYTES);
408-
hmac.input(&nonce.0);
409-
hmac.input(OFFER_PAYMENT_ID_HMAC_INPUT);
410-
hmac.input(&payment_id.0);
411-
412-
Hmac::from_engine(hmac)
408+
hmac_for_payment_id(payment_id, nonce, OFFER_PAYMENT_ID_HMAC_INPUT, expanded_key)
413409
}
414410

415411
pub(crate) fn verify_offer_payment_id(
@@ -436,3 +432,30 @@ pub(crate) fn verify_payment_hash(
436432
) -> Result<(), ()> {
437433
if hmac_for_payment_hash(payment_hash, nonce, expanded_key) == hmac { Ok(()) } else { Err(()) }
438434
}
435+
436+
#[cfg(async_payments)]
437+
pub(crate) fn hmac_for_async_payment_id(
438+
payment_id: PaymentId, nonce: Nonce, expanded_key: &ExpandedKey,
439+
) -> Hmac<Sha256> {
440+
hmac_for_payment_id(payment_id, nonce, ASYNC_PAYMENT_ID_HMAC_INPUT, expanded_key)
441+
}
442+
443+
#[cfg(async_payments)]
444+
pub(crate) fn verify_async_payment_id(
445+
payment_id: PaymentId, hmac: Hmac<Sha256>, nonce: Nonce, expanded_key: &ExpandedKey,
446+
) -> Result<(), ()> {
447+
if hmac_for_async_payment_id(payment_id, nonce, expanded_key) == hmac { Ok(()) } else { Err(()) }
448+
}
449+
450+
fn hmac_for_payment_id(
451+
payment_id: PaymentId, nonce: Nonce, hmac_input: &[u8; 16], expanded_key: &ExpandedKey,
452+
) -> Hmac<Sha256> {
453+
const IV_BYTES: &[u8; IV_LEN] = b"LDK Payment ID ~";
454+
let mut hmac = expanded_key.hmac_for_offer();
455+
hmac.input(IV_BYTES);
456+
hmac.input(&nonce.0);
457+
hmac.input(hmac_input);
458+
hmac.input(&payment_id.0);
459+
460+
Hmac::from_engine(hmac)
461+
}

0 commit comments

Comments
 (0)