@@ -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,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.
249260func 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.
275289func 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.
363396func 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.
437479func 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