Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(crypto): add check in multisig verification (backport #23395) #23438

Merged
merged 1 commit into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crypto/keys/multisig/multisig.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ func (m *LegacyAminoPubKey) VerifyMultisignature(getSignBytes multisigtypes.GetS
sigs := sig.Signatures
size := bitarray.Count()
pubKeys := m.GetPubKeys()

// N-M rule verification
if int(m.Threshold) <= 0 {
return fmt.Errorf("invalid threshold: must be > 0, got %d", m.Threshold)
}
if len(pubKeys) <= int(m.Threshold) {
return fmt.Errorf("invalid N-M multisig: N (%d) must be > M (%d)", len(pubKeys), m.Threshold)
}
// ensure bit array is the correct size
if len(pubKeys) != size {
return fmt.Errorf("bit array size is incorrect, expecting: %d", len(pubKeys))
Expand Down
128 changes: 124 additions & 4 deletions crypto/keys/multisig/multisig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,8 @@ func TestAddSignatureFromPubKeyNilCheck(t *testing.T) {

func TestMultiSigMigration(t *testing.T) {
msg := []byte{1, 2, 3, 4}
pkSet, sigs := generatePubKeysAndSignatures(2, msg)
multisignature := multisig.NewMultisig(2)
pkSet, sigs := generatePubKeysAndSignatures(3, msg)
multisignature := multisig.NewMultisig(3)

multisigKey := kmultisig.NewLegacyAminoPubKey(2, pkSet)
signBytesFn := func(mode signing.SignMode) ([]byte, error) { return msg, nil }
Expand Down Expand Up @@ -318,6 +318,22 @@ func generatePubKeysAndSignatures(n int, msg []byte) (pubKeys []cryptotypes.PubK
return
}

// generateNestedMultiSignature creates a nested multisig structure for testing purposes.
// It generates a top-level multisig with 'n' sub-multisigs, where each sub-multisig contains
// 5 individual signatures.
//
// Parameters:
// - n: number of nested multisigs to generate (determines the size of the top-level multisig)
// - msg: the message to be signed
//
// Returns:
// - multisig.PubKey: the top-level multisig public key containing n sub-multisig public keys
// - *signing.MultiSignatureData: the corresponding signature data structure containing n sub-multisig signatures
//
// Each sub-multisig is configured with:
// - threshold of 5 (requires all 4 signatures)
// - 5 individual secp256k1 public keys and their corresponding signatures
// All signature bits are set to true to simulate a fully signed multisig.
func generateNestedMultiSignature(n int, msg []byte) (multisig.PubKey, *signing.MultiSignatureData) {
pubKeys := make([]cryptotypes.PubKey, n)
signatures := make([]signing.SignatureData, n)
Expand All @@ -333,10 +349,10 @@ func generateNestedMultiSignature(n int, msg []byte) (multisig.PubKey, *signing.
Signatures: nestedSigs,
}
signatures[i] = nestedSig
pubKeys[i] = kmultisig.NewLegacyAminoPubKey(5, nestedPks)
pubKeys[i] = kmultisig.NewLegacyAminoPubKey(4, nestedPks)
bitArray.SetIndex(i, true)
}
return kmultisig.NewLegacyAminoPubKey(n, pubKeys), &signing.MultiSignatureData{
return kmultisig.NewLegacyAminoPubKey(n-1, pubKeys), &signing.MultiSignatureData{
BitArray: bitArray,
Signatures: signatures,
}
Expand Down Expand Up @@ -453,3 +469,107 @@ func TestAminoUnmarshalJSON(t *testing.T) {
require.NoError(t, err)
}
}

func TestVerifyMultisignatureNMRule(t *testing.T) {
makeTestKeysAndSignatures := func(n int, msg []byte) ([]cryptotypes.PubKey, []signing.SignatureData) {
pubKeys := make([]cryptotypes.PubKey, n)
sigs := make([]signing.SignatureData, n)

for i := 0; i < n; i++ {
// Generate private key and get its public key
privKey := secp256k1.GenPrivKey()
pubKeys[i] = privKey.PubKey()

// Create real signature
sig, err := privKey.Sign(msg)
require.NoError(t, err)
sigs[i] = &signing.SingleSignatureData{
Signature: sig,
}
}
return pubKeys, sigs
}

tests := []struct {
name string
n int // number of keys
m uint32 // threshold
expectErr string
}{
{
name: "valid case: N=3 M=2",
n: 3,
m: 2,
expectErr: "",
},
{
name: "invalid: M=0",
n: 3,
m: 0,
expectErr: "invalid threshold: must be > 0, got 0",
},
{
name: "invalid: N=M",
n: 3,
m: 3,
expectErr: "invalid N-M multisig: N (3) must be > M (3)",
},
{
name: "invalid: N < M",
n: 2,
m: 3,
expectErr: "invalid N-M multisig: N (2) must be > M (3)",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
msg := []byte("test message")
pubKeys, sigs := makeTestKeysAndSignatures(tc.n, msg)

// Create the key directly instead of using NewLegacyAminoPubKey
pubKeysAny := make([]*types.Any, len(pubKeys))
for i, key := range pubKeys {
anyKey, err := types.NewAnyWithValue(key)
require.NoError(t, err)
pubKeysAny[i] = anyKey
}

multisigKey := &kmultisig.LegacyAminoPubKey{
Threshold: tc.m,
PubKeys: pubKeysAny,
}

// Create a dummy signature with a proper bit array
bitArray := cryptotypes.NewCompactBitArray(tc.n)
// Set the first M bits to simulate M signatures
for i := 0; i < int(tc.m) && i < tc.n; i++ {
bitArray.SetIndex(i, true)
}

multiSig := &signing.MultiSignatureData{
BitArray: bitArray,
Signatures: make([]signing.SignatureData, 0, tc.m),
}

// Fill in dummy signatures
for i := 0; i < int(tc.m) && i < tc.n; i++ {
multiSig.Signatures = append(multiSig.Signatures, sigs[i])
}

// Create getSignBytes function that returns our test message
getSignBytes := func(mode signing.SignMode) ([]byte, error) {
return msg, nil
}

err := multisigKey.VerifyMultisignature(getSignBytes, multiSig)

if tc.expectErr == "" {
require.NoError(t, err)
} else {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectErr)
}
})
}
}
5 changes: 4 additions & 1 deletion tests/integration/auth/client/cli/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ func (s *CLITestSuite) SetupSuite() {
s.Require().NoError(err)
s.val, err = valAcc.GetAddress()
s.Require().NoError(err)
pub, err := valAcc.GetPubKey()
s.Require().NoError(err)

account1, _, err := kb.NewMnemonic("newAccount1", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
s.Require().NoError(err)
Expand All @@ -102,7 +104,8 @@ func (s *CLITestSuite) SetupSuite() {
_, _, err = kb.NewMnemonic("dummyAccount", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
s.Require().NoError(err)

multi := kmultisig.NewLegacyAminoPubKey(2, []cryptotypes.PubKey{pub1, pub2})
// create a 2-3 multisig
multi := kmultisig.NewLegacyAminoPubKey(2, []cryptotypes.PubKey{pub, pub1, pub2})
_, err = kb.SaveMultisig("multi", multi)
s.Require().NoError(err)

Expand Down
Loading