@@ -340,17 +340,40 @@ const minOnionErrorLength = minPaddedOnionErrorLength + sha256.Size
340340// returned that contains the decrypted error message and information of the
341341// error sender. We also report the hold times in ms for each hop on the error
342342// path.
343+ //
344+ // The strictAttribution flag controls the behavior of the decryption logic
345+ // surrounding the presence of attribution data:
346+ //
347+ // - If set, then the first node with bad attribution data will be blamed
348+ // immediately.
349+ //
350+ // - If unset, decryption continues optimistically until a successful error
351+ // message decryption occurs, regardless of attribution data validity. Hold
352+ // times are still extracted for nodes that provided valid attribution data.
343353func (o * OnionErrorDecrypter ) DecryptError (encryptedData []byte ,
344- attrData []byte ) (* DecryptedError , error ) {
354+ attrData []byte , strictAttribution bool ) (* DecryptedError , error ) {
345355
346- // Ensure the error message and attribution data length is as expected.
347- if len (encryptedData ) < minOnionErrorLength ||
348- len (attrData ) < o .hmacsAndPayloadsLen () {
356+ // Ensure the error message field is present and has the correct length.
357+ if len (encryptedData ) < minOnionErrorLength {
358+ return nil , fmt .Errorf ("invalid error length: " +
359+ "expected at least %v got %v" , minOnionErrorLength ,
360+ len (encryptedData ))
361+ }
349362
350- return & DecryptedError {
351- Sender : o .circuit .PaymentPath [0 ],
352- SenderIdx : 1 ,
353- }, nil
363+ validAttr := true
364+
365+ // If we're decrypting with strict attribution, we need to have the
366+ // correct attribution data present too. If strictAttribution is set
367+ // then we immediately blame the first hop.
368+ if len (attrData ) < o .hmacsAndPayloadsLen () {
369+ if strictAttribution {
370+ return & DecryptedError {
371+ Sender : o .circuit .PaymentPath [0 ],
372+ SenderIdx : 1 ,
373+ }, nil
374+ } else {
375+ validAttr = false
376+ }
354377 }
355378
356379 sharedSecrets , err := generateSharedSecrets (
@@ -393,49 +416,69 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte,
393416
394417 // With the shared secret, we'll now strip off a layer of
395418 // encryption from the encrypted failure and attribution
396- // data.
419+ // data. This needs to be done before parsing the attribution
420+ // data, as the attribution data HMACs commit to it.
397421 failData = onionEncrypt (AMMAG , & sharedSecret , failData )
398- attrData = onionEncrypt (AMMAG_EXT , & sharedSecret , attrData )
399-
400- payloads := o .payloads (attrData )
401- hmacs := o .hmacs (attrData )
402422
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
423+ // If the attribution data are valid then do another round of
424+ // attribution data decryption.
425+ if validAttr {
426+ attrData = onionEncrypt (
427+ AMMAG_EXT , & sharedSecret , attrData ,
428+ )
429+
430+ payloads := o .payloads (attrData )
431+ hmacs := o .hmacs (attrData )
432+
433+ // Let's calculate the HMAC we expect for the
434+ // corresponding payloads.
435+ position := o .hopCount - i - 1
436+ expectedAttrHmac := o .calculateHmac (
437+ sharedSecret , position , failData , payloads ,
438+ hmacs ,
439+ )
440+
441+ // Let's retrieve the actual HMAC from the correct
442+ // position in the HMACs array.
443+ actualAttrHmac := hmacs [i * o .hmacSize : (i + 1 )* o .hmacSize ]
444+
445+ // If the hmac does not match up, exit with a nil
446+ // message. This is not done for the dummy iterations.
447+ if ! bytes .Equal (actualAttrHmac , expectedAttrHmac ) &&
448+ sender == 0 && i < len (o .circuit .PaymentPath ) {
449+
450+ switch strictAttribution {
451+ case true :
452+ sender = i + 1
453+ msg = nil
454+
455+ case false :
456+ // Flag the attribution data as invalid
457+ // from this point onwards. This will
458+ // prevent the loop from trying to
459+ // extract anything from the attribution
460+ // data.
461+ validAttr = false
462+ }
463+ }
464+
465+ // Extract the payload and exit with a nil message if it
466+ // is invalid.
467+ holdTime := o .extractPayload (payloads )
468+ if sender == 0 {
469+ // Store hold time reported by this node.
470+ hopPayloads = append (hopPayloads , holdTime )
471+
472+ // Update the message.
473+ msg = failData [sha256 .Size :]
474+ }
475+
476+ // Shift payloads and hmacs to the left to prepare for
477+ // the next iteration.
478+ o .shiftPayloadsLeft (payloads )
479+ o .shiftHmacsLeft (hmacs )
421480 }
422481
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-
439482 // Next, we'll need to separate the failure data, from the MAC
440483 // itself so we can reconstruct and verify it.
441484 expectedMac := failData [:sha256 .Size ]
0 commit comments