-
Notifications
You must be signed in to change notification settings - Fork 127
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
multi: Route blinding #57
Conversation
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.
very high level pass, just submitting in case GH loses my comments
Also a meta thing that I noticed while using this PR. When this is merged I think that it would make sense to make two tags: one on the blinded route commit, and one on the final merge/full PR. Reason being that code that imports this change and lnd requires lnd to be upgraded to use the new arg in |
e552258
to
91a33c8
Compare
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.
Very cool PR. Have not tested it yet and want to compare it to other implementations.
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.
This PR makes really good use of the existing functionality, nicely leveraging the similarities between route blinding and normal sphinx processing. Props for not getting lost in the “blinding” sauce (route blinding, blinding factor, blinding point, etc.)! I also like how the implementation shields any complexity of the route blinding protocol from callers. They just proceed like normal and leave the nuances of decrypting onions in a blinded route to the library. I think the effort there will help with integrating this into the Channel Link.
A few of my comments are notes. Let me know if our understandings match and feel free to resolve them so they don’t add clutter! My favorite PR - only to be superseded by the PR which adds this to LND :) Thanks for working on this!
crypto.go
Outdated
var blindingFactor btcec.ModNScalar | ||
blindingFactor.SetBytes(&blindingFactorBytes) | ||
|
||
ephemeral := blindGroupElement(dhKey, blindingFactor) |
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.
Subtle Note: Rather than directly computing the blinded private key, b(i)
, we follow this section of the route blinding proposal document. This is ephemeral
= blinding_factor
*Esender
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.
yes indeed :) i have added a comment to point this out
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.
Nit: I haven't seen this definition in the spec, especially the context you are usingblinding_factor
here @calvinrzachman ?
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.
Apologies for any confusion, Ziggie. It's been a while since I've looked at this, but I'm pretty sure that when I say blinding_factor
here I am referring to Elle's variable blindingFactor := HMAC256("blinded_node_id", ss(i))
. Which you are correct does differ from how the term is used in the spec document. Brought that up with Elle briefly here
Also, looks like my link may have gotten rug pulled by a change to t-bast's underlying spec PR. I usually pick an older, non-master release or tag so that links point to the expected line, but I don't know how to do that for an actively changing PR :/ In any case, the section this used to link to is now here.
crypto.go
Outdated
blindingFactor.SetBytes(&blindingFactorBytes) | ||
|
||
ephemeral := blindGroupElement(dhKey, blindingFactor) | ||
return sharedSecret(r.onionKey, ephemeral) |
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.
This step mixes the blinding factor and node ID private key, k(i)
, implicitly computing the blinded node ID private key, b(i)
, and is equivalent to using b(i)
in an ECDH with the sender's usual ephemeral key from the onion, Esender.
ECDH(b(i)
, Esender) is not done explicitly. Rather we have ECDH(k(i)
, blinding_factor
*Esender), which correctly returns a shared secret between onion encryptor and routing node:
sssender/node = k*blinding_factor
*Esender = (blinding_factor
*k)*Esender = b(i)
*Esender = ECDH(b(i)
, Esender)
From here the caller is free to use rho
= HMAC-256("rho", sssender/node) to decrypt the onion like normal.
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.
In the line for ss_sender/node
you're missing hash functions, thanks for the comment, this helped me 🙏.
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.
Thanks so much, very useful comment, we need this definitely in the codebase too.
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.
In the line for ss_sender/node you're missing hash functions, ...
You are quite right. Glad the message still came through!
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.
cool - added some more comments to help describe this
91a33c8
to
462661d
Compare
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.
Thanks for the reviews y'all!! updated 🔥
crypto.go
Outdated
var blindingFactor btcec.ModNScalar | ||
blindingFactor.SetBytes(&blindingFactorBytes) | ||
|
||
ephemeral := blindGroupElement(dhKey, blindingFactor) |
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.
yes indeed :) i have added a comment to point this out
making a note here for myself to update the PR to point to the latest state of the spec PR & its test vectors |
462661d
to
d06c852
Compare
ah ok, looks like the updated test vectors still pass with the existing code. I think the main changes were related to the tlv fields/types which does not affect this pr since pr assumes the tlvs are already encoded. Will double check this later though |
@ellemouton, remember to re-request review from reviewers when ready |
!lightninglabs-deploy mute |
d06c852
to
654a7f6
Compare
654a7f6
to
4bf61b8
Compare
4bf61b8
to
4bf26cb
Compare
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! Just needs rebase on ca23184 - when I import this to LND now some tests fail.
I've used this for onion messagaing and have interop with CL + LDK, and everything works as advertized in a WIP branch for forwarding blinded routes for LND.
Rebase-beg when you have a minute! |
4bf26cb
to
d0b521d
Compare
done! cc @carlaKC |
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.
Cool work, this looks really nice 👍. Looking forward to explore the usage of this fundamental implementation 🎉
f3212d5
to
f050833
Compare
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.
Impressive work ⚡️ :)
Took me a a bit to go through and understand the big picture here 😅, especially the blinding variables where tricky for me, so I left some question regarding the whole topic, apart from that I had only some nits.
Keep up the very good work 🚀
blinding.go
Outdated
BlindedHops []*BlindedHopInfo | ||
} | ||
|
||
// hopInfo represents a single hop in a blinded path. The BlindedHopInfo wrapper |
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.
Q: should this note be the other way around, shouldn't we use the BlindedHopInfo wrapper for hops with the blinded nodeId?
blinding.go
Outdated
// point that the introduction node will use in order to create a shared | ||
// secret with the builder of the blinded route. This point will need | ||
// to be communicated to the introduction node by the sender in some | ||
// way. |
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.
Q: What are the ways how this introduction point could be delivered to the First node in the blinded path? Do you refer in this comment to concatenate blinded paths for example ?
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.
What are the ways how this introduction point could be delivered to the First node in the blinded path?
it will be communicated via update_add_htlc
Do you refer in this comment to concatenate blinded paths for example ?
not sure what you mean?
crypto.go
Outdated
var blindingFactor btcec.ModNScalar | ||
blindingFactor.SetBytes(&blindingFactorBytes) | ||
|
||
ephemeral := blindGroupElement(dhKey, blindingFactor) |
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.
Nit: I haven't seen this definition in the spec, especially the context you are usingblinding_factor
here @calvinrzachman ?
crypto.go
Outdated
blindingFactor.SetBytes(&blindingFactorBytes) | ||
|
||
ephemeral := blindGroupElement(dhKey, blindingFactor) | ||
return sharedSecret(r.onionKey, ephemeral) |
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.
Thanks so much, very useful comment, we need this definitely in the codebase too.
blinding.go
Outdated
@@ -0,0 +1,169 @@ | |||
package sphinx |
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.
Nit: Update Commit BlindBlindedRoute => BuildBlindedRoute
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.
DecryptBlindedData
would need an update as well
blinding.go
Outdated
return nil, err | ||
} | ||
|
||
blindingFactor := computeBlindingFactor(ephemPub, ss[:]) |
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, so we have blindingFactor
but also blindingFactorBytes
(in this PR). The first one adheres with the definition in the specification the other one not. I agree both are blinding scalars but this confused me, maybe adding some comments where we diverge ?
blinding_test.go
Outdated
pathED, err := BuildBlindedPath(eveSessKey, eveDavePath) | ||
require.NoError(t, err) | ||
|
||
// At this point, Eve will give her blinded path to Bob who will then |
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.
Q: I do not quite understand what sense it makes for Bob to use a blinded Path to Carol when it's only one Hop from Bob => Carol, does it make sense in the real world?
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.
no it probably doesnt make sense in the real world. This is just to mimic the scenario in the spec test vector
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 🔥 🚀 again, very nice work!
blinding.go
Outdated
@@ -0,0 +1,169 @@ | |||
package sphinx |
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.
DecryptBlindedData
would need an update as well
f050833
to
531080e
Compare
Update the TestVariablePayloadOnion test to make use of the `require` package.
Add a TestTLVPayloadOnion test that tests the newer spec test vector which tests the construction of an onion in the case where all the hops are using the TLV version payload.
This commit updates the cli tool to use the urfave library. This makes things easier to read and will make it easier to extend the tool in future. A `genKeys` function is also added as a helper for quickly generating key pairs.
This commit is a pure refactor and move commit. It moves the HopPayload methods out from the `path.go` file and into a dedicated `payload.go` file in order to prepare for a follow up commit that will add a blinded path struct to the `path.go` file. The opportunity is also taken to separate out the tlv vs legacy encoding of the hop payload a bit (seprate constructors for example) in order to prepare for a future where the legacy encoding will be removed.
531080e
to
5a8aff7
Compare
crypto.go
Outdated
} | ||
|
||
// Encrypt the message and append the ciphertext to the nonce. | ||
return aead.Seal(plainTxt[:0], chaChaPolyZeroNonce, plainTxt, nil), nil |
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.
Is plainTxt
actually always appropriately sized? IIUC this is only useful when you have a buffer that is already pre-allocated, as then you end up with plaintext || ciphertext
after the routine ends. In that case, you don't even need the return value though.
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.
the comment for Seal
says:
// To reuse plaintext's storage for the encrypted output, use plaintext[:0]
// as dst. Otherwise, the remaining capacity of dst must not overlap plaintext.
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.
so kinda just went with that suggestion
This commit introduces a new `BlindedPath` struct which holds all the information defining a blinded route. The commit also includes the addition of various helper methods to blind and unblind node IDs and hop data. Test vectors from the spec are also added.
5a8aff7
to
e8adea9
Compare
In this commit, ProcessOnionPacket is updated to take in a blinding key as a parameter. This key is then used in order to determine the blinding factor necessary to decrypt the onion. This change is accompanied with a test that tests this change against a test vector from the route blinding spec PR.
In this commit, DecryptBlindHopData and NextEphemeral methods are added to Router which makes use of the Router's onion key in order to unblind blinded data and compute the next ephermal key respectively.
Add a blinded-key option to the parse command so that it can be used to test parsing of an onion for hops in a blinded route. Also add a helper nextephemeral command.
e8adea9
to
f971c75
Compare
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.
Thanks for the review @Roasbeef 🙏 ready for another round!
crypto.go
Outdated
} | ||
|
||
// Encrypt the message and append the ciphertext to the nonce. | ||
return aead.Seal(plainTxt[:0], chaChaPolyZeroNonce, plainTxt, nil), nil |
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.
the comment for Seal
says:
// To reuse plaintext's storage for the encrypted output, use plaintext[:0]
// as dst. Otherwise, the remaining capacity of dst must not overlap plaintext.
crypto.go
Outdated
} | ||
|
||
// Encrypt the message and append the ciphertext to the nonce. | ||
return aead.Seal(plainTxt[:0], chaChaPolyZeroNonce, plainTxt, nil), nil |
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.
so kinda just went with that suggestion
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 ⛷️
This PR adds the changes that higher layers will need in order to take advantage of route blinding as defined in the spec proposal
Note that callers of this code will need to know how to extract the
next_blinding_point
(if the caller is the introduction node in the route) andnext_blinding_override
points from the onion packet payloads.