mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-13 12:32:48 +02:00
lnwire: validate MuSig2 nonce points on wire decode
Add point-on-curve validation for MuSig2 public nonces at the TLV decode layer. A MuSig2 nonce is 66 bytes (two 33-byte compressed secp256k1 public keys). Previously, nonce bytes were accepted without validation, with invalid points only failing later during MuSig2 session creation deep in the signing flow. Now, malformed nonces from a peer are rejected immediately at decode time with clear errors. This hardens all nonce-carrying messages: ClosingComplete (JIT closer nonces in PartialSigWithNonce), ClosingSig (NextCloseeNonce), Shutdown, ChannelReestablish, CommitSig, and others.
This commit is contained in:
parent
21ee770e8c
commit
7517295ee0
6 changed files with 64 additions and 17 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue