Skip to content

Commit 898f975

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 2032c14 commit 898f975

File tree

11 files changed

+547
-66
lines changed

11 files changed

+547
-66
lines changed

feature/default_sets.go

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

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: 126 additions & 51 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,10 @@ 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+
191+
return payload, RouteRoleCleartext, nil
184192

185193
// Otherwise, if this is the TLV payload, then we'll make a new stream
186194
// to decode only what we need to make routing decisions.
@@ -203,12 +211,15 @@ func extractTLVPayload(r *sphinxHopIterator) (*Payload, RouteRole, error) {
203211
// Initial payload parsing and validation
204212
payload, routeRole, recipientData, err := parseAndValidateSenderPayload(
205213
r.processedPacket.Payload.Payload, isFinal,
206-
r.blindingKit.UpdateAddBlinding.IsSome(),
214+
r.blindingKit.UpdateAddBlinding.IsSome(), r.isOnionMessage,
207215
)
208216
if err != nil {
209217
return nil, routeRole, err
210218
}
211219

220+
// Indicate whether this is the final hop in the blinded path.
221+
payload.isFinal = isFinal
222+
212223
// If the payload contained no recipient data, then we can exit now.
213224
if !recipientData {
214225
return payload, routeRole, nil
@@ -234,29 +245,33 @@ func parseAndValidateRecipientData(r *sphinxHopIterator, payload *Payload,
234245
// This is the final node in the blinded route.
235246
if isFinal {
236247
return deriveBlindedRouteFinalHopForwardingInfo(
237-
routeData, payload, routeRole,
248+
routeData, payload, routeRole, r.isOnionMessage,
238249
)
239250
}
240251

241252
// Else, we are a forwarding node in this blinded path.
242253
return deriveBlindedRouteForwardingInfo(
243254
r, routeData, payload, routeRole, blindingPoint,
255+
r.isOnionMessage,
244256
)
245257
}
246258

247259
// deriveBlindedRouteFinalHopForwardingInfo extracts the PathID from the
248260
// routeData and constructs the ForwardingInfo accordingly.
249261
func deriveBlindedRouteFinalHopForwardingInfo(
250262
routeData *record.BlindedRouteData, payload *Payload,
251-
routeRole RouteRole) (*Payload, RouteRole, error) {
263+
routeRole RouteRole, isOnionMessage bool) (*Payload, RouteRole, error) {
252264

253265
var pathID *chainhash.Hash
254266
routeData.PathID.WhenSome(func(r tlv.RecordT[tlv.TlvType6, []byte]) {
255267
var id chainhash.Hash
256268
copy(id[:], r.Val)
257269
pathID = &id
258270
})
259-
if pathID == nil {
271+
272+
// If this is not an onion message, then we expect the path ID to be
273+
// set.
274+
if !isOnionMessage && pathID == nil {
260275
return nil, routeRole, ErrInvalidPayload{
261276
Type: tlv.Type(6),
262277
Violation: InsufficientViolation,
@@ -274,22 +289,30 @@ func deriveBlindedRouteFinalHopForwardingInfo(
274289
// recipient to derive the ForwardingInfo for the payment.
275290
func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
276291
routeData *record.BlindedRouteData, payload *Payload,
277-
routeRole RouteRole, blindingPoint *btcec.PublicKey) (*Payload,
278-
RouteRole, error) {
292+
routeRole RouteRole, blindingPoint *btcec.PublicKey,
293+
isOnionMessage bool) (*Payload, RouteRole, error) {
279294

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

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
309+
fwdAmt, err = calculateForwardingAmount(
310+
r.blindingKit.IncomingAmount, relayInfo.Val.BaseFee,
311+
relayInfo.Val.FeeRate,
312+
)
313+
if err != nil {
314+
return nil, routeRole, err
315+
}
293316
}
294317

295318
nextEph, err := routeData.NextBlindingOverride.UnwrapOrFuncErr(
@@ -313,23 +336,30 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
313336
// payload.
314337
if checkForDummyHop(routeData, r.router.OnionPublicKey()) {
315338
return peelBlindedPathDummyHop(
316-
r, uint32(relayInfo.Val.CltvExpiryDelta), fwdAmt,
317-
routeRole, nextEph,
339+
r, cltvExpiryDelta, fwdAmt,
340+
routeRole, nextEph, isOnionMessage,
318341
)
319342
}
320343

344+
var nextNodeID tlv.RecordT[tlv.TlvType4, *btcec.PublicKey]
321345
nextSCID, err := routeData.ShortChannelID.UnwrapOrErr(
322346
fmt.Errorf("next SCID not set for non-final blinded hop"),
323347
)
348+
if err != nil && !isOnionMessage {
349+
return nil, routeRole, err
350+
} else if isOnionMessage {
351+
nextNodeID, err = routeData.NextNodeID.UnwrapOrErr(
352+
fmt.Errorf("next SCID nor NodeID set for non-final " +
353+
"blinded onion message hop"),
354+
)
355+
}
324356
if err != nil {
325357
return nil, routeRole, err
326358
}
359+
327360
payload.FwdInfo = ForwardingInfo{
328-
NextHop: nextSCID.Val,
329-
AmountToForward: fwdAmt,
330-
OutgoingCTLV: r.blindingKit.IncomingCltv - uint32(
331-
relayInfo.Val.CltvExpiryDelta,
332-
),
361+
NextHop: nextSCID.Val,
362+
NextNodeID: nextNodeID.Val,
333363
// Remap from blinding override type to blinding point type.
334364
NextBlinding: tlv.SomeRecordT(
335365
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](
@@ -338,6 +368,12 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
338368
),
339369
}
340370

371+
if !isOnionMessage {
372+
payload.FwdInfo.AmountToForward = fwdAmt
373+
outgoingCTLV := r.blindingKit.IncomingCltv - cltvExpiryDelta
374+
payload.FwdInfo.OutgoingCTLV = outgoingCTLV
375+
}
376+
341377
return payload, routeRole, nil
342378
}
343379

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

368404
onionPkt := r.processedPacket.NextPacket
369405
sphinxPacket, err := r.router.ReconstructOnionPacket(
@@ -373,18 +409,24 @@ func peelBlindedPathDummyHop(r *sphinxHopIterator, cltvExpiryDelta uint32,
373409
return nil, routeRole, err
374410
}
375411

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-
),
412+
blindingKit := BlindingKit{
413+
Processor: r.router,
414+
UpdateAddBlinding: tlv.SomeRecordT(
415+
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( //nolint:ll
416+
nextEph.Val,
383417
),
384-
IncomingAmount: fwdAmt,
385-
IncomingCltv: r.blindingKit.IncomingCltv -
386-
cltvExpiryDelta,
387-
}, r.rHash,
418+
),
419+
}
420+
421+
if !isOnionMessage {
422+
blindingKit.IncomingAmount = fwdAmt
423+
incomingCltv := r.blindingKit.IncomingCltv - cltvExpiryDelta
424+
blindingKit.IncomingCltv = incomingCltv
425+
}
426+
427+
iterator := makeSphinxHopIterator(
428+
r.router, onionPkt, sphinxPacket, blindingKit, r.rHash,
429+
isOnionMessage,
388430
)
389431

390432
return extractTLVPayload(iterator)
@@ -416,10 +458,14 @@ func decryptAndValidateBlindedRouteData(r *sphinxHopIterator,
416458
return nil, nil, fmt.Errorf("%w: %w", ErrDecodeFailed, err)
417459
}
418460

419-
err = ValidateBlindedRouteData(
420-
routeData, r.blindingKit.IncomingAmount,
421-
r.blindingKit.IncomingCltv,
422-
)
461+
if r.isOnionMessage {
462+
err = ValidateBlindedFeatures(routeData)
463+
} else {
464+
err = ValidateBlindedRouteData(
465+
routeData, r.blindingKit.IncomingAmount,
466+
r.blindingKit.IncomingCltv,
467+
)
468+
}
423469
if err != nil {
424470
return nil, nil, err
425471
}
@@ -435,10 +481,25 @@ func decryptAndValidateBlindedRouteData(r *sphinxHopIterator,
435481
// value indicates that the sender payload includes encrypted data from the
436482
// recipient that should be parsed.
437483
func parseAndValidateSenderPayload(payloadBytes []byte, isFinalHop,
438-
updateAddBlindingSet bool) (*Payload, RouteRole, bool, error) {
484+
updateAddBlindingSet, isOnionMessage bool) (*Payload, RouteRole, bool,
485+
error) {
439486

487+
var (
488+
payload *Payload
489+
parsed map[tlv.Type][]byte
490+
err error
491+
)
440492
// Extract TLVs from the packet constructor (the sender).
441-
payload, parsed, err := ParseTLVPayload(bytes.NewReader(payloadBytes))
493+
if !isOnionMessage {
494+
payload, parsed, err = ParseTLVPayload(
495+
bytes.NewReader(payloadBytes),
496+
)
497+
} else {
498+
payload, parsed, err = ParseTLVPayloadOnionMessage(
499+
bytes.NewReader(payloadBytes),
500+
)
501+
}
502+
442503
if err != nil {
443504
// If we couldn't even parse our payload then we do a
444505
// best-effort of determining our role in a blinded route,
@@ -459,14 +520,23 @@ func parseAndValidateSenderPayload(payloadBytes []byte, isFinalHop,
459520

460521
// Validate the presence of the various payload fields we received from
461522
// the sender.
462-
err = ValidateTLVPayload(parsed, isFinalHop, updateAddBlindingSet)
523+
err = ValidateTLVPayload(
524+
parsed, isFinalHop, updateAddBlindingSet, isOnionMessage,
525+
)
463526
if err != nil {
464527
return nil, routeRole, false, err
465528
}
466529

530+
// If this is an onion message the payload is now fully validated. Since
531+
// onion messages contain recipient data by definition, we return true
532+
// for that boolean.
533+
if isOnionMessage {
534+
return payload, routeRole, true, nil
535+
}
536+
467537
// 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.
538+
// payload as is since the forwarding info would have been received from
539+
// the sender.
470540
if payload.encryptedData == nil {
471541
return payload, routeRole, false, nil
472542
}
@@ -706,7 +776,7 @@ func (p *OnionProcessor) ReconstructHopIterator(r io.Reader, rHash []byte,
706776
UpdateAddBlinding: blindingInfo.BlindingKey,
707777
IncomingAmount: blindingInfo.IncomingAmt,
708778
IncomingCltv: blindingInfo.IncomingExpiry,
709-
}, rHash,
779+
}, rHash, false,
710780
), nil
711781
}
712782

@@ -719,6 +789,7 @@ type DecodeHopIteratorRequest struct {
719789
IncomingCltv uint32
720790
IncomingAmount lnwire.MilliSatoshi
721791
BlindingPoint lnwire.BlindingPointRecord
792+
IsOnionMessage bool
722793
}
723794

724795
// DecodeHopIteratorResponse encapsulates the outcome of a batched sphinx onion
@@ -785,6 +856,10 @@ func (p *OnionProcessor) DecodeHopIterators(id []byte,
785856
))
786857
})
787858

859+
if req.IsOnionMessage {
860+
opts = append(opts, sphinx.WithTLVPayloadOnly())
861+
}
862+
788863
// TODO(yy): use `p.router.ProcessOnionPacket` instead.
789864
err = tx.ProcessOnionPacket(
790865
seqNum, onionPkt, req.RHash, req.IncomingCltv, opts...,
@@ -826,7 +901,7 @@ func (p *OnionProcessor) DecodeHopIterators(id []byte,
826901
wg.Wait()
827902

828903
// 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
904+
// secrets to disk. This operation will return the set of indices that
830905
// were detected as replays, and the computed sphinx packets for all
831906
// indices that did not fail the above loop. Only indices that are not
832907
// in the replay set should be considered valid, as they are
@@ -894,7 +969,7 @@ func (p *OnionProcessor) DecodeHopIterators(id []byte,
894969
UpdateAddBlinding: reqs[i].BlindingPoint,
895970
IncomingAmount: reqs[i].IncomingAmount,
896971
IncomingCltv: reqs[i].IncomingCltv,
897-
}, reqs[i].RHash,
972+
}, reqs[i].RHash, reqs[i].IsOnionMessage,
898973
)
899974
}
900975

0 commit comments

Comments
 (0)