Skip to content

Commit 7f17f54

Browse files
committed
sphinx: replace prior EOB test with TestSphinxHopVariableSizedPayloads
We no longer have the concept of frames, so the old test no longer applies.
1 parent 60a2703 commit 7f17f54

File tree

1 file changed

+242
-59
lines changed

1 file changed

+242
-59
lines changed

sphinx_test.go

Lines changed: 242 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package sphinx
33
import (
44
"bytes"
55
"encoding/hex"
6+
"encoding/json"
67
"fmt"
8+
"io/ioutil"
79
"reflect"
810
"testing"
911

@@ -87,11 +89,6 @@ var (
8789
)
8890

8991
func 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

Comments
 (0)