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. user 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,146 @@ 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 , 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
426508 }
427509
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 )
429569}
0 commit comments