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
@@ -97,6 +106,10 @@ type DecryptedError struct {
97106
98107 // Message is the decrypted error message.
99108 Message []byte
109+
110+ // HoldTimes is an array of hold times reported by each node on the error
111+ // path.
112+ HoldTimes []uint32
100113}
101114
102115// zeroHMAC is the special HMAC value that allows the final node to determine
@@ -301,10 +314,10 @@ func sharedSecret(priv SingleKeyECDH, pub *btcec.PublicKey) (Hash256, error) {
301314// onionEncrypt obfuscates the data with compliance with BOLT#4. As we use a
302315// stream cipher, calling onionEncrypt on an already encrypted piece of data
303316// will decrypt it.
304- func onionEncrypt (sharedSecret * Hash256 , data []byte ) []byte {
317+ func onionEncrypt (keyType string , sharedSecret * Hash256 , data []byte ) []byte {
305318 p := make ([]byte , len (data ))
306319
307- ammagKey := generateKey ("ammag" , sharedSecret )
320+ ammagKey := generateKey (keyType , sharedSecret )
308321 streamBytes := generateCipherStream (ammagKey , uint (len (data )))
309322 xor (p , data , streamBytes )
310323
@@ -324,16 +337,20 @@ const minOnionErrorLength = minPaddedOnionErrorLength + sha256.Size
324337// onion failure is encrypted in backward manner, starting from the node where
325338// error have occurred. As a result, in order to decrypt the error we need get
326339// all shared secret and apply decryption in the reverse order. A structure is
327- // returned that contains the decrypted error message and information on the
328- // sender.
329- func (o * OnionErrorDecrypter ) DecryptError (encryptedData []byte ) (
330- * DecryptedError , error ) {
331-
332- // Ensure the error message length is as expected.
333- if len (encryptedData ) < minOnionErrorLength {
334- return nil , fmt .Errorf ("invalid error length: " +
335- "expected at least %v got %v" , minOnionErrorLength ,
336- len (encryptedData ))
340+ // returned that contains the decrypted error message and information of the
341+ // error sender. We also report the hold times in ms for each hop on the error
342+ // path.
343+ func (o * OnionErrorDecrypter ) DecryptError (encryptedData []byte ,
344+ attrData []byte ) (* DecryptedError , error ) {
345+
346+ // Ensure the error message and attribution data length is as expected.
347+ if len (encryptedData ) < minOnionErrorLength ||
348+ len (attrData ) < o .hmacsAndPayloadsLen () {
349+
350+ return & DecryptedError {
351+ Sender : o .circuit .PaymentPath [0 ],
352+ SenderIdx : 1 ,
353+ }, nil
337354 }
338355
339356 sharedSecrets , err := generateSharedSecrets (
@@ -352,10 +369,16 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
352369 )
353370 copy (dummySecret [:], bytes .Repeat ([]byte {1 }, 32 ))
354371
372+ // Copy the failure message data in a new variable.
373+ failData := make ([]byte , len (encryptedData ))
374+ copy (failData , encryptedData )
375+
376+ hopPayloads := make ([]uint32 , 0 )
377+
355378 // We'll iterate a constant amount of hops to ensure that we don't give
356379 // away an timing information pertaining to the position in the route
357380 // that the error emanated from.
358- for i := 0 ; i < NumMaxHops ; i ++ {
381+ for i := 0 ; i < o . hopCount ; i ++ {
359382 var sharedSecret Hash256
360383
361384 // If we've already found the sender, then we'll use our dummy
@@ -369,13 +392,54 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
369392 }
370393
371394 // With the shared secret, we'll now strip off a layer of
372- // encryption from the encrypted error payload.
373- encryptedData = onionEncrypt (& sharedSecret , encryptedData )
395+ // encryption from the encrypted failure and attribution
396+ // data.
397+ failData = onionEncrypt (AMMAG , & sharedSecret , failData )
398+ attrData = onionEncrypt (AMMAG_EXT , & sharedSecret , attrData )
399+
400+ payloads := o .payloads (attrData )
401+ hmacs := o .hmacs (attrData )
402+
403+ // Let's calculate the HMAC we expect for the corresponding
404+ // payloads.
405+ position := o .hopCount - i - 1
406+ expectedAttrHmac := o .calculateHmac (
407+ sharedSecret , position , failData , payloads , hmacs ,
408+ )
409+
410+ // Let's retrieve the actual HMAC from the correct position in
411+ // the HMACs array.
412+ actualAttrHmac := hmacs [i * o .hmacSize : (i + 1 )* o .hmacSize ]
413+
414+ // If the hmac does not match up, exit with a nil message. This
415+ // is not done for the dummy iterations.
416+ if ! bytes .Equal (actualAttrHmac , expectedAttrHmac ) &&
417+ sender == 0 && i < len (o .circuit .PaymentPath ) {
418+
419+ sender = i + 1
420+ msg = nil
421+ }
374422
375- // Next, we'll need to separate the data, from the MAC itself
376- // so we can reconstruct and verify it.
377- expectedMac := encryptedData [:sha256 .Size ]
378- data := encryptedData [sha256 .Size :]
423+ // Extract the payload and exit with a nil message if it is
424+ // invalid.
425+ holdTime := o .extractPayload (payloads )
426+ if sender == 0 {
427+ // Store hold time reported by this node.
428+ hopPayloads = append (hopPayloads , holdTime )
429+
430+ // Update the message.
431+ msg = failData [sha256 .Size :]
432+ }
433+
434+ // Shift payloads and hmacs to the left to prepare for the next
435+ // iteration.
436+ o .shiftPayloadsLeft (payloads )
437+ o .shiftHmacsLeft (hmacs )
438+
439+ // Next, we'll need to separate the failure data, from the MAC
440+ // itself so we can reconstruct and verify it.
441+ expectedMac := failData [:sha256 .Size ]
442+ data := failData [sha256 .Size :]
379443
380444 // With the data split, we'll now re-generate the MAC using its
381445 // specified key.
@@ -399,12 +463,55 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
399463 }
400464
401465 return & DecryptedError {
402- SenderIdx : sender ,
403466 Sender : o .circuit .PaymentPath [sender - 1 ],
467+ SenderIdx : sender ,
404468 Message : msg ,
469+ HoldTimes : hopPayloads ,
405470 }, nil
406471}
407472
473+ // extractPayload extracts the payload and payload origin information from the
474+ // given byte slice.
475+ func (o * OnionErrorDecrypter ) extractPayload (payloadBytes []byte ) uint32 {
476+ // Extract payload.
477+ holdTime := binary .BigEndian .Uint32 (payloadBytes [0 :o .payloadLen ()])
478+
479+ return holdTime
480+ }
481+
482+ func (o * OnionErrorDecrypter ) shiftPayloadsLeft (payloads []byte ) {
483+ copy (payloads , payloads [o .payloadLen ():o .hopCount * o .payloadLen ()])
484+ }
485+
486+ func (o * OnionErrorDecrypter ) shiftHmacsLeft (hmacs []byte ) {
487+ // Work from left to right to avoid overwriting data that is still
488+ // needed later on in the shift operation.
489+ srcIdx := o .hopCount
490+ destIdx := 0
491+ copyLen := o .hopCount - 1
492+ for i := 0 ; i < o .hopCount - 1 ; i ++ {
493+ // Clear first hmac slot. This slot is for the position farthest
494+ // away from the error source. Because we are shifting, this
495+ // cannot be relevant.
496+ copy (hmacs [destIdx * o .hmacSize :], o .zeroHmac )
497+
498+ // The hmacs of the downstream hop become the remaining hmacs
499+ // for the current hop.
500+ copy (
501+ hmacs [(destIdx + 1 )* o .hmacSize :],
502+ hmacs [srcIdx * o .hmacSize :(srcIdx + copyLen )* o .hmacSize ],
503+ )
504+
505+ srcIdx += copyLen
506+ destIdx += copyLen + 1
507+ copyLen --
508+ }
509+
510+ // Clear the very last hmac slot. Because we just shifted, the most
511+ // downstream hop can never be the error source.
512+ copy (hmacs [destIdx * o .hmacSize :], o .zeroHmac )
513+ }
514+
408515// EncryptError is used to make data obfuscation using the generated shared
409516// secret.
410517//
@@ -413,17 +520,146 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
413520// for backward failure obfuscation of the onion failure blob. By obfuscating
414521// the onion failure on every node in the path we are adding additional step of
415522// 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 {
523+ // The reason for using onion obfuscation is to not give away to the nodes in
524+ // the payment path the information about the exact failure and its origin.
525+ // Every node down the error path reports the recorded hold times for the HTLC,
526+ // so this is also passed as an argument to this function in order for this node
527+ // to append its own value. The attribution data is a structure which helps with
528+ // identifying malicious intermediate hops that may have modified the failure
529+ // data.
530+ func (o * OnionErrorEncrypter ) EncryptError (initial bool , legacyData []byte ,
531+ attrData []byte , holdTime uint32 ) ([]byte , []byte , error ) {
532+
533+ if initial && attrData != nil {
534+ return nil , nil , fmt .Errorf ("unable to encrypt, cannot " +
535+ "initialize error with existing attribution data" )
536+ }
537+
538+ if attrData == nil {
539+ attrData = o .initializePayload (holdTime )
540+ }
541+
420542 if initial {
543+ if len (legacyData ) < minPaddedOnionErrorLength {
544+ return nil , nil , fmt .Errorf ("initial data size less " +
545+ "than %v" , minPaddedOnionErrorLength )
546+ }
547+
421548 umKey := generateKey ("um" , & o .sharedSecret )
422549 hash := hmac .New (sha256 .New , umKey [:])
423- hash .Write (data )
550+ hash .Write (legacyData )
424551 h := hash .Sum (nil )
425- data = append (h , data ... )
552+ legacyData = append (h , legacyData ... )
553+ } else {
554+ if len (attrData ) < o .hmacsAndPayloadsLen () {
555+ return nil , nil , fmt .Errorf ("invalid attribution data" +
556+ "length, have %v expected %v" , len (attrData ),
557+ o .hmacsAndPayloadsLen ())
558+ }
559+
560+ // Add our hold time.
561+ o .addIntermediatePayload (attrData , holdTime )
562+
563+ // Shift hmacs to create space for the new hmacs.
564+ o .shiftHmacsRight (o .hmacs (attrData ))
565+ }
566+
567+ // Update hmac block.
568+ o .addHmacs (attrData , legacyData )
569+
570+ legacy := onionEncrypt (AMMAG , & o .sharedSecret , legacyData )
571+ attrError := onionEncrypt (AMMAG_EXT , & o .sharedSecret , attrData )
572+
573+ return legacy , attrError , nil
574+ }
575+
576+ func (o * OnionErrorEncrypter ) shiftHmacsRight (hmacs []byte ) {
577+ totalHmacs := (o .hopCount * (o .hopCount + 1 )) / 2
578+
579+ // Work from right to left to avoid overwriting data that is still
580+ // needed.
581+ srcIdx := totalHmacs - 2
582+ destIdx := totalHmacs - 1
583+
584+ // The variable copyLen contains the number of hmacs to copy for the
585+ // current hop.
586+ copyLen := 1
587+ for i := 0 ; i < o .hopCount - 1 ; i ++ {
588+ // Shift the hmacs to the right for the current hop. The hmac
589+ // corresponding to the assumed position that is farthest away
590+ // from the error source is discarded.
591+ copy (
592+ hmacs [destIdx * o .hmacSize :],
593+ hmacs [srcIdx * o .hmacSize :(srcIdx + copyLen )* o .hmacSize ],
594+ )
595+
596+ // The number of hmacs to copy increases by one for each
597+ // iteration. The further away from the error source, the more
598+ // downstream hmacs exist that are relevant.
599+ copyLen ++
600+
601+ // Update indices backwards for the next iteration.
602+ srcIdx -= copyLen + 1
603+ destIdx -= copyLen
604+ }
605+
606+ // Zero out the hmac slots corresponding to every possible position
607+ // relative to the error source for the current hop. This is not
608+ // strictly necessary as these slots are overwritten anyway, but we
609+ // clear them for cleanliness.
610+ for i := 0 ; i < o .hopCount ; i ++ {
611+ copy (hmacs [i * o .hmacSize :], o .zeroHmac )
612+ }
613+ }
614+
615+ // addHmacs updates the failure data with a series of hmacs corresponding to all
616+ // possible positions in the path for the current node.
617+ func (o * OnionErrorEncrypter ) addHmacs (data []byte , message []byte ) {
618+ payloads := o .payloads (data )
619+ hmacs := o .hmacs (data )
620+
621+ for i := 0 ; i < o .hopCount ; i ++ {
622+ position := o .hopCount - i - 1
623+ hmac := o .calculateHmac (
624+ o .sharedSecret , position , message , payloads , hmacs ,
625+ )
626+
627+ copy (hmacs [i * o .hmacSize :], hmac )
426628 }
629+ }
630+
631+ func (o * OnionErrorEncrypter ) initializePayload (holdTime uint32 ) []byte {
632+
633+ // Add space for payloads and hmacs.
634+ data := make ([]byte , o .hmacsAndPayloadsLen ())
635+
636+ payloads := o .payloads (data )
637+
638+ // Signal final hops in the payload.
639+ addPayload (payloads , holdTime )
640+
641+ return data
642+ }
643+
644+ func (o * OnionErrorEncrypter ) addIntermediatePayload (data []byte ,
645+ holdTime uint32 ) {
646+
647+ payloads := o .payloads (data )
648+
649+ // Shift payloads to create space for the new payload.
650+ o .shiftPayloadsRight (payloads )
651+
652+ // Signal intermediate hop in the payload.
653+ addPayload (payloads , holdTime )
654+ }
655+
656+ func (o * OnionErrorEncrypter ) shiftPayloadsRight (payloads []byte ) {
657+ copy (payloads [o .payloadLen ():], payloads )
658+ }
659+
660+ func addPayload (payloads []byte , holdTime uint32 ) {
427661
428- return onionEncrypt (& o .sharedSecret , data )
662+ payload := make ([]byte , 4 )
663+ binary .BigEndian .PutUint32 (payload , holdTime )
664+ copy (payloads , payload )
429665}
0 commit comments