From 6397497bb6762817b1cd00cf6651e5ff41f5aa9d Mon Sep 17 00:00:00 2001 From: Matt Morehouse Date: Tue, 18 Apr 2023 10:36:26 -0500 Subject: [PATCH 1/4] brontide: s/nilAndPanic/dumpAndFail The nilAndPanic function does not actually nil the Curve fields as it claims. dumpAndFail is a more descriptive name. --- brontide/fuzz_test.go | 91 +++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/brontide/fuzz_test.go b/brontide/fuzz_test.go index 91b5cd8dc..6e29eb7eb 100644 --- a/brontide/fuzz_test.go +++ b/brontide/fuzz_test.go @@ -62,7 +62,7 @@ func completeHandshake(t *testing.T, initiator, responder *Machine) { t.Helper() if err := handshake(initiator, responder); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } } @@ -98,9 +98,8 @@ func handshake(initiator, responder *Machine) error { return responder.RecvActThree(actThree) } -// nilAndPanic first nils the initiator and responder's Curve fields and then -// panics. -func nilAndPanic(t *testing.T, initiator, responder *Machine, err error) { +// dumpAndFail dumps the initiator and responder Machines and fails. +func dumpAndFail(t *testing.T, initiator, responder *Machine, err error) { t.Helper() t.Fatalf("error: %v, initiator: %v, responder: %v", err, @@ -159,7 +158,7 @@ func FuzzRandomActOne(f *testing.F) { // Responder receives ActOne, should fail on the MAC check. if err := responder.RecvActOne(actOne); err == nil { - nilAndPanic(t, nil, responder, nil) + dumpAndFail(t, nil, responder, nil) } }) } @@ -178,12 +177,12 @@ func FuzzRandomActThree(f *testing.F) { // Generate ActOne and send to the responder. actOne, err := initiator.GenActOne() if err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Receiving ActOne should succeed, so we panic on error. if err := responder.RecvActOne(actOne); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Generate ActTwo - this is not sent to the initiator because @@ -192,7 +191,7 @@ func FuzzRandomActThree(f *testing.F) { // the appropriate state in the responder machine. _, err = responder.GenActTwo() if err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Copy data into [ActThreeSize]byte. @@ -201,7 +200,7 @@ func FuzzRandomActThree(f *testing.F) { // Responder receives ActThree, should fail on the MAC check. if err := responder.RecvActThree(actThree); err == nil { - nilAndPanic(t, initiator, responder, nil) + dumpAndFail(t, initiator, responder, nil) } }) } @@ -223,7 +222,7 @@ func FuzzRandomActTwo(f *testing.F) { // appropriate state in the initiator machine. _, err := initiator.GenActOne() if err != nil { - nilAndPanic(t, initiator, nil, err) + dumpAndFail(t, initiator, nil, err) } // Copy data into [ActTwoSize]byte. @@ -232,7 +231,7 @@ func FuzzRandomActTwo(f *testing.F) { // Initiator receives ActTwo, should fail. if err := initiator.RecvActTwo(actTwo); err == nil { - nilAndPanic(t, initiator, nil, nil) + dumpAndFail(t, initiator, nil, nil) } }) } @@ -253,7 +252,7 @@ func FuzzRandomInitDecrypt(f *testing.F) { // Decrypt the encrypted message using ReadMessage w/ initiator // machine. if _, err := initiator.ReadMessage(r); err == nil { - nilAndPanic(t, initiator, responder, nil) + dumpAndFail(t, initiator, responder, nil) } }) } @@ -278,25 +277,25 @@ func FuzzRandomInitEncDec(f *testing.F) { // Encrypt the message using WriteMessage w/ initiator machine. if err := initiator.WriteMessage(data); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Flush the encrypted message w/ initiator machine. if _, err := initiator.Flush(&b); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Decrypt the ciphertext using ReadMessage w/ responder // machine. plaintext, err := responder.ReadMessage(&b) if err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Check that the decrypted message and the original message are // equal. if !bytes.Equal(data, plaintext) { - nilAndPanic(t, initiator, responder, nil) + dumpAndFail(t, initiator, responder, nil) } }) } @@ -321,12 +320,12 @@ func FuzzRandomInitEncrypt(f *testing.F) { // Encrypt the message using WriteMessage w/ initiator machine. if err := initiator.WriteMessage(data); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Flush the encrypted message w/ initiator machine. if _, err := initiator.Flush(&b); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } }) } @@ -347,7 +346,7 @@ func FuzzRandomRespDecrypt(f *testing.F) { // Decrypt the encrypted message using ReadMessage w/ responder // machine. if _, err := responder.ReadMessage(r); err == nil { - nilAndPanic(t, initiator, responder, nil) + dumpAndFail(t, initiator, responder, nil) } }) } @@ -372,25 +371,25 @@ func FuzzRandomRespEncDec(f *testing.F) { // Encrypt the message using WriteMessage w/ responder machine. if err := responder.WriteMessage(data); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Flush the encrypted message w/ responder machine. if _, err := responder.Flush(&b); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Decrypt the ciphertext using ReadMessage w/ initiator // machine. plaintext, err := initiator.ReadMessage(&b) if err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Check that the decrypted message and the original message are // equal. if !bytes.Equal(data, plaintext) { - nilAndPanic(t, initiator, responder, nil) + dumpAndFail(t, initiator, responder, nil) } }) } @@ -415,12 +414,12 @@ func FuzzRandomRespEncrypt(f *testing.F) { // Encrypt the message using WriteMessage w/ responder machine. if err := responder.WriteMessage(data); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Flush the encrypted message w/ responder machine. if _, err := responder.Flush(&b); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } }) } @@ -442,7 +441,7 @@ func FuzzStaticActOne(f *testing.F) { // Responder receives ActOne, should fail. if err := responder.RecvActOne(actOne); err == nil { - nilAndPanic(t, nil, responder, nil) + dumpAndFail(t, nil, responder, nil) } }) } @@ -461,12 +460,12 @@ func FuzzStaticActThree(f *testing.F) { // Generate ActOne and send to the responder. actOne, err := initiator.GenActOne() if err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Receiving ActOne should succeed, so we panic on error. if err := responder.RecvActOne(actOne); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Generate ActTwo - this is not sent to the initiator because @@ -475,7 +474,7 @@ func FuzzStaticActThree(f *testing.F) { // the appropriate state in the responder machine. _, err = responder.GenActTwo() if err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Copy data into [ActThreeSize]byte. @@ -484,7 +483,7 @@ func FuzzStaticActThree(f *testing.F) { // Responder receives ActThree, should fail. if err := responder.RecvActThree(actThree); err == nil { - nilAndPanic(t, initiator, responder, nil) + dumpAndFail(t, initiator, responder, nil) } }) } @@ -506,7 +505,7 @@ func FuzzStaticActTwo(f *testing.F) { // appropriate state in the initiator machine. _, err := initiator.GenActOne() if err != nil { - nilAndPanic(t, initiator, nil, err) + dumpAndFail(t, initiator, nil, err) } // Copy data into [ActTwoSize]byte. @@ -515,7 +514,7 @@ func FuzzStaticActTwo(f *testing.F) { // Initiator receives ActTwo, should fail. if err := initiator.RecvActTwo(actTwo); err == nil { - nilAndPanic(t, initiator, nil, nil) + dumpAndFail(t, initiator, nil, nil) } }) } @@ -536,7 +535,7 @@ func FuzzStaticInitDecrypt(f *testing.F) { // Decrypt the encrypted message using ReadMessage w/ initiator // machine. if _, err := initiator.ReadMessage(r); err == nil { - nilAndPanic(t, initiator, responder, nil) + dumpAndFail(t, initiator, responder, nil) } }) } @@ -561,25 +560,25 @@ func FuzzStaticInitEncDec(f *testing.F) { // Encrypt the message using WriteMessage w/ initiator machine. if err := initiator.WriteMessage(data); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Flush the encrypted message w/ initiator machine. if _, err := initiator.Flush(&b); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Decrypt the ciphertext using ReadMessage w/ responder // machine. plaintext, err := responder.ReadMessage(&b) if err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Check that the decrypted message and the original message are // equal. if !bytes.Equal(data, plaintext) { - nilAndPanic(t, initiator, responder, nil) + dumpAndFail(t, initiator, responder, nil) } }) } @@ -604,12 +603,12 @@ func FuzzStaticInitEncrypt(f *testing.F) { // Encrypt the message using WriteMessage w/ initiator machine. if err := initiator.WriteMessage(data); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Flush the encrypted message w/ initiator machine. if _, err := initiator.Flush(&b); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } }) } @@ -630,7 +629,7 @@ func FuzzStaticRespDecrypt(f *testing.F) { // Decrypt the encrypted message using ReadMessage w/ responder // machine. if _, err := responder.ReadMessage(r); err == nil { - nilAndPanic(t, initiator, responder, nil) + dumpAndFail(t, initiator, responder, nil) } }) } @@ -655,25 +654,25 @@ func FuzzStaticRespEncDec(f *testing.F) { // Encrypt the message using WriteMessage w/ responder machine. if err := responder.WriteMessage(data); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Flush the encrypted message w/ responder machine. if _, err := responder.Flush(&b); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Decrypt the ciphertext using ReadMessage w/ initiator // machine. plaintext, err := initiator.ReadMessage(&b) if err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Check that the decrypted message and the original message are // equal. if !bytes.Equal(data, plaintext) { - nilAndPanic(t, initiator, responder, nil) + dumpAndFail(t, initiator, responder, nil) } }) } @@ -698,12 +697,12 @@ func FuzzStaticRespEncrypt(f *testing.F) { // Encrypt the message using WriteMessage w/ responder machine. if err := responder.WriteMessage(data); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } // Flush the encrypted message w/ responder machine. if _, err := responder.Flush(&b); err != nil { - nilAndPanic(t, initiator, responder, err) + dumpAndFail(t, initiator, responder, err) } }) } From eb31d47094504615a908c5bf6b849eb71684fe15 Mon Sep 17 00:00:00 2001 From: Matt Morehouse Date: Tue, 18 Apr 2023 10:45:01 -0500 Subject: [PATCH 2/4] brontide: remove handshake helper function Move the functionality directly into completeHandshake instead. If a failure does happen at any point during the handshake, it is beneficial to know which line it happens on for debugging. The helper function was hiding this information. --- brontide/fuzz_test.go | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/brontide/fuzz_test.go b/brontide/fuzz_test.go index 6e29eb7eb..4cd43c084 100644 --- a/brontide/fuzz_test.go +++ b/brontide/fuzz_test.go @@ -59,43 +59,35 @@ var ( // and completes the brontide handshake between them. If any part of the // handshake fails, this function will panic. func completeHandshake(t *testing.T, initiator, responder *Machine) { - t.Helper() - - if err := handshake(initiator, responder); err != nil { - dumpAndFail(t, initiator, responder, err) - } -} - -// handshake actually completes the brontide handshake and bubbles up -// an error to the calling function. -func handshake(initiator, responder *Machine) error { // Generate ActOne and send to the responder. actOne, err := initiator.GenActOne() if err != nil { - return err + dumpAndFail(t, initiator, responder, err) } if err := responder.RecvActOne(actOne); err != nil { - return err + dumpAndFail(t, initiator, responder, err) } // Generate ActTwo and send to initiator. actTwo, err := responder.GenActTwo() if err != nil { - return err + dumpAndFail(t, initiator, responder, err) } if err := initiator.RecvActTwo(actTwo); err != nil { - return err + dumpAndFail(t, initiator, responder, err) } // Generate ActThree and send to responder. actThree, err := initiator.GenActThree() if err != nil { - return err + dumpAndFail(t, initiator, responder, err) } - return responder.RecvActThree(actThree) + if err := responder.RecvActThree(actThree); err != nil { + dumpAndFail(t, initiator, responder, err) + } } // dumpAndFail dumps the initiator and responder Machines and fails. From bad4a662795cf86107e318e9a6e2edafc71d88eb Mon Sep 17 00:00:00 2001 From: Matt Morehouse Date: Tue, 18 Apr 2023 14:02:28 -0500 Subject: [PATCH 3/4] brontide: derandomize fuzz tests It is best to have deterministic fuzz targets, so that if a failure occurs, it can be easily reproduced. This commit swaps the cryptographically secure RNG for a deterministic one seeded from fuzzer input. --- brontide/fuzz_test.go | 76 ++++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/brontide/fuzz_test.go b/brontide/fuzz_test.go index 4cd43c084..56592d1ff 100644 --- a/brontide/fuzz_test.go +++ b/brontide/fuzz_test.go @@ -2,12 +2,16 @@ package brontide import ( "bytes" + "crypto/ecdsa" "encoding/hex" + "io" "math" + "math/rand" "testing" "github.com/btcsuite/btcd/btcec/v2" "github.com/davecgh/go-spew/spew" + "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lightningnetwork/lnd/keychain" ) @@ -98,18 +102,38 @@ func dumpAndFail(t *testing.T, initiator, responder *Machine, err error) { spew.Sdump(initiator), spew.Sdump(responder)) } -// getBrontideMachines returns two brontide machines that use random keys -// everywhere. -func getBrontideMachines() (*Machine, *Machine) { - initPriv, _ := btcec.NewPrivateKey() - respPriv, _ := btcec.NewPrivateKey() - respPub := (respPriv.PubKey()) +// newInsecurePrivateKey returns a private key that is generated using a +// cryptographically insecure RNG. This function should only be used for testing +// where reproducibility is required. +func newInsecurePrivateKey(t *testing.T, + insecureRNG io.Reader) *btcec.PrivateKey { + + key, err := ecdsa.GenerateKey(secp256k1.S256(), insecureRNG) + if err != nil { + t.Fatalf("error generating private key: %v", err) + } + + return secp256k1.PrivKeyFromBytes(key.D.Bytes()) +} + +// getBrontideMachines returns two brontide machines that use pseudorandom keys +// everywhere, generated from seed. +func getBrontideMachines(t *testing.T, seed int64) (*Machine, *Machine) { + rng := rand.New(rand.NewSource(seed)) + + initPriv := newInsecurePrivateKey(t, rng) + respPriv := newInsecurePrivateKey(t, rng) + respPub := respPriv.PubKey() initPrivECDH := &keychain.PrivKeyECDH{PrivKey: initPriv} respPrivECDH := &keychain.PrivKeyECDH{PrivKey: respPriv} - initiator := NewBrontideMachine(true, initPrivECDH, respPub) - responder := NewBrontideMachine(false, respPrivECDH, nil) + ephGen := EphemeralGenerator(func() (*btcec.PrivateKey, error) { + return newInsecurePrivateKey(t, rng), nil + }) + + initiator := NewBrontideMachine(true, initPrivECDH, respPub, ephGen) + responder := NewBrontideMachine(false, respPrivECDH, nil, ephGen) return initiator, responder } @@ -135,14 +159,14 @@ func getStaticBrontideMachines() (*Machine, *Machine) { // FuzzRandomActOne fuzz tests ActOne in the brontide handshake. func FuzzRandomActOne(f *testing.F) { - f.Fuzz(func(t *testing.T, data []byte) { + f.Fuzz(func(t *testing.T, seed int64, data []byte) { // Check if data is large enough. if len(data) < ActOneSize { return } // This will return brontide machines with random keys. - _, responder := getBrontideMachines() + _, responder := getBrontideMachines(t, seed) // Copy data into [ActOneSize]byte. var actOne [ActOneSize]byte @@ -157,14 +181,14 @@ func FuzzRandomActOne(f *testing.F) { // FuzzRandomActThree fuzz tests ActThree in the brontide handshake. func FuzzRandomActThree(f *testing.F) { - f.Fuzz(func(t *testing.T, data []byte) { + f.Fuzz(func(t *testing.T, seed int64, data []byte) { // Check if data is large enough. if len(data) < ActThreeSize { return } // This will return brontide machines with random keys. - initiator, responder := getBrontideMachines() + initiator, responder := getBrontideMachines(t, seed) // Generate ActOne and send to the responder. actOne, err := initiator.GenActOne() @@ -199,14 +223,14 @@ func FuzzRandomActThree(f *testing.F) { // FuzzRandomActTwo fuzz tests ActTwo in the brontide handshake. func FuzzRandomActTwo(f *testing.F) { - f.Fuzz(func(t *testing.T, data []byte) { + f.Fuzz(func(t *testing.T, seed int64, data []byte) { // Check if data is large enough. if len(data) < ActTwoSize { return } // This will return brontide machines with random keys. - initiator, _ := getBrontideMachines() + initiator, _ := getBrontideMachines(t, seed) // Generate ActOne - this isn't sent to the responder because // nothing is done with the responder machine and this would @@ -231,9 +255,9 @@ func FuzzRandomActTwo(f *testing.F) { // FuzzRandomInitDecrypt fuzz tests decrypting arbitrary data with the // initiator. func FuzzRandomInitDecrypt(f *testing.F) { - f.Fuzz(func(t *testing.T, data []byte) { + f.Fuzz(func(t *testing.T, seed int64, data []byte) { // This will return brontide machines with random keys. - initiator, responder := getBrontideMachines() + initiator, responder := getBrontideMachines(t, seed) // Complete the brontide handshake. completeHandshake(t, initiator, responder) @@ -252,7 +276,7 @@ func FuzzRandomInitDecrypt(f *testing.F) { // FuzzRandomInitEncDec fuzz tests round-trip encryption and decryption between // the initiator and the responder. func FuzzRandomInitEncDec(f *testing.F) { - f.Fuzz(func(t *testing.T, data []byte) { + f.Fuzz(func(t *testing.T, seed int64, data []byte) { // Ensure that length of message is not greater than max allowed // size. if len(data) > math.MaxUint16 { @@ -260,7 +284,7 @@ func FuzzRandomInitEncDec(f *testing.F) { } // This will return brontide machines with random keys. - initiator, responder := getBrontideMachines() + initiator, responder := getBrontideMachines(t, seed) // Complete the brontide handshake. completeHandshake(t, initiator, responder) @@ -295,7 +319,7 @@ func FuzzRandomInitEncDec(f *testing.F) { // FuzzRandomInitEncrypt fuzz tests the encryption of arbitrary data with the // initiator. func FuzzRandomInitEncrypt(f *testing.F) { - f.Fuzz(func(t *testing.T, data []byte) { + f.Fuzz(func(t *testing.T, seed int64, data []byte) { // Ensure that length of message is not greater than max allowed // size. if len(data) > math.MaxUint16 { @@ -303,7 +327,7 @@ func FuzzRandomInitEncrypt(f *testing.F) { } // This will return brontide machines with random keys. - initiator, responder := getBrontideMachines() + initiator, responder := getBrontideMachines(t, seed) // Complete the brontide handshake. completeHandshake(t, initiator, responder) @@ -325,9 +349,9 @@ func FuzzRandomInitEncrypt(f *testing.F) { // FuzzRandomRespDecrypt fuzz tests the decryption of arbitrary data with the // responder. func FuzzRandomRespDecrypt(f *testing.F) { - f.Fuzz(func(t *testing.T, data []byte) { + f.Fuzz(func(t *testing.T, seed int64, data []byte) { // This will return brontide machines with random keys. - initiator, responder := getBrontideMachines() + initiator, responder := getBrontideMachines(t, seed) // Complete the brontide handshake. completeHandshake(t, initiator, responder) @@ -346,7 +370,7 @@ func FuzzRandomRespDecrypt(f *testing.F) { // FuzzRandomRespEncDec fuzz tests round-trip encryption and decryption between // the responder and the initiator. func FuzzRandomRespEncDec(f *testing.F) { - f.Fuzz(func(t *testing.T, data []byte) { + f.Fuzz(func(t *testing.T, seed int64, data []byte) { // Ensure that length of message is not greater than max allowed // size. if len(data) > math.MaxUint16 { @@ -354,7 +378,7 @@ func FuzzRandomRespEncDec(f *testing.F) { } // This will return brontide machines with random keys. - initiator, responder := getBrontideMachines() + initiator, responder := getBrontideMachines(t, seed) // Complete the brontide handshake. completeHandshake(t, initiator, responder) @@ -389,7 +413,7 @@ func FuzzRandomRespEncDec(f *testing.F) { // FuzzRandomRespEncrypt fuzz tests encryption of arbitrary data with the // responder. func FuzzRandomRespEncrypt(f *testing.F) { - f.Fuzz(func(t *testing.T, data []byte) { + f.Fuzz(func(t *testing.T, seed int64, data []byte) { // Ensure that length of message is not greater than max allowed // size. if len(data) > math.MaxUint16 { @@ -397,7 +421,7 @@ func FuzzRandomRespEncrypt(f *testing.F) { } // This will return brontide machines with random keys. - initiator, responder := getBrontideMachines() + initiator, responder := getBrontideMachines(t, seed) // Complete the brontide handshake. completeHandshake(t, initiator, responder) From 354133e41ece3bcd91fe86275f2e098485a699b0 Mon Sep 17 00:00:00 2001 From: Matt Morehouse Date: Tue, 23 May 2023 08:34:36 -0500 Subject: [PATCH 4/4] docs: release note for #7618 --- docs/release-notes/release-notes-0.17.0.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes/release-notes-0.17.0.md b/docs/release-notes/release-notes-0.17.0.md index bbabc667b..4bd799d61 100644 --- a/docs/release-notes/release-notes-0.17.0.md +++ b/docs/release-notes/release-notes-0.17.0.md @@ -70,6 +70,9 @@ unlock or create. * [Started](https://github.com/lightningnetwork/lnd/pull/7494) running fuzz tests in CI. +* [Derandomized](https://github.com/lightningnetwork/lnd/pull/7618) the BOLT + 8 fuzz tests. + * [Added fuzz tests](https://github.com/lightningnetwork/lnd/pull/7649) for signature parsing and conversion.