44 "bytes"
55 "crypto/hmac"
66 "crypto/sha256"
7+ "encoding/binary"
78 "errors"
89 "fmt"
910
@@ -18,6 +19,14 @@ const (
1819 // the onion. Any value lower than 32 will truncate the HMAC both
1920 // during onion creation as well as during the verification.
2021 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. Used in cypher stream generation.
29+ AMMAG_EXT = "ammagext"
2130)
2231
2332// chaChaPolyZeroNonce is a slice of zero bytes used in the chacha20poly1305
@@ -301,10 +310,10 @@ func sharedSecret(priv SingleKeyECDH, pub *btcec.PublicKey) (Hash256, error) {
301310// onionEncrypt obfuscates the data with compliance with BOLT#4. As we use a
302311// stream cipher, calling onionEncrypt on an already encrypted piece of data
303312// will decrypt it.
304- func onionEncrypt (sharedSecret * Hash256 , data []byte ) []byte {
313+ func onionEncrypt (keyType string , sharedSecret * Hash256 , data []byte ) []byte {
305314 p := make ([]byte , len (data ))
306315
307- ammagKey := generateKey ("ammag" , sharedSecret )
316+ ammagKey := generateKey (keyType , sharedSecret )
308317 streamBytes := generateCipherStream (ammagKey , uint (len (data )))
309318 xor (p , data , streamBytes )
310319
@@ -370,7 +379,9 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
370379
371380 // With the shared secret, we'll now strip off a layer of
372381 // encryption from the encrypted error payload.
373- encryptedData = onionEncrypt (& sharedSecret , encryptedData )
382+ encryptedData = onionEncrypt (
383+ AMMAG , & sharedSecret , encryptedData ,
384+ )
374385
375386 // Next, we'll need to separate the data, from the MAC itself
376387 // so we can reconstruct and verify it.
@@ -413,17 +424,141 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
413424// for backward failure obfuscation of the onion failure blob. By obfuscating
414425// the onion failure on every node in the path we are adding additional step of
415426// 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+
420446 if initial {
447+ if len (legacyData ) < minPaddedOnionErrorLength {
448+ return nil , nil , fmt .Errorf ("initial data size less " +
449+ "than %v" , minPaddedOnionErrorLength )
450+ }
451+
421452 umKey := generateKey ("um" , & o .sharedSecret )
422453 hash := hmac .New (sha256 .New , umKey [:])
423- hash .Write (data )
454+ hash .Write (legacyData )
424455 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 , ErrInvalidAttrStructure
460+ }
461+
462+ // Add our hold time.
463+ o .addIntermediatePayload (attrData , holdTime )
464+
465+ // Shift hmacs to create space for the new hmacs.
466+ o .shiftHmacsRight (o .hmacs (attrData ))
467+ }
468+
469+ // Update hmac block.
470+ o .addHmacs (attrData , legacyData )
471+
472+ legacy := onionEncrypt (AMMAG , & o .sharedSecret , legacyData )
473+ attrError := onionEncrypt (AMMAG_EXT , & o .sharedSecret , attrData )
474+
475+ return legacy , attrError , nil
476+ }
477+
478+ func (o * OnionErrorEncrypter ) shiftHmacsRight (hmacs []byte ) {
479+ totalHmacs := (o .hopCount * (o .hopCount + 1 )) / 2
480+
481+ // Work from right to left to avoid overwriting data that is still
482+ // needed.
483+ srcIdx := totalHmacs - 2
484+ destIdx := totalHmacs - 1
485+
486+ // The variable copyLen contains the number of hmacs to copy for the
487+ // current hop.
488+ copyLen := 1
489+ for i := 0 ; i < o .hopCount - 1 ; i ++ {
490+ // Shift the hmacs to the right for the current hop. The hmac
491+ // corresponding to the assumed position that is farthest away
492+ // from the error source is discarded.
493+ copy (
494+ hmacs [destIdx * o .hmacSize :],
495+ hmacs [srcIdx * o .hmacSize :(srcIdx + copyLen )* o .hmacSize ],
496+ )
497+
498+ // The number of hmacs to copy increases by one for each
499+ // iteration. The further away from the error source, the more
500+ // downstream hmacs exist that are relevant.
501+ copyLen ++
502+
503+ // Update indices backwards for the next iteration.
504+ srcIdx -= copyLen + 1
505+ destIdx -= copyLen
506+ }
507+
508+ // Zero out the hmac slots corresponding to every possible position
509+ // relative to the error source for the current hop. This is not
510+ // strictly necessary as these slots are overwritten anyway, but we
511+ // clear them for cleanliness.
512+ for i := 0 ; i < o .hopCount ; i ++ {
513+ copy (hmacs [i * o .hmacSize :], o .zeroHmac )
426514 }
515+ }
516+
517+ // addHmacs updates the failure data with a series of hmacs corresponding to all
518+ // possible positions in the path for the current node.
519+ func (o * OnionErrorEncrypter ) addHmacs (data []byte , message []byte ) {
520+ payloads := o .payloads (data )
521+ hmacs := o .hmacs (data )
522+
523+ for i := 0 ; i < o .hopCount ; i ++ {
524+ position := o .hopCount - i - 1
525+ hmac := o .calculateHmac (
526+ o .sharedSecret , position , message , payloads , hmacs ,
527+ )
528+
529+ copy (hmacs [i * o .hmacSize :], hmac )
530+ }
531+ }
532+
533+ func (o * OnionErrorEncrypter ) initializePayload (holdTime uint32 ) []byte {
534+
535+ // Add space for payloads and hmacs.
536+ data := make ([]byte , o .hmacsAndPayloadsLen ())
537+
538+ payloads := o .payloads (data )
539+
540+ // Signal final hops in the payload.
541+ addPayload (payloads , holdTime )
542+
543+ return data
544+ }
545+
546+ func (o * OnionErrorEncrypter ) addIntermediatePayload (data []byte ,
547+ holdTime uint32 ) {
548+
549+ payloads := o .payloads (data )
550+
551+ // Shift payloads to create space for the new payload.
552+ o .shiftPayloadsRight (payloads )
553+
554+ // Signal intermediate hop in the payload.
555+ addPayload (payloads , holdTime )
556+ }
557+
558+ func (o * OnionErrorEncrypter ) shiftPayloadsRight (payloads []byte ) {
559+ copy (payloads [o .payloadLen ():], payloads )
560+ }
427561
428- return onionEncrypt (& o .sharedSecret , data )
562+ func addPayload (payloads []byte , holdTime uint32 ) {
563+ binary .BigEndian .PutUint32 (payloads , holdTime )
429564}
0 commit comments