Merge pull request #3027 from Roasbeef/new-onion-structs

router+build: update to the latest version of lightning-onion
This commit is contained in:
Olaoluwa Osuntokun 2019-05-03 13:30:41 -07:00 committed by GitHub
commit a8fa4094ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 245 additions and 90 deletions

3
go.mod

@ -5,7 +5,6 @@ require (
github.com/NebulousLabs/fastrand v0.0.0-20180208210444-3cf7173006a0 // indirect
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/btcsuite/btcd v0.0.0-20190426011420-63f50db2f70a
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
@ -29,7 +28,7 @@ require (
github.com/juju/version v0.0.0-20180108022336-b64dbd566305 // indirect
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec
github.com/lightninglabs/neutrino v0.0.0-20190426010803-a655679fe131
github.com/lightningnetwork/lightning-onion v0.0.0-20180605012408-ac4d9da8f1d6
github.com/lightningnetwork/lightning-onion v0.0.0-20190430041606-751fb4dd8b72
github.com/lightningnetwork/lnd/queue v1.0.1
github.com/lightningnetwork/lnd/ticker v1.0.0
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796

7
go.sum

@ -14,6 +14,7 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBA
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/btcsuite/btcd v0.0.0-20180823030728-d81d8877b8f3/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ=
github.com/btcsuite/btcd v0.0.0-20181130015935-7d2daa5bfef2/go.mod h1:Jr9bmNVGZ7TH2Ux1QuP0ec+yGgh0gE9FIlkzQiI5bR0=
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32 h1:qkOC5Gd33k54tobS36cXdAzJbeHaduLtnLQQwNoIi78=
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8=
github.com/btcsuite/btcd v0.0.0-20190426011420-63f50db2f70a h1:wH5Nq2kt+BdVJhVyYZD54lHQpz95mog0Z7r1h638TY4=
@ -110,8 +111,8 @@ github.com/lightninglabs/neutrino v0.0.0-20190313035638-e1ad4c33fb18 h1:lxD7RgKY
github.com/lightninglabs/neutrino v0.0.0-20190313035638-e1ad4c33fb18/go.mod h1:v6tz6jbuAubTrRpX8ke2KH9sJxml8KlPQTKgo9mAp1Q=
github.com/lightninglabs/neutrino v0.0.0-20190426010803-a655679fe131 h1:1qKraSAbJFxd2BUHrxFEswNRav749pt4P37Ez8avbAA=
github.com/lightninglabs/neutrino v0.0.0-20190426010803-a655679fe131/go.mod h1:/XWY/6/btfsknUpLPV8vvIZyhod61zYaUJiE8HxsFUs=
github.com/lightningnetwork/lightning-onion v0.0.0-20180605012408-ac4d9da8f1d6 h1:ONLGrYJVQdbtP6CE/ff1KNWZtygRGEh12RzonTiCzPs=
github.com/lightningnetwork/lightning-onion v0.0.0-20180605012408-ac4d9da8f1d6/go.mod h1:8EgEt4a/NUOVQd+3kk6n9aZCJ1Ssj96Pb6lCrci+6oc=
github.com/lightningnetwork/lightning-onion v0.0.0-20190430041606-751fb4dd8b72 h1:KgmypyQfJnEf2vhwboKCtTp4mHxIcLeXPBPWDbPuzFQ=
github.com/lightningnetwork/lightning-onion v0.0.0-20190430041606-751fb4dd8b72/go.mod h1:Sooe/CoCqa85JxqHV+IBR2HW+6t2Cv+36awSmoccswM=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
@ -134,6 +135,7 @@ go.etcd.io/bbolt v1.3.0/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -156,6 +158,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20180821140842-3b58ed4ad339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503 h1:5SvYFrOM3W8Mexn9/oA44Ji7vhXAZQ9hiP+1Q/DMrWg=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=

@ -87,6 +87,13 @@ type ErrorEncrypter interface {
// slightly, in that it computes a proper MAC over the error.
EncryptFirstHop(lnwire.FailureMessage) (lnwire.OpaqueReason, error)
// EncryptMalformedError is similar to EncryptFirstHop (it adds the
// MAC), but it accepts an opaque failure reason rather than a failure
// message. This method is used when we receive an
// UpdateFailMalformedHTLC from the remote peer and then need to
// convert that into a proper error from only the raw bytes.
EncryptMalformedError(lnwire.OpaqueReason) lnwire.OpaqueReason
// IntermediateEncrypt wraps an already encrypted opaque reason error
// in an additional layer of onion encryption. This process repeats
// until the error arrives at the source of the payment.
@ -153,6 +160,17 @@ func (s *SphinxErrorEncrypter) EncryptFirstHop(failure lnwire.FailureMessage) (l
return s.EncryptError(true, b.Bytes()), nil
}
// EncryptMalformedError is similar to EncryptFirstHop (it adds the MAC), but
// it accepts an opaque failure reason rather than a failure message. This
// method is used when we receive an UpdateFailMalformedHTLC from the remote
// peer and then need to convert that into an proper error from only the raw
// bytes.
//
// NOTE: Part of the ErrorEncrypter interface.
func (s *SphinxErrorEncrypter) EncryptMalformedError(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
return s.EncryptError(true, reason)
}
// IntermediateEncrypt wraps an already encrypted opaque reason error in an
// additional layer of onion encryption. This process repeats until the error
// arrives at the source of the payment. We re-encrypt the message on the

@ -2404,10 +2404,24 @@ func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg,
outgoingHTLCID: pd.ParentIndex,
destRef: pd.DestRef,
htlc: &lnwire.UpdateFailHTLC{
Reason: lnwire.OpaqueReason(pd.FailReason),
Reason: lnwire.OpaqueReason(
pd.FailReason,
),
},
}
// If the failure message lacks an HMAC (but includes
// the 4 bytes for encoding the message and padding
// lengths, then this means that we received it as an
// UpdateFailMalformedHTLC. As a result, we'll signal
// that we need to convert this error within the switch
// to an actual error, by encrypting it as if we were
// the originating hop.
convertedErrorSize := lnwire.FailureMessageLength + 4
if len(pd.FailReason) == convertedErrorSize {
failPacket.convertedError = true
}
// Add the packet to the batch to be forwarded, and
// notify the overflow queue that a spare spot has been
// freed up within the commitment state.

@ -368,7 +368,10 @@ func (o *mockObfuscator) EncryptFirstHop(failure lnwire.FailureMessage) (
func (o *mockObfuscator) IntermediateEncrypt(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
return reason
}
func (o *mockObfuscator) EncryptMalformedError(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
return reason
}
// mockDeobfuscator mock implementation of the failure deobfuscator which
@ -400,6 +403,8 @@ type mockIteratorDecoder struct {
mu sync.RWMutex
responses map[[32]byte][]DecodeHopIteratorResponse
decodeFail bool
}
func newMockIteratorDecoder() *mockIteratorDecoder {
@ -451,6 +456,10 @@ func (p *mockIteratorDecoder) DecodeHopIterators(id []byte,
req.OnionReader, req.RHash, req.IncomingCltv,
)
if p.decodeFail {
failcode = lnwire.CodeTemporaryChannelFailure
}
resp := DecodeHopIteratorResponse{
HopIterator: iterator,
FailCode: failcode,

@ -61,6 +61,14 @@ type htlcPacket struct {
// encrypted with any shared secret.
localFailure bool
// convertedError is set to true if this is an HTLC fail that was
// created using an UpdateFailMalformedHTLC from the remote party. If
// this is true, then when forwarding this failure packet, we'll need
// to wrap it as if we were the first hop if it's a multi-hop HTLC. If
// it's a direct HTLC, then we'll decode the error as no encryption has
// taken place.
convertedError bool
// hasSource is set to true if the incomingChanID and incomingHTLCID
// fields of a forwarded fail packet are already set and do not need to
// be looked up in the circuit map.

@ -956,7 +956,7 @@ func (s *Switch) parseFailedPayment(payment *pendingPayment, pkt *htlcPacket,
// The payment never cleared the link, so we don't need to
// decrypt the error, simply decode it them report back to the
// user.
case pkt.localFailure:
case pkt.localFailure || pkt.convertedError:
var userErr string
r := bytes.NewReader(htlc.Reason)
failureMsg, err := lnwire.DecodeFailure(r, 0)
@ -1172,13 +1172,12 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
fail, isFail := htlc.(*lnwire.UpdateFailHTLC)
if isFail && !packet.hasSource {
switch {
// No message to encrypt, locally sourced payment.
case circuit.ErrorEncrypter == nil:
// No message to encrypt, locally sourced
// payment.
// If this is a resolution message, then we'll need to
// encrypt it as it's actually internally sourced.
case packet.isResolution:
// If this is a resolution message, then we'll need to encrypt
// it as it's actually internally sourced.
var err error
// TODO(roasbeef): don't need to pass actually?
failure := &lnwire.FailPermanentChannelFailure{}
@ -1191,6 +1190,25 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
log.Error(err)
}
// Alternatively, if the remote party send us an
// UpdateFailMalformedHTLC, then we'll need to convert
// this into a proper well formatted onion error as
// there's no HMAC currently.
case packet.convertedError:
log.Infof("Converting malformed HTLC error "+
"for circuit for Circuit(%x: "+
"(%s, %d) <-> (%s, %d))", packet.circuit.PaymentHash,
packet.incomingChanID, packet.incomingHTLCID,
packet.outgoingChanID, packet.outgoingHTLCID)
fail.Reason = circuit.ErrorEncrypter.EncryptMalformedError(
fail.Reason,
)
if err != nil {
err = fmt.Errorf("unable to obfuscate "+
"error: %v", err)
log.Error(err)
}
default:
// Otherwise, it's a forwarded error, so we'll perform a
// wrapper encryption as normal.

@ -2049,3 +2049,76 @@ func TestMultiHopPaymentForwardingEvents(t *testing.T) {
}
}
}
// TestUpdateFailMalformedHTLCErrorConversion tests that we're able to properly
// convert malformed HTLC errors that originate at the direct link, as well as
// during multi-hop HTLC forwarding.
func TestUpdateFailMalformedHTLCErrorConversion(t *testing.T) {
t.Parallel()
// First, we'll create our traditional three hop network.
channels, cleanUp, _, err := createClusterChannels(
btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5,
)
if err != nil {
t.Fatalf("unable to create channel: %v", err)
}
defer cleanUp()
n := newThreeHopNetwork(
t, channels.aliceToBob, channels.bobToAlice,
channels.bobToCarol, channels.carolToBob, testStartingHeight,
)
if err := n.start(); err != nil {
t.Fatalf("unable to start three hop network: %v", err)
}
assertPaymentFailure := func(t *testing.T) {
// With the decoder modified, we'll now attempt to send a
// payment from Alice to carol.
finalAmt := lnwire.NewMSatFromSatoshis(100000)
htlcAmt, totalTimelock, hops := generateHops(
finalAmt, testStartingHeight, n.firstBobChannelLink,
n.carolChannelLink,
)
firstHop := n.firstBobChannelLink.ShortChanID()
_, err = makePayment(
n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
htlcAmt, totalTimelock,
).Wait(30 * time.Second)
// The payment should fail as Carol is unable to decode the
// onion blob sent to her.
if err == nil {
t.Fatalf("unable to send payment: %v", err)
}
fwdingErr := err.(*ForwardingError)
failureMsg := fwdingErr.FailureMessage
if _, ok := failureMsg.(*lnwire.FailTemporaryChannelFailure); !ok {
t.Fatalf("expected temp chan failure instead got: %v",
fwdingErr.FailureMessage)
}
}
t.Run("multi-hop error conversion", func(t *testing.T) {
// Now that we have our network up, we'll modify the hop
// iterator for the Bob <-> Carol channel to fail to decode in
// order to simulate either a replay attack or an issue
// decoding the onion.
n.carolOnionDecoder.decodeFail = true
assertPaymentFailure(t)
})
t.Run("direct channel error conversion", func(t *testing.T) {
// Similar to the above test case, we'll now make the Alice <->
// Bob link always fail to decode an onion. This differs from
// the above test case in that there's no encryption on the
// error at all since Alice will directly receive a
// UpdateFailMalformedHTLC message.
n.bobOnionDecoder.decodeFail = true
assertPaymentFailure(t)
})
}

@ -589,15 +589,18 @@ func generateRoute(hops ...ForwardingInfo) ([lnwire.OnionPacketSize]byte, error)
// threeHopNetwork is used for managing the created cluster of 3 hops.
type threeHopNetwork struct {
aliceServer *mockServer
aliceChannelLink *channelLink
aliceServer *mockServer
aliceChannelLink *channelLink
aliceOnionDecoder *mockIteratorDecoder
bobServer *mockServer
firstBobChannelLink *channelLink
secondBobChannelLink *channelLink
bobOnionDecoder *mockIteratorDecoder
carolServer *mockServer
carolChannelLink *channelLink
carolServer *mockServer
carolChannelLink *channelLink
carolOnionDecoder *mockIteratorDecoder
hopNetwork
}
@ -948,15 +951,18 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
}
return &threeHopNetwork{
aliceServer: aliceServer,
aliceChannelLink: aliceChannelLink.(*channelLink),
aliceServer: aliceServer,
aliceChannelLink: aliceChannelLink.(*channelLink),
aliceOnionDecoder: aliceDecoder,
bobServer: bobServer,
firstBobChannelLink: firstBobChannelLink.(*channelLink),
secondBobChannelLink: secondBobChannelLink.(*channelLink),
bobOnionDecoder: bobDecoder,
carolServer: carolServer,
carolChannelLink: carolChannelLink.(*channelLink),
carolServer: carolServer,
carolChannelLink: carolChannelLink.(*channelLink),
carolOnionDecoder: carolDecoder,
hopNetwork: *hopNetwork,
}

@ -3522,13 +3522,10 @@ func testSphinxReplayPersistence(net *lntest.NetworkHarness, t *harnessTest) {
// Construct the response we expect after sending a duplicate packet
// that fails due to sphinx replay detection.
replayErr := fmt.Sprintf("unable to route payment to destination: "+
"TemporaryChannelFailure: unable to de-obfuscate onion failure, "+
"htlc with hash(%x): unable to retrieve onion failure",
invoiceResp.RHash)
if resp.PaymentError != replayErr {
t.Fatalf("received payment error: %v", resp.PaymentError)
replayErr := "TemporaryChannelFailure"
if !strings.Contains(resp.PaymentError, replayErr) {
t.Fatalf("received payment error: %v, expected %v",
resp.PaymentError, replayErr)
}
// Since the payment failed, the balance should still be left

@ -26,9 +26,9 @@ type FailureMessage interface {
Error() string
}
// failureMessageLength is the size of the failure message plus the size of
// FailureMessageLength is the size of the failure message plus the size of
// padding. The FailureMessage message should always be EXACTLY this size.
const failureMessageLength = 256
const FailureMessageLength = 256
const (
// FlagBadOnion error flag describes an unparsable, encrypted by
@ -1101,7 +1101,7 @@ func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
if err := ReadElement(r, &failureLength); err != nil {
return nil, fmt.Errorf("unable to read error len: %v", err)
}
if failureLength > failureMessageLength {
if failureLength > FailureMessageLength {
return nil, fmt.Errorf("failure message is too "+
"long: %v", failureLength)
}
@ -1170,14 +1170,14 @@ func EncodeFailure(w io.Writer, failure FailureMessage, pver uint32) error {
// The combined size of this message must be below the max allowed
// failure message length.
failureMessage := failureMessageBuffer.Bytes()
if len(failureMessage) > failureMessageLength {
if len(failureMessage) > FailureMessageLength {
return fmt.Errorf("failure message exceed max "+
"available size: %v", len(failureMessage))
}
// Finally, we'll add some padding in order to ensure that all failure
// messages are fixed size.
pad := make([]byte, failureMessageLength-len(failureMessage))
pad := make([]byte, FailureMessageLength-len(failureMessage))
return WriteElements(w,
uint16(len(failureMessage)),

@ -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

@ -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
}

@ -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
}