@@ -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.
142146func 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.
249261func 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.
275290func 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.
363399func 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.
437483func 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