Skip to content

Commit 4f2dbed

Browse files
committed
sphinx_test: onion message packet creation
TestTLVPayloadMessagePacket creates a onion message with payload and the blinded route from the test vector. It then checks if the onion packet we create is equal to the one provided in the test vector.
1 parent b9c2b35 commit 4f2dbed

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed

sphinx_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"encoding/hex"
66
"encoding/json"
7+
"errors"
78
"fmt"
89
"io"
910
"os"
@@ -39,6 +40,58 @@ var (
3940
testLegacyRouteNumHops = 20
4041
)
4142

43+
// Static errors used in tests.
44+
var (
45+
// ErrInsufficientHops is returned when there are not enough hops to
46+
// create a route.
47+
ErrInsufficientHops = errors.New("at least 2 hops are required to " +
48+
"create an onion message route")
49+
)
50+
51+
// encodeTLVRecord encodes a TLV record with the given type and value.
52+
func encodeTLVRecord(recordType uint64, value []byte) []byte {
53+
var buf bytes.Buffer
54+
55+
// Encode type as varint
56+
writeVarInt(&buf, recordType)
57+
58+
// Encode length as varint
59+
writeVarInt(&buf, uint64(len(value)))
60+
61+
// Write value
62+
buf.Write(value)
63+
64+
return buf.Bytes()
65+
}
66+
67+
// writeVarInt writes a variable-length integer to the buffer.
68+
func writeVarInt(buf *bytes.Buffer, n uint64) {
69+
switch {
70+
case n < 0xfd:
71+
buf.WriteByte(byte(n))
72+
case n <= 0xffff:
73+
buf.WriteByte(0xfd)
74+
buf.WriteByte(byte(n))
75+
buf.WriteByte(byte(n >> 8))
76+
case n <= 0xffffffff:
77+
buf.WriteByte(0xfe)
78+
buf.WriteByte(byte(n))
79+
buf.WriteByte(byte(n >> 8))
80+
buf.WriteByte(byte(n >> 16))
81+
buf.WriteByte(byte(n >> 24))
82+
default:
83+
buf.WriteByte(0xff)
84+
buf.WriteByte(byte(n))
85+
buf.WriteByte(byte(n >> 8))
86+
buf.WriteByte(byte(n >> 16))
87+
buf.WriteByte(byte(n >> 24))
88+
buf.WriteByte(byte(n >> 32))
89+
buf.WriteByte(byte(n >> 40))
90+
buf.WriteByte(byte(n >> 48))
91+
buf.WriteByte(byte(n >> 56))
92+
}
93+
}
94+
4295
func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacket, error) {
4396
nodes := make([]*Router, numHops)
4497

@@ -162,6 +215,90 @@ func TestBolt4Packet(t *testing.T) {
162215
}
163216
}
164217

218+
// TestTLVPayloadMessagePacket tests the creation and encoding of an onion
219+
// message packet that uses a TLV payload for each hop in the route. This test
220+
// uses the test vectors defined in the BOLT 4 specification. The test reads a
221+
// JSON file containing a predefined route, session key, and the expected final
222+
// onion packet. It then constructs the route hop-by-hop, manually creating the
223+
// TLV payload for each, before creating a new onion packet with NewOnionPacket.
224+
// The test concludes by asserting that the newly encoded packet is identical to
225+
// the one specified in the test vector.
226+
func TestTLVPayloadMessagePacket(t *testing.T) {
227+
t.Parallel()
228+
229+
// First, we'll read out the raw JSON file at the target location.
230+
jsonBytes, err := os.ReadFile(testOnionMessageFileName)
231+
require.NoError(t, err)
232+
233+
// Once we have the raw file, we'll unpack it into our
234+
// onionMessageJsonTestCase struct defined in path_test.go.
235+
testCase := &onionMessageJsonTestCase{}
236+
require.NoError(t, json.Unmarshal(jsonBytes, testCase))
237+
238+
// Next, we'll populate a new OnionHop using the information included
239+
// in this test case.
240+
var route PaymentPath
241+
for i, hop := range testCase.Route.Hops {
242+
blindedPKbytes, err := hex.DecodeString(hop.BlindedNodeID)
243+
require.NoError(t, err)
244+
245+
blindedPubKey, err := btcec.ParsePubKey(blindedPKbytes)
246+
require.NoError(t, err)
247+
248+
encryptedRecipientData, err := hex.DecodeString(
249+
hop.EncryptedRecipientData,
250+
)
251+
require.NoError(t, err)
252+
253+
// Manually encode our onion payload
254+
var b bytes.Buffer
255+
256+
if i == len(testCase.Route.Hops)-1 {
257+
helloBytes := []byte("hello")
258+
// Encode TLV record for type 1 (hello message)
259+
b.Write(encodeTLVRecord(1, helloBytes))
260+
}
261+
262+
// Encode TLV record for type 4 (encrypted recipient data)
263+
b.Write(encodeTLVRecord(4, encryptedRecipientData))
264+
265+
route[i] = OnionHop{
266+
NodePub: *blindedPubKey,
267+
HopPayload: HopPayload{
268+
// Onion messages always use TLV payloads.
269+
Type: PayloadTLV,
270+
Payload: b.Bytes(),
271+
},
272+
}
273+
}
274+
275+
finalPacket, err := hex.DecodeString(
276+
testCase.OnionMessage.OnionMessagePacket,
277+
)
278+
require.NoError(t, err)
279+
280+
sessionKeyBytes, err := hex.DecodeString(testCase.Generate.SessionKey)
281+
282+
require.NoError(t, err)
283+
284+
// With all the required data assembled, we'll craft a new packet.
285+
sessionKey, _ := btcec.PrivKeyFromBytes(sessionKeyBytes)
286+
287+
pkt, err := NewOnionPacket(
288+
&route, sessionKey, nil, DeterministicPacketFiller,
289+
)
290+
require.NoError(t, err)
291+
292+
var b bytes.Buffer
293+
require.NoError(t, pkt.Encode(&b))
294+
295+
// Finally, we expect that our packet matches the packet included in
296+
// the spec's test vectors.
297+
require.Equalf(t, finalPacket, b.Bytes(), "final packet does not "+
298+
"match expected BOLT 4 packet, want: %s, got %s",
299+
hex.EncodeToString(finalPacket), hex.EncodeToString(b.Bytes()))
300+
}
301+
165302
func TestSphinxCorrectness(t *testing.T) {
166303
nodes, _, hopDatas, fwdMsg, err := newTestRoute(testLegacyRouteNumHops)
167304
if err != nil {
@@ -755,6 +892,9 @@ const (
755892

756893
// testTLVFileName is the name of the tlv-payload-only onion test file.
757894
testTLVFileName = "testdata/onion-test.json"
895+
896+
// testOnionMessageFileName is the name of the onion message test file.
897+
testOnionMessageFileName = "testdata/blinded-onion-message-onion-test.json" //nolint:lll
758898
)
759899

760900
type jsonHop struct {

0 commit comments

Comments
 (0)