From bab957382fc6a1c351340d79cc7cf5fb0ef430ab Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 10 Jan 2019 20:03:07 -0800 Subject: [PATCH] router: convert Route.ToHopPayloads() to Route.ToSphinxPath() In this commit, we update the process that we use to generate a sphinx packet to send our onion routed HTLC. Due to recent changes in the `sphinx` package we use, we now need to use a new PaymentPath struct. As a result, it no longer makes sense to split up the nodes in a route and their per hop paylods as they're now in the same struct. All tests have been updated accordingly. --- routing/pathfind_test.go | 17 +++++---- routing/route/route.go | 80 +++++++++++++++++++++++----------------- routing/router.go | 33 +++++++---------- 3 files changed, 70 insertions(+), 60 deletions(-) diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 3865dc2b0..3a8e18684 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -807,19 +807,22 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc // Next, we'll assert that the "next hop" field in each route payload // properly points to the channel ID that the HTLC should be forwarded // along. - hopPayloads := route.ToHopPayloads() - if len(hopPayloads) != expectedHopCount { + sphinxPath, err := route.ToSphinxPath() + if err != nil { + t.Fatalf("unable to make sphinx path: %v", err) + } + if sphinxPath.TrueRouteLength() != expectedHopCount { t.Fatalf("incorrect number of hop payloads: expected %v, got %v", - expectedHopCount, len(hopPayloads)) + expectedHopCount, sphinxPath.TrueRouteLength()) } // Hops should point to the next hop for i := 0; i < len(expectedHops)-1; i++ { var expectedHop [8]byte binary.BigEndian.PutUint64(expectedHop[:], route.Hops[i+1].ChannelID) - if !bytes.Equal(hopPayloads[i].NextAddress[:], expectedHop[:]) { + if !bytes.Equal(sphinxPath[i].HopData.NextAddress[:], expectedHop[:]) { t.Fatalf("first hop has incorrect next hop: expected %x, got %x", - expectedHop[:], hopPayloads[i].NextAddress) + expectedHop[:], sphinxPath[i].HopData.NextAddress) } } @@ -827,9 +830,9 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc // to indicate it's the exit hop. var exitHop [8]byte lastHopIndex := len(expectedHops) - 1 - if !bytes.Equal(hopPayloads[lastHopIndex].NextAddress[:], exitHop[:]) { + if !bytes.Equal(sphinxPath[lastHopIndex].HopData.NextAddress[:], exitHop[:]) { t.Fatalf("first hop has incorrect next hop: expected %x, got %x", - exitHop[:], hopPayloads[lastHopIndex].NextAddress) + exitHop[:], sphinxPath[lastHopIndex].HopData.NextAddress) } var expectedTotalFee lnwire.MilliSatoshi diff --git a/routing/route/route.go b/routing/route/route.go index fb0a046d8..6aaa4e498 100644 --- a/routing/route/route.go +++ b/routing/route/route.go @@ -104,40 +104,6 @@ func (r *Route) HopFee(hopIndex int) lnwire.MilliSatoshi { return incomingAmt - r.Hops[hopIndex].AmtToForward } -// ToHopPayloads converts a complete route into the series of per-hop payloads -// that is to be encoded within each HTLC using an opaque Sphinx packet. -func (r *Route) ToHopPayloads() []sphinx.HopData { - hopPayloads := make([]sphinx.HopData, len(r.Hops)) - - // For each hop encoded within the route, we'll convert the hop struct - // to the matching per-hop payload struct as used by the sphinx - // package. - for i, hop := range r.Hops { - hopPayloads[i] = sphinx.HopData{ - // TODO(roasbeef): properly set realm, make sphinx type - // an enum actually? - Realm: 0, - ForwardAmount: uint64(hop.AmtToForward), - OutgoingCltv: hop.OutgoingTimeLock, - } - - // As a base case, the next hop is set to all zeroes in order - // to indicate that the "last hop" as no further hops after it. - nextHop := uint64(0) - - // If we aren't on the last hop, then we set the "next address" - // field to be the channel that directly follows it. - if i != len(r.Hops)-1 { - nextHop = r.Hops[i+1].ChannelID - } - - binary.BigEndian.PutUint64(hopPayloads[i].NextAddress[:], - nextHop) - } - - return hopPayloads -} - // NewRouteFromHops creates a new Route structure from the minimally required // information to perform the payment. It infers fee amounts and populates the // node, chan and prev/next hop maps. @@ -163,3 +129,49 @@ func NewRouteFromHops(amtToSend lnwire.MilliSatoshi, timeLock uint32, return route, nil } + +// ToSphinxPath converts a complete route into a sphinx PaymentPath that +// contains the per-hop paylods used to encoding the HTLC routing data for each +// hop in the route. +func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) { + var path sphinx.PaymentPath + + // For each hop encoded within the route, we'll convert the hop struct + // to an OnionHop with matching per-hop payload within the path as used + // by the sphinx package. + for i, hop := range r.Hops { + pub, err := btcec.ParsePubKey( + hop.PubKeyBytes[:], btcec.S256(), + ) + if err != nil { + return nil, err + } + + path[i] = sphinx.OnionHop{ + NodePub: *pub, + HopData: sphinx.HopData{ + // TODO(roasbeef): properly set realm, make + // sphinx type an enum actually? + Realm: [1]byte{0}, + ForwardAmount: uint64(hop.AmtToForward), + OutgoingCltv: hop.OutgoingTimeLock, + }, + } + + // As a base case, the next hop is set to all zeroes in order + // to indicate that the "last hop" as no further hops after it. + nextHop := uint64(0) + + // If we aren't on the last hop, then we set the "next address" + // field to be the channel that directly follows it. + if i != len(r.Hops)-1 { + nextHop = r.Hops[i+1].ChannelID + } + + binary.BigEndian.PutUint64( + path[i].HopData.NextAddress[:], nextHop, + ) + } + + return &path, nil +} diff --git a/routing/router.go b/routing/router.go index 3afb8c5ec..de393212c 100644 --- a/routing/router.go +++ b/routing/router.go @@ -1467,30 +1467,25 @@ func generateSphinxPacket(rt *route.Route, paymentHash []byte) ([]byte, return nil, nil, route.ErrNoRouteHopsProvided } - // First obtain all the public keys along the route which are contained - // in each hop. - nodes := make([]*btcec.PublicKey, len(rt.Hops)) - for i, hop := range rt.Hops { - pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:], - btcec.S256()) - if err != nil { - return nil, nil, err - } - - nodes[i] = pub + // Now that we know we have an actual route, we'll map the route into a + // sphinx payument path which includes per-hop paylods for each hop + // that give each node within the route the necessary information + // (fees, CLTV value, etc) to properly forward the payment. + sphinxPath, err := rt.ToSphinxPath() + if err != nil { + return nil, nil, err } - // Next we generate the per-hop payload which gives each node within - // the route the necessary information (fees, CLTV value, etc) to - // properly forward the payment. - hopPayloads := rt.ToHopPayloads() - log.Tracef("Constructed per-hop payloads for payment_hash=%x: %v", paymentHash[:], newLogClosure(func() string { - return spew.Sdump(hopPayloads) + return spew.Sdump(sphinxPath[:sphinxPath.TrueRouteLength()]) }), ) + // Generate a new random session key to ensure that we don't trigger + // any replay. + // + // TODO(roasbeef): add more sources of randomness? sessionKey, err := btcec.NewPrivateKey(btcec.S256()) if err != nil { return nil, nil, err @@ -1499,7 +1494,7 @@ func generateSphinxPacket(rt *route.Route, paymentHash []byte) ([]byte, // Next generate the onion routing packet which allows us to perform // privacy preserving source routing across the network. sphinxPacket, err := sphinx.NewOnionPacket( - nodes, sessionKey, hopPayloads, paymentHash, + sphinxPath, sessionKey, paymentHash, ) if err != nil { return nil, nil, err @@ -1523,7 +1518,7 @@ func generateSphinxPacket(rt *route.Route, paymentHash []byte) ([]byte, return onionBlob.Bytes(), &sphinx.Circuit{ SessionKey: sessionKey, - PaymentPath: nodes, + PaymentPath: sphinxPath.NodeKeys(), }, nil }