@@ -45,10 +45,17 @@ var (
4545var (
4646 // ErrInsufficientHops is returned when there are not enough hops to
4747 // create a route.
48- ErrInsufficientHops = errors .New ("at least 2 hops are required to " +
48+ ErrInsufficientHops = errors .New ("at least 1 hop is required to " +
4949 "create an onion message route" )
5050)
5151
52+ // Session keys used in onion message tests.
53+ var (
54+ sessionKeyA = bytes .Repeat ([]byte {'A' }, 32 )
55+ sessionKeyB = bytes .Repeat ([]byte {'B' }, 32 )
56+ sessionKeyC = bytes .Repeat ([]byte {'C' }, 32 )
57+ )
58+
5259// encodeTLVRecord encodes a TLV record with the given type and value.
5360func encodeTLVRecord (recordType uint64 , value []byte ) []byte {
5461 var buf bytes.Buffer
@@ -159,7 +166,7 @@ func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacke
159166func newOnionMessageRoute (numHops int ) (* OnionPacket , * PaymentPath , []* Router ,
160167 error ) {
161168
162- if numHops < 2 {
169+ if numHops < 1 {
163170 return nil , nil , nil , ErrInsufficientHops
164171 }
165172
@@ -178,8 +185,8 @@ func newOnionMessageRoute(numHops int) (*OnionPacket, *PaymentPath, []*Router,
178185 secondPathNodes := nodes [mid :]
179186
180187 // Create the sessions keys for the two blinded paths.
181- firstSessionKey , _ := btcec .NewPrivateKey ( )
182- secondSessionKey , _ := btcec .NewPrivateKey ( )
188+ firstSessionKey , _ := btcec .PrivKeyFromBytes ( sessionKeyA )
189+ secondSessionKey , _ := btcec .PrivKeyFromBytes ( sessionKeyB )
183190
184191 // Create the first blinded path, adding a next_path_key_override TLV
185192 // at the last node.
@@ -227,7 +234,7 @@ func newOnionMessageRoute(numHops int) (*OnionPacket, *PaymentPath, []*Router,
227234 }
228235
229236 // Generate the onion packet.
230- sessionKey , _ := btcec .NewPrivateKey ( )
237+ sessionKey , _ := btcec .PrivKeyFromBytes ( sessionKeyC )
231238
232239 onionPacket , err := NewOnionPacket (
233240 & route , sessionKey , nil , DeterministicPacketFiller ,
@@ -267,11 +274,18 @@ func createBlindedPath(firstPathNodes []*Router, secondPathNodes []*Router,
267274 }
268275 }
269276
270- firstBlindedPath , err := BuildBlindedPath (
271- firstSessionKey , firstPathInfos ,
272- )
273- if err != nil {
274- return nil , err
277+ // The first blinded path may be empty if the sender has a direct
278+ // channel with the introduction point.
279+ var firstBlindedPath * BlindedPathInfo
280+ if len (firstPathNodes ) > 0 {
281+ var err error
282+
283+ firstBlindedPath , err = BuildBlindedPath (
284+ firstSessionKey , firstPathInfos ,
285+ )
286+ if err != nil {
287+ return nil , err
288+ }
275289 }
276290
277291 // Create the second blinded path, omitting the next_node_id TLV for the
@@ -308,13 +322,26 @@ func createBlindedPath(firstPathNodes []*Router, secondPathNodes []*Router,
308322 return nil , err
309323 }
310324
311- blindedPath := & BlindedPath {
312- IntroductionPoint : firstBlindedPath .Path .IntroductionPoint ,
313- BlindingPoint : firstBlindedPath .Path .BlindingPoint ,
314- BlindedHops : append (
315- firstBlindedPath .Path .BlindedHops ,
316- secondBlindedPath .Path .BlindedHops ... ,
317- ),
325+ var blindedPath * BlindedPath
326+ if len (firstPathNodes ) == 0 {
327+ // If the sender has a direct channel to the introduction point
328+ // of the blinded path the receiver gave us, then the sender
329+ // doesn't need to create its own blinded path. We can just use
330+ // the receiver's blinded path as is.
331+ blindedPath = secondBlindedPath .Path
332+ } else {
333+ // Otherwise, we use the introduction point of the blinded path
334+ // created by the sender and concatenate the hops of both paths.
335+ introductionPoint := firstBlindedPath .Path .IntroductionPoint
336+ blindingPoint := firstBlindedPath .Path .BlindingPoint
337+ blindedPath = & BlindedPath {
338+ IntroductionPoint : introductionPoint ,
339+ BlindingPoint : blindingPoint ,
340+ BlindedHops : append (
341+ firstBlindedPath .Path .BlindedHops ,
342+ secondBlindedPath .Path .BlindedHops ... ,
343+ ),
344+ }
318345 }
319346
320347 return blindedPath , nil
@@ -601,7 +628,7 @@ func TestSphinxSingleHop(t *testing.T) {
601628 // The destination node should detect that the packet is destined for
602629 // itself.
603630 if processedPacket .Action != ExitNode {
604- t .Fatalf ("processed action is correct , is %v should be %v" ,
631+ t .Fatalf ("processed action is incorrect , is %v should be %v" ,
605632 processedPacket .Action , ExitNode )
606633 }
607634}
@@ -903,6 +930,49 @@ func TestPaymentPathTotalPayloadSizeExceeds1300(t *testing.T) {
903930 "MaxOnionMessagePayloadSize" )
904931}
905932
933+ // TestSingleHopOnionMessage test that we can create and encode a single-hop
934+ // onion message packet.
935+ func TestSingleHopOnionMessage (t * testing.T ) {
936+ t .Parallel ()
937+
938+ packet , route , nodes , err := newOnionMessageRoute (1 )
939+ require .NoError (t , err , "newOnionMessageRoute should not return an " +
940+ "error" )
941+
942+ require .Equal (t , 1 , route .TrueRouteLength ())
943+
944+ // Start the ReplayLog and defer shutdown
945+ err = nodes [0 ].log .Start ()
946+ require .NoError (t , err , "unable to start ReplayLog" )
947+
948+ defer func () {
949+ require .NoError (t , nodes [0 ].log .Stop ())
950+ }()
951+
952+ // In a single-hop onion message the blinding point is the session key
953+ // used by the receiver to create its blinded path. The sender didn't
954+ // need to create a blinded path since it has a direct channel to the
955+ // receiver/introduction point. Knowing this we use the session key that
956+ // the receiver used.
957+ blindingPoint , _ := btcec .PrivKeyFromBytes (sessionKeyB )
958+
959+ // Simulating a direct single-hop onion message, send the sphinx packet
960+ // to the destination node, making it process the packet fully.
961+ processedPacket , err := nodes [0 ].ProcessOnionPacket (
962+ packet , nil , 1 , WithBlindingPoint (blindingPoint .PubKey ()),
963+ )
964+ if err != nil {
965+ t .Fatalf ("unable to process sphinx packet: %v" , err )
966+ }
967+
968+ // The destination node should detect that the packet is destined for
969+ // itself.
970+ if processedPacket .Action != ExitNode {
971+ t .Fatalf ("processed action is incorrect, is %v should be %v" ,
972+ processedPacket .Action , ExitNode )
973+ }
974+ }
975+
906976// TestCustomPayloadSize tests that we can create an onion packet with any size
907977// of routing info.
908978func TestCustomPayloadSize (t * testing.T ) {
0 commit comments