Skip to content

Commit a8b85c0

Browse files
committed
multi: onion message forwarding
With this commit we implement the logic to parse, decrypt, and forward onion messages. It contains a refactor to its constructor to accept dependencies like the onionProcessor and a message sender function. In brontide.go and server.go it adds the plumbing to for passing through the onionProcessor from the hop iterator and the SendOnionMessage function to the OnionEndpoint's constructor.
1 parent 017c61d commit a8b85c0

File tree

11 files changed

+540
-69
lines changed

11 files changed

+540
-69
lines changed

feature/default_sets.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,12 @@ var defaultSetDesc = setDesc{
111111
SetInit: {}, // I
112112
SetNodeAnn: {}, // N
113113
},
114+
lnwire.OnionMessagesOptional: {
115+
SetInit: {}, // I
116+
SetNodeAnn: {}, // N
117+
},
118+
lnwire.OnionMessagesRequired: {
119+
SetInit: {}, // I
120+
SetNodeAnn: {}, // N
121+
},
114122
}

htlcswitch/hop/forwarding_info.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package hop
22

33
import (
4+
"github.com/btcsuite/btcd/btcec/v2"
45
"github.com/btcsuite/btcd/chaincfg/chainhash"
56
"github.com/lightningnetwork/lnd/lnwire"
67
)
@@ -16,6 +17,11 @@ type ForwardingInfo struct {
1617
// end-to-end route.
1718
NextHop lnwire.ShortChannelID
1819

20+
// NextNodeID is the public key of the next node in the route. This is
21+
// used by onion messages that do not necessarily care about the channel
22+
// ID.
23+
NextNodeID *btcec.PublicKey
24+
1925
// AmountToForward is the amount of milli-satoshis that the receiving
2026
// node should forward to the next hop.
2127
AmountToForward lnwire.MilliSatoshi

htlcswitch/hop/iterator.go

Lines changed: 123 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ type sphinxHopIterator struct {
133133
// This is required for peeling of dummy hops in a blinded path where
134134
// the same node will iteratively need to unwrap the onion.
135135
router *sphinx.Router
136+
137+
// isOnionMessage is a flag that indicates whether the iterator is for
138+
// an onion message.
139+
isOnionMessage bool
136140
}
137141

138142
// makeSphinxHopIterator converts a processed packet returned from a sphinx
@@ -141,14 +145,15 @@ type sphinxHopIterator struct {
141145
// for blinded routes.
142146
func makeSphinxHopIterator(router *sphinx.Router, ogPacket *sphinx.OnionPacket,
143147
packet *sphinx.ProcessedPacket, blindingKit BlindingKit,
144-
rHash []byte) *sphinxHopIterator {
148+
rHash []byte, isOnionMessage bool) *sphinxHopIterator {
145149

146150
return &sphinxHopIterator{
147151
router: router,
148152
ogPacket: ogPacket,
149153
processedPacket: packet,
150154
blindingKit: blindingKit,
151155
rHash: rHash,
156+
isOnionMessage: isOnionMessage,
152157
}
153158
}
154159

@@ -180,7 +185,9 @@ func (r *sphinxHopIterator) HopPayload() (*Payload, RouteRole, error) {
180185
// directly from the pre-populated ForwardingInstructions field.
181186
case sphinx.PayloadLegacy:
182187
fwdInst := r.processedPacket.ForwardingInstructions
183-
return NewLegacyPayload(fwdInst), RouteRoleCleartext, nil
188+
payload := NewLegacyPayload(fwdInst)
189+
payload.isFinal = r.processedPacket.Action == sphinx.ExitNode
190+
return payload, RouteRoleCleartext, nil
184191

185192
// Otherwise, if this is the TLV payload, then we'll make a new stream
186193
// to decode only what we need to make routing decisions.
@@ -203,12 +210,15 @@ func extractTLVPayload(r *sphinxHopIterator) (*Payload, RouteRole, error) {
203210
// Initial payload parsing and validation
204211
payload, routeRole, recipientData, err := parseAndValidateSenderPayload(
205212
r.processedPacket.Payload.Payload, isFinal,
206-
r.blindingKit.UpdateAddBlinding.IsSome(),
213+
r.blindingKit.UpdateAddBlinding.IsSome(), r.isOnionMessage,
207214
)
208215
if err != nil {
209216
return nil, routeRole, err
210217
}
211218

219+
// Indicate whether this is the final hop in the blinded path.
220+
payload.isFinal = isFinal
221+
212222
// If the payload contained no recipient data, then we can exit now.
213223
if !recipientData {
214224
return payload, routeRole, nil
@@ -234,29 +244,33 @@ func parseAndValidateRecipientData(r *sphinxHopIterator, payload *Payload,
234244
// This is the final node in the blinded route.
235245
if isFinal {
236246
return deriveBlindedRouteFinalHopForwardingInfo(
237-
routeData, payload, routeRole,
247+
routeData, payload, routeRole, r.isOnionMessage,
238248
)
239249
}
240250

241251
// Else, we are a forwarding node in this blinded path.
242252
return deriveBlindedRouteForwardingInfo(
243253
r, routeData, payload, routeRole, blindingPoint,
254+
r.isOnionMessage,
244255
)
245256
}
246257

247258
// deriveBlindedRouteFinalHopForwardingInfo extracts the PathID from the
248259
// routeData and constructs the ForwardingInfo accordingly.
249260
func deriveBlindedRouteFinalHopForwardingInfo(
250261
routeData *record.BlindedRouteData, payload *Payload,
251-
routeRole RouteRole) (*Payload, RouteRole, error) {
262+
routeRole RouteRole, isOnionMessage bool) (*Payload, RouteRole, error) {
252263

253264
var pathID *chainhash.Hash
254265
routeData.PathID.WhenSome(func(r tlv.RecordT[tlv.TlvType6, []byte]) {
255266
var id chainhash.Hash
256267
copy(id[:], r.Val)
257268
pathID = &id
258269
})
259-
if pathID == nil {
270+
271+
// If this is not an onion message, then we expect the path ID to be
272+
// set.
273+
if !isOnionMessage && pathID == nil {
260274
return nil, routeRole, ErrInvalidPayload{
261275
Type: tlv.Type(6),
262276
Violation: InsufficientViolation,
@@ -274,22 +288,29 @@ func deriveBlindedRouteFinalHopForwardingInfo(
274288
// recipient to derive the ForwardingInfo for the payment.
275289
func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
276290
routeData *record.BlindedRouteData, payload *Payload,
277-
routeRole RouteRole, blindingPoint *btcec.PublicKey) (*Payload,
278-
RouteRole, error) {
291+
routeRole RouteRole, blindingPoint *btcec.PublicKey,
292+
isOnionMessage bool) (*Payload, RouteRole, error) {
279293

280-
relayInfo, err := routeData.RelayInfo.UnwrapOrErr(
281-
fmt.Errorf("relay info not set for non-final blinded hop"),
294+
var (
295+
cltvExpiryDelta uint32
296+
fwdAmt lnwire.MilliSatoshi
282297
)
283-
if err != nil {
284-
return nil, routeRole, err
285-
}
298+
if !isOnionMessage {
299+
relayInfo, err := routeData.RelayInfo.UnwrapOrErr(
300+
fmt.Errorf("relay info not set for non-final blinded hop"),
301+
)
302+
cltvExpiryDelta = uint32(relayInfo.Val.CltvExpiryDelta)
303+
if err != nil {
304+
return nil, routeRole, err
305+
}
286306

287-
fwdAmt, err := calculateForwardingAmount(
288-
r.blindingKit.IncomingAmount, relayInfo.Val.BaseFee,
289-
relayInfo.Val.FeeRate,
290-
)
291-
if err != nil {
292-
return nil, routeRole, err
307+
fwdAmt, err = calculateForwardingAmount(
308+
r.blindingKit.IncomingAmount, relayInfo.Val.BaseFee,
309+
relayInfo.Val.FeeRate,
310+
)
311+
if err != nil {
312+
return nil, routeRole, err
313+
}
293314
}
294315

295316
nextEph, err := routeData.NextBlindingOverride.UnwrapOrFuncErr(
@@ -313,23 +334,30 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
313334
// payload.
314335
if checkForDummyHop(routeData, r.router.OnionPublicKey()) {
315336
return peelBlindedPathDummyHop(
316-
r, uint32(relayInfo.Val.CltvExpiryDelta), fwdAmt,
317-
routeRole, nextEph,
337+
r, cltvExpiryDelta, fwdAmt,
338+
routeRole, nextEph, isOnionMessage,
318339
)
319340
}
320341

342+
var nextNodeID tlv.RecordT[tlv.TlvType4, *btcec.PublicKey]
321343
nextSCID, err := routeData.ShortChannelID.UnwrapOrErr(
322344
fmt.Errorf("next SCID not set for non-final blinded hop"),
323345
)
346+
if err != nil && !isOnionMessage {
347+
return nil, routeRole, err
348+
} else if isOnionMessage {
349+
nextNodeID, err = routeData.NextNodeID.UnwrapOrErr(
350+
fmt.Errorf("next SCID nor NodeID set for non-final " +
351+
"blinded onion message hop"),
352+
)
353+
}
324354
if err != nil {
325355
return nil, routeRole, err
326356
}
357+
327358
payload.FwdInfo = ForwardingInfo{
328-
NextHop: nextSCID.Val,
329-
AmountToForward: fwdAmt,
330-
OutgoingCTLV: r.blindingKit.IncomingCltv - uint32(
331-
relayInfo.Val.CltvExpiryDelta,
332-
),
359+
NextHop: nextSCID.Val,
360+
NextNodeID: nextNodeID.Val,
333361
// Remap from blinding override type to blinding point type.
334362
NextBlinding: tlv.SomeRecordT(
335363
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](
@@ -338,6 +366,11 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
338366
),
339367
}
340368

369+
if !isOnionMessage {
370+
payload.FwdInfo.AmountToForward = fwdAmt
371+
payload.FwdInfo.OutgoingCTLV = r.blindingKit.IncomingCltv - cltvExpiryDelta
372+
}
373+
341374
return payload, routeRole, nil
342375
}
343376

@@ -362,8 +395,8 @@ func checkForDummyHop(routeData *record.BlindedRouteData,
362395
// to be the final hop on the path.
363396
func peelBlindedPathDummyHop(r *sphinxHopIterator, cltvExpiryDelta uint32,
364397
fwdAmt lnwire.MilliSatoshi, routeRole RouteRole,
365-
nextEph tlv.RecordT[tlv.TlvType8, *btcec.PublicKey]) (*Payload,
366-
RouteRole, error) {
398+
nextEph tlv.RecordT[tlv.TlvType8, *btcec.PublicKey],
399+
isOnionMessage bool) (*Payload, RouteRole, error) {
367400

368401
onionPkt := r.processedPacket.NextPacket
369402
sphinxPacket, err := r.router.ReconstructOnionPacket(
@@ -373,18 +406,23 @@ func peelBlindedPathDummyHop(r *sphinxHopIterator, cltvExpiryDelta uint32,
373406
return nil, routeRole, err
374407
}
375408

376-
iterator := makeSphinxHopIterator(
377-
r.router, onionPkt, sphinxPacket, BlindingKit{
378-
Processor: r.router,
379-
UpdateAddBlinding: tlv.SomeRecordT(
380-
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( //nolint:ll
381-
nextEph.Val,
382-
),
409+
blindingKit := BlindingKit{
410+
Processor: r.router,
411+
UpdateAddBlinding: tlv.SomeRecordT(
412+
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( //nolint:ll
413+
nextEph.Val,
383414
),
384-
IncomingAmount: fwdAmt,
385-
IncomingCltv: r.blindingKit.IncomingCltv -
386-
cltvExpiryDelta,
387-
}, r.rHash,
415+
),
416+
}
417+
418+
if !isOnionMessage {
419+
blindingKit.IncomingAmount = fwdAmt
420+
blindingKit.IncomingCltv = r.blindingKit.IncomingCltv - cltvExpiryDelta
421+
}
422+
423+
iterator := makeSphinxHopIterator(
424+
r.router, onionPkt, sphinxPacket, blindingKit, r.rHash,
425+
isOnionMessage,
388426
)
389427

390428
return extractTLVPayload(iterator)
@@ -416,10 +454,14 @@ func decryptAndValidateBlindedRouteData(r *sphinxHopIterator,
416454
return nil, nil, fmt.Errorf("%w: %w", ErrDecodeFailed, err)
417455
}
418456

419-
err = ValidateBlindedRouteData(
420-
routeData, r.blindingKit.IncomingAmount,
421-
r.blindingKit.IncomingCltv,
422-
)
457+
if r.isOnionMessage {
458+
err = ValidateBlindedFeatures(routeData)
459+
} else {
460+
err = ValidateBlindedRouteData(
461+
routeData, r.blindingKit.IncomingAmount,
462+
r.blindingKit.IncomingCltv,
463+
)
464+
}
423465
if err != nil {
424466
return nil, nil, err
425467
}
@@ -435,10 +477,25 @@ func decryptAndValidateBlindedRouteData(r *sphinxHopIterator,
435477
// value indicates that the sender payload includes encrypted data from the
436478
// recipient that should be parsed.
437479
func parseAndValidateSenderPayload(payloadBytes []byte, isFinalHop,
438-
updateAddBlindingSet bool) (*Payload, RouteRole, bool, error) {
480+
updateAddBlindingSet, isOnionMessage bool) (*Payload, RouteRole, bool,
481+
error) {
439482

483+
var (
484+
payload *Payload
485+
parsed map[tlv.Type][]byte
486+
err error
487+
)
440488
// Extract TLVs from the packet constructor (the sender).
441-
payload, parsed, err := ParseTLVPayload(bytes.NewReader(payloadBytes))
489+
if !isOnionMessage {
490+
payload, parsed, err = ParseTLVPayload(
491+
bytes.NewReader(payloadBytes),
492+
)
493+
} else {
494+
payload, parsed, err = ParseTLVPayloadOnionMessage(
495+
bytes.NewReader(payloadBytes),
496+
)
497+
}
498+
442499
if err != nil {
443500
// If we couldn't even parse our payload then we do a
444501
// best-effort of determining our role in a blinded route,
@@ -459,15 +516,24 @@ func parseAndValidateSenderPayload(payloadBytes []byte, isFinalHop,
459516

460517
// Validate the presence of the various payload fields we received from
461518
// the sender.
462-
err = ValidateTLVPayload(parsed, isFinalHop, updateAddBlindingSet)
519+
err = ValidateTLVPayload(
520+
parsed, isFinalHop, updateAddBlindingSet, isOnionMessage,
521+
)
463522
if err != nil {
464523
return nil, routeRole, false, err
465524
}
466525

526+
// If this is an onion message the payload is now fully validated. Since
527+
// onion messages contain recipient data by defintion, we return true
528+
// for that boolean.
529+
if isOnionMessage {
530+
return payload, routeRole, true, nil
531+
}
532+
467533
// If there is no encrypted data from the receiver then return the
468-
// payload as is since the forwarding info would have been received
469-
// from the sender.
470-
if payload.encryptedData == nil {
534+
// payload as is since the forwarding info would have been received from
535+
// the sender.
536+
if payload.encryptedData == nil || isOnionMessage {
471537
return payload, routeRole, false, nil
472538
}
473539

@@ -706,7 +772,7 @@ func (p *OnionProcessor) ReconstructHopIterator(r io.Reader, rHash []byte,
706772
UpdateAddBlinding: blindingInfo.BlindingKey,
707773
IncomingAmount: blindingInfo.IncomingAmt,
708774
IncomingCltv: blindingInfo.IncomingExpiry,
709-
}, rHash,
775+
}, rHash, false,
710776
), nil
711777
}
712778

@@ -719,6 +785,7 @@ type DecodeHopIteratorRequest struct {
719785
IncomingCltv uint32
720786
IncomingAmount lnwire.MilliSatoshi
721787
BlindingPoint lnwire.BlindingPointRecord
788+
IsOnionMessage bool
722789
}
723790

724791
// DecodeHopIteratorResponse encapsulates the outcome of a batched sphinx onion
@@ -785,6 +852,10 @@ func (p *OnionProcessor) DecodeHopIterators(id []byte,
785852
))
786853
})
787854

855+
if req.IsOnionMessage {
856+
opts = append(opts, sphinx.WithTLVPayloadOnly())
857+
}
858+
788859
// TODO(yy): use `p.router.ProcessOnionPacket` instead.
789860
err = tx.ProcessOnionPacket(
790861
seqNum, onionPkt, req.RHash, req.IncomingCltv, opts...,
@@ -826,7 +897,7 @@ func (p *OnionProcessor) DecodeHopIterators(id []byte,
826897
wg.Wait()
827898

828899
// With that batch created, we will now attempt to write the shared
829-
// secrets to disk. This operation will returns the set of indices that
900+
// secrets to disk. This operation will return the set of indices that
830901
// were detected as replays, and the computed sphinx packets for all
831902
// indices that did not fail the above loop. Only indices that are not
832903
// in the replay set should be considered valid, as they are
@@ -894,7 +965,7 @@ func (p *OnionProcessor) DecodeHopIterators(id []byte,
894965
UpdateAddBlinding: reqs[i].BlindingPoint,
895966
IncomingAmount: reqs[i].IncomingAmount,
896967
IncomingCltv: reqs[i].IncomingCltv,
897-
}, reqs[i].RHash,
968+
}, reqs[i].RHash, reqs[i].IsOnionMessage,
898969
)
899970
}
900971

0 commit comments

Comments
 (0)