Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support receiving async payments #3440

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
90a1405
Add expected_htlc_destination argument to run_onion_failure_test
wpaulino Mar 7, 2024
2734012
Enable decoding new incoming HTLC onions when fully committed
wpaulino Mar 8, 2024
a52b43b
Fix outdated static invoice docs.
valentinewallace Jul 26, 2024
0684b86
Don't take() outbound invoice requests on retry
valentinewallace Oct 29, 2024
5ecbfa3
Fix failure to abandon async payments on invalid static invoice
valentinewallace Nov 13, 2024
ddc5154
Make create_blinded_payment_paths methods amount optional.
valentinewallace Jun 24, 2024
2538d3f
Factor invoice expiry into blinded path max_cltv_expiry
valentinewallace Jul 8, 2024
d3f02ee
Add PaymentContext for async payments
valentinewallace Jun 24, 2024
8ad7f66
Add onion message AsyncPaymentsContext for inbound payments
valentinewallace Oct 30, 2024
5e172f8
Add utils to create static invoices and their corresponding offers
valentinewallace Oct 30, 2024
24350ee
Test failures on paying static invoices
valentinewallace Nov 14, 2024
94ef25a
Move blinded keysend tests into new async_payments_tests.rs
valentinewallace Jan 3, 2025
a149c36
Tests: DRY failing a blinded HTLC backwards
valentinewallace Jan 6, 2025
bea56e3
Tests: DRY static invoice creation
valentinewallace Jan 8, 2025
53866bf
Add handle_held_htlc_available MessageContext param
valentinewallace Dec 4, 2024
fc5f27d
Fail earlier on expired static invoice
valentinewallace Jan 6, 2025
1a51825
Fail out-of-PaymentContext inbound keysends
valentinewallace Jan 6, 2025
1daabbf
Support expiring async payments blinded message paths
valentinewallace Jan 8, 2025
5ebdd17
Release async payment HTLCs held upstream via OM
valentinewallace Jan 8, 2025
e0d18a1
Bubble up invreq from htlc onion to forwarding flow
valentinewallace Jul 30, 2024
d93aecc
Support receiving async payment HTLCs
valentinewallace Aug 3, 2024
d19ebae
Test utils: fix config overrides for private channels
valentinewallace Jan 8, 2025
6f7fcba
Test async receive
valentinewallace Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl Router for FuzzRouter {

fn create_blinded_payment_paths<T: secp256k1::Signing + secp256k1::Verification>(
&self, _recipient: PublicKey, _first_hops: Vec<ChannelDetails>, _tlvs: ReceiveTlvs,
_amount_msats: u64, _secp_ctx: &Secp256k1<T>,
_amount_msats: Option<u64>, _secp_ctx: &Secp256k1<T>,
) -> Result<Vec<BlindedPaymentPath>, ()> {
unreachable!()
}
Expand Down
26 changes: 13 additions & 13 deletions fuzz/src/full_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ impl Router for FuzzRouter {

fn create_blinded_payment_paths<T: secp256k1::Signing + secp256k1::Verification>(
&self, _recipient: PublicKey, _first_hops: Vec<ChannelDetails>, _tlvs: ReceiveTlvs,
_amount_msats: u64, _secp_ctx: &Secp256k1<T>,
_amount_msats: Option<u64>, _secp_ctx: &Secp256k1<T>,
) -> Result<Vec<BlindedPaymentPath>, ()> {
unreachable!()
}
Expand Down Expand Up @@ -1338,8 +1338,8 @@ mod tests {
// end of update_add_htlc from 0 to 1 via client and mac
ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ab00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test);

// Two feerate requests to check dust exposure
ext_from_hex("00fd00fd", &mut test);
// One feerate request to check dust exposure
ext_from_hex("00fd", &mut test);

// inbound read from peer id 0 of len 18
ext_from_hex("030012", &mut test);
Expand All @@ -1362,8 +1362,8 @@ mod tests {

// process the now-pending HTLC forward
ext_from_hex("07", &mut test);
// Three feerate requests to check dust exposure
ext_from_hex("00fd00fd00fd", &mut test);
// Four feerate requests to check dust exposure while forwarding the HTLC
ext_from_hex("00fd00fd00fd00fd", &mut test);
// client now sends id 1 update_add_htlc and commitment_signed (CHECK 7: UpdateHTLCs event for node 03020000 with 1 HTLCs for channel 3f000000)

// we respond with commitment_signed then revoke_and_ack (a weird, but valid, order)
Expand Down Expand Up @@ -1439,8 +1439,8 @@ mod tests {
// end of update_add_htlc from 0 to 1 via client and mac
ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ab00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test);

// Two feerate requests to check dust exposure
ext_from_hex("00fd00fd", &mut test);
// One feerate request to check dust exposure
ext_from_hex("00fd", &mut test);

// now respond to the update_fulfill_htlc+commitment_signed messages the client sent to peer 0
// inbound read from peer id 0 of len 18
Expand Down Expand Up @@ -1474,8 +1474,8 @@ mod tests {
// process the now-pending HTLC forward
ext_from_hex("07", &mut test);

// Three feerate requests to check dust exposure
ext_from_hex("00fd00fd00fd", &mut test);
// Four feerate requests to check dust exposure while forwarding the HTLC
ext_from_hex("00fd00fd00fd00fd", &mut test);

// client now sends id 1 update_add_htlc and commitment_signed (CHECK 7 duplicate)
// we respond with revoke_and_ack, then commitment_signed, then update_fail_htlc
Expand Down Expand Up @@ -1574,8 +1574,8 @@ mod tests {
// end of update_add_htlc from 0 to 1 via client and mac
ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 5300000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test);

// Two feerate requests to check dust exposure
ext_from_hex("00fd00fd", &mut test);
// One feerate request to check dust exposure
ext_from_hex("00fd", &mut test);

// inbound read from peer id 0 of len 18
ext_from_hex("030012", &mut test);
Expand All @@ -1598,8 +1598,8 @@ mod tests {

// process the now-pending HTLC forward
ext_from_hex("07", &mut test);
// Three feerate requests to check dust exposure
ext_from_hex("00fd00fd00fd", &mut test);
// Four feerate requests to check dust exposure while forwarding the HTLC
ext_from_hex("00fd00fd00fd00fd", &mut test);
// client now sends id 1 update_add_htlc and commitment_signed (CHECK 7 duplicate)

// connect a block with one transaction of len 125
Expand Down
3 changes: 2 additions & 1 deletion fuzz/src/onion_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ struct TestAsyncPaymentsMessageHandler {}

impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler {
fn handle_held_htlc_available(
&self, _message: HeldHtlcAvailable, responder: Option<Responder>,
&self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext,
responder: Option<Responder>,
) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> {
let responder = match responder {
Some(resp) => resp,
Expand Down
27 changes: 27 additions & 0 deletions lightning/src/blinded_path/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,28 @@ pub enum AsyncPaymentsContext {
/// containing the expected [`PaymentId`].
hmac: Hmac<Sha256>,
},
/// Context contained within the [`BlindedMessagePath`]s we put in static invoices, provided back
/// to us in corresponding [`HeldHtlcAvailable`] messages.
///
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
InboundPayment {
/// A nonce used for authenticating that a [`HeldHtlcAvailable`] message is valid for a
/// preceding static invoice.
///
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
nonce: Nonce,
/// Authentication code for the [`HeldHtlcAvailable`] message.
///
/// Prevents nodes from creating their own blinded path to us, sending a [`HeldHtlcAvailable`]
/// message and trivially getting notified whenever we come online.
///
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
hmac: Hmac<Sha256>,
/// The time as duration since the Unix epoch at which this path expires and messages sent over
/// it should be ignored. Without this, anyone with the path corresponding to this context is
/// able to trivially ask if we're online forever.
path_absolute_expiry: core::time::Duration,
},
}

impl_writeable_tlv_based_enum!(MessageContext,
Expand Down Expand Up @@ -433,6 +455,11 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
(2, nonce, required),
(4, hmac, required),
},
(1, InboundPayment) => {
(0, nonce, required),
(2, hmac, required),
(4, path_absolute_expiry, required),
},
);

/// Contains a simple nonce for use in a blinded path's context.
Expand Down
48 changes: 39 additions & 9 deletions lightning/src/blinded_path/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use bitcoin::hashes::hmac::Hmac;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
use bitcoin::secp256k1::ecdh::SharedSecret;

use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NodeIdLookUp};
use crate::blinded_path::utils;
Expand Down Expand Up @@ -170,15 +171,8 @@ impl BlindedPaymentPath {
NL::Target: NodeIdLookUp,
T: secp256k1::Signing + secp256k1::Verification,
{
let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.inner_path.blinding_point, None)?;
let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
let encrypted_control_tlvs = &self.inner_path.blinded_hops.get(0).ok_or(())?.encrypted_payload;
let mut s = Cursor::new(encrypted_control_tlvs);
let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
match ChaChaPolyReadAdapter::read(&mut reader, rho) {
Ok(ChaChaPolyReadAdapter {
readable: BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, .. })
}) => {
match self.decrypt_intro_payload::<NS>(node_signer) {
Ok((BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, .. }), control_tlvs_ss)) => {
let next_node_id = match node_id_lookup.next_node_id(short_channel_id) {
Some(node_id) => node_id,
None => return Err(()),
Expand All @@ -195,6 +189,20 @@ impl BlindedPaymentPath {
}
}

pub(crate) fn decrypt_intro_payload<NS: Deref>(
&self, node_signer: &NS
) -> Result<(BlindedPaymentTlvs, SharedSecret), ()> where NS::Target: NodeSigner {
let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.inner_path.blinding_point, None)?;
let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
let encrypted_control_tlvs = &self.inner_path.blinded_hops.get(0).ok_or(())?.encrypted_payload;
let mut s = Cursor::new(encrypted_control_tlvs);
let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
match ChaChaPolyReadAdapter::read(&mut reader, rho) {
Ok(ChaChaPolyReadAdapter { readable, .. }) => Ok((readable, control_tlvs_ss)),
_ => Err(())
}
}

pub(crate) fn inner_blinded_path(&self) -> &BlindedPath {
&self.inner_path
}
Expand Down Expand Up @@ -349,6 +357,11 @@ pub enum PaymentContext {
/// [`Offer`]: crate::offers::offer::Offer
Bolt12Offer(Bolt12OfferContext),

/// The payment was made for a static invoice requested from a BOLT 12 [`Offer`].
///
/// [`Offer`]: crate::offers::offer::Offer
AsyncBolt12Offer(AsyncBolt12OfferContext),

/// The payment was made for an invoice sent for a BOLT 12 [`Refund`].
///
/// [`Refund`]: crate::offers::refund::Refund
Expand Down Expand Up @@ -378,6 +391,18 @@ pub struct Bolt12OfferContext {
pub invoice_request: InvoiceRequestFields,
}

/// The context of a payment made for a static invoice requested from a BOLT 12 [`Offer`].
///
/// [`Offer`]: crate::offers::offer::Offer
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AsyncBolt12OfferContext {
/// The [`Nonce`] used to verify that an inbound [`InvoiceRequest`] corresponds to this static
/// invoice's offer.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub offer_nonce: Nonce,
}

/// The context of a payment made for an invoice sent for a BOLT 12 [`Refund`].
///
/// [`Refund`]: crate::offers::refund::Refund
Expand Down Expand Up @@ -627,6 +652,7 @@ impl_writeable_tlv_based_enum_legacy!(PaymentContext,
// 0 for Unknown removed in version 0.1.
(1, Bolt12Offer),
(2, Bolt12Refund),
(3, AsyncBolt12Offer),
);

impl<'a> Writeable for PaymentContextRef<'a> {
Expand All @@ -651,6 +677,10 @@ impl_writeable_tlv_based!(Bolt12OfferContext, {
(2, invoice_request, required),
});

impl_writeable_tlv_based!(AsyncBolt12OfferContext, {
(0, offer_nonce, required),
});

impl_writeable_tlv_based!(Bolt12RefundContext, {});

#[cfg(test)]
Expand Down
38 changes: 27 additions & 11 deletions lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::ln::msgs;
use crate::ln::types::ChannelId;
use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret};
use crate::offers::invoice::Bolt12Invoice;
use crate::offers::invoice_request::VerifiedInvoiceRequest;
use crate::onion_message::messenger::Responder;
use crate::routing::gossip::NetworkUpdate;
use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters};
Expand Down Expand Up @@ -180,29 +181,41 @@ impl PaymentPurpose {

pub(crate) fn from_parts(
payment_preimage: Option<PaymentPreimage>, payment_secret: PaymentSecret,
payment_context: Option<PaymentContext>,
) -> Self {
payment_context: Option<PaymentContext>, invreq: Option<VerifiedInvoiceRequest>,
) -> Result<Self, ()> {
match payment_context {
None => {
PaymentPurpose::Bolt11InvoicePayment {
Ok(PaymentPurpose::Bolt11InvoicePayment {
payment_preimage,
payment_secret,
}
})
},
Some(PaymentContext::Bolt12Offer(context)) => {
PaymentPurpose::Bolt12OfferPayment {
Ok(PaymentPurpose::Bolt12OfferPayment {
payment_preimage,
payment_secret,
payment_context: context,
}
})
},
Some(PaymentContext::Bolt12Refund(context)) => {
PaymentPurpose::Bolt12RefundPayment {
Ok(PaymentPurpose::Bolt12RefundPayment {
payment_preimage,
payment_secret,
payment_context: context,
}
})
},
Some(PaymentContext::AsyncBolt12Offer(_)) => {
let invoice_request = invreq.ok_or(())?;
if payment_preimage.is_none() { return Err(()) }
Ok(PaymentPurpose::Bolt12OfferPayment {
payment_preimage,
payment_secret,
payment_context: Bolt12OfferContext {
offer_id: invoice_request.offer_id,
invoice_request: invoice_request.fields(),
},
})
}
}
}
}
Expand Down Expand Up @@ -1185,12 +1198,12 @@ pub enum Event {
/// events generated or serialized by versions prior to 0.0.122.
next_user_channel_id: Option<u128>,
/// The node id of the previous node.
///
///
/// This is only `None` for HTLCs received prior to 0.1 or for events serialized by
/// versions prior to 0.1
prev_node_id: Option<PublicKey>,
/// The node id of the next node.
///
///
/// This is only `None` for HTLCs received prior to 0.1 or for events serialized by
/// versions prior to 0.1
next_node_id: Option<PublicKey>,
Expand Down Expand Up @@ -1432,6 +1445,8 @@ pub enum Event {
/// * When an unknown SCID is requested for forwarding a payment.
/// * Expected MPP amount has already been reached
/// * The HTLC has timed out
/// * The HTLC failed to meet the forwarding requirements (i.e. insufficient fees paid, or a
/// CLTV that is too soon)
///
/// This event, however, does not get generated if an HTLC fails to meet the forwarding
/// requirements (i.e. insufficient fees paid, or a CLTV that is too soon).
Expand Down Expand Up @@ -1865,7 +1880,8 @@ impl MaybeReadable for Event {
(13, payment_id, option),
});
let purpose = match payment_secret {
Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context),
Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context, None)
.map_err(|()| msgs::DecodeError::InvalidValue)?,
None if payment_preimage.is_some() => PaymentPurpose::SpontaneousPayment(payment_preimage.unwrap()),
None => return Err(msgs::DecodeError::InvalidValue),
};
Expand Down
Loading
Loading