4
4
"bytes"
5
5
"crypto/hmac"
6
6
"crypto/sha256"
7
+ "encoding/binary"
7
8
"errors"
8
9
"fmt"
9
10
@@ -19,6 +20,8 @@ const (
19
20
HMACSize = 32
20
21
)
21
22
23
+ var byteOrder = binary .BigEndian
24
+
22
25
// Hash256 is a statically sized, 32-byte array, typically containing
23
26
// the output of a SHA256 hash.
24
27
type Hash256 [sha256 .Size ]byte
@@ -92,6 +95,10 @@ type DecryptedError struct {
92
95
93
96
// Message is the decrypted error message.
94
97
Message []byte
98
+
99
+ // HoldTimesMs is an array of millisecond durations reported by each node on
100
+ // the (error) path.
101
+ HoldTimesMs []uint64
95
102
}
96
103
97
104
// zeroHMAC is the special HMAC value that allows the final node to determine
@@ -249,10 +256,11 @@ const onionErrorLength = 2 + 2 + 256 + sha256.Size
249
256
func (o * OnionErrorDecrypter ) DecryptError (encryptedData []byte ) (
250
257
* DecryptedError , error ) {
251
258
252
- // Ensure the error message length is as expected.
253
- if len (encryptedData ) != onionErrorLength {
259
+ // Ensure the error message length is enough to contain the payloads and
260
+ // hmacs blocks.
261
+ if len (encryptedData ) < hmacsAndPayloadsLen {
254
262
return nil , fmt .Errorf ("invalid error length: " +
255
- "expected %v got %v" , onionErrorLength ,
263
+ "expected at least %v got %v" , hmacsAndPayloadsLen ,
256
264
len (encryptedData ))
257
265
}
258
266
@@ -275,6 +283,7 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
275
283
// We'll iterate a constant amount of hops to ensure that we don't give
276
284
// away an timing information pertaining to the position in the route
277
285
// that the error emanated from.
286
+ holdTimesMs := make ([]uint64 , 0 )
278
287
for i := 0 ; i < NumMaxHops ; i ++ {
279
288
var sharedSecret Hash256
280
289
@@ -292,39 +301,185 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
292
301
// encryption from the encrypted error payload.
293
302
encryptedData = onionEncrypt (& sharedSecret , encryptedData )
294
303
295
- // Next, we'll need to separate the data, from the MAC itself
296
- // so we can reconstruct and verify it.
297
- expectedMac := encryptedData [:sha256 .Size ]
298
- data := encryptedData [sha256 .Size :]
299
-
300
- // With the data split, we'll now re-generate the MAC using its
301
- // specified key.
302
- umKey := generateKey ("um" , & sharedSecret )
303
- h := hmac .New (sha256 .New , umKey [:])
304
- h .Write (data )
305
-
306
- // If the MAC matches up, then we've found the sender of the
307
- // error and have also obtained the fully decrypted message.
308
- realMac := h .Sum (nil )
309
- if hmac .Equal (realMac , expectedMac ) && sender == 0 {
304
+ message , payloads , hmacs := getMsgComponents (encryptedData )
305
+
306
+ expectedHmac := calculateHmac (sharedSecret , i , message , payloads , hmacs )
307
+ actualHmac := hmacs [i * sha256 .Size : (i + 1 )* sha256 .Size ]
308
+
309
+ // If the hmac does not match up, exit with a nil message.
310
+ if ! bytes .Equal (actualHmac , expectedHmac [:]) && sender == 0 {
310
311
sender = i + 1
311
- msg = data
312
+ msg = nil
313
+ }
314
+
315
+ // Extract the payload and exit with a nil message if it is invalid.
316
+ payloadType , holdTimeMs , err := extractPayload (payloads )
317
+ if sender == 0 {
318
+ if err != nil {
319
+ sender = i + 1
320
+ msg = nil
321
+ }
322
+
323
+ // Store hold time reported by this node.
324
+ holdTimesMs = append (holdTimesMs , holdTimeMs )
325
+
326
+ // If we are at the node that is the source of the error, we can now
327
+ // save the message in our return variable.
328
+ if payloadType == payloadFinal {
329
+ sender = i + 1
330
+ msg = message
331
+ }
312
332
}
333
+
334
+ // Shift payloads and hmacs to the left to prepare for the next
335
+ // iteration.
336
+ shiftPayloadsLeft (payloads )
337
+ shiftHmacsLeft (hmacs )
313
338
}
314
339
315
- // If the sender index is still zero, then we haven't found the sender,
316
- // meaning we've failed to decrypt.
340
+ // If the sender index is still zero, all hmacs checked out but none of the
341
+ // payloads was a final payload. In this case we must be dealing with a max
342
+ // length route and a final hop that returned an intermediate payload. Blame
343
+ // the final hop.
317
344
if sender == 0 {
318
- return nil , errors .New ("unable to retrieve onion failure" )
345
+ sender = NumMaxHops
346
+ msg = nil
319
347
}
320
348
321
349
return & DecryptedError {
322
- SenderIdx : sender ,
323
- Sender : o .circuit .PaymentPath [sender - 1 ],
324
- Message : msg ,
350
+ SenderIdx : sender ,
351
+ Sender : o .circuit .PaymentPath [sender - 1 ],
352
+ Message : msg ,
353
+ HoldTimesMs : holdTimesMs ,
325
354
}, nil
326
355
}
327
356
357
+ const (
358
+ totalHmacs = (NumMaxHops * (NumMaxHops + 1 )) / 2
359
+ allHmacsLen = totalHmacs * sha256 .Size
360
+ hmacsAndPayloadsLen = allHmacsLen + allPayloadsLen
361
+
362
+ // payloadLen is the size of the per-node payload. It consists of a 1-byte
363
+ // payload type and an 8-byte hold time.
364
+ payloadLen = 1 + 8
365
+
366
+ allPayloadsLen = payloadLen * NumMaxHops
367
+
368
+ payloadFinal = 1
369
+ payloadIntermediate = 0
370
+ )
371
+
372
+ func shiftHmacsRight (hmacs []byte ) {
373
+ if len (hmacs ) != allHmacsLen {
374
+ panic ("invalid hmac block length" )
375
+ }
376
+
377
+ srcIdx := totalHmacs - 2
378
+ destIdx := totalHmacs - 1
379
+ copyLen := 1
380
+ for i := 0 ; i < NumMaxHops - 1 ; i ++ {
381
+ copy (hmacs [destIdx * sha256 .Size :], hmacs [srcIdx * sha256 .Size :(srcIdx + copyLen )* sha256 .Size ])
382
+
383
+ copyLen ++
384
+
385
+ srcIdx -= copyLen + 1
386
+ destIdx -= copyLen
387
+ }
388
+ }
389
+
390
+ func shiftHmacsLeft (hmacs []byte ) {
391
+ if len (hmacs ) != allHmacsLen {
392
+ panic ("invalid hmac block length" )
393
+ }
394
+
395
+ srcIdx := NumMaxHops
396
+ destIdx := 1
397
+ copyLen := NumMaxHops - 1
398
+ for i := 0 ; i < NumMaxHops - 1 ; i ++ {
399
+ copy (hmacs [destIdx * sha256 .Size :], hmacs [srcIdx * sha256 .Size :(srcIdx + copyLen )* sha256 .Size ])
400
+
401
+ srcIdx += copyLen
402
+ destIdx += copyLen + 1
403
+ copyLen --
404
+ }
405
+ }
406
+
407
+ func shiftPayloadsRight (payloads []byte ) {
408
+ if len (payloads ) != allPayloadsLen {
409
+ panic ("invalid payload block length" )
410
+ }
411
+
412
+ copy (payloads [payloadLen :], payloads )
413
+ }
414
+
415
+ func shiftPayloadsLeft (payloads []byte ) {
416
+ if len (payloads ) != allPayloadsLen {
417
+ panic ("invalid payload block length" )
418
+ }
419
+
420
+ copy (payloads , payloads [payloadLen :NumMaxHops * payloadLen ])
421
+ }
422
+
423
+ // getMsgComponents splits a complete failure message into its components
424
+ // without re-allocating memory.
425
+ func getMsgComponents (data []byte ) ([]byte , []byte , []byte ) {
426
+ payloads := data [len (data )- hmacsAndPayloadsLen : len (data )- allHmacsLen ]
427
+ hmacs := data [len (data )- allHmacsLen :]
428
+ message := data [:len (data )- hmacsAndPayloadsLen ]
429
+
430
+ return message , payloads , hmacs
431
+ }
432
+
433
+ // calculateHmac calculates an hmac given a shared secret and a presumed
434
+ // position in the path. Position is expressed as the distance to the error
435
+ // source. The error source itself is at position 0.
436
+ func calculateHmac (sharedSecret Hash256 , position int ,
437
+ message , payloads , hmacs []byte ) []byte {
438
+
439
+ var dataToHmac []byte
440
+
441
+ // Include payloads including our own.
442
+ dataToHmac = append (dataToHmac , payloads [:(NumMaxHops - position )* payloadLen ]... )
443
+
444
+ // Include downstream hmacs.
445
+ var downstreamHmacsIdx = position + NumMaxHops
446
+ for j := 0 ; j < NumMaxHops - position - 1 ; j ++ {
447
+ dataToHmac = append (dataToHmac , hmacs [downstreamHmacsIdx * sha256 .Size :(downstreamHmacsIdx + 1 )* sha256 .Size ]... )
448
+
449
+ downstreamHmacsIdx += NumMaxHops - j - 1
450
+ }
451
+
452
+ // Include message.
453
+ dataToHmac = append (dataToHmac , message ... )
454
+
455
+ // Calculate and return hmac.
456
+ umKey := generateKey ("um" , & sharedSecret )
457
+ hash := hmac .New (sha256 .New , umKey [:])
458
+ hash .Write (dataToHmac )
459
+
460
+ return hash .Sum (nil )
461
+ }
462
+
463
+ // calculateHmac calculates an hmac using the shared secret for this
464
+ // OnionErrorEncryptor instance.
465
+ func (o * OnionErrorEncrypter ) calculateHmac (position int ,
466
+ message , payloads , hmacs []byte ) []byte {
467
+
468
+ return calculateHmac (o .sharedSecret , position , message , payloads , hmacs )
469
+ }
470
+
471
+ // addHmacs updates the failure data with a series of hmacs corresponding to all
472
+ // possible positions in the path for the current node.
473
+ func (o * OnionErrorEncrypter ) addHmacs (data []byte ) {
474
+ message , payloads , hmacs := getMsgComponents (data )
475
+
476
+ for i := 0 ; i < NumMaxHops ; i ++ {
477
+ hmac := o .calculateHmac (i , message , payloads , hmacs )
478
+
479
+ copy (hmacs [i * sha256 .Size :], hmac )
480
+ }
481
+ }
482
+
328
483
// EncryptError is used to make data obfuscation using the generated shared
329
484
// secret.
330
485
//
@@ -336,14 +491,68 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
336
491
// The reason for using onion obfuscation is to not give
337
492
// away to the nodes in the payment path the information about the exact
338
493
// failure and its origin.
339
- func (o * OnionErrorEncrypter ) EncryptError (initial bool , data []byte ) []byte {
494
+ func (o * OnionErrorEncrypter ) EncryptError (initial bool , data []byte ,
495
+ holdTimeMs uint64 ) []byte {
496
+
340
497
if initial {
341
- umKey := generateKey ("um" , & o .sharedSecret )
342
- hash := hmac .New (sha256 .New , umKey [:])
343
- hash .Write (data )
344
- h := hash .Sum (nil )
345
- data = append (h , data ... )
498
+ data = o .initializePayload (data , holdTimeMs )
499
+ } else {
500
+ o .addIntermediatePayload (data , holdTimeMs )
346
501
}
347
502
503
+ // Update hmac block.
504
+ o .addHmacs (data )
505
+
506
+ // Obfuscate.
348
507
return onionEncrypt (& o .sharedSecret , data )
349
508
}
509
+
510
+ func (o * OnionErrorEncrypter ) initializePayload (message []byte ,
511
+ holdTimeMs uint64 ) []byte {
512
+
513
+ // Add space for payloads and hmacs.
514
+ data := make ([]byte , len (message )+ hmacsAndPayloadsLen )
515
+ copy (data , message )
516
+
517
+ _ , payloads , _ := getMsgComponents (data )
518
+
519
+ // Signal final hops in the payload.
520
+ addPayload (payloads , payloadFinal , holdTimeMs )
521
+
522
+ return data
523
+ }
524
+
525
+ func addPayload (payloads []byte , payloadType PayloadType , holdTimeMs uint64 ) {
526
+ byteOrder .PutUint64 (payloads [1 :], uint64 (holdTimeMs ))
527
+
528
+ payloads [0 ] = byte (payloadType )
529
+ }
530
+
531
+ func extractPayload (payloads []byte ) (PayloadType , uint64 , error ) {
532
+ var payloadType PayloadType
533
+
534
+ switch payloads [0 ] {
535
+ case payloadFinal , payloadIntermediate :
536
+ payloadType = PayloadType (payloads [0 ])
537
+
538
+ default :
539
+ return 0 , 0 , errors .New ("invalid payload type" )
540
+ }
541
+
542
+ holdTimeMs := byteOrder .Uint64 (payloads [1 :])
543
+
544
+ return payloadType , holdTimeMs , nil
545
+ }
546
+
547
+ func (o * OnionErrorEncrypter ) addIntermediatePayload (data []byte ,
548
+ holdTimeMs uint64 ) {
549
+
550
+ _ , payloads , hmacs := getMsgComponents (data )
551
+
552
+ // Shift hmacs and payloads to create space for the payload.
553
+ shiftPayloadsRight (payloads )
554
+ shiftHmacsRight (hmacs )
555
+
556
+ // Signal intermediate hop in the payload.
557
+ addPayload (payloads , payloadIntermediate , holdTimeMs )
558
+ }
0 commit comments