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

lightning-onion: route blinding implementation PR review notes #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

calvinrzachman
Copy link
Owner

Rather than clutter the actual PR with comments for the sake of my own learning, leave the comments on this fork instead.

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.
@calvinrzachman calvinrzachman self-assigned this Jul 21, 2022
Copy link
Owner Author

@calvinrzachman calvinrzachman left a 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
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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)
Copy link
Owner Author

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)
Copy link
Owner Author

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)
Copy link
Owner Author

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)
Copy link
Owner Author

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)
Copy link
Owner Author

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,
Copy link
Owner Author

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,
Copy link
Owner Author

@calvinrzachman calvinrzachman Jul 21, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants