44 "bytes"
55 "crypto/hmac"
66 "crypto/sha256"
7+ "encoding/binary"
8+ "errors"
79 "fmt"
810
911 "github.com/aead/chacha20"
@@ -18,6 +20,8 @@ const (
1820 HMACSize = 32
1921)
2022
23+ var byteOrder = binary .BigEndian
24+
2125// Hash256 is a statically sized, 32-byte array, typically containing
2226// the output of a SHA256 hash.
2327type Hash256 [sha256 .Size ]byte
@@ -91,6 +95,10 @@ type DecryptedError struct {
9195
9296 // Message is the decrypted error message.
9397 Message []byte
98+
99+ // HoldTimesMs is an array of millisecond durations reported by each node on
100+ // the (error) path.
101+ HoldTimesMs []uint64
94102}
95103
96104// zeroHMAC is the special HMAC value that allows the final node to determine
@@ -275,6 +283,7 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
275283 // We'll iterate a constant amount of hops to ensure that we don't give
276284 // away an timing information pertaining to the position in the route
277285 // that the error emanated from.
286+ holdTimesMs := make ([]uint64 , 0 )
278287 for i := 0 ; i < NumMaxHops ; i ++ {
279288 var sharedSecret Hash256
280289
@@ -294,9 +303,6 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
294303
295304 message , payloads , hmacs := getMsgComponents (encryptedData )
296305
297- final := payloads [0 ] == payloadFinal
298- // TODO: Extract hold time from payload.
299-
300306 expectedHmac := calculateHmac (sharedSecret , i , message , payloads , hmacs )
301307 actualHmac := hmacs [i * sha256 .Size : (i + 1 )* sha256 .Size ]
302308
@@ -306,11 +312,23 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
306312 msg = nil
307313 }
308314
309- // If we are at the node that is the source of the error, we can now
310- // save the message in our return variable.
311- if final && sender == 0 {
312- sender = i + 1
313- msg = message
315+ // Extract the payload and exit with a nil message if it is invalid.
316+ payloadType , holdTimeMs , err := extractPayload (payloads )
317+ if sender == 0 {
318+ if err != nil {
319+ sender = i + 1
320+ msg = nil
321+ }
322+
323+ // Store hold time reported by this node.
324+ holdTimesMs = append (holdTimesMs , holdTimeMs )
325+
326+ // If we are at the node that is the source of the error, we can now
327+ // save the message in our return variable.
328+ if payloadType == payloadFinal {
329+ sender = i + 1
330+ msg = message
331+ }
314332 }
315333
316334 // Shift payloads and hmacs to the left to prepare for the next
@@ -329,9 +347,10 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
329347 }
330348
331349 return & DecryptedError {
332- SenderIdx : sender ,
333- Sender : o .circuit .PaymentPath [sender - 1 ],
334- Message : msg ,
350+ SenderIdx : sender ,
351+ Sender : o .circuit .PaymentPath [sender - 1 ],
352+ Message : msg ,
353+ HoldTimesMs : holdTimesMs ,
335354 }, nil
336355}
337356
@@ -472,11 +491,13 @@ func (o *OnionErrorEncrypter) addHmacs(data []byte) {
472491// The reason for using onion obfuscation is to not give
473492// away to the nodes in the payment path the information about the exact
474493// failure and its origin.
475- func (o * OnionErrorEncrypter ) EncryptError (initial bool , data []byte ) []byte {
494+ func (o * OnionErrorEncrypter ) EncryptError (initial bool , data []byte ,
495+ holdTimeMs uint64 ) []byte {
496+
476497 if initial {
477- data = o .initializePayload (data )
498+ data = o .initializePayload (data , holdTimeMs )
478499 } else {
479- o .addIntermediatePayload (data )
500+ o .addIntermediatePayload (data , holdTimeMs )
480501 }
481502
482503 // Update hmac block.
@@ -486,28 +507,52 @@ func (o *OnionErrorEncrypter) EncryptError(initial bool, data []byte) []byte {
486507 return onionEncrypt (& o .sharedSecret , data )
487508}
488509
489- func (o * OnionErrorEncrypter ) initializePayload (message []byte ) []byte {
510+ func (o * OnionErrorEncrypter ) initializePayload (message []byte ,
511+ holdTimeMs uint64 ) []byte {
512+
490513 // Add space for payloads and hmacs.
491514 data := make ([]byte , len (message )+ hmacsAndPayloadsLen )
492515 copy (data , message )
493516
494517 _ , payloads , _ := getMsgComponents (data )
495518
496519 // Signal final hops in the payload.
497- // TODO: Add hold time to payload.
498- payloads [0 ] = payloadFinal
520+ addPayload (payloads , payloadFinal , holdTimeMs )
499521
500522 return data
501523}
502524
503- func (o * OnionErrorEncrypter ) addIntermediatePayload (data []byte ) {
525+ func addPayload (payloads []byte , payloadType PayloadType , holdTimeMs uint64 ) {
526+ byteOrder .PutUint64 (payloads [1 :], uint64 (holdTimeMs ))
527+
528+ payloads [0 ] = byte (payloadType )
529+ }
530+
531+ func extractPayload (payloads []byte ) (PayloadType , uint64 , error ) {
532+ var payloadType PayloadType
533+
534+ switch payloads [0 ] {
535+ case payloadFinal , payloadIntermediate :
536+ payloadType = PayloadType (payloads [0 ])
537+
538+ default :
539+ return 0 , 0 , errors .New ("invalid payload type" )
540+ }
541+
542+ holdTimeMs := byteOrder .Uint64 (payloads [1 :])
543+
544+ return payloadType , holdTimeMs , nil
545+ }
546+
547+ func (o * OnionErrorEncrypter ) addIntermediatePayload (data []byte ,
548+ holdTimeMs uint64 ) {
549+
504550 _ , payloads , hmacs := getMsgComponents (data )
505551
506552 // Shift hmacs and payloads to create space for the payload.
507553 shiftPayloadsRight (payloads )
508554 shiftHmacsRight (hmacs )
509555
510556 // Signal intermediate hop in the payload.
511- // TODO: Add hold time to payload.
512- payloads [0 ] = payloadIntermediate
557+ addPayload (payloads , payloadIntermediate , holdTimeMs )
513558}
0 commit comments