-
Notifications
You must be signed in to change notification settings - Fork 384
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
Verify blinded keysend payments #3383
Verify blinded keysend payments #3383
Conversation
I'm seeking conceptual feedback on this PR before finishing the tests. At a high level, verifying So, let me know if this approach seems acceptable or if another one is preferred. Since we're basically only using the payment secret to verify the static invoice isn't expired1, there may be other options such as putting the expiry in the Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some code review, but at a high level I guess there's really two ways to verify this - we can verify at the payment level, like here, or we could verify at the blinded path level, deciding whether to accept or reject the payment by examining if the blinded path we receive the payment over meets requirements we put on the blinded path. ISTM the second may well be more flexible and may even already cover the restrictions we want.
Vaguely related to this PR, we should probably ensure we have whatever is required in the blinded path to make sure a normal BOLT 12 invoice payment blinded path isn't payable as a static invoice payment and vice-versa.
I spent some time looking into 2nd approach you mention, which basically amounts to verifying the payment using the ISTM that in our static invoice's blinded payment paths, we'd end up with a /// The context of a payment made for a static invoice requested from a BOLT 12 [`Offer`].
pub struct AsyncBolt12OfferContext {
/// The [`Nonce`] used to verify that an inbound [`InvoiceRequest`] corresponds to this static
/// invoice's offer.
pub offer_nonce: Nonce,
/// Used instead of the `PaymentSecret` to verify that the offer's min amount is met.
pub offer_min_amt_msat: Option<u64>,
/// Used instead of the `PaymentSecret` to verify that the invoice's expiry is met.
pub invoice_absolute_expiry: core::time::Duration,
/// Fields to verify that this blinded path was created by us below.
pub nonce: Nonce,
pub hmac: Hmac<Sha256>,
} This context would allow us to check that the invoice's min amount and expiry are satisfied in The main issue I ran into with this approach was that we wouldn't have a natural payment secret to put in I don't really have a strong feeling between the two approaches. It kind of feels like we have nice infrastructure built for creating/verifying |
I'm definitely okay using the |
c947de3
to
3eaa89e
Compare
Yeah, attempting to pay a non-static invoice with keysend should fail payment secret verification. Non-keysend to static invoice blinded path doesn't really make sense because we just won't know the payment preimage, IIUC. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3383 +/- ##
==========================================
+ Coverage 89.57% 89.64% +0.06%
==========================================
Files 125 128 +3
Lines 101756 104810 +3054
Branches 101756 104810 +3054
==========================================
+ Hits 91151 93958 +2807
- Misses 7884 8145 +261
+ Partials 2721 2707 -14 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM modulo some minor comments. Feel free to squash, IMO.
LDK versions prior to 0.0.104 had stateful inbound payments written in this map. In 0.0.104, we added support for stateless inbound payments with deterministically generated payment secrets, and maintained deprecated support for stateful inbound payments until 0.0.116. After 0.0.116, no further inbound payments could have been written into this map.
This key will be used in upcoming commits for encrypting metadata bytes for spontaneous payments' payment secrets, to be included in the blinded paths of static invoices for async payments. We need a new type of payment secret for these payments because they don't have an a prior known payment hash, see the next commit.
Add a new payment type for this, because normally the payment hash is factored into the payment secrets we create for invoices, but static invoices don't have a payment hash since they are paid via keysend.
3eaa89e
to
bf2ccb8
Compare
Squashed with the following diff from Jeff's feedback: diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs
index c0913d51d..dcfd71a86 100644
--- a/lightning/src/ln/blinded_payment_tests.rs
+++ b/lightning/src/ln/blinded_payment_tests.rs
@@ -1345,7 +1345,6 @@ fn invalid_keysend_payment_secret() {
let args = PassAlongPathArgs::new(
&nodes[0], &expected_route[0], amt_msat, payment_hash, ev.clone()
)
- .without_claimable_event()
.with_payment_secret(invalid_payment_secret)
.with_payment_preimage(keysend_preimage)
.expect_failure(HTLCDestination::FailedPayment { payment_hash });
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index bb951d3c2..29920f191 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -5721,8 +5721,8 @@ where
},
PendingHTLCRouting::ReceiveKeysend {
payment_data, payment_preimage, payment_metadata,
- incoming_cltv_expiry, custom_tlvs, has_recipient_created_payment_secret,
- requires_blinded_error: _,
+ incoming_cltv_expiry, custom_tlvs, requires_blinded_error: _,
+ has_recipient_created_payment_secret,
} => {
let onion_fields = RecipientOnionFields {
payment_secret: payment_data.as_ref().map(|data| data.payment_secret),
diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs
index 8a5d8c20b..359e58688 100644
--- a/lightning/src/ln/functional_test_utils.rs
+++ b/lightning/src/ln/functional_test_utils.rs
@@ -2631,6 +2631,7 @@ impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> {
self
}
pub fn expect_failure(mut self, failure: HTLCDestination) -> Self {
+ self.payment_claimable_expected = false;
self.expected_failure = Some(failure);
self
} |
lightning/src/ln/channelmanager.rs
Outdated
} | ||
} | ||
payment_preimage | ||
} else { debug_assert!(false); None } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else { debug_assert!(false); None } | |
} else { debug_assert!(false); None } |
why we do this dabug assert here? and not just panic always or emit a log if we are not panicking in production? I feel this can be hard to track down if this happen in some wallet in production, is it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We tend to add debug_assert
s for cases that should be unreachable, if that makes sense. So we should never hit this.
hash.write(writer)?; | ||
pending_payment.write(writer)?; | ||
} | ||
// LDK versions prior to 0.0.104 wrote `pending_inbound_payments` here, with deprecated support |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// LDK versions prior to 0.0.104 wrote `pending_inbound_payments` here, with deprecated support | |
// LDK versions prior to 0.0.104 wrote `pending_inbound_payments` here, with deprecated support |
if the version number right? probably is 0.0.115
but I can miss understanding what you want to say
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, why do you think it's 115?
If we're receiving a keysend to a blinded path, then we created the payment secret within. Using our inbound_payment_key, we can decrypt the payment secret bytes to get the payment's min_cltv_expiry_delta and min amount, to verify the payment is valid. However, if we're receiving an MPP keysend *not* to a blinded path, then we did not create the payment secret and shouldn't verify it since it's only used to correlate MPP parts. Therefore, store whether the payment secret is recipient-generated in our pending inbound payment data so we know whether to verify it or not.
bf2ccb8
to
9cc6969
Compare
Squashed in Matt's feedback with the following diff: diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 29920f191..60b6b6f29 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -5913,7 +5913,7 @@ where
}
}
payment_preimage
- } else { debug_assert!(false); None }
+ } else { fail_htlc!(claimable_htlc, payment_hash); }
} else { None };
match claimable_htlc.onion_payload {
OnionPayload::Invoice { .. } => { |
If we're receiving a keysend to a blinded path, then we created the payment
secret within. Using our inbound_payment_key, we can decrypt the payment secret
bytes to get the payment's
min_cltv_expiry_delta
and min amount, to verify thepayment is valid. However, if we're receiving an MPP keysend not to a blinded
path, then we did not create the payment secret and shouldn't verify it since
it's only used to correlate MPP parts.
We also take this opportunity to remove the
ChannelManager::inbound_payments
map, since it's long deprecated and can't have been added to since 0.0.116.