4
4
"bytes"
5
5
"crypto/hmac"
6
6
"crypto/sha256"
7
+ "encoding/binary"
7
8
"errors"
8
9
"fmt"
9
10
@@ -18,6 +19,14 @@ const (
18
19
// the onion. Any value lower than 32 will truncate the HMAC both
19
20
// during onion creation as well as during the verification.
20
21
HMACSize = 32
22
+
23
+ // AMMAG is the string representation for the ammag key type. Used in
24
+ // cypher stream generation.
25
+ AMMAG = "ammag"
26
+
27
+ // AMMAG_EXT is the string representation for the extended ammag key
28
+ // type. user in cypher stream generation.
29
+ AMMAG_EXT = "ammagext"
21
30
)
22
31
23
32
// chaChaPolyZeroNonce is a slice of zero bytes used in the chacha20poly1305
@@ -301,10 +310,10 @@ func sharedSecret(priv SingleKeyECDH, pub *btcec.PublicKey) (Hash256, error) {
301
310
// onionEncrypt obfuscates the data with compliance with BOLT#4. As we use a
302
311
// stream cipher, calling onionEncrypt on an already encrypted piece of data
303
312
// will decrypt it.
304
- func onionEncrypt (sharedSecret * Hash256 , data []byte ) []byte {
313
+ func onionEncrypt (keyType string , sharedSecret * Hash256 , data []byte ) []byte {
305
314
p := make ([]byte , len (data ))
306
315
307
- ammagKey := generateKey ("ammag" , sharedSecret )
316
+ ammagKey := generateKey (keyType , sharedSecret )
308
317
streamBytes := generateCipherStream (ammagKey , uint (len (data )))
309
318
xor (p , data , streamBytes )
310
319
@@ -370,7 +379,9 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
370
379
371
380
// With the shared secret, we'll now strip off a layer of
372
381
// encryption from the encrypted error payload.
373
- encryptedData = onionEncrypt (& sharedSecret , encryptedData )
382
+ encryptedData = onionEncrypt (
383
+ AMMAG , & sharedSecret , encryptedData ,
384
+ )
374
385
375
386
// Next, we'll need to separate the data, from the MAC itself
376
387
// so we can reconstruct and verify it.
@@ -413,17 +424,146 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
413
424
// for backward failure obfuscation of the onion failure blob. By obfuscating
414
425
// the onion failure on every node in the path we are adding additional step of
415
426
// the security and barrier for malware nodes to retrieve valuable information.
416
- // The reason for using onion obfuscation is to not give
417
- // away to the nodes in the payment path the information about the exact
418
- // failure and its origin.
419
- func (o * OnionErrorEncrypter ) EncryptError (initial bool , data []byte ) []byte {
427
+ // The reason for using onion obfuscation is to not give away to the nodes in
428
+ // the payment path the information about the exact failure and its origin.
429
+ // Every node down the error path reports the recorded hold times for the HTLC,
430
+ // so this is also passed as an argument to this function in order for this node
431
+ // to append its own value. The attribution data is a structure which helps with
432
+ // identifying malicious intermediate hops that may have modified the failure
433
+ // data.
434
+ func (o * OnionErrorEncrypter ) EncryptError (initial bool , legacyData []byte ,
435
+ attrData []byte , holdTime uint32 ) ([]byte , []byte , error ) {
436
+
437
+ if initial && attrData != nil {
438
+ return nil , nil , fmt .Errorf ("unable to encrypt, cannot " +
439
+ "initialize error with existing attribution data" )
440
+ }
441
+
442
+ if attrData == nil {
443
+ attrData = o .initializePayload (holdTime )
444
+ }
445
+
420
446
if initial {
447
+ if len (legacyData ) < minPaddedOnionErrorLength {
448
+ return nil , nil , fmt .Errorf ("initial data size less " +
449
+ "than %v" , minPaddedOnionErrorLength )
450
+ }
451
+
421
452
umKey := generateKey ("um" , & o .sharedSecret )
422
453
hash := hmac .New (sha256 .New , umKey [:])
423
- hash .Write (data )
454
+ hash .Write (legacyData )
424
455
h := hash .Sum (nil )
425
- data = append (h , data ... )
456
+ legacyData = append (h , legacyData ... )
457
+ } else {
458
+ if len (attrData ) < o .hmacsAndPayloadsLen () {
459
+ return nil , nil , fmt .Errorf ("invalid attribution data" +
460
+ "length, have %v expected %v" , len (attrData ),
461
+ o .hmacsAndPayloadsLen ())
462
+ }
463
+
464
+ // Add our hold time.
465
+ o .addIntermediatePayload (attrData , holdTime )
466
+
467
+ // Shift hmacs to create space for the new hmacs.
468
+ o .shiftHmacsRight (o .hmacs (attrData ))
469
+ }
470
+
471
+ // Update hmac block.
472
+ o .addHmacs (attrData , legacyData )
473
+
474
+ legacy := onionEncrypt (AMMAG , & o .sharedSecret , legacyData )
475
+ attrError := onionEncrypt (AMMAG_EXT , & o .sharedSecret , attrData )
476
+
477
+ return legacy , attrError , nil
478
+ }
479
+
480
+ func (o * OnionErrorEncrypter ) shiftHmacsRight (hmacs []byte ) {
481
+ totalHmacs := (o .hopCount * (o .hopCount + 1 )) / 2
482
+
483
+ // Work from right to left to avoid overwriting data that is still
484
+ // needed.
485
+ srcIdx := totalHmacs - 2
486
+ destIdx := totalHmacs - 1
487
+
488
+ // The variable copyLen contains the number of hmacs to copy for the
489
+ // current hop.
490
+ copyLen := 1
491
+ for i := 0 ; i < o .hopCount - 1 ; i ++ {
492
+ // Shift the hmacs to the right for the current hop. The hmac
493
+ // corresponding to the assumed position that is farthest away
494
+ // from the error source is discarded.
495
+ copy (
496
+ hmacs [destIdx * o .hmacSize :],
497
+ hmacs [srcIdx * o .hmacSize :(srcIdx + copyLen )* o .hmacSize ],
498
+ )
499
+
500
+ // The number of hmacs to copy increases by one for each
501
+ // iteration. The further away from the error source, the more
502
+ // downstream hmacs exist that are relevant.
503
+ copyLen ++
504
+
505
+ // Update indices backwards for the next iteration.
506
+ srcIdx -= copyLen + 1
507
+ destIdx -= copyLen
426
508
}
427
509
428
- return onionEncrypt (& o .sharedSecret , data )
510
+ // Zero out the hmac slots corresponding to every possible position
511
+ // relative to the error source for the current hop. This is not
512
+ // strictly necessary as these slots are overwritten anyway, but we
513
+ // clear them for cleanliness.
514
+ for i := 0 ; i < o .hopCount ; i ++ {
515
+ copy (hmacs [i * o .hmacSize :], o .zeroHmac )
516
+ }
517
+ }
518
+
519
+ // addHmacs updates the failure data with a series of hmacs corresponding to all
520
+ // possible positions in the path for the current node.
521
+ func (o * OnionErrorEncrypter ) addHmacs (data []byte , message []byte ) {
522
+ payloads := o .payloads (data )
523
+ hmacs := o .hmacs (data )
524
+
525
+ for i := 0 ; i < o .hopCount ; i ++ {
526
+ position := o .hopCount - i - 1
527
+ hmac := o .calculateHmac (
528
+ o .sharedSecret , position , message , payloads , hmacs ,
529
+ )
530
+
531
+ copy (hmacs [i * o .hmacSize :], hmac )
532
+ }
533
+ }
534
+
535
+ func (o * OnionErrorEncrypter ) initializePayload (holdTime uint32 ) []byte {
536
+
537
+ // Add space for payloads and hmacs.
538
+ data := make ([]byte , o .hmacsAndPayloadsLen ())
539
+
540
+ payloads := o .payloads (data )
541
+
542
+ // Signal final hops in the payload.
543
+ addPayload (payloads , holdTime )
544
+
545
+ return data
546
+ }
547
+
548
+ func (o * OnionErrorEncrypter ) addIntermediatePayload (data []byte ,
549
+ holdTime uint32 ) {
550
+
551
+ payloads := o .payloads (data )
552
+
553
+ // Shift payloads to create space for the new payload.
554
+ o .shiftPayloadsRight (payloads )
555
+
556
+ // Signal intermediate hop in the payload.
557
+ addPayload (payloads , holdTime )
558
+ }
559
+
560
+ func (o * OnionErrorEncrypter ) shiftPayloadsRight (payloads []byte ) {
561
+ copy (payloads [o .payloadLen ():], payloads )
562
+ }
563
+
564
+ func addPayload (payloads []byte , holdTime uint32 ) {
565
+
566
+ payload := make ([]byte , 4 )
567
+ binary .BigEndian .PutUint32 (payload , holdTime )
568
+ copy (payloads , payload )
429
569
}
0 commit comments