Skip to content

Commit 538e0d7

Browse files
committed
sphinx_test: add blinded onion message test
This commit adds the spec test vector for blinded onion messages. It also adds a test that tests BuildBlindedRoute, decryptBlindedHopData and NextEphemeral against this vector.
1 parent 827d8fc commit 538e0d7

File tree

2 files changed

+316
-2
lines changed

2 files changed

+316
-2
lines changed

path_test.go

Lines changed: 173 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import (
1212
)
1313

1414
const (
15-
routeBlindingTestFileName = "testdata/route-blinding-test.json"
16-
onionRouteBlindingTestFileName = "testdata/onion-route-blinding-test.json"
15+
routeBlindingTestFileName = "testdata/route-blinding-test.json"
16+
onionRouteBlindingTestFileName = "testdata/onion-route-blinding-test.json"
17+
blindedOnionMessageOnionTestFileName = "testdata/blinded-onion-message-onion-test.json"
1718
)
1819

1920
// TestBuildBlindedRoute tests BuildBlindedRoute and decryptBlindedHopData against
@@ -117,6 +118,128 @@ func TestBuildBlindedRoute(t *testing.T) {
117118
}
118119
}
119120

121+
// TestBuildOnionMessageBlindedRoute tests BuildBlindedRoute,
122+
// decryptBlindedHopData and NextEphemeral against the spec test vectors.
123+
func TestBuildOnionMessageBlindedRoute(t *testing.T) {
124+
t.Parallel()
125+
126+
// First, we'll read out the raw Json file at the target location.
127+
jsonBytes, err := os.ReadFile(blindedOnionMessageOnionTestFileName)
128+
require.NoError(t, err)
129+
130+
// Once we have the raw file, we'll unpack it into our
131+
// blindingJsonTestCase struct defined below.
132+
testCase := &onionMessageJsonTestCase{}
133+
require.NoError(t, json.Unmarshal(jsonBytes, testCase))
134+
require.Len(t, testCase.Generate.Hops, 4)
135+
136+
// buildMessagePath is a helper closure used to convert
137+
// hopOnionMessageData objects into HopInfo objects.
138+
buildMessagePath := func(h []hopOnionMessageData, e string) []*HopInfo {
139+
path := make([]*HopInfo, len(h))
140+
currentHop := e
141+
for i, hop := range h {
142+
// The json test vector only specifies the current node
143+
// ID as the next node id in the payload of the previous
144+
// node, so we get that from the previous hop.
145+
nodeIDStr, _ := hex.DecodeString(
146+
currentHop,
147+
)
148+
nodeID, _ := btcec.ParsePubKey(nodeIDStr)
149+
payload, _ := hex.DecodeString(hop.EncryptedDataTlv)
150+
151+
path[i] = &HopInfo{
152+
NodePub: nodeID,
153+
PlainText: payload,
154+
}
155+
currentHop = hop.EncodedOnionMessageTLVs.NextNodeID
156+
}
157+
return path
158+
}
159+
160+
// First, Dave will build a blinded path from Bob to itself.
161+
DaveSessKey := privKeyFromString(
162+
testCase.Generate.Hops[1].PathKeySecret,
163+
)
164+
daveBobPath := buildMessagePath(
165+
testCase.Generate.Hops[1:], bolt4PubKeys[1],
166+
)
167+
pathDB, err := BuildBlindedPath(DaveSessKey, daveBobPath)
168+
require.NoError(t, err)
169+
170+
// At this point, Dave will give his blinded path to the Sender who will
171+
// then build its own blinded route from itself to Bob via Alice. The
172+
// sender will then concatenate the two paths. Note that in the payload
173+
// for Alice, the `next_path_key_override` field is added which is set
174+
// to the first path key in Dave's blinded route. This will indicate to
175+
// Alice that she should use this point for the next path key instead of
176+
// the next path key that she derives.
177+
aliceBobPath := buildMessagePath(
178+
testCase.Generate.Hops[:1], bolt4PubKeys[0],
179+
)
180+
senderSessKey := privKeyFromString(
181+
testCase.Generate.Hops[0].PathKeySecret,
182+
)
183+
pathAB, err := BuildBlindedPath(senderSessKey, aliceBobPath)
184+
require.NoError(t, err)
185+
186+
// Construct the concatenated path.
187+
path := &BlindedPath{
188+
IntroductionPoint: pathAB.Path.IntroductionPoint,
189+
BlindingPoint: pathAB.Path.BlindingPoint,
190+
BlindedHops: append(pathAB.Path.BlindedHops,
191+
pathDB.Path.BlindedHops...),
192+
}
193+
194+
// Check that the constructed path is equal to the test vector path.
195+
require.True(t, equalPubKeys(
196+
testCase.Route.FirstNodeId, path.IntroductionPoint,
197+
))
198+
require.True(t, equalPubKeys(
199+
testCase.Route.FirstPathKey, path.BlindingPoint,
200+
))
201+
202+
for i, hop := range testCase.Route.Hops {
203+
require.True(t, equalPubKeys(
204+
hop.BlindedNodeID, path.BlindedHops[i].BlindedNodePub,
205+
))
206+
207+
data, _ := hex.DecodeString(hop.EncryptedRecipientData)
208+
require.True(
209+
t, bytes.Equal(data, path.BlindedHops[i].CipherText),
210+
)
211+
}
212+
213+
// Assert that each hop is able to decode the encrypted data meant for
214+
// it.
215+
for i, hop := range testCase.Decrypt.Hops {
216+
genData := testCase.Generate.Hops[i]
217+
priv := privKeyFromString(hop.PrivKey)
218+
ephem := pubKeyFromString(
219+
genData.EphemeralPubKey,
220+
)
221+
222+
// Now we'll decrypt the blinded hop data using the private key
223+
// and the ephemeral public key.
224+
data, err := decryptBlindedHopData(
225+
&PrivKeyECDH{PrivKey: priv}, ephem,
226+
path.BlindedHops[i].CipherText,
227+
)
228+
require.NoError(t, err)
229+
230+
// check if the decrypted data is what we expect it to be.
231+
decoded, _ := hex.DecodeString(genData.EncryptedDataTlv)
232+
require.True(t, bytes.Equal(data, decoded))
233+
234+
nextEphem, err := NextEphemeral(&PrivKeyECDH{priv}, ephem)
235+
require.NoError(t, err)
236+
237+
nextE := privKeyFromString(genData.NextEphemeralPrivKey)
238+
239+
require.Equal(t, nextE.PubKey(), nextEphem)
240+
}
241+
}
242+
120243
// TestOnionRouteBlinding tests that an onion packet can correctly be processed
121244
// by a node in a blinded route.
122245
func TestOnionRouteBlinding(t *testing.T) {
@@ -223,24 +346,47 @@ type decryptData struct {
223346
Hops []decryptHops `json:"hops"`
224347
}
225348

349+
type decryptOnionMessageData struct {
350+
Hops []decryptOnionMessageHops `json:"hops"`
351+
}
352+
226353
type decryptHops struct {
227354
Onion string `json:"onion"`
228355
NodePrivKey string `json:"node_privkey"`
229356
NextBlinding string `json:"next_blinding"`
230357
}
231358

359+
type decryptOnionMessageHops struct {
360+
OnionMessage string `json:"onion_message"`
361+
PrivKey string `json:"privkey"`
362+
NextNodeID string `json:"next_node_id"`
363+
}
364+
232365
type blindingJsonTestCase struct {
233366
Generate generateData `json:"generate"`
234367
Route routeData `json:"route"`
235368
Unblind unblindData `json:"unblind"`
236369
}
237370

371+
type onionMessageJsonTestCase struct {
372+
Generate generateOnionMessageData `json:"generate"`
373+
Route routeOnionMessageData `json:"route"`
374+
// OnionMessage onionMessageData `json:"onionmessage"`
375+
Decrypt decryptOnionMessageData `json:"decrypt"`
376+
}
377+
238378
type routeData struct {
239379
IntroductionNodeID string `json:"introduction_node_id"`
240380
Blinding string `json:"blinding"`
241381
Hops []blindedHop `json:"hops"`
242382
}
243383

384+
type routeOnionMessageData struct {
385+
FirstNodeId string `json:"first_node_id"`
386+
FirstPathKey string `json:"first_path_key"`
387+
Hops []blindedOnionMessageHop `json:"hops"`
388+
}
389+
244390
type unblindData struct {
245391
Hops []unblindedHop `json:"hops"`
246392
}
@@ -249,6 +395,11 @@ type generateData struct {
249395
Hops []hopData `json:"hops"`
250396
}
251397

398+
type generateOnionMessageData struct {
399+
SessionKey string `json:"session_key"`
400+
Hops []hopOnionMessageData `json:"hops"`
401+
}
402+
252403
type unblindedHop struct {
253404
NodePrivKey string `json:"node_privkey"`
254405
EphemeralPubKey string `json:"ephemeral_pubkey"`
@@ -262,11 +413,31 @@ type hopData struct {
262413
EncodedTLVs string `json:"encoded_tlvs"`
263414
}
264415

416+
type hopOnionMessageData struct {
417+
PathKeySecret string `json:"path_key_secret"`
418+
EncodedOnionMessageTLVs encodedOnionMessageTLVs `json:"tlvs"`
419+
EncryptedDataTlv string `json:"encrypted_data_tlv"`
420+
EphemeralPubKey string `json:"E"`
421+
NextEphemeralPrivKey string `json:"next_e"`
422+
}
423+
424+
type encodedOnionMessageTLVs struct {
425+
NextNodeID string `json:"next_node_id"`
426+
NextPathKeyOverride string `json:"next_path_key_override"`
427+
PathKeyOverrideSecret string `json:"path_key_override_secret"`
428+
PathID string `json:"path_id"`
429+
}
430+
265431
type blindedHop struct {
266432
BlindedNodeID string `json:"blinded_node_id"`
267433
EncryptedData string `json:"encrypted_data"`
268434
}
269435

436+
type blindedOnionMessageHop struct {
437+
BlindedNodeID string `json:"blinded_node_id"`
438+
EncryptedRecipientData string `json:"encrypted_recipient_data"`
439+
}
440+
270441
func equalPubKeys(pkStr string, pk *btcec.PublicKey) bool {
271442
return hex.EncodeToString(pk.SerializeCompressed()) == pkStr
272443
}

0 commit comments

Comments
 (0)