diff --git a/channeldb/mp_payment.go b/channeldb/mp_payment.go index 0e29db95da..3137be640e 100644 --- a/channeldb/mp_payment.go +++ b/channeldb/mp_payment.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "io" + "math" "time" "github.com/btcsuite/btcd/btcec/v2" @@ -298,7 +299,7 @@ func deserializeHTLCFailInfo(r io.Reader) (*HTLCFailInfo, error) { // Read failure. failureBytes, err := wire.ReadVarBytes( - r, 0, lnwire.FailureMessageLength, "failure", + r, 0, math.MaxUint16, "failure", ) if err != nil { return nil, err diff --git a/docs/release-notes/release-notes-0.16.0.md b/docs/release-notes/release-notes-0.16.0.md index 1bc9bfb135..cc4c199903 100644 --- a/docs/release-notes/release-notes-0.16.0.md +++ b/docs/release-notes/release-notes-0.16.0.md @@ -5,6 +5,10 @@ * Warning messages from peers are now recognized and [logged](https://github.com/lightningnetwork/lnd/pull/6546) by lnd. +* Decrypt onion failure messages with a [length greater than 256 + bytes](https://github.com/lightningnetwork/lnd/pull/6913). This moves LND + closer to being spec compliant. + ## RPC * The `RegisterConfirmationsNtfn` call of the `chainnotifier` RPC sub-server diff --git a/go.mod b/go.mod index c95f1358eb..5433b550c6 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/kkdai/bstream v1.0.0 github.com/lightninglabs/neutrino v0.14.2 github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display - github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5 + github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1 github.com/lightningnetwork/lnd/cert v1.1.1 github.com/lightningnetwork/lnd/clock v1.1.0 github.com/lightningnetwork/lnd/healthcheck v1.2.2 diff --git a/go.sum b/go.sum index 30e24f44ab..4003fcfdfd 100644 --- a/go.sum +++ b/go.sum @@ -440,8 +440,8 @@ github.com/lightninglabs/neutrino v0.14.2 h1:yrnZUCYMZ5ECtXhgDrzqPq2oX8awoAN2D/c github.com/lightninglabs/neutrino v0.14.2/go.mod h1:OICUeTCn+4Tu27YRJIpWvvqySxx4oH4vgdP33Sw9RDc= github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display h1:RZJ8H4ueU/aQ9pFtx5wqsuD3B/DezrewJeVwDKKYY8E= github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display/go.mod h1:2oKOBU042GKFHrdbgGiKax4xVrFiZu51lhacUZQ9MnE= -github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5 h1:TkKwqFcQTGYoI+VEqyxA8rxpCin8qDaYX0AfVRinT3k= -github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5/go.mod h1:7dDx73ApjEZA0kcknI799m2O5kkpfg4/gr7N092ojNo= +github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1 h1:Wm0g70gkcAu2pGpNZwfWPSVOY21j8IyYsNewwK4OkT4= +github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1/go.mod h1:7dDx73ApjEZA0kcknI799m2O5kkpfg4/gr7N092ojNo= github.com/lightningnetwork/lnd/cert v1.1.1 h1:Nsav0RlIDRbOnzz2Yu69SQlK939IKya3Q2S0mDviIN8= github.com/lightningnetwork/lnd/cert v1.1.1/go.mod h1:1P46svkkd73oSoeI4zjkVKgZNwGq8bkGuPR8z+5vQUs= github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= diff --git a/htlcswitch/failure_test.go b/htlcswitch/failure_test.go new file mode 100644 index 0000000000..3dfadc8820 --- /dev/null +++ b/htlcswitch/failure_test.go @@ -0,0 +1,86 @@ +package htlcswitch + +import ( + "encoding/hex" + "encoding/json" + "os" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + sphinx "github.com/lightningnetwork/lightning-onion" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" +) + +// TestLongFailureMessage tests that longer failure messages can be interpreted +// correctly. +func TestLongFailureMessage(t *testing.T) { + t.Parallel() + + var testData struct { + SessionKey string + Path []string + Reason string + } + + // Use long 1024-byte test vector from BOLT 04. + testDataBytes, err := os.ReadFile("testdata/long_failure_msg.json") + require.NoError(t, err) + require.NoError(t, json.Unmarshal(testDataBytes, &testData)) + + sessionKeyBytes, _ := hex.DecodeString(testData.SessionKey) + + reason, _ := hex.DecodeString(testData.Reason) + + sphinxPath := make([]*btcec.PublicKey, len(testData.Path)) + for i, sKey := range testData.Path { + bKey, err := hex.DecodeString(sKey) + require.NoError(t, err) + + key, err := btcec.ParsePubKey(bKey) + require.NoError(t, err) + + sphinxPath[i] = key + } + + sessionKey, _ := btcec.PrivKeyFromBytes(sessionKeyBytes) + + circuit := &sphinx.Circuit{ + SessionKey: sessionKey, + PaymentPath: sphinxPath, + } + + errorDecryptor := &SphinxErrorDecrypter{ + OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(circuit), + } + + // Assert that the failure message can still be extracted. + failure, err := errorDecryptor.DecryptError(reason) + require.NoError(t, err) + + incorrectDetails, ok := failure.msg.(*lnwire.FailIncorrectDetails) + require.True(t, ok) + + var value varBytesRecordProducer + + extraData := incorrectDetails.ExtraOpaqueData() + typeMap, err := extraData.ExtractRecords(&value) + require.NoError(t, err) + require.Len(t, typeMap, 1) + + expectedValue := make([]byte, 300) + for i := range expectedValue { + expectedValue[i] = 128 + } + + require.Equal(t, expectedValue, value.data) +} + +type varBytesRecordProducer struct { + data []byte +} + +func (v *varBytesRecordProducer) Record() tlv.Record { + return tlv.MakePrimitiveRecord(34001, &v.data) +} diff --git a/htlcswitch/link.go b/htlcswitch/link.go index c3c824afc1..23c37cebd0 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -2,6 +2,7 @@ package htlcswitch import ( "bytes" + crand "crypto/rand" "crypto/sha256" "fmt" prand "math/rand" @@ -1850,6 +1851,35 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { } case *lnwire.UpdateFailHTLC: + // Verify that the failure reason is at least 256 bytes plus + // overhead. + const minimumFailReasonLength = lnwire.FailureMessageLength + + 2 + 2 + 32 + + if len(msg.Reason) < minimumFailReasonLength { + // We've received a reason with a non-compliant length. + // Older nodes happily relay back these failures that + // may originate from a node further downstream. + // Therefore we can't just fail the channel. + // + // We want to be compliant ourselves, so we also can't + // pass back the reason unmodified. And we must make + // sure that we don't hit the magic length check of 260 + // bytes in processRemoteSettleFails either. + // + // Because the reason is unreadable for the payer + // anyway, we just replace it by a compliant-length + // series of random bytes. + msg.Reason = make([]byte, minimumFailReasonLength) + _, err := crand.Read(msg.Reason[:]) + if err != nil { + l.log.Errorf("Random generation error: %v", err) + + return + } + } + + // Add fail to the update log. idx := msg.ID err := l.channel.ReceiveFailHTLC(idx, msg.Reason[:]) if err != nil { diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index b16f3893c5..cd3890d17c 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -2255,11 +2255,14 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) { // With that processed, we'll now generate an HTLC fail (sent by the // remote peer) to cancel the HTLC we just added. This should return us // back to the bandwidth of the link right before the HTLC was sent. - err = bobChannel.FailHTLC(bobIndex, []byte("nop"), nil, nil, nil) + reason := make([]byte, 292) + copy(reason, []byte("nop")) + + err = bobChannel.FailHTLC(bobIndex, reason, nil, nil, nil) require.NoError(t, err, "unable to fail htlc") failMsg := &lnwire.UpdateFailHTLC{ ID: 1, - Reason: lnwire.OpaqueReason([]byte("nop")), + Reason: lnwire.OpaqueReason(reason), } aliceLink.HandleChannelUpdate(failMsg) @@ -2431,7 +2434,8 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) { outgoingChanID: addPkt.outgoingChanID, outgoingHTLCID: addPkt.outgoingHTLCID, htlc: &lnwire.UpdateFailHTLC{ - ID: 1, + ID: 1, + Reason: reason, }, obfuscator: NewMockObfuscator(), } @@ -6606,3 +6610,104 @@ func assertFailureCode(t *testing.T, err error, code lnwire.FailCode) { code, rtErr.WireMessage().Code()) } } + +// TestChannelLinkShortFailureRelay tests that failure reasons that are too +// short are replaced by a spec-compliant length failure message and relayed +// back. +func TestChannelLinkShortFailureRelay(t *testing.T) { + t.Parallel() + + defer timeout()() + + const chanAmt = btcutil.SatoshiPerBitcoin * 5 + + aliceLink, bobChannel, batchTicker, start, _, err := + newSingleLinkTestHarness(t, chanAmt, 0) + require.NoError(t, err, "unable to create link") + + require.NoError(t, start()) + + coreLink, ok := aliceLink.(*channelLink) + require.True(t, ok) + + mockPeer, ok := coreLink.cfg.Peer.(*mockPeer) + require.True(t, ok) + + aliceMsgs := mockPeer.sentMsgs + switchChan := make(chan *htlcPacket) + + coreLink.cfg.ForwardPackets = func(linkQuit chan struct{}, _ bool, + packets ...*htlcPacket) error { + + for _, p := range packets { + switchChan <- p + } + + return nil + } + + ctx := linkTestContext{ + t: t, + aliceLink: aliceLink, + aliceMsgs: aliceMsgs, + bobChannel: bobChannel, + } + + // Send and lock in htlc from Alice to Bob. + const htlcID = 0 + + htlc, _ := generateHtlcAndInvoice(t, htlcID) + ctx.sendHtlcAliceToBob(htlcID, htlc) + ctx.receiveHtlcAliceToBob() + + batchTicker <- time.Now() + + ctx.receiveCommitSigAliceToBob(1) + ctx.sendRevAndAckBobToAlice() + + ctx.sendCommitSigBobToAlice(1) + ctx.receiveRevAndAckAliceToBob() + + // Return a short htlc failure from Bob to Alice and lock in. + shortReason := make([]byte, 260) + + err = bobChannel.FailHTLC(0, shortReason, nil, nil, nil) + require.NoError(t, err) + + aliceLink.HandleChannelUpdate(&lnwire.UpdateFailHTLC{ + ID: htlcID, + Reason: shortReason, + }) + + ctx.sendCommitSigBobToAlice(0) + ctx.receiveRevAndAckAliceToBob() + ctx.receiveCommitSigAliceToBob(0) + ctx.sendRevAndAckBobToAlice() + + // Assert that switch gets the fail message. + msg := <-switchChan + + htlcFailMsg, ok := msg.htlc.(*lnwire.UpdateFailHTLC) + require.True(t, ok) + + // Assert that it is not a converted error. + require.False(t, msg.convertedError) + + // Assert that the length is corrected to the spec-compliant length of + // 256 bytes plus overhead. + require.Len(t, htlcFailMsg.Reason, 292) + + // Stop the link + aliceLink.Stop() + + // Check that no unexpected messages were sent. + select { + case msg := <-aliceMsgs: + require.Fail(t, "did not expect message %T", msg) + + case msg := <-switchChan: + require.Fail(t, "did not expect switch message %T", msg) + + default: + } +} diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index 9eac221dd2..b7bc9b34a6 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -419,12 +419,16 @@ func (o *mockObfuscator) Reextract( return nil } +var fakeHmac = []byte("hmachmachmachmachmachmachmachmac") + func (o *mockObfuscator) EncryptFirstHop(failure lnwire.FailureMessage) ( lnwire.OpaqueReason, error) { o.failure = failure var b bytes.Buffer + b.Write(fakeHmac) + if err := lnwire.EncodeFailure(&b, failure, 0); err != nil { return nil, err } @@ -436,7 +440,12 @@ func (o *mockObfuscator) IntermediateEncrypt(reason lnwire.OpaqueReason) lnwire. } func (o *mockObfuscator) EncryptMalformedError(reason lnwire.OpaqueReason) lnwire.OpaqueReason { - return reason + var b bytes.Buffer + b.Write(fakeHmac) + + b.Write(reason) + + return b.Bytes() } // mockDeobfuscator mock implementation of the failure deobfuscator which @@ -447,7 +456,13 @@ func newMockDeobfuscator() ErrorDecrypter { return &mockDeobfuscator{} } -func (o *mockDeobfuscator) DecryptError(reason lnwire.OpaqueReason) (*ForwardingError, error) { +func (o *mockDeobfuscator) DecryptError(reason lnwire.OpaqueReason) ( + *ForwardingError, error) { + + if !bytes.Equal(reason[:32], fakeHmac) { + return nil, errors.New("fake decryption error") + } + reason = reason[32:] r := bytes.NewReader(reason) failure, err := lnwire.DecodeFailure(r, 0) diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go index 461d8875a8..4d666b7607 100644 --- a/htlcswitch/switch_test.go +++ b/htlcswitch/switch_test.go @@ -1,7 +1,6 @@ package htlcswitch import ( - "bytes" "crypto/rand" "crypto/sha256" "fmt" @@ -23,7 +22,6 @@ import ( "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/ticker" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -4088,10 +4086,10 @@ func TestSwitchHoldForward(t *testing.T) { expectedFailure := &lnwire.FailInvalidOnionKey{ OnionSHA256: shaOnionBlob, } - var b bytes.Buffer - require.NoError(t, lnwire.EncodeFailure(&b, expectedFailure, 0)) - assert.Equal(t, lnwire.OpaqueReason(b.Bytes()), failPacket.Reason) + fwdErr, err := newMockDeobfuscator().DecryptError(failPacket.Reason) + require.NoError(t, err) + require.Equal(t, expectedFailure, fwdErr.WireMessage()) assertNumCircuits(t, c.s, 0, 0) @@ -5515,10 +5513,13 @@ func testSwitchAliasInterceptFail(t *testing.T, zeroConf bool) { failHtlc, ok := failPacket.htlc.(*lnwire.UpdateFailHTLC) require.True(t, ok) - r := bytes.NewReader(failHtlc.Reason) - failure, err := lnwire.DecodeFailure(r, 0) + fwdErr, err := newMockDeobfuscator().DecryptError( + failHtlc.Reason, + ) require.NoError(t, err) + failure := fwdErr.WireMessage() + failureMsg, ok := failure.(*lnwire.FailTemporaryChannelFailure) require.True(t, ok) diff --git a/htlcswitch/testdata/long_failure_msg.json b/htlcswitch/testdata/long_failure_msg.json new file mode 100644 index 0000000000..aabe6adc47 --- /dev/null +++ b/htlcswitch/testdata/long_failure_msg.json @@ -0,0 +1,11 @@ +{ + "sessionKey": "4141414141414141414141414141414141414141414141414141414141414141", + "path": [ + "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619", + "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", + "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", + "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", + "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145" + ], + "reason": "2dd2f49c1f5af0fcad371d96e8cddbdcd5096dc309c1d4e110f955926506b3c03b44c192896f45610741c85ed4074212537e0c118d472ff3a559ae244acd9d783c65977765c5d4e00b723d00f12475aafaafff7b31c1be5a589e6e25f8da2959107206dd42bbcb43438129ce6cce2b6b4ae63edc76b876136ca5ea6cd1c6a04ca86eca143d15e53ccdc9e23953e49dc2f87bb11e5238cd6536e57387225b8fff3bf5f3e686fd08458ffe0211b87d64770db9353500af9b122828a006da754cf979738b4374e146ea79dd93656170b89c98c5f2299d6e9c0410c826c721950c780486cd6d5b7130380d7eaff994a8503a8fef3270ce94889fe996da66ed121741987010f785494415ca991b2e8b39ef2df6bde98efd2aec7d251b2772485194c8368451ad49c2354f9d30d95367bde316fec6cbdddc7dc0d25e99d3075e13d3de0822669861dafcd29de74eac48b64411987285491f98d78584d0c2a163b7221ea796f9e8671b2bb91e38ef5e18aaf32c6c02f2fb690358872a1ed28166172631a82c2568d23238017188ebbd48944a147f6cdb3690d5f88e51371cb70adf1fa02afe4ed8b581afc8bcc5104922843a55d52acde09bc9d2b71a663e178788280f3c3eae127d21b0b95777976b3eb17be40a702c244d0e5f833ff49dae6403ff44b131e66df8b88e33ab0a58e379f2c34bf5113c66b9ea8241fc7aa2b1fa53cf4ed3cdd91d407730c66fb039ef3a36d4050dde37d34e80bcfe02a48a6b14ae28227b1627b5ad07608a7763a531f2ffc96dff850e8c583461831b19feffc783bc1beab6301f647e9617d14c92c4b1d63f5147ccda56a35df8ca4806b8884c4aa3c3cc6a174fdc2232404822569c01aba686c1df5eecc059ba97e9688c8b16b70f0d24eacfdba15db1c71f72af1b2af85bd168f0b0800483f115eeccd9b02adf03bdd4a88eab03e43ce342877af2b61f9d3d85497cd1c6b96674f3d4f07f635bb26add1e36835e321d70263b1c04234e222124dad30ffb9f2a138e3ef453442df1af7e566890aedee568093aa922dd62db188aa8361c55503f8e2c2e6ba93de744b55c15260f15ec8e69bb01048ca1fa7bbbd26975bde80930a5b95054688a0ea73af0353cc84b997626a987cc06a517e18f91e02908829d4f4efc011b9867bd9bfe04c5f94e4b9261d30cc39982eb7b250f12aee2a4cce0484ff34eebba89bc6e35bd48d3968e4ca2d77527212017e202141900152f2fd8af0ac3aa456aae13276a13b9b9492a9a636e18244654b3245f07b20eb76b8e1cea8c55e5427f08a63a16b0a633af67c8e48ef8e53519041c9138176eb14b8782c6c2ee76146b8490b97978ee73cd0104e12f483be5a4af414404618e9f6633c55dda6f22252cb793d3d16fae4f0e1431434e7acc8fa2c009d4f6e345ade172313d558a4e61b4377e31b8ed4e28f7cd13a7fe3f72a409bc3bdabfe0ba47a6d861e21f64d2fac706dab18b3e546df4" +} \ No newline at end of file diff --git a/lnwire/onion_error.go b/lnwire/onion_error.go index 593f58e3d6..623ca0bf4a 100644 --- a/lnwire/onion_error.go +++ b/lnwire/onion_error.go @@ -7,6 +7,7 @@ import ( "encoding/binary" "fmt" "io" + "io/ioutil" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" @@ -344,6 +345,9 @@ type FailIncorrectDetails struct { // height is the block height when the htlc was received. height uint32 + + // extraOpaqueData contains additional failure message tlv data. + extraOpaqueData ExtraOpaqueData } // NewFailIncorrectDetails makes a new instance of the FailIncorrectDetails @@ -352,8 +356,9 @@ func NewFailIncorrectDetails(amt MilliSatoshi, height uint32) *FailIncorrectDetails { return &FailIncorrectDetails{ - amount: amt, - height: height, + amount: amt, + height: height, + extraOpaqueData: []byte{}, } } @@ -367,6 +372,11 @@ func (f *FailIncorrectDetails) Height() uint32 { return f.height } +// ExtraOpaqueData returns additional failure message tlv data. +func (f *FailIncorrectDetails) ExtraOpaqueData() ExtraOpaqueData { + return f.extraOpaqueData +} + // Code returns the failure unique code. // // NOTE: Part of the FailureMessage interface. @@ -412,7 +422,7 @@ func (f *FailIncorrectDetails) Decode(r io.Reader, pver uint32) error { return err } - return nil + return f.extraOpaqueData.Decode(r) } // Encode writes the failure in bytes stream. @@ -423,7 +433,11 @@ func (f *FailIncorrectDetails) Encode(w *bytes.Buffer, pver uint32) error { return err } - return WriteUint32(w, f.height) + if err := WriteUint32(w, f.height); err != nil { + return err + } + + return f.extraOpaqueData.Encode(w) } // FailFinalExpiryTooSoon is returned if the cltv_expiry is too low, the final @@ -1222,18 +1236,41 @@ func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) { // is a 2 byte length followed by the payload itself. var failureLength uint16 if err := ReadElement(r, &failureLength); err != nil { - return nil, fmt.Errorf("unable to read error len: %v", err) - } - if failureLength > FailureMessageLength { - return nil, fmt.Errorf("failure message is too "+ - "long: %v", failureLength) + return nil, fmt.Errorf("unable to read failure len: %w", err) } + failureData := make([]byte, failureLength) if _, err := io.ReadFull(r, failureData); err != nil { return nil, fmt.Errorf("unable to full read payload of "+ - "%v: %v", failureLength, err) + "%v: %w", failureLength, err) + } + + // Read the padding. + var padLength uint16 + if err := ReadElement(r, &padLength); err != nil { + return nil, fmt.Errorf("unable to read pad len: %w", err) + } + + if _, err := io.CopyN(ioutil.Discard, r, int64(padLength)); err != nil { + return nil, fmt.Errorf("unable to read padding %w", err) + } + + // Verify that we are at the end of the stream now. + scratch := make([]byte, 1) + _, err := r.Read(scratch) + if err != io.EOF { + return nil, fmt.Errorf("unexpected failure bytes") + } + + // Check the total length. Convert to 32 bits to prevent overflow. + totalLength := uint32(padLength) + uint32(failureLength) + if totalLength < FailureMessageLength { + return nil, fmt.Errorf("failure message too short: "+ + "msg=%v, pad=%v, total=%v", + failureLength, padLength, totalLength) } + // Decode the failure message. dataReader := bytes.NewReader(failureData) return DecodeFailureMessage(dataReader, pver) diff --git a/routing/missioncontrol_store.go b/routing/missioncontrol_store.go index 27be86821d..a6c98571fd 100644 --- a/routing/missioncontrol_store.go +++ b/routing/missioncontrol_store.go @@ -5,6 +5,7 @@ import ( "container/list" "encoding/binary" "fmt" + "math" "sync" "time" @@ -245,7 +246,7 @@ func deserializeResult(k, v []byte) (*paymentResult, error) { // Read failure. failureBytes, err := wire.ReadVarBytes( - r, 0, lnwire.FailureMessageLength, "failure", + r, 0, math.MaxUint16, "failure", ) if err != nil { return nil, err