@@ -12,8 +12,9 @@ import (
1212)
1313
1414const (
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.
122245func 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+
226353type 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+
232365type 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+
238378type 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+
244390type 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+
252403type 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+
265431type 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+
270441func equalPubKeys (pkStr string , pk * btcec.PublicKey ) bool {
271442 return hex .EncodeToString (pk .SerializeCompressed ()) == pkStr
272443}
0 commit comments