mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-13 12:32:48 +02:00
htlcswitch/hop: decode next_node_id blinded hops
Some implementations (e.g. Core Lightning) identify the next hop in a
blinded route by the next node's ID (next_node_id) instead of a short
channel ID. Decode such a hop into a node-ID next hop, the Right of
ForwardingInfo.NextHop, holding the next node's public key. The switch
resolves that key to one of our channels with the peer in a later commit.
BOLT 4 requires a non-final blinded hop to carry exactly one of
short_channel_id or next_node_id, so a hop that sets both is rejected.
(cherry picked from commit 4fd4289a08)
This commit is contained in:
parent
f7e745c113
commit
0bbacacab3
6 changed files with 471 additions and 8 deletions
|
|
@ -48,6 +48,15 @@ func NewChannelNextHop(
|
|||
return fn.NewLeft[lnwire.ShortChannelID, [33]byte](scid)
|
||||
}
|
||||
|
||||
// NewNodeNextHop returns a next-hop value that identifies the next hop by the
|
||||
// next node's compressed public key, as used by blinded routes that set
|
||||
// next_node_id instead of a short channel ID.
|
||||
func NewNodeNextHop(
|
||||
nodeID [33]byte) fn.Either[lnwire.ShortChannelID, [33]byte] {
|
||||
|
||||
return fn.NewRight[lnwire.ShortChannelID, [33]byte](nodeID)
|
||||
}
|
||||
|
||||
// IsExit returns true if this forwarding info denotes the exit hop, i.e. we are
|
||||
// the final recipient of the HTLC. This is the case when the next hop is a
|
||||
// short channel ID equal to hop.Exit. A node-ID next hop (used by some blinded
|
||||
|
|
@ -69,6 +78,13 @@ func (f ForwardingInfo) NextHopChannel() fn.Option[lnwire.ShortChannelID] {
|
|||
return f.NextHop.LeftToSome()
|
||||
}
|
||||
|
||||
// NextHopNode returns the next hop's compressed pubkey when it is identified by
|
||||
// node ID (blinded routes via next_node_id), or None when identified by
|
||||
// channel.
|
||||
func (f ForwardingInfo) NextHopNode() fn.Option[[33]byte] {
|
||||
return f.NextHop.RightToSome()
|
||||
}
|
||||
|
||||
// FinalHtlcValidationResult describes the result of checking a final-hop
|
||||
// HTLC against the onion payload and supported final-hop CLTV range.
|
||||
type FinalHtlcValidationResult uint8
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package hop
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -135,3 +136,41 @@ func TestValidateFinalHtlc(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestForwardingInfoNextHop asserts the next-hop accessors for both the short
|
||||
// channel ID (Left) and node ID (Right) representations, including the
|
||||
// invariant that the zero-value ForwardingInfo denotes the exit hop.
|
||||
func TestForwardingInfoNextHop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scid := lnwire.NewShortChanIDFromInt(12345)
|
||||
nodeID := [33]byte{0x02}
|
||||
|
||||
// The zero-value ForwardingInfo must denote the exit hop, since its
|
||||
// NextHop is a Left equal to hop.Exit. Callers rely on this to detect
|
||||
// that we are the final recipient.
|
||||
zero := ForwardingInfo{}
|
||||
require.True(t, zero.IsExit(), "zero value must be the exit hop")
|
||||
require.Equal(
|
||||
t, fn.Some(Exit), zero.NextHopChannel(),
|
||||
"zero value must expose the Exit channel",
|
||||
)
|
||||
|
||||
// An explicit channel next hop equal to Exit is likewise the exit hop.
|
||||
exit := ForwardingInfo{NextHop: NewChannelNextHop(Exit)}
|
||||
require.True(t, exit.IsExit())
|
||||
|
||||
// A channel next hop with a real SCID is a forward, and exposes that
|
||||
// SCID through NextHopChannel.
|
||||
channel := ForwardingInfo{NextHop: NewChannelNextHop(scid)}
|
||||
require.False(t, channel.IsExit())
|
||||
require.Equal(t, fn.Some(scid), channel.NextHopChannel())
|
||||
|
||||
// A node-ID next hop is always a forward and never exposes an outgoing
|
||||
// channel, since the switch selects one via non-strict forwarding.
|
||||
node := ForwardingInfo{NextHop: NewNodeNextHop(nodeID)}
|
||||
require.False(t, node.IsExit())
|
||||
require.Equal(
|
||||
t, fn.None[lnwire.ShortChannelID](), node.NextHopChannel(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
|
|
@ -231,6 +232,13 @@ func parseAndValidateRecipientData(r *sphinxHopIterator, payload *Payload,
|
|||
return nil, routeRole, err
|
||||
}
|
||||
|
||||
// BOLT 4 requires a blinded hop to set exactly one of short_channel_id
|
||||
// or next_node_id. Reject a hop that sets both here.
|
||||
if routeData.ShortChannelID.IsSome() && routeData.NextNodeID.IsSome() {
|
||||
return nil, routeRole, fmt.Errorf("blinded hop sets both " +
|
||||
"short channel ID and next node ID")
|
||||
}
|
||||
|
||||
// This is the final node in the blinded route.
|
||||
if isFinal {
|
||||
return deriveBlindedRouteFinalHopForwardingInfo(
|
||||
|
|
@ -318,15 +326,35 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
|
|||
)
|
||||
}
|
||||
|
||||
nextSCID, err := routeData.ShortChannelID.UnwrapOrErr(
|
||||
fmt.Errorf("next SCID not set for non-final blinded hop"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, routeRole, err
|
||||
// Determine the next hop. The recipient identifies it either by a short
|
||||
// channel ID (the common case) or, as some implementations do for
|
||||
// blinded routes, by the next node's ID (next_node_id). Setting both is
|
||||
// already rejected upstream, and the dummy hop check above has handled
|
||||
// a next_node_id that points at us.
|
||||
var nextHop fn.Either[lnwire.ShortChannelID, [33]byte]
|
||||
switch {
|
||||
case routeData.ShortChannelID.IsSome():
|
||||
scid := routeData.ShortChannelID.UnwrapOr(
|
||||
routeData.ShortChannelID.Zero(),
|
||||
)
|
||||
nextHop = NewChannelNextHop(scid.Val)
|
||||
|
||||
case routeData.NextNodeID.IsSome():
|
||||
nodeID := routeData.NextNodeID.UnwrapOr(
|
||||
routeData.NextNodeID.Zero(),
|
||||
)
|
||||
var pubKey [33]byte
|
||||
copy(pubKey[:], nodeID.Val.SerializeCompressed())
|
||||
|
||||
nextHop = NewNodeNextHop(pubKey)
|
||||
|
||||
default:
|
||||
return nil, routeRole, fmt.Errorf("next hop not set for " +
|
||||
"non-final blinded hop")
|
||||
}
|
||||
|
||||
payload.FwdInfo = ForwardingInfo{
|
||||
NextHop: NewChannelNextHop(nextSCID.Val),
|
||||
NextHop: nextHop,
|
||||
AmountToForward: fwdAmt,
|
||||
OutgoingCLTV: r.blindingKit.IncomingCltv - uint32(
|
||||
relayInfo.Val.CltvExpiryDelta,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
|
|
@ -305,3 +306,378 @@ func TestParseAndValidateRecipientData(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeriveBlindedRouteNextHop asserts how a non-final blinded hop's next hop
|
||||
// is derived from the recipient data: a short channel ID becomes a Left, a
|
||||
// next_node_id becomes a Right, having both set is rejected with an error, and
|
||||
// the absence of both is also an error.
|
||||
func TestDeriveBlindedRouteNextHop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nodeKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
nextNodeKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
nextNodePub := nextNodeKey.PubKey()
|
||||
|
||||
var nextNodeRaw [33]byte
|
||||
copy(nextNodeRaw[:], nextNodePub.SerializeCompressed())
|
||||
|
||||
scid := lnwire.NewShortChanIDFromInt(1500)
|
||||
|
||||
relayInfo := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType10](
|
||||
record.PaymentRelayInfo{
|
||||
CltvExpiryDelta: 10,
|
||||
BaseFee: 100,
|
||||
FeeRate: 0,
|
||||
},
|
||||
))
|
||||
constraints := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType12](
|
||||
record.PaymentConstraints{
|
||||
MaxCltvExpiry: 1000,
|
||||
HtlcMinimumMsat: lnwire.MilliSatoshi(1),
|
||||
},
|
||||
))
|
||||
scidRecord := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType2](scid))
|
||||
nodeIDRecord := tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType4](nextNodePub),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
data *record.BlindedRouteData
|
||||
expectedHop fn.Either[lnwire.ShortChannelID, [33]byte]
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "short channel id only",
|
||||
data: &record.BlindedRouteData{
|
||||
ShortChannelID: scidRecord,
|
||||
RelayInfo: relayInfo,
|
||||
Constraints: constraints,
|
||||
},
|
||||
expectedHop: NewChannelNextHop(scid),
|
||||
},
|
||||
{
|
||||
name: "next node id only",
|
||||
data: &record.BlindedRouteData{
|
||||
NextNodeID: nodeIDRecord,
|
||||
RelayInfo: relayInfo,
|
||||
Constraints: constraints,
|
||||
},
|
||||
expectedHop: NewNodeNextHop(nextNodeRaw),
|
||||
},
|
||||
{
|
||||
// BOLT 4 requires a non-final blinded hop to set
|
||||
// exactly one of short_channel_id or next_node_id, so
|
||||
// setting both must be rejected.
|
||||
name: "both present is an error",
|
||||
data: &record.BlindedRouteData{
|
||||
ShortChannelID: scidRecord,
|
||||
NextNodeID: nodeIDRecord,
|
||||
RelayInfo: relayInfo,
|
||||
Constraints: constraints,
|
||||
},
|
||||
expectedErr: "both short channel ID and next node ID",
|
||||
},
|
||||
{
|
||||
name: "neither present",
|
||||
data: &record.BlindedRouteData{
|
||||
RelayInfo: relayInfo,
|
||||
Constraints: constraints,
|
||||
},
|
||||
expectedErr: "next hop not set",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data, err := record.EncodeBlindedRouteData(
|
||||
testCase.data,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
kit := BlindingKit{
|
||||
Processor: &mockProcessor{},
|
||||
IncomingAmount: 10000,
|
||||
IncomingCltv: 500,
|
||||
UpdateAddBlinding: tlv.SomeRecordT(
|
||||
//nolint:ll
|
||||
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](&btcec.PublicKey{}),
|
||||
),
|
||||
}
|
||||
iterator := &sphinxHopIterator{
|
||||
blindingKit: kit,
|
||||
router: sphinx.NewRouter(
|
||||
&sphinx.PrivKeyECDH{PrivKey: nodeKey},
|
||||
sphinx.NewMemoryReplayLog(),
|
||||
),
|
||||
}
|
||||
|
||||
payload, _, err := parseAndValidateRecipientData(
|
||||
iterator, &Payload{encryptedData: data},
|
||||
false, RouteRoleCleartext,
|
||||
)
|
||||
|
||||
if testCase.expectedErr != "" {
|
||||
require.ErrorContains(
|
||||
t, err, testCase.expectedErr,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(
|
||||
t, testCase.expectedHop,
|
||||
payload.FwdInfo.NextHop,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlindedHopBothNextHopFieldsRejected asserts that a blinded hop setting
|
||||
// both short_channel_id and next_node_id is rejected for a final hop and for a
|
||||
// dummy hop (next_node_id == our own pubkey), not just an intermediate hop. The
|
||||
// mutual-exclusivity check runs before the final-hop and dummy-hop branches, so
|
||||
// none of them accept a hop that violates BOLT 4. The intermediate case is
|
||||
// already covered by TestDeriveBlindedRouteNextHop.
|
||||
func TestBlindedHopBothNextHopFieldsRejected(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nodeKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
nodePub := nodeKey.PubKey()
|
||||
|
||||
// Route data that sets both short_channel_id and next_node_id. The node
|
||||
// ID is our own pubkey, which for a non-final hop would otherwise
|
||||
// signal a dummy hop; the both-set check must still fire first.
|
||||
bothData := &record.BlindedRouteData{
|
||||
ShortChannelID: tlv.SomeRecordT(
|
||||
tlv.NewRecordT[tlv.TlvType2](
|
||||
lnwire.NewShortChanIDFromInt(1500),
|
||||
),
|
||||
),
|
||||
NextNodeID: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType4](nodePub),
|
||||
),
|
||||
RelayInfo: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType10](
|
||||
record.PaymentRelayInfo{
|
||||
CltvExpiryDelta: 10,
|
||||
BaseFee: 100,
|
||||
FeeRate: 0,
|
||||
},
|
||||
)),
|
||||
Constraints: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType12](
|
||||
record.PaymentConstraints{
|
||||
MaxCltvExpiry: 1000,
|
||||
HtlcMinimumMsat: lnwire.MilliSatoshi(1),
|
||||
},
|
||||
)),
|
||||
}
|
||||
data, err := record.EncodeBlindedRouteData(bothData)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Both the dummy/forwarding path (isFinal=false, next_node_id points at
|
||||
// us) and the final path (isFinal=true) must reject the hop.
|
||||
for _, isFinal := range []bool{false, true} {
|
||||
name := "forwarding hop"
|
||||
if isFinal {
|
||||
name = "final hop"
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
kit := BlindingKit{
|
||||
Processor: &mockProcessor{},
|
||||
IncomingAmount: 10000,
|
||||
IncomingCltv: 500,
|
||||
UpdateAddBlinding: tlv.SomeRecordT(
|
||||
//nolint:ll
|
||||
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](&btcec.PublicKey{}),
|
||||
),
|
||||
}
|
||||
iterator := &sphinxHopIterator{
|
||||
blindingKit: kit,
|
||||
router: sphinx.NewRouter(
|
||||
&sphinx.PrivKeyECDH{PrivKey: nodeKey},
|
||||
sphinx.NewMemoryReplayLog(),
|
||||
),
|
||||
}
|
||||
|
||||
_, _, err := parseAndValidateRecipientData(
|
||||
iterator, &Payload{encryptedData: data},
|
||||
isFinal, RouteRoleCleartext,
|
||||
)
|
||||
require.ErrorContains(
|
||||
t, err,
|
||||
"both short channel ID and next node ID",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlindedRouteDummyHopPeeledLocally asserts that a blinded route hop where
|
||||
// next_node_id is our own public key is recognized as a dummy hop and is peeled
|
||||
// locally rather than falling through to the generic next_node_id forwarding
|
||||
// branch.
|
||||
func TestBlindedRouteDummyHopPeeledLocally(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Construct a realistic onion packet that contains a blinded final hop.
|
||||
// We'll use this to test that we can peel a dummy hop locally and
|
||||
// extract the forwarding information from the decrypted final hop's
|
||||
// payload.
|
||||
nodeKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
nodePub := nodeKey.PubKey()
|
||||
|
||||
relayInfo := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType10](
|
||||
record.PaymentRelayInfo{
|
||||
CltvExpiryDelta: 10,
|
||||
BaseFee: 100,
|
||||
FeeRate: 0,
|
||||
},
|
||||
))
|
||||
constraints := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType12](
|
||||
record.PaymentConstraints{
|
||||
MaxCltvExpiry: 1000,
|
||||
HtlcMinimumMsat: lnwire.MilliSatoshi(1),
|
||||
},
|
||||
))
|
||||
|
||||
// Set next_node_id to our own public key. This signals a dummy hop.
|
||||
nodeIDRecord := tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType4](nodePub),
|
||||
)
|
||||
|
||||
// We'll generate a valid, cryptographically blinded final hop's payload
|
||||
// using sphinx.BuildBlindedPath. This contains the PathID.
|
||||
secret := make([]byte, 32)
|
||||
secret[0] = 2
|
||||
finalHopData := &record.BlindedRouteData{
|
||||
PathID: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType6](secret),
|
||||
),
|
||||
}
|
||||
finalHopDataBytes, err := record.EncodeBlindedRouteData(finalHopData)
|
||||
require.NoError(t, err)
|
||||
|
||||
hopInfo := &sphinx.HopInfo{
|
||||
NodePub: nodePub,
|
||||
PlainText: finalHopDataBytes,
|
||||
}
|
||||
|
||||
blindingSessionKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
blindedPathInfo, err := sphinx.BuildBlindedPath(
|
||||
blindingSessionKey, []*sphinx.HopInfo{hopInfo},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Since we are peeling a dummy hop locally, we want the next blinding
|
||||
// override to be the blinding point generated for our blinded final
|
||||
// hop.
|
||||
dummyHopData := &record.BlindedRouteData{
|
||||
NextNodeID: nodeIDRecord,
|
||||
RelayInfo: relayInfo,
|
||||
Constraints: constraints,
|
||||
NextBlindingOverride: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType8](
|
||||
blindedPathInfo.Path.BlindingPoint,
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
data, err := record.EncodeBlindedRouteData(dummyHopData)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Encode a valid TLV payload for the next hop (which we will peel).
|
||||
var hop2Buffer bytes.Buffer
|
||||
amt := uint64(10000)
|
||||
cltv := uint32(500)
|
||||
encryptedDataRecord := record.NewEncryptedDataRecord(
|
||||
&blindedPathInfo.Path.BlindedHops[0].CipherText,
|
||||
)
|
||||
tlvRecords := []tlv.Record{
|
||||
record.NewAmtToFwdRecord(&amt),
|
||||
record.NewLockTimeRecord(&cltv),
|
||||
encryptedDataRecord,
|
||||
}
|
||||
tlvStream, err := tlv.NewStream(tlvRecords...)
|
||||
require.NoError(t, err)
|
||||
err = tlvStream.Encode(&hop2Buffer)
|
||||
require.NoError(t, err)
|
||||
|
||||
hopPayload, err := sphinx.NewTLVHopPayload(hop2Buffer.Bytes())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a valid 1-hop onion path using our blinded public key.
|
||||
var paymentPath sphinx.PaymentPath
|
||||
paymentPath[0] = sphinx.OnionHop{
|
||||
NodePub: *blindedPathInfo.Path.BlindedHops[0].BlindedNodePub,
|
||||
HopPayload: hopPayload,
|
||||
}
|
||||
|
||||
sessionKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
rHash := [32]byte{1}
|
||||
|
||||
// Generate a cryptographically valid onion packet for this path.
|
||||
onionPacket, err := sphinx.NewOnionPacket(
|
||||
&paymentPath, sessionKey, rHash[:],
|
||||
sphinx.DeterministicPacketFiller,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Simulate an incoming HTLC with a blinding point and a valid onion
|
||||
// packet. The blinding point is used to decrypt the dummy hop's
|
||||
// payload, which contains the blinding point for the next hop (the
|
||||
// blinded final hop).
|
||||
kit := BlindingKit{
|
||||
Processor: &mockProcessor{},
|
||||
IncomingAmount: 12000,
|
||||
IncomingCltv: 510,
|
||||
UpdateAddBlinding: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](
|
||||
nodePub,
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
iterator := &sphinxHopIterator{
|
||||
blindingKit: kit,
|
||||
rHash: rHash[:],
|
||||
router: sphinx.NewRouter(
|
||||
&sphinx.PrivKeyECDH{PrivKey: nodeKey},
|
||||
sphinx.NewMemoryReplayLog(),
|
||||
),
|
||||
// Set our valid onion packet to be peeled.
|
||||
processedPacket: &sphinx.ProcessedPacket{
|
||||
NextPacket: onionPacket,
|
||||
},
|
||||
}
|
||||
|
||||
// When we parse and validate the recipient data, it should enter the
|
||||
// dummy-hop peeling path. Since our onion packet is valid and matches
|
||||
// our private key, it should be successfully peeled and parsed.
|
||||
pld, _, err := parseAndValidateRecipientData(
|
||||
iterator, &Payload{encryptedData: data},
|
||||
false, RouteRoleCleartext,
|
||||
)
|
||||
|
||||
// Assert that we successfully peeled the dummy hop and extracted the
|
||||
// decrypted final payload.
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pld)
|
||||
|
||||
fwdInfo := pld.ForwardingInfo()
|
||||
require.Equal(t, lnwire.MilliSatoshi(0), fwdInfo.AmountToForward)
|
||||
require.Equal(t, uint32(0), fwdInfo.OutgoingCLTV)
|
||||
require.NotNil(t, fwdInfo.PathID)
|
||||
require.Equal(t, secret, fwdInfo.PathID[:])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ type BlindedRouteData struct {
|
|||
|
||||
// NextNodeID is the node ID of the next node on the path. In the
|
||||
// context of blinded path payments, this is used to indicate the
|
||||
// presence of dummy hops that need to be peeled from the onion.
|
||||
// presence of dummy hops that need to be peeled from the onion, or to
|
||||
// identify a real next-node forwarding target when the public key is
|
||||
// not ours.
|
||||
NextNodeID tlv.OptionalRecordT[tlv.TlvType4, *btcec.PublicKey]
|
||||
|
||||
// PathID is a secret set of bytes that the blinded path creator will
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ type BlindedRouteData struct {
|
|||
|
||||
// NextNodeID is the node ID of the next node on the path. In the
|
||||
// context of blinded path payments, this is used to indicate the
|
||||
// presence of dummy hops that need to be peeled from the onion.
|
||||
// presence of dummy hops that need to be peeled from the onion, or to
|
||||
// identify a real next-node forwarding target when the public key is
|
||||
// not ours.
|
||||
NextNodeID tlv.OptionalRecordT[tlv.TlvType4, *btcec.PublicKey]
|
||||
|
||||
// PathID is a secret set of bytes that the blinded path creator will
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue