diff --git a/htlcswitch/hop/payload.go b/htlcswitch/hop/payload.go index d8aafd9ca0b..b72ffbf0432 100644 --- a/htlcswitch/hop/payload.go +++ b/htlcswitch/hop/payload.go @@ -5,6 +5,8 @@ import ( "fmt" "io" + "github.com/lightningnetwork/lnd/routing/route" + sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" @@ -89,6 +91,8 @@ type Payload struct { // customRecords are user-defined records in the custom type range that // were included in the payload. customRecords record.CustomSet + + NextHopPubKey *route.Vertex } // NewLegacyPayload builds a Payload from the amount, cltv, and next hop @@ -111,10 +115,11 @@ func NewLegacyPayload(f *sphinx.HopData) *Payload { // should correspond to the bytes encapsulated in a TLV onion payload. func NewPayloadFromReader(r io.Reader) (*Payload, error) { var ( - cid uint64 - amt uint64 - cltv uint32 - mpp = &record.MPP{} + cid uint64 + amt uint64 + cltv uint32 + mpp = &record.MPP{} + nextHopPubKey [33]byte ) tlvStream, err := tlv.NewStream( @@ -122,6 +127,7 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) { record.NewLockTimeRecord(&cltv), record.NewNextHopIDRecord(&cid), mpp.Record(), + record.NewNextHopPubKeyRecord(&nextHopPubKey), ) if err != nil { return nil, err @@ -159,6 +165,12 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) { // Filter out the custom records. customRecords := NewCustomRecords(parsedTypes) + var pubkey *route.Vertex + if _, ok := parsedTypes[record.NextHopPubKeyType]; ok { + t := route.Vertex(nextHopPubKey) + pubkey = &t + } + return &Payload{ FwdInfo: ForwardingInfo{ Network: BitcoinNetwork, @@ -168,6 +180,7 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) { }, MPP: mpp, customRecords: customRecords, + NextHopPubKey: pubkey, }, nil } diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 974e3cc196e..b5d00090770 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -2682,8 +2682,9 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, fwdInfo := pld.ForwardingInfo() - switch fwdInfo.NextHop { - case hop.Exit: + isExit := fwdInfo.NextHop == hop.Exit && pld.NextHopPubKey == nil + switch isExit { + case true: err := l.processExitHop( pd, obfuscator, fwdInfo, heightNow, pld, ) @@ -2748,6 +2749,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, incomingTimeout: pd.Timeout, outgoingTimeout: fwdInfo.OutgoingCTLV, customRecords: pld.CustomRecords(), + nextHopPubKey: pld.NextHopPubKey, } switchPackets = append( switchPackets, updatePacket, @@ -2812,6 +2814,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, incomingTimeout: pd.Timeout, outgoingTimeout: fwdInfo.OutgoingCTLV, customRecords: pld.CustomRecords(), + nextHopPubKey: pld.NextHopPubKey, } fwdPkg.FwdFilter.Set(idx) diff --git a/htlcswitch/packet.go b/htlcswitch/packet.go index 78c3af8e231..bbceaca694c 100644 --- a/htlcswitch/packet.go +++ b/htlcswitch/packet.go @@ -5,6 +5,7 @@ import ( "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" + "github.com/lightningnetwork/lnd/routing/route" ) // htlcPacket is a wrapper around htlc lnwire update, which adds additional @@ -96,6 +97,8 @@ type htlcPacket struct { // customRecords are user-defined records in the custom type range that // were included in the payload. customRecords record.CustomSet + + nextHopPubKey *route.Vertex } // inKey returns the circuit key used to identify the incoming htlc. diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index 1f755bb074c..0de98b25223 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -993,23 +993,32 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { } s.indexMtx.RLock() - targetLink, err := s.getLinkByShortID(packet.outgoingChanID) - if err != nil { - s.indexMtx.RUnlock() - log.Debugf("unable to find link with "+ - "destination %v", packet.outgoingChanID) + var targetPeerKey [33]byte + if packet.nextHopPubKey == nil { + targetLink, err := s.getLinkByShortID(packet.outgoingChanID) + if err != nil { + s.indexMtx.RUnlock() + + log.Debugf("unable to find link with "+ + "destination %v", packet.outgoingChanID) - // If packet was forwarded from another channel link - // than we should notify this link that some error - // occurred. - linkError := NewLinkError( - &lnwire.FailUnknownNextPeer{}, - ) + // If packet was forwarded from another channel link + // than we should notify this link that some error + // occurred. + linkError := NewLinkError( + &lnwire.FailUnknownNextPeer{}, + ) + + return s.failAddPacket(packet, linkError) + } + targetPeerKey = targetLink.Peer().PubKey() + } else { + targetPeerKey = *packet.nextHopPubKey - return s.failAddPacket(packet, linkError) + log.Debugf("Using pubkey routing to %x", targetPeerKey) } - targetPeerKey := targetLink.Peer().PubKey() + interfaceLinks, _ := s.getLinks(targetPeerKey) s.indexMtx.RUnlock() diff --git a/lnrpc/invoicesrpc/addinvoice.go b/lnrpc/invoicesrpc/addinvoice.go index 7628c4adadf..f4b065c6c8a 100644 --- a/lnrpc/invoicesrpc/addinvoice.go +++ b/lnrpc/invoicesrpc/addinvoice.go @@ -403,8 +403,9 @@ func addHopHint(hopHints *[]func(*zpay32.Invoice), channel *channeldb.OpenChannel, chanPolicy *channeldb.ChannelEdgePolicy) { hopHint := zpay32.HopHint{ - NodeID: channel.IdentityPub, - ChannelID: channel.ShortChanID().ToUint64(), + NodeID: channel.IdentityPub, + // Zeroed out for privacy reasons. + // ChannelID: channel.ShortChanID().ToUint64(), FeeBaseMSat: uint32(chanPolicy.FeeBaseMSat), FeeProportionalMillionths: uint32( chanPolicy.FeeProportionalMillionths, diff --git a/record/hop.go b/record/hop.go index 3f515e6b0d8..be62e2ebcf1 100644 --- a/record/hop.go +++ b/record/hop.go @@ -16,6 +16,8 @@ const ( // NextHopOnionType is the type used in the onion to reference the ID // of the next hop. NextHopOnionType tlv.Type = 6 + + NextHopPubKeyType tlv.Type = 10 ) // NewAmtToFwdRecord creates a tlv.Record that encodes the amount_to_forward @@ -45,3 +47,7 @@ func NewLockTimeRecord(lockTime *uint32) tlv.Record { func NewNextHopIDRecord(cid *uint64) tlv.Record { return tlv.MakePrimitiveRecord(NextHopOnionType, cid) } + +func NewNextHopPubKeyRecord(key *[33]byte) tlv.Record { + return tlv.MakePrimitiveRecord(NextHopPubKeyType, key) +} diff --git a/routing/route/route.go b/routing/route/route.go index 63944af18a0..17b17fbd0d6 100644 --- a/routing/route/route.go +++ b/routing/route/route.go @@ -153,7 +153,7 @@ func (h *Hop) Copy() *Hop { // references the _outgoing_ channel ID that follows this hop. This field // follows the same semantics as the NextAddress field in the onion: it should // be set to zero to indicate the terminal hop. -func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error { +func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64, nextHopPubKey *Vertex) error { // If this is a legacy payload, then we'll exit here as this method // shouldn't be called. if h.LegacyPayload == true { @@ -180,6 +180,13 @@ func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error { records = append(records, record.NewNextHopIDRecord(&nextChanID), ) + } else { + if nextHopPubKey != nil { + pubKey := [33]byte(*nextHopPubKey) + records = append(records, + record.NewNextHopPubKeyRecord(&pubKey), + ) + } } // If an MPP record is destined for this hop, ensure that we only ever @@ -449,7 +456,11 @@ func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) { // channel should be forwarded to so we can construct a // valid payload. var b bytes.Buffer - err := hop.PackHopPayload(&b, nextHop) + var nextHopPubKey *Vertex + if i < len(r.Hops)-1 { + nextHopPubKey = &r.Hops[i+1].PubKeyBytes + } + err := hop.PackHopPayload(&b, nextHop, nextHopPubKey) if err != nil { return nil, err }