/// which of our pending outbound payments should be released to its often-offline payee.
///
/// [`Offer`]: crate::offers::offer::Offer
- payment_id: PaymentId
+ payment_id: PaymentId,
+ /// A nonce used for authenticating that a [`ReleaseHeldHtlc`] message is valid for a preceding
+ /// [`HeldHtlcAvailable`] message.
+ ///
+ /// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
+ /// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
+ nonce: Nonce,
+ /// Authentication code for the [`PaymentId`].
+ ///
+ /// Prevents the recipient from being able to deanonymize us by creating a blinded path to us
+ /// containing the expected [`PaymentId`].
+ hmac: Hmac<Sha256>,
},
}
impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
(0, OutboundPayment) => {
(0, payment_id, required),
+ (2, nonce, required),
+ (4, hmac, required),
},
);
impl PaymentId {
/// Number of bytes in the id.
pub const LENGTH: usize = 32;
+
+ /// Constructs an HMAC to include in [`AsyncPaymentsContext::OutboundPayment`] for the payment id
+ /// along with the given [`Nonce`].
+ #[cfg(async_payments)]
+ pub fn hmac_for_async_payment(
+ &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey,
+ ) -> Hmac<Sha256> {
+ signer::hmac_for_async_payment_id(*self, nonce, expanded_key)
+ }
+
+ /// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an
+ /// [`AsyncPaymentsContext::OutboundPayment`].
+ #[cfg(async_payments)]
+ pub fn verify_for_async_payment(
+ &self, hmac: Hmac<Sha256>, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey,
+ ) -> Result<(), ()> {
+ signer::verify_async_payment_id(*self, hmac, nonce, expanded_key)
+ }
}
impl Verification for PaymentId {
}
};
+ let nonce = Nonce::from_entropy_source(&*self.entropy_source);
+ let hmac = payment_id.hmac_for_async_payment(nonce, &self.inbound_payment_key);
let reply_paths = match self.create_blinded_paths(
- MessageContext::AsyncPayments(AsyncPaymentsContext::OutboundPayment { payment_id })
+ MessageContext::AsyncPayments(
+ AsyncPaymentsContext::OutboundPayment { payment_id, nonce, hmac }
+ )
) {
Ok(paths) => paths,
Err(()) => {
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {
#[cfg(async_payments)] {
- let AsyncPaymentsContext::OutboundPayment { payment_id } = _context;
+ let AsyncPaymentsContext::OutboundPayment { payment_id, hmac, nonce } = _context;
+ if payment_id.verify_for_async_payment(hmac, nonce, &self.inbound_payment_key).is_err() { return }
if let Err(e) = self.send_payment_for_static_invoice(payment_id, _message.payment_release_secret) {
log_trace!(
self.logger, "Failed to release held HTLC with payment id {} and release secret {:02x?}: {:?}",
// HMAC input for a `PaymentId`. The HMAC is used in `OffersContext::OutboundPayment`.
const OFFER_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[5; 16];
+// HMAC input for a `PaymentId`. The HMAC is used in `AsyncPaymentsContext::OutboundPayment`.
+#[cfg(async_payments)]
+const ASYNC_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[6; 16];
// HMAC input for a `PaymentHash`. The HMAC is used in `OffersContext::InboundPayment`.
-const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[6; 16];
+const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[7; 16];
/// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be
/// verified.
pub(crate) fn hmac_for_offer_payment_id(
payment_id: PaymentId, nonce: Nonce, expanded_key: &ExpandedKey,
) -> Hmac<Sha256> {
- const IV_BYTES: &[u8; IV_LEN] = b"LDK Payment ID ~";
- let mut hmac = expanded_key.hmac_for_offer();
- hmac.input(IV_BYTES);
- hmac.input(&nonce.0);
- hmac.input(OFFER_PAYMENT_ID_HMAC_INPUT);
- hmac.input(&payment_id.0);
-
- Hmac::from_engine(hmac)
+ hmac_for_payment_id(payment_id, nonce, OFFER_PAYMENT_ID_HMAC_INPUT, expanded_key)
}
pub(crate) fn verify_offer_payment_id(
) -> Result<(), ()> {
if hmac_for_payment_hash(payment_hash, nonce, expanded_key) == hmac { Ok(()) } else { Err(()) }
}
+
+#[cfg(async_payments)]
+pub(crate) fn hmac_for_async_payment_id(
+ payment_id: PaymentId, nonce: Nonce, expanded_key: &ExpandedKey,
+) -> Hmac<Sha256> {
+ hmac_for_payment_id(payment_id, nonce, ASYNC_PAYMENT_ID_HMAC_INPUT, expanded_key)
+}
+
+#[cfg(async_payments)]
+pub(crate) fn verify_async_payment_id(
+ payment_id: PaymentId, hmac: Hmac<Sha256>, nonce: Nonce, expanded_key: &ExpandedKey,
+) -> Result<(), ()> {
+ if hmac_for_async_payment_id(payment_id, nonce, expanded_key) == hmac { Ok(()) } else { Err(()) }
+}
+
+fn hmac_for_payment_id(
+ payment_id: PaymentId, nonce: Nonce, hmac_input: &[u8; 16], expanded_key: &ExpandedKey,
+) -> Hmac<Sha256> {
+ const IV_BYTES: &[u8; IV_LEN] = b"LDK Payment ID ~";
+ let mut hmac = expanded_key.hmac_for_offer();
+ hmac.input(IV_BYTES);
+ hmac.input(&nonce.0);
+ hmac.input(hmac_input);
+ hmac.input(&payment_id.0);
+
+ Hmac::from_engine(hmac)
+}