@@ -3,7 +3,9 @@ package sphinx
33import (
44 "bytes"
55 "encoding/hex"
6+ "encoding/json"
67 "fmt"
8+ "io/ioutil"
79 "reflect"
810 "testing"
911
8789)
8890
8991func newTestRoute (numHops int ) ([]* Router , * PaymentPath , * []HopData , * OnionPacket , error ) {
90- extraPayloadSize := make ([]int , numHops )
91- return newTestVarSizeRoute (numHops , extraPayloadSize )
92- }
93-
94- func newTestVarSizeRoute (numHops int , extraPayloadSize []int ) ([]* Router , * PaymentPath , * []HopData , * OnionPacket , error ) {
9592 nodes := make ([]* Router , numHops )
9693
9794 // Create numHops random sphinx nodes.
@@ -118,14 +115,6 @@ func newTestVarSizeRoute(numHops int, extraPayloadSize []int) ([]*Router, *Payme
118115 }
119116 copy (hopData .NextAddress [:], bytes .Repeat ([]byte {byte (i )}, 8 ))
120117
121- var extraData []byte
122-
123- // If we were told to increase the payload with some extra data
124- // do it now.
125- if extraPayloadSize [i ] > 0 {
126- extraData = bytes .Repeat ([]byte {'A' }, extraPayloadSize [i ])
127- }
128-
129118 hopPayload , err := NewHopPayload (& hopData , nil )
130119 if err != nil {
131120 return nil , nil , nil , nil , fmt .Errorf ("unable to " +
@@ -138,9 +127,6 @@ func newTestVarSizeRoute(numHops int, extraPayloadSize []int) ([]*Router, *Payme
138127 }
139128 }
140129
141- for i := 0 ; i < route .TrueRouteLength (); i ++ {
142- }
143-
144130 // Generate a forwarding message to route to the final node via the
145131 // generated intermediate nodes above. Destination should be Hash160,
146132 // adding padding so parsing still works.
@@ -533,61 +519,258 @@ func TestSphinxEncodeDecode(t *testing.T) {
533519 }
534520}
535521
536- // Create an onion with 5 hops, and the 3rd hop
537- func TestMultiFrameEncodeDecode ( t * testing. T ) {
522+ func newEOBRoute ( numHops uint32 ,
523+ eobMapping map [ int ] HopPayload ) ( * OnionPacket , [] * Router , error ) {
538524
539- var payloadtests = []struct {
540- extraPayload []int
541- expectedFrames []int
542- }{
543- {[]int {0 , 0 , 0 , 0 , 0 }, []int {1 , 1 , 1 , 1 , 1 }},
544- {[]int {60 , 0 , 0 , 0 , 0 }, []int {2 , 1 , 1 , 1 , 1 }},
545- {[]int {0 , 0 , 0 , 0 , 60 }, []int {1 , 1 , 1 , 1 , 2 }},
546- {[]int {0 , 0 , 0 , 0 , 130 }, []int {1 , 1 , 1 , 1 , 3 }},
547- {[]int {0 , 0 , 60 , 0 , 0 }, []int {1 , 1 , 2 , 1 , 1 }},
525+ nodes := make ([]* Router , numHops )
526+
527+ if uint32 (len (eobMapping )) != numHops {
528+ return nil , nil , fmt .Errorf ("must provide payload " +
529+ "mapping for all hops" )
530+ }
531+
532+ // First, we'll assemble a set of routers that will consume all the
533+ // hops we create in this path.
534+ for i := 0 ; i < len (nodes ); i ++ {
535+ privKey , err := btcec .NewPrivateKey (btcec .S256 ())
536+ if err != nil {
537+ return nil , nil , fmt .Errorf ("Unable to generate " +
538+ "random key for sphinx node: %v" , err )
539+ }
540+
541+ nodes [i ] = NewRouter (
542+ privKey , & chaincfg .MainNetParams , NewMemoryReplayLog (),
543+ )
544+ }
545+
546+ // Next we'll gather all the pubkeys in the path, checking our eob
547+ // mapping to see which hops need an extra payload.
548+ var (
549+ route PaymentPath
550+ )
551+ for i := 0 ; i < len (nodes ); i ++ {
552+ route [i ] = OnionHop {
553+ NodePub : * nodes [i ].onionKey .PubKey (),
554+ HopPayload : eobMapping [i ],
555+ }
556+ }
557+
558+ // Generate a forwarding message to route to the final node via the
559+ // generated intermdiates nodes above. Destination should be Hash160,
560+ // adding padding so parsing still works.
561+ sessionKey , _ := btcec .PrivKeyFromBytes (
562+ btcec .S256 (), bytes .Repeat ([]byte {'A' }, 32 ),
563+ )
564+ fwdMsg , err := NewOnionPacket (& route , sessionKey , nil )
565+ if err != nil {
566+ return nil , nil , fmt .Errorf ("unable to create forwarding " +
567+ "message: %#v" , err )
568+ }
569+
570+ return fwdMsg , nodes , nil
571+ }
572+
573+ func mustNewHopPayload (hopData * HopData , eob []byte ) HopPayload {
574+ payload , err := NewHopPayload (hopData , eob )
575+ if err != nil {
576+ panic (err )
548577 }
549578
550- for _ , tt := range payloadtests {
551- t .Run (fmt .Sprintf ("%v" , tt ), func (t * testing.T ) {
552- nodes , path , _ , fwdMsg , err := newTestVarSizeRoute (5 , tt .extraPayload )
579+ return payload
580+ }
581+
582+ // TestSphinxHopVariableSizedPayloads tests that we're able to fully decode an
583+ // EOB payload that was targeted at the final hop in a route, and also when
584+ // intermediate nodes have EOB data encoded as well. Additionally, we test that
585+ // we're able to mix the legacy and current format within the same route.
586+ func TestSphinxHopVariableSizedPayloads (t * testing.T ) {
587+ t .Parallel ()
588+
589+ var testCases = []struct {
590+ numNodes uint32
591+ eobMapping map [int ]HopPayload
592+ }{
593+ // A single hop route with a payload going to the last hop in
594+ // the route. The payload is enough to fit into what would be
595+ // the normal frame type, but it's a TLV hop.
596+ {
597+ numNodes : 1 ,
598+ eobMapping : map [int ]HopPayload {
599+ 0 : HopPayload {
600+ Type : PayloadTLV ,
601+ Payload : bytes .Repeat ([]byte ("a" ), HopDataSize - HMACSize ),
602+ },
603+ },
604+ },
605+
606+ // A single hop route where the payload to the final node needs
607+ // to shift more than a single frame.
608+ {
609+ numNodes : 1 ,
610+ eobMapping : map [int ]HopPayload {
611+ 0 : HopPayload {
612+ Type : PayloadTLV ,
613+ Payload : bytes .Repeat ([]byte ("a" ), HopDataSize * 3 ),
614+ },
615+ },
616+ },
617+
618+ // A two hop route, so one going over 3 nodes, with the sender
619+ // encrypting a payload to the final node. The payload of the
620+ // final node will require more shifts than normal to parse the
621+ // data The first hop is a legacy hop containing the usual
622+ // amount of data.
623+ {
624+ numNodes : 2 ,
625+ eobMapping : map [int ]HopPayload {
626+ 0 : mustNewHopPayload (& HopData {
627+ Realm : [1 ]byte {0x00 },
628+ ForwardAmount : 2 ,
629+ OutgoingCltv : 3 ,
630+ NextAddress : [8 ]byte {1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 },
631+ }, nil ),
632+ 1 : HopPayload {
633+ Type : PayloadTLV ,
634+ Payload : bytes .Repeat ([]byte ("a" ), HopDataSize * 2 ),
635+ },
636+ },
637+ },
638+
639+ // A 3 hop route (4 nodes) with all but the middle node
640+ // receiving a TLV payload. Each of the TLV hops will use a
641+ // distinct amount of data in each hop.
642+ {
643+ numNodes : 3 ,
644+ eobMapping : map [int ]HopPayload {
645+ 0 : HopPayload {
646+ Type : PayloadTLV ,
647+ Payload : bytes .Repeat ([]byte ("a" ), 100 ),
648+ },
649+ 1 : mustNewHopPayload (& HopData {
650+ Realm : [1 ]byte {0x00 },
651+ ForwardAmount : 22 ,
652+ OutgoingCltv : 9 ,
653+ NextAddress : [8 ]byte {1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 },
654+ }, nil ),
655+ 2 : HopPayload {
656+ Type : PayloadTLV ,
657+ Payload : bytes .Repeat ([]byte ("a" ), 256 ),
658+ },
659+ },
660+ },
661+
662+ // A 3 hop route (4 nodes), each hop is a TLV hop and will use
663+ // a distinct amount of data for each of their hops.
664+ {
665+ numNodes : 3 ,
666+ eobMapping : map [int ]HopPayload {
667+ 0 : HopPayload {
668+ Type : PayloadTLV ,
669+ Payload : bytes .Repeat ([]byte ("a" ), 200 ),
670+ },
671+ 1 : HopPayload {
672+ Type : PayloadTLV ,
673+ Payload : bytes .Repeat ([]byte ("a" ), 256 ),
674+ },
675+ 2 : HopPayload {
676+ Type : PayloadTLV ,
677+ Payload : bytes .Repeat ([]byte ("a" ), 150 ),
678+ },
679+ },
680+ },
681+ }
682+
683+ for testCaseNum , testCase := range testCases {
684+ nextPkt , routers , err := newEOBRoute (
685+ testCase .numNodes , testCase .eobMapping ,
686+ )
687+ if err != nil {
688+ t .Fatalf ("#%v: unable to create eob " +
689+ "route: %v" , testCase , err )
690+ }
691+
692+ // We'll now walk thru manually each actual hop within the
693+ // route. We use the size of the routers rather than the number
694+ // of hops here as virtual EOB hops may have been inserted into
695+ // the route.
696+ for i := 0 ; i < len (routers ); i ++ {
697+ // Start each node's ReplayLog and defer shutdown
698+ routers [i ].log .Start ()
699+ defer routers [i ].log .Stop ()
700+
701+ currentHop := routers [i ]
702+
703+ // Ensure that this hop is able to properly process
704+ // this onion packet. If additional EOB hops were
705+ // added, then it should be able to properly decrypt
706+ // all the layers and pass them on to the next node
707+ // properly.
708+ processedPacket , err := currentHop .ProcessOnionPacket (
709+ nextPkt , nil , uint32 (i ),
710+ )
553711 if err != nil {
554- t .Fatalf ("unable to create random onion packet: %v" , err )
712+ t .Fatalf ("#%v: unable to process packet at " +
713+ "hop #%v: %v" , testCaseNum , i , err )
555714 }
556715
557- for i := 0 ; i < len (nodes ); i ++ {
558- // Start each node's ReplayLog and defer shutdown
559- nodes [i ].log .Start ()
560- defer nodes [i ].log .Stop ()
716+ // If this hop is expected to have EOB data, then we'll
717+ // check now to ensure the bytes were properly
718+ // recovered on the other end.
719+ eobData := testCase .eobMapping [i ]
720+ if ! reflect .DeepEqual (eobData .Payload ,
721+ processedPacket .Payload .Payload ) {
722+ t .Fatalf ("#%v (hop %v): eob mismatch: expected " +
723+ "%v, got %v" , testCaseNum , i ,
724+ spew .Sdump (eobData .Payload ),
725+ spew .Sdump (processedPacket .Payload .Payload ))
726+ }
561727
562- hop := nodes [i ]
728+ if eobData .Type != processedPacket .Payload .Type {
729+ t .Fatalf ("mismatched types: expected %v " +
730+ "got %v" , eobData .Type ,
731+ processedPacket .Payload .Type )
732+ }
563733
564- t .Logf ("Processing at hop: %v \n " , i )
565- onionPacket , err := hop .ProcessOnionPacket (fwdMsg , nil , uint32 (i )+ 1 )
566- if err != nil {
567- t .Fatalf ("node %v was unable to process the " +
568- "forwarding message: %v" , i , err )
734+ // If this is the last node (but not necessarily hop
735+ // due to EOB expansion), then it should recognize that
736+ // it's the exit node.
737+ if i == len (routers )- 1 {
738+ if processedPacket .Action != ExitNode {
739+ t .Fatalf ("#%v: Processing error, " +
740+ "node %v is the last hop in " +
741+ "the path, yet it doesn't " +
742+ "recognize so" , testCaseNum , i )
569743 }
744+ continue
745+ }
570746
571- // Check that the frame count matches what we expect
572- frameCount := onionPacket . rawPayload . NumFrames ()
573- if tt . expectedFrames [ i ] != frameCount {
574- t . Fatalf ( "incorrect number of payload " +
575- "frames: expected %d, got %d" ,
576- tt . expectedFrames [ i ], frameCount ,
577- )
578- }
747+ // If this isn't the last node in the path, then the
748+ // returned action should indicate that there are more
749+ // hops to go.
750+ if processedPacket . Action != MoreHops {
751+ t . Fatalf ( "#%v: Processing error, node %v is " +
752+ "not the final hop, yet thinks it is." ,
753+ testCaseNum , i )
754+ }
579755
580- // Check that the payload contents are identical
581- expected := path [i ].HopPayload
582- if ! bytes .Equal (path [i ].HopPayload .Payload , expected .Payload ) {
583- t .Fatalf ("processing error, hop-payload " +
584- "parsed incorrectly. expected %x, got %x" ,
585- expected .Payload ,
586- onionPacket .rawPayload .Payload )
756+ // The next hop should have been parsed as node[i+1],
757+ // but only if this was a legacy hop.
758+ if processedPacket .ForwardingInstructions != nil {
759+ parsedNextHop := processedPacket .ForwardingInstructions .NextAddress [:]
760+
761+ expected := bytes .Repeat ([]byte {byte (1 )}, AddressSize )
762+ if ! bytes .Equal (parsedNextHop , expected ) {
763+ t .Fatalf ("#%v: Processing error, next hop parsed " +
764+ "incorrectly. next hop should be %v, " +
765+ "was instead parsed as %v" , testCaseNum ,
766+ hex .EncodeToString (expected ),
767+ hex .EncodeToString (parsedNextHop ))
587768 }
588-
589- fwdMsg = onionPacket .NextPacket
590769 }
591- })
770+
771+ nextPkt = processedPacket .NextPacket
772+ }
773+ }
774+ }
592775 }
593776}
0 commit comments