-
Notifications
You must be signed in to change notification settings - Fork 0
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
lightning-onion: route blinding implementation PR review notes #1
base: master
Are you sure you want to change the base?
Conversation
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.
In this commit, some route blinding helper methods are added, namely `BlindBlindedRoute` which the creator of a blinded route will be able to use to construct a blinded route and `DecryptBlindedData` which a hop in the blinded route will be able to use to decrypt data encrypted for it by the constructor of the blinded route. Test vectors from the spec proposal are also added.
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.
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 nextBlindedKey command.
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.
PR Review Notes (7/18/22)
@@ -209,17 +238,47 @@ type sharedSecretGenerator interface { | |||
generateSharedSecret(dhKey *btcec.PublicKey) (Hash256, error) | |||
} | |||
|
|||
// generateSharedSecret generates the shared secret by given ephemeral key. | |||
func (r *Router) generateSharedSecret(dhKey *btcec.PublicKey) (Hash256, error) { | |||
// generateSharedSecret generates the shared secret by given |
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.
// generateSharedSecret generates the shared secret by given | |
// generateSharedSecret generates a shared secret between | |
// processing node and onion encryptor/sphinx packet constructor. | |
// This shared secret is used to decrypt the onion. |
// If no blinding point is provided, then the un-tweaked dhKey can | ||
// be used to derive the shared secret | ||
if blindingPoint == nil { | ||
return sharedSecret(r.onionKey, dhKey) |
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.
NOTE: This is our usual sssender/node = ECDH(k(i)
, Esender) = k*Esender = k*(esender*G) = esender*(k*G) = esender*N = ECDH(esender, N(i)
)
// receiver used with us so that we can use it to tweak our priv key. | ||
// The sender would have created their shared secret with our blinded | ||
// pub key. | ||
ssReceiver, err := sharedSecret(r.onionKey, blindingPoint) |
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.
Establish shared secret, ssblinded-path-builder/node, between processing node in blinded portion of route and the node which built the blinded route (usually recipient).
NOTE: This is ECDH(k(i)
, Erecipient) = k*Erecipient = k*(erecipient*G) = erecipient*(k*G) = erecipient*N
return Hash256{}, err | ||
} | ||
|
||
blindingFactorBytes := generateKey(routeBlindingHMACKey, &ssReceiver) |
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.
NOTE: This is blinding_factor
= HMAC-256("blinded_node_id", ssblinded-path-builder/node)
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 compute the blinded private key, b(i)
, we follow this section of the route blinding proposal document. This is ephemeral
= blinding_factor
*Esender
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.
It is a bit harder to follow because there is no place where an ECDH(b(i)
, Esender) is done explicitly. Rather we have ECDH(k(i)
, blinding_factor
*Esender). An alternative implementation might be:
// TODO: would need to define scalarMulModN()
blindedPrivateKey := scalarMulModN(r.onionKey, blindingFactor)
// Establish shared secret between blinded routing node and sender.
sharedSecret(blindedPrivateKey, dhKey)
Either method 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.
@@ -530,11 +530,14 @@ func (r *Router) Stop() { | |||
// In the case of a successful packet processing, and ProcessedPacket struct is | |||
// returned which houses the newly parsed packet, along with instructions on | |||
// what to do next. | |||
func (r *Router) ProcessOnionPacket(onionPkt *OnionPacket, | |||
assocData []byte, incomingCltv uint32) (*ProcessedPacket, error) { | |||
func (r *Router) ProcessOnionPacket(onionPkt *OnionPacket, assocData []byte, |
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.
I like how we absolve the caller from having to compute any blinded ID key and keep that computation confined to the internals of this package. This keeps the top level API nice and clean:
ProcessOnionPacket(pkt, ..., nil)
ProcessOnionPacket(pkt, ..., ephemeralBlindingPoint)
From a caller's perspective it calls the same function and includes the optional blinding point if it finds one in UpdateAddHTLC
! This keeps the lnd
diff smaller!
|
||
// NextEphemeral computes the next ephemeral key given the current ephemeral | ||
// key and this node's private key. | ||
func NextEphemeral(privKey SingleKeyECDH, |
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 checks out with our implementation. TODO: remove before submitting review.
Rather than clutter the actual PR with comments for the sake of my own learning, leave the comments on this fork instead.