• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 21485572389

29 Jan 2026 04:09PM UTC coverage: 65.247% (+0.2%) from 65.074%
21485572389

Pull #10089

github

web-flow
Merge 22d34d15e into 19b2ad797
Pull Request #10089: Onion message forwarding

1152 of 1448 new or added lines in 23 files covered. (79.56%)

4109 existing lines in 29 files now uncovered.

139515 of 213825 relevant lines covered (65.25%)

20529.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.5
/onionmessage/hop.go
1
package onionmessage
2

3
import (
4
        "bytes"
5

6
        "github.com/btcsuite/btcd/btcec/v2"
7
        sphinx "github.com/lightningnetwork/lightning-onion"
8
        "github.com/lightningnetwork/lnd/fn/v2"
9
        "github.com/lightningnetwork/lnd/lnwire"
10
        "github.com/lightningnetwork/lnd/record"
11
        "github.com/lightningnetwork/lnd/tlv"
12
)
13

14
// forwardAction contains the information needed to forward an onion message to
15
// the next node as well as update any subscribers with the payload we received.
16
type forwardAction struct {
17
        // nextNodeID is the public key of the peer to forward the message to
18
        nextNodeID *btcec.PublicKey
19

20
        // nextPathKey is the path key for the next hop, used for route
21
        // blinding.
22
        nextPathKey *btcec.PublicKey
23

24
        // nextPacket is the serialized onion packet to send to the next hop.
25
        nextPacket []byte
26

27
        // payload contains the decoded payload for this hop, which may include
28
        // custom records and routing information.
29
        payload *lnwire.OnionMessagePayload
30
}
31

32
// deliverAction contains the information needed to deliver the payload to any
33
// subscribers. Since we only support forwarding onion messages, this is only
34
// needed in itest to verify correct handling and behavior.
35
type deliverAction struct {
36
        // payload contains the decoded payload for this hop, which may include
37
        // custom records and routing information.
38
        payload *lnwire.OnionMessagePayload
39
}
40

41
type routingAction = fn.Either[forwardAction, deliverAction]
42

43
// NodeIDResolver defines an interface to resolve a node public key from a short
44
// channel ID.
45
type NodeIDResolver interface {
46
        RemotePubFromSCID(scid lnwire.ShortChannelID) (*btcec.PublicKey, error)
47
}
48

49
// processOnionMessage decodes and processes an onion message packet and its
50
// contents. It assumes route blinding is used, so it also decrypts encrypted
51
// recipient data, and derives the next path key. It returns a fn.Result type
52
// containing a routingAction, which contains all the information required to
53
// execute the next step in the routing process.
54
func processOnionMessage(router *sphinx.Router, resolver NodeIDResolver,
55
        msg *lnwire.OnionMessage) fn.Result[routingAction] {
14✔
56

14✔
57
        var onionPkt sphinx.OnionPacket
14✔
58
        err := onionPkt.Decode(bytes.NewReader(msg.OnionBlob))
14✔
59
        if err != nil {
15✔
60
                return fn.Err[routingAction](err)
1✔
61
        }
1✔
62

63
        // TODO(gijs): We should not use the magic value 10 here. It's the
64
        // incomingCltv value and only has use for the replay protection that we
65
        // don't need anyway.
66
        processedPkt, err := router.ProcessOnionPacket(
13✔
67
                &onionPkt, nil, 10, sphinx.WithBlindingPoint(msg.PathKey),
13✔
68
        )
13✔
69
        if err != nil {
13✔
NEW
70
                return fn.Err[routingAction](err)
×
NEW
71
        }
×
72

73
        payload := lnwire.NewOnionMessagePayload()
13✔
74
        _, err = payload.Decode(
13✔
75
                bytes.NewReader(processedPkt.Payload.Payload),
13✔
76
        )
13✔
77
        if err != nil {
13✔
NEW
78
                return fn.Err[routingAction](err)
×
NEW
79
        }
×
80

81
        // Create a shallow copy of the payload but deep copy the EncryptedData
82
        // field, as the decryption below will overwrite the EncryptedData field
83
        // in-place.
84
        originalPayload := *payload
13✔
85
        originalPayload.EncryptedData = bytes.Clone(payload.EncryptedData)
13✔
86

13✔
87
        decrypted, err := router.DecryptBlindedHopData(
13✔
88
                msg.PathKey, payload.EncryptedData,
13✔
89
        )
13✔
90
        if err != nil {
13✔
NEW
91
                return fn.Err[routingAction](err)
×
NEW
92
        }
×
93

94
        routeData, err := record.DecodeBlindedRouteData(
13✔
95
                bytes.NewReader(decrypted),
13✔
96
        )
13✔
97
        if err != nil {
14✔
98
                return fn.Err[routingAction](err)
1✔
99
        }
1✔
100

101
        nextPathKey := deriveNextPathKey(router, msg.PathKey,
12✔
102
                routeData.NextBlindingOverride)
12✔
103

12✔
104
        action, err := createRoutingAction(
12✔
105
                resolver, processedPkt, &originalPayload, routeData,
12✔
106
                nextPathKey,
12✔
107
        )
12✔
108
        if err != nil {
13✔
109
                return fn.Err[routingAction](err)
1✔
110
        }
1✔
111

112
        return fn.Ok(action)
11✔
113
}
114

115
// createRoutingAction creates the routing action based on whether we are
116
// forwarding or the receiver of the onion message.
117
func createRoutingAction(resolver NodeIDResolver,
118
        packet *sphinx.ProcessedPacket, payload *lnwire.OnionMessagePayload,
119
        routeData *record.BlindedRouteData,
120
        nextPathKey *btcec.PublicKey) (routingAction, error) {
12✔
121

12✔
122
        if isForwarding(packet) {
22✔
123
                var nextNodeID *btcec.PublicKey
10✔
124
                if routeData.NextNodeID.IsSome() {
17✔
125
                        n, err := routeData.NextNodeID.UnwrapOrErr(
7✔
126
                                ErrNextNodeIdEmpty,
7✔
127
                        )
7✔
128
                        if err != nil {
7✔
NEW
129
                                return routingAction{}, err
×
NEW
130
                        }
×
131
                        nextNodeID = n.Val
7✔
132
                } else {
5✔
133
                        scid, err := routeData.ShortChannelID.UnwrapOrErr(
5✔
134
                                ErrSCIDEmpty,
5✔
135
                        )
5✔
136
                        if err != nil {
5✔
NEW
137
                                return routingAction{}, err
×
NEW
138
                        }
×
139
                        nextNodeID, err = resolver.RemotePubFromSCID(scid.Val)
5✔
140
                        if err != nil {
6✔
141
                                return routingAction{}, err
1✔
142
                        }
1✔
143
                }
144

145
                buf := new(bytes.Buffer)
9✔
146
                err := packet.NextPacket.Encode(buf)
9✔
147
                if err != nil {
9✔
NEW
148
                        return routingAction{}, err
×
NEW
149
                }
×
150
                nextPacket := buf.Bytes()
9✔
151

9✔
152
                return fn.NewLeft[forwardAction, deliverAction](forwardAction{
9✔
153
                        nextNodeID:  nextNodeID,
9✔
154
                        nextPathKey: nextPathKey,
9✔
155
                        nextPacket:  nextPacket,
9✔
156
                        payload:     payload,
9✔
157
                }), nil
9✔
158
        }
159

160
        return fn.NewRight[forwardAction](deliverAction{
4✔
161
                payload: payload,
4✔
162
        }), nil
4✔
163
}
164

165
// deriveNextPathKey derives the next path key using the router and current
166
// path key. If an override is provided, it is used instead.
167
func deriveNextPathKey(router *sphinx.Router, currentPathKey *btcec.PublicKey,
168
        override tlv.OptionalRecordT[tlv.TlvType8,
169
                *btcec.PublicKey]) *btcec.PublicKey {
14✔
170

14✔
171
        // If an override is provided, use it.
14✔
172
        return override.UnwrapOrFunc(func() tlv.RecordT[tlv.TlvType8,
14✔
173
                *btcec.PublicKey] {
25✔
174

11✔
175
                // Otherwise, derive the next path key using the router.
11✔
176
                nextKey, err := router.NextEphemeral(currentPathKey)
11✔
177
                if err != nil {
11✔
NEW
178
                        // If the derivation fails, log and return a zero key.
×
NEW
179
                        log.Warnf("Failed to derive next path key: %v", err)
×
NEW
180

×
NEW
181
                        return override.Zero()
×
NEW
182
                }
×
183

184
                return tlv.NewPrimitiveRecord[tlv.TlvType8](nextKey)
11✔
185
        }).Val
186
}
187

188
// isForwarding checks if the packet is to be forwarded or delivered.
189
func isForwarding(packet *sphinx.ProcessedPacket) bool {
14✔
190
        return packet.Action != sphinx.ExitNode
14✔
191
}
14✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc