Skip to content

Commit fcae597

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 45c8e38 commit fcae597

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed

sphinx_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,49 @@ var (
3939
testLegacyRouteNumHops = 20
4040
)
4141

42+
// encodeTLVRecord encodes a TLV record with the given type and value.
43+
func encodeTLVRecord(recordType uint64, value []byte) []byte {
44+
var buf bytes.Buffer
45+
46+
// Encode type as varint
47+
writeVarInt(&buf, recordType)
48+
49+
// Encode length as varint
50+
writeVarInt(&buf, uint64(len(value)))
51+
52+
// Write value
53+
buf.Write(value)
54+
55+
return buf.Bytes()
56+
}
57+
58+
// writeVarInt writes a variable-length integer to the buffer.
59+
func writeVarInt(buf *bytes.Buffer, n uint64) {
60+
if n < 0xfd {
61+
buf.WriteByte(byte(n))
62+
} else if n <= 0xffff {
63+
buf.WriteByte(0xfd)
64+
buf.WriteByte(byte(n))
65+
buf.WriteByte(byte(n >> 8))
66+
} else if n <= 0xffffffff {
67+
buf.WriteByte(0xfe)
68+
buf.WriteByte(byte(n))
69+
buf.WriteByte(byte(n >> 8))
70+
buf.WriteByte(byte(n >> 16))
71+
buf.WriteByte(byte(n >> 24))
72+
} else {
73+
buf.WriteByte(0xff)
74+
buf.WriteByte(byte(n))
75+
buf.WriteByte(byte(n >> 8))
76+
buf.WriteByte(byte(n >> 16))
77+
buf.WriteByte(byte(n >> 24))
78+
buf.WriteByte(byte(n >> 32))
79+
buf.WriteByte(byte(n >> 40))
80+
buf.WriteByte(byte(n >> 48))
81+
buf.WriteByte(byte(n >> 56))
82+
}
83+
}
84+
4285
func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacket, error) {
4386
nodes := make([]*Router, numHops)
4487

@@ -162,6 +205,89 @@ func TestBolt4Packet(t *testing.T) {
162205
}
163206
}
164207

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

756882
// testTLVFileName is the name of the tlv-payload-only onion test file.
757883
testTLVFileName = "testdata/onion-test.json"
884+
885+
// testOnionMessageFileName is the name of the onion message test file.
886+
testOnionMessageFileName = "testdata/blinded-onion-message-onion-test.json"
758887
)
759888

760889
type jsonHop struct {

0 commit comments

Comments
 (0)