@@ -106,6 +106,10 @@ type DecryptedError struct {
106106
107107 // Message is the decrypted error message.
108108 Message []byte
109+
110+ // HoldTimes is an array of hold times reported by each node on the error
111+ // path.
112+ HoldTimes []uint32
109113}
110114
111115// zeroHMAC is the special HMAC value that allows the final node to determine
@@ -333,16 +337,20 @@ const minOnionErrorLength = minPaddedOnionErrorLength + sha256.Size
333337// onion failure is encrypted in backward manner, starting from the node where
334338// error have occurred. As a result, in order to decrypt the error we need get
335339// all shared secret and apply decryption in the reverse order. A structure is
336- // returned that contains the decrypted error message and information on the
337- // sender.
338- func (o * OnionErrorDecrypter ) DecryptError (encryptedData []byte ) (
339- * DecryptedError , error ) {
340-
341- // Ensure the error message length is as expected.
342- if len (encryptedData ) < minOnionErrorLength {
343- return nil , fmt .Errorf ("invalid error length: " +
344- "expected at least %v got %v" , minOnionErrorLength ,
345- 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
346354 }
347355
348356 sharedSecrets , _ , err := generateSharedSecrets (
@@ -361,10 +369,16 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
361369 )
362370 copy (dummySecret [:], bytes .Repeat ([]byte {1 }, 32 ))
363371
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+
364378 // We'll iterate a constant amount of hops to ensure that we don't give
365379 // away an timing information pertaining to the position in the route
366380 // that the error emanated from.
367- for i := 0 ; i < NumMaxHops ; i ++ {
381+ for i := 0 ; i < o . hopCount ; i ++ {
368382 var sharedSecret Hash256
369383
370384 // If we've already found the sender, then we'll use our dummy
@@ -378,15 +392,54 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
378392 }
379393
380394 // With the shared secret, we'll now strip off a layer of
381- // encryption from the encrypted error payload.
382- encryptedData = onionEncrypt (
383- AMMAG , & 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 ,
384408 )
385409
386- // Next, we'll need to separate the data, from the MAC itself
387- // so we can reconstruct and verify it.
388- expectedMac := encryptedData [:sha256 .Size ]
389- data := encryptedData [sha256 .Size :]
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+ }
422+
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 :]
390443
391444 // With the data split, we'll now re-generate the MAC using its
392445 // specified key.
@@ -410,12 +463,55 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
410463 }
411464
412465 return & DecryptedError {
413- SenderIdx : sender ,
414466 Sender : o .circuit .PaymentPath [sender - 1 ],
467+ SenderIdx : sender ,
415468 Message : msg ,
469+ HoldTimes : hopPayloads ,
416470 }, nil
417471}
418472
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+
419515// EncryptError is used to make data obfuscation using the generated shared
420516// secret.
421517//
0 commit comments