44 "bytes"
55 "crypto/hmac"
66 "crypto/sha256"
7+ "encoding/binary"
78 "errors"
89 "fmt"
910
@@ -19,6 +20,8 @@ const (
1920 HMACSize = 32
2021)
2122
23+ var byteOrder = binary .BigEndian
24+
2225// Hash256 is a statically sized, 32-byte array, typically containing
2326// the output of a SHA256 hash.
2427type Hash256 [sha256 .Size ]byte
@@ -92,6 +95,10 @@ type DecryptedError struct {
9295
9396 // Message is the decrypted error message.
9497 Message []byte
98+
99+ // HoldTimesMs is an array of millisecond durations reported by each node on
100+ // the (error) path.
101+ HoldTimesMs []uint64
95102}
96103
97104// zeroHMAC is the special HMAC value that allows the final node to determine
@@ -249,10 +256,11 @@ const onionErrorLength = 2 + 2 + 256 + sha256.Size
249256func (o * OnionErrorDecrypter ) DecryptError (encryptedData []byte ) (
250257 * DecryptedError , error ) {
251258
252- // Ensure the error message length is as expected.
253- if len (encryptedData ) != onionErrorLength {
259+ // Ensure the error message length is enough to contain the payloads and
260+ // hmacs blocks.
261+ if len (encryptedData ) < hmacsAndPayloadsLen {
254262 return nil , fmt .Errorf ("invalid error length: " +
255- "expected %v got %v" , onionErrorLength ,
263+ "expected at least %v got %v" , hmacsAndPayloadsLen ,
256264 len (encryptedData ))
257265 }
258266
@@ -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
@@ -292,39 +301,185 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
292301 // encryption from the encrypted error payload.
293302 encryptedData = onionEncrypt (& sharedSecret , encryptedData )
294303
295- // Next, we'll need to separate the data, from the MAC itself
296- // so we can reconstruct and verify it.
297- expectedMac := encryptedData [:sha256 .Size ]
298- data := encryptedData [sha256 .Size :]
299-
300- // With the data split, we'll now re-generate the MAC using its
301- // specified key.
302- umKey := generateKey ("um" , & sharedSecret )
303- h := hmac .New (sha256 .New , umKey [:])
304- h .Write (data )
305-
306- // If the MAC matches up, then we've found the sender of the
307- // error and have also obtained the fully decrypted message.
308- realMac := h .Sum (nil )
309- if hmac .Equal (realMac , expectedMac ) && sender == 0 {
304+ message , payloads , hmacs := getMsgComponents (encryptedData )
305+
306+ expectedHmac := calculateHmac (sharedSecret , i , message , payloads , hmacs )
307+ actualHmac := hmacs [i * sha256 .Size : (i + 1 )* sha256 .Size ]
308+
309+ // If the hmac does not match up, exit with a nil message.
310+ if ! bytes .Equal (actualHmac , expectedHmac [:]) && sender == 0 {
310311 sender = i + 1
311- msg = data
312+ msg = nil
313+ }
314+
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+ }
312332 }
333+
334+ // Shift payloads and hmacs to the left to prepare for the next
335+ // iteration.
336+ shiftPayloadsLeft (payloads )
337+ shiftHmacsLeft (hmacs )
313338 }
314339
315- // If the sender index is still zero, then we haven't found the sender,
316- // meaning we've failed to decrypt.
340+ // If the sender index is still zero, all hmacs checked out but none of the
341+ // payloads was a final payload. In this case we must be dealing with a max
342+ // length route and a final hop that returned an intermediate payload. Blame
343+ // the final hop.
317344 if sender == 0 {
318- return nil , errors .New ("unable to retrieve onion failure" )
345+ sender = NumMaxHops
346+ msg = nil
319347 }
320348
321349 return & DecryptedError {
322- SenderIdx : sender ,
323- Sender : o .circuit .PaymentPath [sender - 1 ],
324- Message : msg ,
350+ SenderIdx : sender ,
351+ Sender : o .circuit .PaymentPath [sender - 1 ],
352+ Message : msg ,
353+ HoldTimesMs : holdTimesMs ,
325354 }, nil
326355}
327356
357+ const (
358+ totalHmacs = (NumMaxHops * (NumMaxHops + 1 )) / 2
359+ allHmacsLen = totalHmacs * sha256 .Size
360+ hmacsAndPayloadsLen = allHmacsLen + allPayloadsLen
361+
362+ // payloadLen is the size of the per-node payload. It consists of a 1-byte
363+ // payload type and an 8-byte hold time.
364+ payloadLen = 1 + 8
365+
366+ allPayloadsLen = payloadLen * NumMaxHops
367+
368+ payloadFinal = 1
369+ payloadIntermediate = 0
370+ )
371+
372+ func shiftHmacsRight (hmacs []byte ) {
373+ if len (hmacs ) != allHmacsLen {
374+ panic ("invalid hmac block length" )
375+ }
376+
377+ srcIdx := totalHmacs - 2
378+ destIdx := totalHmacs - 1
379+ copyLen := 1
380+ for i := 0 ; i < NumMaxHops - 1 ; i ++ {
381+ copy (hmacs [destIdx * sha256 .Size :], hmacs [srcIdx * sha256 .Size :(srcIdx + copyLen )* sha256 .Size ])
382+
383+ copyLen ++
384+
385+ srcIdx -= copyLen + 1
386+ destIdx -= copyLen
387+ }
388+ }
389+
390+ func shiftHmacsLeft (hmacs []byte ) {
391+ if len (hmacs ) != allHmacsLen {
392+ panic ("invalid hmac block length" )
393+ }
394+
395+ srcIdx := NumMaxHops
396+ destIdx := 1
397+ copyLen := NumMaxHops - 1
398+ for i := 0 ; i < NumMaxHops - 1 ; i ++ {
399+ copy (hmacs [destIdx * sha256 .Size :], hmacs [srcIdx * sha256 .Size :(srcIdx + copyLen )* sha256 .Size ])
400+
401+ srcIdx += copyLen
402+ destIdx += copyLen + 1
403+ copyLen --
404+ }
405+ }
406+
407+ func shiftPayloadsRight (payloads []byte ) {
408+ if len (payloads ) != allPayloadsLen {
409+ panic ("invalid payload block length" )
410+ }
411+
412+ copy (payloads [payloadLen :], payloads )
413+ }
414+
415+ func shiftPayloadsLeft (payloads []byte ) {
416+ if len (payloads ) != allPayloadsLen {
417+ panic ("invalid payload block length" )
418+ }
419+
420+ copy (payloads , payloads [payloadLen :NumMaxHops * payloadLen ])
421+ }
422+
423+ // getMsgComponents splits a complete failure message into its components
424+ // without re-allocating memory.
425+ func getMsgComponents (data []byte ) ([]byte , []byte , []byte ) {
426+ payloads := data [len (data )- hmacsAndPayloadsLen : len (data )- allHmacsLen ]
427+ hmacs := data [len (data )- allHmacsLen :]
428+ message := data [:len (data )- hmacsAndPayloadsLen ]
429+
430+ return message , payloads , hmacs
431+ }
432+
433+ // calculateHmac calculates an hmac given a shared secret and a presumed
434+ // position in the path. Position is expressed as the distance to the error
435+ // source. The error source itself is at position 0.
436+ func calculateHmac (sharedSecret Hash256 , position int ,
437+ message , payloads , hmacs []byte ) []byte {
438+
439+ var dataToHmac []byte
440+
441+ // Include payloads including our own.
442+ dataToHmac = append (dataToHmac , payloads [:(NumMaxHops - position )* payloadLen ]... )
443+
444+ // Include downstream hmacs.
445+ var downstreamHmacsIdx = position + NumMaxHops
446+ for j := 0 ; j < NumMaxHops - position - 1 ; j ++ {
447+ dataToHmac = append (dataToHmac , hmacs [downstreamHmacsIdx * sha256 .Size :(downstreamHmacsIdx + 1 )* sha256 .Size ]... )
448+
449+ downstreamHmacsIdx += NumMaxHops - j - 1
450+ }
451+
452+ // Include message.
453+ dataToHmac = append (dataToHmac , message ... )
454+
455+ // Calculate and return hmac.
456+ umKey := generateKey ("um" , & sharedSecret )
457+ hash := hmac .New (sha256 .New , umKey [:])
458+ hash .Write (dataToHmac )
459+
460+ return hash .Sum (nil )
461+ }
462+
463+ // calculateHmac calculates an hmac using the shared secret for this
464+ // OnionErrorEncryptor instance.
465+ func (o * OnionErrorEncrypter ) calculateHmac (position int ,
466+ message , payloads , hmacs []byte ) []byte {
467+
468+ return calculateHmac (o .sharedSecret , position , message , payloads , hmacs )
469+ }
470+
471+ // addHmacs updates the failure data with a series of hmacs corresponding to all
472+ // possible positions in the path for the current node.
473+ func (o * OnionErrorEncrypter ) addHmacs (data []byte ) {
474+ message , payloads , hmacs := getMsgComponents (data )
475+
476+ for i := 0 ; i < NumMaxHops ; i ++ {
477+ hmac := o .calculateHmac (i , message , payloads , hmacs )
478+
479+ copy (hmacs [i * sha256 .Size :], hmac )
480+ }
481+ }
482+
328483// EncryptError is used to make data obfuscation using the generated shared
329484// secret.
330485//
@@ -336,14 +491,68 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
336491// The reason for using onion obfuscation is to not give
337492// away to the nodes in the payment path the information about the exact
338493// failure and its origin.
339- func (o * OnionErrorEncrypter ) EncryptError (initial bool , data []byte ) []byte {
494+ func (o * OnionErrorEncrypter ) EncryptError (initial bool , data []byte ,
495+ holdTimeMs uint64 ) []byte {
496+
340497 if initial {
341- umKey := generateKey ("um" , & o .sharedSecret )
342- hash := hmac .New (sha256 .New , umKey [:])
343- hash .Write (data )
344- h := hash .Sum (nil )
345- data = append (h , data ... )
498+ data = o .initializePayload (data , holdTimeMs )
499+ } else {
500+ o .addIntermediatePayload (data , holdTimeMs )
346501 }
347502
503+ // Update hmac block.
504+ o .addHmacs (data )
505+
506+ // Obfuscate.
348507 return onionEncrypt (& o .sharedSecret , data )
349508}
509+
510+ func (o * OnionErrorEncrypter ) initializePayload (message []byte ,
511+ holdTimeMs uint64 ) []byte {
512+
513+ // Add space for payloads and hmacs.
514+ data := make ([]byte , len (message )+ hmacsAndPayloadsLen )
515+ copy (data , message )
516+
517+ _ , payloads , _ := getMsgComponents (data )
518+
519+ // Signal final hops in the payload.
520+ addPayload (payloads , payloadFinal , holdTimeMs )
521+
522+ return data
523+ }
524+
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+
550+ _ , payloads , hmacs := getMsgComponents (data )
551+
552+ // Shift hmacs and payloads to create space for the payload.
553+ shiftPayloadsRight (payloads )
554+ shiftHmacsRight (hmacs )
555+
556+ // Signal intermediate hop in the payload.
557+ addPayload (payloads , payloadIntermediate , holdTimeMs )
558+ }
0 commit comments