diff --git a/lnwire/commit_sig_test.go b/lnwire/commit_sig_test.go index 0772a2fb8..2524727d8 100644 --- a/lnwire/commit_sig_test.go +++ b/lnwire/commit_sig_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" ) @@ -42,8 +41,13 @@ func generateCommitSigTestCases(t *testing.T) []commitSigTestCase { sigScalar := new(btcec.ModNScalar) sigScalar.SetByteSlice(sig.RawBytes()) - var nonce [musig2.PubNonceSize]byte - copy(nonce[:], commitSigBytes) + // Generate a valid MuSig2 nonce (two compressed public keys). + _, pub1 := btcec.PrivKeyFromBytes(chanIDBytes) + _, pub2 := btcec.PrivKeyFromBytes(commitSigBytes[:32]) + + var nonce Musig2Nonce + copy(nonce[:33], pub1.SerializeCompressed()) + copy(nonce[33:], pub2.SerializeCompressed()) sigWithNonce := NewPartialSigWithNonce(nonce, *sigScalar) partialSig := MaybePartialSigWithNonce(sigWithNonce) diff --git a/lnwire/musig2.go b/lnwire/musig2.go index 10dbc27af..4b69b7063 100644 --- a/lnwire/musig2.go +++ b/lnwire/musig2.go @@ -1,8 +1,10 @@ package lnwire import ( + "fmt" "io" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/lightningnetwork/lnd/tlv" ) @@ -47,13 +49,36 @@ func nonceTypeEncoder(w io.Writer, val interface{}, _ *[8]byte) error { return tlv.NewTypeForEncodingErr(val, "lnwire.Musig2Nonce") } +// ValidateMusig2Nonce checks that a 66-byte MuSig2 public nonce contains two +// valid compressed secp256k1 points. +func ValidateMusig2Nonce(nonce Musig2Nonce) error { + const compressedKeyLen = 33 + + // A MuSig2 public nonce is two 33-byte compressed public keys (R1, R2). + _, err := btcec.ParsePubKey(nonce[:compressedKeyLen]) + if err != nil { + return fmt.Errorf("invalid first nonce point: %w", err) + } + + _, err = btcec.ParsePubKey(nonce[compressedKeyLen:]) + if err != nil { + return fmt.Errorf("invalid second nonce point: %w", err) + } + + return nil +} + // nonceTypeDecoder is a custom TLV decoder for the Musig2Nonce record. func nonceTypeDecoder(r io.Reader, val interface{}, _ *[8]byte, l uint64) error { if v, ok := val.(*Musig2Nonce); ok && l == musig2.PubNonceSize { _, err := io.ReadFull(r, v[:]) - return err + if err != nil { + return err + } + + return ValidateMusig2Nonce(*v) } return tlv.NewTypeForDecodingErr( diff --git a/lnwire/musig2_test.go b/lnwire/musig2_test.go index eac4a7286..8f55b5a80 100644 --- a/lnwire/musig2_test.go +++ b/lnwire/musig2_test.go @@ -3,17 +3,30 @@ package lnwire import ( "testing" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/stretchr/testify/require" ) -// makeNonce creates a test Musig2Nonce with sequential byte values for testing -// TLV encoding/decoding. +// makeNonce creates a test Musig2Nonce containing two valid compressed public +// keys for testing TLV encoding/decoding. func makeNonce() Musig2Nonce { + _, pub1 := btcec.PrivKeyFromBytes([]byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + }) + _, pub2 := btcec.PrivKeyFromBytes([]byte{ + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, + }) + var n Musig2Nonce - for i := range n { - n[i] = byte(i) - } + copy(n[:33], pub1.SerializeCompressed()) + copy(n[33:], pub2.SerializeCompressed()) return n } diff --git a/lnwire/partial_sig.go b/lnwire/partial_sig.go index 1751ae5ce..d5af8ec90 100644 --- a/lnwire/partial_sig.go +++ b/lnwire/partial_sig.go @@ -210,6 +210,10 @@ func partialSigWithNonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, return err } + if err := ValidateMusig2Nonce(nonce); err != nil { + return err + } + *v = PartialSigWithNonce{ PartialSig: NewPartialSig(s), Nonce: nonce, diff --git a/lnwire/test_message.go b/lnwire/test_message.go index 9aae7d273..9af7621d3 100644 --- a/lnwire/test_message.go +++ b/lnwire/test_message.go @@ -1742,11 +1742,7 @@ func (c *RevokeAndAck) RandTestMessage(t *rapid.T) Message { msg.NextRevocationKey = RandPubKey(t) if rapid.Bool().Draw(t, "includeLocalNonce") { - var nonce Musig2Nonce - nonceBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw( - t, "nonce", - ) - copy(nonce[:], nonceBytes) + nonce := RandMusig2Nonce(t) msg.LocalNonce = tlv.SomeRecordT( tlv.NewRecordT[NonceRecordTypeT, Musig2Nonce](nonce), diff --git a/lnwire/test_utils.go b/lnwire/test_utils.go index 227640a43..602724a9d 100644 --- a/lnwire/test_utils.go +++ b/lnwire/test_utils.go @@ -299,11 +299,16 @@ func RandTLVRecords(t *rapid.T, ignoreRecords fn.Set[uint64], return customRecords, ignoreSet } -// RandMusig2Nonce generates a random musig2 nonce. +// RandMusig2Nonce generates a random musig2 nonce containing two valid +// compressed secp256k1 public keys. func RandMusig2Nonce(t *rapid.T) Musig2Nonce { + // A MuSig2 public nonce is two 33-byte compressed public keys. + pub1 := RandPubKey(t) + pub2 := RandPubKey(t) + var nonce Musig2Nonce - bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "nonce") - copy(nonce[:], bytes) + copy(nonce[:33], pub1.SerializeCompressed()) + copy(nonce[33:], pub2.SerializeCompressed()) return nonce }