mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-16 13:00:19 +02:00
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.
424 lines
11 KiB
Go
424 lines
11 KiB
Go
package lnwire
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/wire"
|
|
sphinx "github.com/lightningnetwork/lightning-onion"
|
|
"github.com/lightningnetwork/lnd/fn/v2"
|
|
"github.com/stretchr/testify/require"
|
|
"pgregory.net/rapid"
|
|
)
|
|
|
|
// charset contains valid UTF-8 characters that can be used to generate random
|
|
// strings for testing purposes.
|
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
|
|
// RandPartialSig generates a random ParialSig message using rapid's
|
|
// generators.
|
|
func RandPartialSig(t *rapid.T) *PartialSig {
|
|
// Generate random private key bytes
|
|
sigBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "privKeyBytes")
|
|
|
|
var s btcec.ModNScalar
|
|
s.SetByteSlice(sigBytes)
|
|
|
|
return &PartialSig{
|
|
Sig: s,
|
|
}
|
|
}
|
|
|
|
// RandPartialSigWithNonce generates a random PartialSigWithNonce using rapid
|
|
// generators.
|
|
func RandPartialSigWithNonce(t *rapid.T) *PartialSigWithNonce {
|
|
sigLen := rapid.IntRange(1, 65).Draw(t, "partialSigLen")
|
|
sigBytes := rapid.SliceOfN(
|
|
rapid.Byte(), sigLen, sigLen,
|
|
).Draw(t, "partialSig")
|
|
|
|
sigScalar := new(btcec.ModNScalar)
|
|
sigScalar.SetByteSlice(sigBytes)
|
|
|
|
return NewPartialSigWithNonce(
|
|
RandMusig2Nonce(t), *sigScalar,
|
|
)
|
|
}
|
|
|
|
// RandPubKey generates a random public key using rapid's generators.
|
|
func RandPubKey(t *rapid.T) *btcec.PublicKey {
|
|
privKeyBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
|
|
t, "privKeyBytes",
|
|
)
|
|
_, pub := btcec.PrivKeyFromBytes(privKeyBytes)
|
|
|
|
return pub
|
|
}
|
|
|
|
// RandBlindedPath generates a random blinded path with 1-5 hops.
|
|
func RandBlindedPath(t *rapid.T) *sphinx.BlindedPath {
|
|
introKey := RandPubKey(t)
|
|
blindingKey := RandPubKey(t)
|
|
|
|
numHops := rapid.IntRange(1, 5).Draw(t, "numBlindedHops")
|
|
hops := make([]*sphinx.BlindedHopInfo, numHops)
|
|
for i := range hops {
|
|
cipherLen := rapid.IntRange(1, 128).Draw(
|
|
t, fmt.Sprintf("cipherLen-%d", i),
|
|
)
|
|
|
|
hops[i] = &sphinx.BlindedHopInfo{
|
|
BlindedNodePub: RandPubKey(t),
|
|
CipherText: rapid.SliceOfN(
|
|
rapid.Byte(), cipherLen, cipherLen,
|
|
).Draw(t, fmt.Sprintf("cipherText-%d", i)),
|
|
}
|
|
}
|
|
|
|
return &sphinx.BlindedPath{
|
|
IntroductionPoint: introKey,
|
|
BlindingPoint: blindingKey,
|
|
BlindedHops: hops,
|
|
}
|
|
}
|
|
|
|
// RandChannelID generates a random channel ID.
|
|
func RandChannelID(t *rapid.T) ChannelID {
|
|
var c ChannelID
|
|
bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "channelID")
|
|
copy(c[:], bytes)
|
|
|
|
return c
|
|
}
|
|
|
|
// RandShortChannelID generates a random short channel ID.
|
|
func RandShortChannelID(t *rapid.T) ShortChannelID {
|
|
return NewShortChanIDFromInt(
|
|
uint64(rapid.IntRange(1, 100000).Draw(t, "shortChanID")),
|
|
)
|
|
}
|
|
|
|
// RandFeatureVector generates a random feature vector.
|
|
func RandFeatureVector(t *rapid.T) *RawFeatureVector {
|
|
featureVec := NewRawFeatureVector()
|
|
|
|
// Add a random number of random feature bits
|
|
numFeatures := rapid.IntRange(0, 20).Draw(t, "numFeatures")
|
|
for i := 0; i < numFeatures; i++ {
|
|
bit := FeatureBit(rapid.IntRange(0, 100).Draw(
|
|
t, fmt.Sprintf("featureBit-%d", i)),
|
|
)
|
|
featureVec.Set(bit)
|
|
}
|
|
|
|
return featureVec
|
|
}
|
|
|
|
// RandSignature generates a signature for testing.
|
|
func RandSignature(t *rapid.T) Sig {
|
|
testRScalar := new(btcec.ModNScalar)
|
|
testSScalar := new(btcec.ModNScalar)
|
|
|
|
// Generate random bytes for R and S
|
|
rBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "rBytes")
|
|
sBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "sBytes")
|
|
_ = testRScalar.SetByteSlice(rBytes)
|
|
_ = testSScalar.SetByteSlice(sBytes)
|
|
|
|
testSig := ecdsa.NewSignature(testRScalar, testSScalar)
|
|
|
|
sig, err := NewSigFromSignature(testSig)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("unable to create signature: %v", err))
|
|
}
|
|
|
|
return sig
|
|
}
|
|
|
|
// RandPaymentHash generates a random payment hash.
|
|
func RandPaymentHash(t *rapid.T) [32]byte {
|
|
var hash [32]byte
|
|
bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "paymentHash")
|
|
copy(hash[:], bytes)
|
|
|
|
return hash
|
|
}
|
|
|
|
// RandPaymentPreimage generates a random payment preimage.
|
|
func RandPaymentPreimage(t *rapid.T) [32]byte {
|
|
var preimage [32]byte
|
|
bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "preimage")
|
|
copy(preimage[:], bytes)
|
|
|
|
return preimage
|
|
}
|
|
|
|
// RandChainHash generates a random chain hash.
|
|
func RandChainHash(t *rapid.T) chainhash.Hash {
|
|
var hash [32]byte
|
|
bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "chainHash")
|
|
copy(hash[:], bytes)
|
|
|
|
return hash
|
|
}
|
|
|
|
// RandNodeAlias generates a random node alias.
|
|
func RandNodeAlias(t *rapid.T) NodeAlias {
|
|
var alias NodeAlias
|
|
aliasLength := rapid.IntRange(0, 32).Draw(t, "aliasLength")
|
|
|
|
aliasBytes := rapid.SliceOfN(
|
|
rapid.SampledFrom([]rune(charset)), aliasLength, aliasLength,
|
|
).Draw(t, "alias")
|
|
|
|
copy(alias[:], string(aliasBytes))
|
|
|
|
return alias
|
|
}
|
|
|
|
// RandNodeAlias2 generates a random node alias that is compatible with
|
|
// NodeAlias2 validation (non-empty, max 32 bytes, valid UTF-8).
|
|
func RandNodeAlias2(t *rapid.T) NodeAlias2 {
|
|
// Ensure length is at least 1 to satisfy NodeAlias2 validation
|
|
aliasLength := rapid.IntRange(1, 32).Draw(t, "aliasLength")
|
|
|
|
aliasBytes := rapid.SliceOfN(
|
|
rapid.SampledFrom([]rune(charset)), aliasLength, aliasLength,
|
|
).Draw(t, "alias")
|
|
|
|
return []byte(string(aliasBytes))
|
|
}
|
|
|
|
// RandNetAddrs generates random network addresses.
|
|
func RandNetAddrs(t *rapid.T) []net.Addr {
|
|
numAddresses := rapid.IntRange(0, 5).Draw(t, "numAddresses")
|
|
if numAddresses == 0 {
|
|
return nil
|
|
}
|
|
|
|
addresses := make([]net.Addr, numAddresses)
|
|
for i := 0; i < numAddresses; i++ {
|
|
addressType := rapid.IntRange(0, 1).Draw(
|
|
t, fmt.Sprintf("addressType-%d", i),
|
|
)
|
|
|
|
switch addressType {
|
|
// IPv4.
|
|
case 0:
|
|
ipBytes := rapid.SliceOfN(rapid.Byte(), 4, 4).Draw(
|
|
t, fmt.Sprintf("ipv4-%d", i),
|
|
)
|
|
port := rapid.IntRange(1, 65535).Draw(
|
|
t, fmt.Sprintf("port-%d", i),
|
|
)
|
|
addresses[i] = &net.TCPAddr{
|
|
IP: ipBytes,
|
|
Port: port,
|
|
}
|
|
|
|
// IPv6.
|
|
case 1:
|
|
ipBytes := rapid.SliceOfN(rapid.Byte(), 16, 16).Draw(
|
|
t, fmt.Sprintf("ipv6-%d", i),
|
|
)
|
|
port := rapid.IntRange(1, 65535).Draw(
|
|
t, fmt.Sprintf("port-%d", i),
|
|
)
|
|
addresses[i] = &net.TCPAddr{
|
|
IP: ipBytes,
|
|
Port: port,
|
|
}
|
|
}
|
|
}
|
|
|
|
return addresses
|
|
}
|
|
|
|
// RandCustomRecords generates random custom TLV records.
|
|
func RandCustomRecords(t *rapid.T, ignoreRecords fn.Set[uint64]) (CustomRecords,
|
|
fn.Set[uint64]) {
|
|
|
|
customRecords, set := RandTLVRecords(
|
|
t, ignoreRecords, MinCustomRecordsTlvType,
|
|
)
|
|
|
|
// Validate the custom records as a sanity check.
|
|
require.NoError(t, customRecords.Validate())
|
|
|
|
return customRecords, set
|
|
}
|
|
|
|
// RandSignedRangeRecords generates a random set of signed records in the
|
|
// second "signed" tlv range for pure TLV messages.
|
|
func RandSignedRangeRecords(t *rapid.T) (CustomRecords, fn.Set[uint64]) {
|
|
return RandTLVRecords(t, nil, pureTLVSignedSecondRangeStart)
|
|
}
|
|
|
|
// RandTLVRecords generates custom TLV records.
|
|
func RandTLVRecords(t *rapid.T, ignoreRecords fn.Set[uint64],
|
|
rangeStart int) (CustomRecords, fn.Set[uint64]) {
|
|
|
|
numRecords := rapid.IntRange(0, 5).Draw(t, "numRecords")
|
|
customRecords := make(CustomRecords)
|
|
|
|
if numRecords == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
rangeStop := rangeStart + 30_000
|
|
|
|
ignoreSet := fn.NewSet[uint64]()
|
|
for i := 0; i < numRecords; i++ {
|
|
recordType := uint64(
|
|
rapid.IntRange(rangeStart, rangeStop).
|
|
Filter(func(i int) bool {
|
|
return !ignoreRecords.Contains(
|
|
uint64(i),
|
|
)
|
|
}).
|
|
Draw(
|
|
t, fmt.Sprintf("recordType-%d", i),
|
|
),
|
|
)
|
|
recordLen := rapid.IntRange(4, 64).Draw(
|
|
t, fmt.Sprintf("recordLen-%d", i),
|
|
)
|
|
record := rapid.SliceOfN(
|
|
rapid.Byte(), recordLen, recordLen,
|
|
).Draw(t, fmt.Sprintf("record-%d", i))
|
|
|
|
customRecords[recordType] = record
|
|
|
|
ignoreSet.Add(recordType)
|
|
}
|
|
|
|
return customRecords, ignoreSet
|
|
}
|
|
|
|
// 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
|
|
copy(nonce[:33], pub1.SerializeCompressed())
|
|
copy(nonce[33:], pub2.SerializeCompressed())
|
|
|
|
return nonce
|
|
}
|
|
|
|
// RandExtraOpaqueData generates random extra opaque data.
|
|
func RandExtraOpaqueData(t *rapid.T,
|
|
ignoreRecords fn.Set[uint64]) ExtraOpaqueData {
|
|
|
|
// Make some random records.
|
|
cRecords, _ := RandTLVRecords(t, ignoreRecords, 0)
|
|
if cRecords == nil {
|
|
return ExtraOpaqueData{}
|
|
}
|
|
|
|
// Encode those records as opaque data.
|
|
recordBytes, err := cRecords.Serialize()
|
|
require.NoError(t, err)
|
|
|
|
return ExtraOpaqueData(recordBytes)
|
|
}
|
|
|
|
// RandOpaqueReason generates a random opaque reason for HTLC failures.
|
|
func RandOpaqueReason(t *rapid.T) OpaqueReason {
|
|
reasonLen := rapid.IntRange(32, 300).Draw(t, "reasonLen")
|
|
return rapid.SliceOfN(rapid.Byte(), reasonLen, reasonLen).Draw(
|
|
t, "opaqueReason",
|
|
)
|
|
}
|
|
|
|
// RandFailCode generates a random HTLC failure code.
|
|
func RandFailCode(t *rapid.T) FailCode {
|
|
// List of known failure codes to choose from Using only the documented
|
|
// codes.
|
|
validCodes := []FailCode{
|
|
CodeInvalidRealm,
|
|
CodeTemporaryNodeFailure,
|
|
CodePermanentNodeFailure,
|
|
CodeRequiredNodeFeatureMissing,
|
|
CodePermanentChannelFailure,
|
|
CodeRequiredChannelFeatureMissing,
|
|
CodeUnknownNextPeer,
|
|
CodeIncorrectOrUnknownPaymentDetails,
|
|
CodeIncorrectPaymentAmount,
|
|
CodeFinalExpiryTooSoon,
|
|
CodeInvalidOnionVersion,
|
|
CodeInvalidOnionHmac,
|
|
CodeInvalidOnionKey,
|
|
CodeTemporaryChannelFailure,
|
|
CodeChannelDisabled,
|
|
CodeExpiryTooSoon,
|
|
CodeMPPTimeout,
|
|
CodeInvalidOnionPayload,
|
|
CodeFeeInsufficient,
|
|
}
|
|
|
|
// Choose a random code from the list.
|
|
idx := rapid.IntRange(0, len(validCodes)-1).Draw(t, "failCodeIndex")
|
|
|
|
return validCodes[idx]
|
|
}
|
|
|
|
// RandSHA256Hash generates a random SHA256 hash.
|
|
func RandSHA256Hash(t *rapid.T) [sha256.Size]byte {
|
|
var hash [sha256.Size]byte
|
|
bytes := rapid.SliceOfN(rapid.Byte(), sha256.Size, sha256.Size).Draw(
|
|
t, "sha256Hash",
|
|
)
|
|
copy(hash[:], bytes)
|
|
|
|
return hash
|
|
}
|
|
|
|
// RandDeliveryAddress generates a random delivery address (script).
|
|
func RandDeliveryAddress(t *rapid.T) DeliveryAddress {
|
|
addrLen := rapid.IntRange(1, 34).Draw(t, "addrLen")
|
|
|
|
return rapid.SliceOfN(rapid.Byte(), addrLen, addrLen).Draw(
|
|
t, "deliveryAddress",
|
|
)
|
|
}
|
|
|
|
// RandChannelType generates a random channel type.
|
|
func RandChannelType(t *rapid.T) *ChannelType {
|
|
vec := RandFeatureVector(t)
|
|
chanType := ChannelType(*vec)
|
|
|
|
return &chanType
|
|
}
|
|
|
|
// RandLeaseExpiry generates a random lease expiry.
|
|
func RandLeaseExpiry(t *rapid.T) *LeaseExpiry {
|
|
exp := LeaseExpiry(
|
|
uint32(rapid.IntRange(1000, 1000000).Draw(t, "leaseExpiry")),
|
|
)
|
|
|
|
return &exp
|
|
}
|
|
|
|
// RandOutPoint generates a random transaction outpoint.
|
|
func RandOutPoint(t *rapid.T) wire.OutPoint {
|
|
// Generate a random transaction ID
|
|
var txid chainhash.Hash
|
|
txidBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "txid")
|
|
copy(txid[:], txidBytes)
|
|
|
|
// Generate a random output index
|
|
vout := uint32(rapid.IntRange(0, 10).Draw(t, "vout"))
|
|
|
|
return wire.OutPoint{
|
|
Hash: txid,
|
|
Index: vout,
|
|
}
|
|
}
|