Add support for "phantom" BOLT 12 offers, up to the invoice_request step#4335
Add support for "phantom" BOLT 12 offers, up to the invoice_request step#4335TheBlueMatt wants to merge 14 commits intolightningdevkit:mainfrom
Conversation
|
👋 Thanks for assigning @jkczyz as a reviewer! |
| /// checked, effectively treating the contents as the AAD for the AAD-containing MAC but behaving | ||
| /// like classic ChaCha20Poly1305 for the non-AAD-containing MAC. | ||
| pub(crate) struct ChaChaDualPolyReadAdapter<R: Readable> { | ||
| pub(crate) struct ChaChaTriPolyReadAdapter<R: Readable> { |
There was a problem hiding this comment.
Would you say there is sufficient test coverage on this? I think there is some higher-level coverage, but no direct unit test.
There was a problem hiding this comment.
I think so? I mean if its horribly broken the higher-level code will also break. Its also somewhat indirectly fuzzed through the onion decoding fuzzing.
There was a problem hiding this comment.
I think lower level unit tests do have value even if there are higher level tests already.
There was a problem hiding this comment.
Not really sure what you'd want to test? That it can decrypt correctly? That's definitely well-covered. The one bug we did have in it was found by our fuzz test. More test coverage is great but more tests to get the same coverage seems like a net-negative.
| let ChaChaDualPolyReadAdapter { readable, used_aad } = | ||
| ChaChaDualPolyReadAdapter::read(&mut reader, (rho, receive_auth_key.0)) | ||
| .map_err(|_| ())?; | ||
| let ChaChaTriPolyReadAdapter { readable, used_aad_a, used_aad_b } = |
There was a problem hiding this comment.
Names like used_local and used_phantom could be helpful
There was a problem hiding this comment.
Sadly I can't rename the fields since I switched to an enum.
There was a problem hiding this comment.
Why not use Local and Phantom in the enum? This adapter isn't used for anything else.
There was a problem hiding this comment.
Because that's really confusing when you read the stream type that isn't dedicated to only use in blinded paths. I'm not at all convinced the callsites are unclear, and we also might need to/want to upstream this at some point to rust-bitcoin.
lightning/src/ln/channelmanager.rs
Outdated
| .iter() | ||
| .filter(|chan| chan.is_usable) | ||
| .filter_map(|chan| chan.short_channel_id) | ||
| .min(), |
There was a problem hiding this comment.
Its the oldest, matching the existing logic we have for the non-phantom case.
There was a problem hiding this comment.
Is that to maximize the chances that it still exist? Curious where the existing logic is located also.
There was a problem hiding this comment.
Yea, basically. Absent any better criteria we assume that the oldest channel is most likely to stick around. We do it in get_peers_for_blinded_path as well using funded_channel.context.channel_creation_height
There was a problem hiding this comment.
Won't an old channel that is spliced recently seem newer? Also, should we use inbound_scid_alias if set as is done in get_peers_for_blinded_path by using FundedChannel::get_inbound_scid?
There was a problem hiding this comment.
Won't an old channel that is spliced recently seem newer?
Indeed it will. There's no obvious fix for that AFAICT, but it also shouldn't really matter - we could just pick a random SCID too if we want...
Also, should we use inbound_scid_alias if set as is done in get_peers_for_blinded_path by using FundedChannel::get_inbound_scid?
Oops, right.
There was a problem hiding this comment.
Could we expose channel_creation_height in ChannelDetails?
| 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 receive_auth_key = node_signer.get_receive_auth_key(); | ||
| let phantom_auth_key = node_signer.get_expanded_key().phantom_node_blinded_path_key; |
There was a problem hiding this comment.
You could have None here in case the node isn't configured for phantom payments. I think that might help keep that case in mind for readers, and also saves some computation?
There was a problem hiding this comment.
Yea, we don't currently have a separate method on the signer that returns None in cases where we aren't doing phantom so we'd have to add a new key-fetcher (or bool-fetcher) to decide whether to do phantom validation. I thought about doing that but it seemed way cleaner to reuse the ExpandedKey (which is already used for all the inbound-payment key logic, including phantom). Its also not very much compute at all to just do one round of poly1305 + the finish logic so I'm not sure its worth the extra work.
|
👋 The first review has been submitted! Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer. |
736cab5 to
5ef29ff
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4335 +/- ##
========================================
Coverage 86.01% 86.02%
========================================
Files 156 156
Lines 102781 102981 +200
Branches 102781 102981 +200
========================================
+ Hits 88409 88588 +179
- Misses 11864 11881 +17
- Partials 2508 2512 +4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
joostjager
left a comment
There was a problem hiding this comment.
No blocking comments. I do think that renaming the enum values would be a good idea. Curious to hear from 2nd reviewer.
| /// checked, effectively treating the contents as the AAD for the AAD-containing MAC but behaving | ||
| /// like classic ChaCha20Poly1305 for the non-AAD-containing MAC. | ||
| pub(crate) struct ChaChaDualPolyReadAdapter<R: Readable> { | ||
| pub(crate) struct ChaChaTriPolyReadAdapter<R: Readable> { |
There was a problem hiding this comment.
I think lower level unit tests do have value even if there are higher level tests already.
| let ChaChaDualPolyReadAdapter { readable, used_aad } = | ||
| ChaChaDualPolyReadAdapter::read(&mut reader, (rho, receive_auth_key.0)) | ||
| .map_err(|_| ())?; | ||
| let ChaChaTriPolyReadAdapter { readable, used_aad_a, used_aad_b } = |
There was a problem hiding this comment.
Why not use Local and Phantom in the enum? This adapter isn't used for anything else.
lightning/src/ln/channelmanager.rs
Outdated
| .iter() | ||
| .filter(|chan| chan.is_usable) | ||
| .filter_map(|chan| chan.short_channel_id) | ||
| .min(), |
There was a problem hiding this comment.
Is that to maximize the chances that it still exist? Curious where the existing logic is located also.
| let keys_manager = if predefined_keys_ids.is_some() { | ||
| let mut seed = [i as u8; 32]; | ||
| if phantom { | ||
| // We would ideally randomize keys on every test run, but some tests fail in that case. |
There was a problem hiding this comment.
I think there are pros and cons. Fully deterministic tests are desirable too sometimes.
|
✅ Added second reviewer: @jkczyz |
|
🔔 1st Reminder Hey @jkczyz! This PR has been waiting for your review. |
|
🔔 2nd Reminder Hey @jkczyz! This PR has been waiting for your review. |
It turns out we also switched the key we use to authenticate offers *created* in the 0.2 upgrade and as a result downgrading to 0.2 will break any offers created on 0.2. This wasn't intentional but it doesn't really seem worth fixing at this point, so just document it.
In the coming commits we'll add support for building a blinded path which can be received to any one of several nodes in a "phantom" configuration (terminology we retain from BOLT 11 though there are no longer any phantom nodes in the paths). Here we adda new key in `ExpandedKey` which we can use to authenticate blinded paths as coming from a phantom node participant.
6c4d335 to
7ae896b
Compare
|
Rebased without further changes. |
lightning/src/ln/channelmanager.rs
Outdated
| .iter() | ||
| .filter(|chan| chan.is_usable) | ||
| .filter_map(|chan| chan.short_channel_id) | ||
| .min(), |
There was a problem hiding this comment.
Won't an old channel that is spliced recently seem newer? Also, should we use inbound_scid_alias if set as is done in get_peers_for_blinded_path by using FundedChannel::get_inbound_scid?
| /// | ||
| /// [`ExpandedKey`]: inbound_payment::ExpandedKey | ||
| pub fn create_phantom_offer_builder( | ||
| &$self, other_nodes_channels: Vec<(PublicKey, Vec<ChannelDetails>)>, |
There was a problem hiding this comment.
For BOLT11 phantom, we have the invoice creator pass all the details, even their own. Should we do the same here? More broadly, are we expecting the caller to query all the nodes for the details? Or do we expect them to have some process that periodically does that and they are just looking up the results?
There was a problem hiding this comment.
For BOLT11 phantom, we have the invoice creator pass all the details, even their own. Should we do the same here?
I don't really have a strong opinion. For BOLT 11 we use a loose method not on ChannelManager but here I wanted to use the offers flow for simplicity. We could make it a loose method on the flow object and make the dev pass in all nodes' details or we could do so on ChannelManager, it just felt pretty weird to require someone pass in local details directly to the ChannelManager they just queried.
More broadly, are we expecting the caller to query all the nodes for the details? Or do we expect them to have some process that periodically does that and they are just looking up the results?
Probably the second? as long as its relatively often-refreshed.
There was a problem hiding this comment.
If the second, then it seems the caller would need to fetch them and then filter their own, though? Unless the request says to filter by some node id.
In the next commit we'll add support for building a BOLT 12 offer which can be paid to any one of a number of participant nodes. Here we add support for validating blinded paths as coming from one of the participating nodes by deriving a new key as a part of the `ExpandedKey`. We keep this separate from the existing `ReceiveAuthKey` which is node-specific to ensure that we only allow this key to be used for blinded payment paths and contexts in `invoice_request` messages. This ensures that normal onion messages are still tied to specific nodes. Note that we will not yet use the blinded payment path phantom support which requires additional future work. However, allowing them to be authenticated in a phantom configuration should allow for compatibility across versions once the building logic lands.
In the BOLT 11 world, we have specific support for what we call "phantom nodes" - creating invoices which can be paid to any one of a number of nodes by adding route-hints which represent nodes that do not exist. In BOLT 12, blinded paths make a similar feature much simpler - we can simply add blinded paths which terminate at different nodes. The blinding means that the sender is none the wiser. Here we add logic to fetch an `OfferBuilder` which can generate an offer payable to any one of a set of nodes. We retain the "phantom" terminology even though there are no longer any "phantom" nodes. Note that the current logic only supports the `invoice_request` message going to any of the participating nodes, it then replies with a `Bolt12Invoice` which can only be paid to the responding node. Future work may relax this restriction.
7ae896b to
5a672bd
Compare
In the BOLT 11 world, we have specific support for what we call
"phantom nodes" - creating invoices which can be paid to any one of
a number of nodes by adding route-hints which represent nodes that
do not exist.
In BOLT 12, blinded paths make a similar feature much simpler - we
can simply add blinded paths which terminate at different nodes.
The blinding means that the sender is none the wiser.
Here we add logic to fetch an
OfferBuilderwhich can generate anoffer payable to any one of a set of nodes. We retain the "phantom"
terminology even though there are no longer any "phantom" nodes.
Note that the current logic only supports the
invoice_requestmessage going to any of the participating nodes, it then replies
with a
Bolt12Invoicewhich can only be paid to the respondingnode. Future work may relax this restriction.
Also,
Progress towards full solution for #4313, but enough to get folks started.