466 lines
14 KiB
Go
466 lines
14 KiB
Go
package multisig_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
"github.com/cosmos/cosmos-sdk/codec/legacy"
|
|
"github.com/cosmos/cosmos-sdk/codec/types"
|
|
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
|
kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
|
"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
|
|
"github.com/cosmos/cosmos-sdk/simapp"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
|
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
|
)
|
|
|
|
func TestNewMultiSig(t *testing.T) {
|
|
require := require.New(t)
|
|
pk1 := secp256k1.GenPrivKey().PubKey()
|
|
pks := []cryptotypes.PubKey{pk1, pk1}
|
|
|
|
require.NotNil(kmultisig.NewLegacyAminoPubKey(1, pks),
|
|
"Should support not unique public keys")
|
|
}
|
|
|
|
func TestAddress(t *testing.T) {
|
|
pubKeys := generatePubKeys(5)
|
|
multisigKey := kmultisig.NewLegacyAminoPubKey(2, pubKeys)
|
|
|
|
require.Len(t, multisigKey.Address().Bytes(), 20)
|
|
}
|
|
|
|
func TestEquals(t *testing.T) {
|
|
pubKey1 := secp256k1.GenPrivKey().PubKey()
|
|
pubKey2 := secp256k1.GenPrivKey().PubKey()
|
|
|
|
multisigKey := kmultisig.NewLegacyAminoPubKey(1, []cryptotypes.PubKey{pubKey1, pubKey2})
|
|
otherMultisigKey := kmultisig.NewLegacyAminoPubKey(1, []cryptotypes.PubKey{pubKey1, multisigKey})
|
|
|
|
testCases := []struct {
|
|
msg string
|
|
other cryptotypes.PubKey
|
|
expectEq bool
|
|
}{
|
|
{
|
|
"equals with proto pub key",
|
|
&kmultisig.LegacyAminoPubKey{Threshold: 1, PubKeys: multisigKey.PubKeys},
|
|
true,
|
|
},
|
|
{
|
|
"different threshold",
|
|
&kmultisig.LegacyAminoPubKey{Threshold: 2, PubKeys: multisigKey.PubKeys},
|
|
false,
|
|
},
|
|
{
|
|
"different pub keys length",
|
|
&kmultisig.LegacyAminoPubKey{Threshold: 1, PubKeys: []*types.Any{multisigKey.PubKeys[0]}},
|
|
false,
|
|
},
|
|
{
|
|
"different pub keys",
|
|
otherMultisigKey,
|
|
false,
|
|
},
|
|
{
|
|
"different types",
|
|
secp256k1.GenPrivKey().PubKey(),
|
|
false,
|
|
},
|
|
{
|
|
"ensure that reordering pubkeys is treated as a different pubkey",
|
|
reorderPubKey(multisigKey),
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.msg, func(t *testing.T) {
|
|
eq := multisigKey.Equals(tc.other)
|
|
require.Equal(t, eq, tc.expectEq)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestVerifyMultisignature(t *testing.T) {
|
|
var (
|
|
pk multisig.PubKey
|
|
sig *signing.MultiSignatureData
|
|
)
|
|
msg := []byte{1, 2, 3, 4}
|
|
signBytesFn := func(mode signing.SignMode) ([]byte, error) { return msg, nil }
|
|
|
|
testCases := []struct {
|
|
msg string
|
|
malleate func(*require.Assertions)
|
|
expectPass bool
|
|
}{
|
|
{
|
|
"nested multisignature",
|
|
func(require *require.Assertions) {
|
|
genPk, genSig := generateNestedMultiSignature(3, msg)
|
|
sig = genSig
|
|
pk = genPk
|
|
},
|
|
true,
|
|
}, {
|
|
"wrong size for sig bit array",
|
|
func(require *require.Assertions) {
|
|
pubKeys := generatePubKeys(3)
|
|
pk = kmultisig.NewLegacyAminoPubKey(3, pubKeys)
|
|
sig = multisig.NewMultisig(1)
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"single signature data, expects the first k signatures to be valid",
|
|
func(require *require.Assertions) {
|
|
k := 2
|
|
signingIndices := []int{0, 3, 1}
|
|
pubKeys, sigs := generatePubKeysAndSignatures(5, msg)
|
|
pk = kmultisig.NewLegacyAminoPubKey(k, pubKeys)
|
|
sig = multisig.NewMultisig(len(pubKeys))
|
|
signBytesFn := func(mode signing.SignMode) ([]byte, error) { return msg, nil }
|
|
|
|
for i := 0; i < k-1; i++ {
|
|
signingIndex := signingIndices[i]
|
|
require.NoError(
|
|
multisig.AddSignatureFromPubKey(sig, sigs[signingIndex], pubKeys[signingIndex], pubKeys),
|
|
)
|
|
require.Error(
|
|
pk.VerifyMultisignature(signBytesFn, sig),
|
|
"multisig passed when i < k, i %d", i,
|
|
)
|
|
require.NoError(
|
|
multisig.AddSignatureFromPubKey(sig, sigs[signingIndex], pubKeys[signingIndex], pubKeys),
|
|
)
|
|
require.Equal(
|
|
i+1,
|
|
len(sig.Signatures),
|
|
"adding a signature for the same pubkey twice increased signature count by 2, index %d", i,
|
|
)
|
|
}
|
|
require.Error(
|
|
pk.VerifyMultisignature(signBytesFn, sig),
|
|
"multisig passed with k - 1 sigs",
|
|
)
|
|
require.NoError(
|
|
multisig.AddSignatureFromPubKey(
|
|
sig,
|
|
sigs[signingIndices[k]],
|
|
pubKeys[signingIndices[k]],
|
|
pubKeys,
|
|
),
|
|
)
|
|
require.NoError(
|
|
pk.VerifyMultisignature(signBytesFn, sig),
|
|
"multisig failed after k good signatures",
|
|
)
|
|
},
|
|
true,
|
|
}, {
|
|
"duplicate signatures",
|
|
func(require *require.Assertions) {
|
|
pubKeys, sigs := generatePubKeysAndSignatures(5, msg)
|
|
pk = kmultisig.NewLegacyAminoPubKey(2, pubKeys)
|
|
sig = multisig.NewMultisig(5)
|
|
|
|
require.Error(pk.VerifyMultisignature(signBytesFn, sig))
|
|
multisig.AddSignatureFromPubKey(sig, sigs[0], pubKeys[0], pubKeys)
|
|
// Add second signature manually
|
|
sig.Signatures = append(sig.Signatures, sigs[0])
|
|
},
|
|
false,
|
|
}, {
|
|
"duplicated key",
|
|
func(require *require.Assertions) {
|
|
// here we test an edge case where we create a multi sig with two same
|
|
// keys. It should work.
|
|
pubkeys, sigs := generatePubKeysAndSignatures(3, msg)
|
|
pubkeys[1] = pubkeys[0]
|
|
pk = kmultisig.NewLegacyAminoPubKey(2, pubkeys)
|
|
sig = multisig.NewMultisig(len(pubkeys))
|
|
multisig.AddSignature(sig, sigs[0], 0)
|
|
multisig.AddSignature(sig, sigs[0], 1)
|
|
},
|
|
true,
|
|
}, {
|
|
"same key used twice",
|
|
func(require *require.Assertions) {
|
|
pubkeys, sigs := generatePubKeysAndSignatures(3, msg)
|
|
pk = kmultisig.NewLegacyAminoPubKey(2, pubkeys)
|
|
sig = multisig.NewMultisig(len(pubkeys))
|
|
multisig.AddSignature(sig, sigs[0], 0)
|
|
multisig.AddSignature(sig, sigs[0], 1)
|
|
},
|
|
false,
|
|
}, {
|
|
"unable to verify signature",
|
|
func(require *require.Assertions) {
|
|
pubKeys := generatePubKeys(2)
|
|
_, sigs := generatePubKeysAndSignatures(2, msg)
|
|
pk = kmultisig.NewLegacyAminoPubKey(2, pubKeys)
|
|
sig = multisig.NewMultisig(2)
|
|
multisig.AddSignatureFromPubKey(sig, sigs[0], pubKeys[0], pubKeys)
|
|
multisig.AddSignatureFromPubKey(sig, sigs[1], pubKeys[1], pubKeys)
|
|
},
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.msg, func(t *testing.T) {
|
|
tc.malleate(require.New(t))
|
|
err := pk.VerifyMultisignature(signBytesFn, sig)
|
|
if tc.expectPass {
|
|
require.NoError(t, err)
|
|
} else {
|
|
require.Error(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddSignatureFromPubKeyNilCheck(t *testing.T) {
|
|
pkSet, sigs := generatePubKeysAndSignatures(5, []byte{1, 2, 3, 4})
|
|
multisignature := multisig.NewMultisig(5)
|
|
|
|
// verify no error is returned with all non-nil values
|
|
err := multisig.AddSignatureFromPubKey(multisignature, sigs[0], pkSet[0], pkSet)
|
|
require.NoError(t, err)
|
|
// verify error is returned when key value is nil
|
|
err = multisig.AddSignatureFromPubKey(multisignature, sigs[0], pkSet[0], nil)
|
|
require.Error(t, err)
|
|
// verify error is returned when pubkey value is nil
|
|
err = multisig.AddSignatureFromPubKey(multisignature, sigs[0], nil, pkSet)
|
|
require.Error(t, err)
|
|
// verify error is returned when signature value is nil
|
|
err = multisig.AddSignatureFromPubKey(multisignature, nil, pkSet[0], pkSet)
|
|
require.Error(t, err)
|
|
// verify error is returned when multisignature value is nil
|
|
err = multisig.AddSignatureFromPubKey(nil, sigs[0], pkSet[0], pkSet)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestMultiSigMigration(t *testing.T) {
|
|
msg := []byte{1, 2, 3, 4}
|
|
pkSet, sigs := generatePubKeysAndSignatures(2, msg)
|
|
multisignature := multisig.NewMultisig(2)
|
|
|
|
multisigKey := kmultisig.NewLegacyAminoPubKey(2, pkSet)
|
|
signBytesFn := func(mode signing.SignMode) ([]byte, error) { return msg, nil }
|
|
|
|
cdc := codec.NewLegacyAmino()
|
|
|
|
require.NoError(t, multisig.AddSignatureFromPubKey(multisignature, sigs[0], pkSet[0], pkSet))
|
|
|
|
// create a StdSignature for msg, and convert it to sigV2
|
|
sig := legacytx.StdSignature{PubKey: pkSet[1], Signature: sigs[1].(*signing.SingleSignatureData).Signature}
|
|
sigV2, err := legacytx.StdSignatureToSignatureV2(cdc, sig)
|
|
require.NoError(t, multisig.AddSignatureV2(multisignature, sigV2, pkSet))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, sigV2)
|
|
|
|
require.NoError(t, multisigKey.VerifyMultisignature(signBytesFn, multisignature))
|
|
}
|
|
|
|
func TestPubKeyMultisigThresholdAminoToIface(t *testing.T) {
|
|
pubkeys := generatePubKeys(5)
|
|
multisigKey := kmultisig.NewLegacyAminoPubKey(2, pubkeys)
|
|
|
|
ab, err := legacy.Cdc.MarshalLengthPrefixed(multisigKey)
|
|
require.NoError(t, err)
|
|
// like other cryptotypes.Pubkey implementations (e.g. ed25519.PubKey),
|
|
// LegacyAminoPubKey should be deserializable into a cryptotypes.LegacyAminoPubKey:
|
|
var pubKey kmultisig.LegacyAminoPubKey
|
|
err = legacy.Cdc.UnmarshalLengthPrefixed(ab, &pubKey)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, multisigKey.Equals(&pubKey), true)
|
|
}
|
|
|
|
func generatePubKeys(n int) []cryptotypes.PubKey {
|
|
pks := make([]cryptotypes.PubKey, n)
|
|
for i := 0; i < n; i++ {
|
|
pks[i] = secp256k1.GenPrivKey().PubKey()
|
|
}
|
|
return pks
|
|
}
|
|
|
|
func generatePubKeysAndSignatures(n int, msg []byte) (pubKeys []cryptotypes.PubKey, signatures []signing.SignatureData) {
|
|
pubKeys = make([]cryptotypes.PubKey, n)
|
|
signatures = make([]signing.SignatureData, n)
|
|
|
|
for i := 0; i < n; i++ {
|
|
privkey := secp256k1.GenPrivKey()
|
|
pubKeys[i] = privkey.PubKey()
|
|
|
|
sig, _ := privkey.Sign(msg)
|
|
signatures[i] = &signing.SingleSignatureData{Signature: sig}
|
|
}
|
|
return
|
|
}
|
|
|
|
func generateNestedMultiSignature(n int, msg []byte) (multisig.PubKey, *signing.MultiSignatureData) {
|
|
pubKeys := make([]cryptotypes.PubKey, n)
|
|
signatures := make([]signing.SignatureData, n)
|
|
bitArray := cryptotypes.NewCompactBitArray(n)
|
|
for i := 0; i < n; i++ {
|
|
nestedPks, nestedSigs := generatePubKeysAndSignatures(5, msg)
|
|
nestedBitArray := cryptotypes.NewCompactBitArray(5)
|
|
for j := 0; j < 5; j++ {
|
|
nestedBitArray.SetIndex(j, true)
|
|
}
|
|
nestedSig := &signing.MultiSignatureData{
|
|
BitArray: nestedBitArray,
|
|
Signatures: nestedSigs,
|
|
}
|
|
signatures[i] = nestedSig
|
|
pubKeys[i] = kmultisig.NewLegacyAminoPubKey(5, nestedPks)
|
|
bitArray.SetIndex(i, true)
|
|
}
|
|
return kmultisig.NewLegacyAminoPubKey(n, pubKeys), &signing.MultiSignatureData{
|
|
BitArray: bitArray,
|
|
Signatures: signatures,
|
|
}
|
|
}
|
|
|
|
func reorderPubKey(pk *kmultisig.LegacyAminoPubKey) (other *kmultisig.LegacyAminoPubKey) {
|
|
pubkeysCpy := make([]*types.Any, len(pk.PubKeys))
|
|
copy(pubkeysCpy, pk.PubKeys)
|
|
pubkeysCpy[0] = pk.PubKeys[1]
|
|
pubkeysCpy[1] = pk.PubKeys[0]
|
|
other = &kmultisig.LegacyAminoPubKey{Threshold: 2, PubKeys: pubkeysCpy}
|
|
return
|
|
}
|
|
|
|
func TestDisplay(t *testing.T) {
|
|
require := require.New(t)
|
|
pubKeys := generatePubKeys(3)
|
|
msig := kmultisig.NewLegacyAminoPubKey(2, pubKeys)
|
|
|
|
// LegacyAminoPubKey wraps PubKeys into Amino (for serialization) and Any String method doesn't work.
|
|
require.PanicsWithValue("reflect.Value.Interface: cannot return value obtained from unexported field or method",
|
|
func() { require.Empty(msig.String()) },
|
|
)
|
|
ccfg := simapp.MakeTestEncodingConfig()
|
|
bz, err := ccfg.Codec.MarshalInterfaceJSON(msig)
|
|
require.NoError(err)
|
|
expectedPrefix := `{"@type":"/cosmos.crypto.multisig.LegacyAminoPubKey","threshold":2,"public_keys":[{"@type":"/cosmos.crypto.secp256k1.PubKey"`
|
|
require.True(strings.HasPrefix(string(bz), expectedPrefix))
|
|
// Example output:
|
|
// {"@type":"/cosmos.crypto.multisig.LegacyAminoPubKey","threshold":2,"public_keys":[{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AymUY3J2HKIyy9cbpGKcBFUTuDQsRH9NO/orKF/0WQ76"},{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AkvnCDzSYF+tQV/FoI217V7CDIRPzjJj7zBE2nw7x3xT"},{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A0yiqgcM5EB1i0h79+sQp+C0jLPFnT3+dFmdZmGa+H1s"}]}
|
|
}
|
|
|
|
func TestAminoBinary(t *testing.T) {
|
|
pubkeys := generatePubKeys(2)
|
|
msig := kmultisig.NewLegacyAminoPubKey(2, pubkeys)
|
|
|
|
// Do a round-trip key->bytes->key.
|
|
bz, err := legacy.Cdc.Marshal(msig)
|
|
require.NoError(t, err)
|
|
var newMsig cryptotypes.PubKey
|
|
err = legacy.Cdc.Unmarshal(bz, &newMsig)
|
|
require.NoError(t, err)
|
|
require.Equal(t, msig.Threshold, newMsig.(*kmultisig.LegacyAminoPubKey).Threshold)
|
|
}
|
|
|
|
func TestAminoMarshalJSON(t *testing.T) {
|
|
pubkeys := generatePubKeys(2)
|
|
multisigKey := kmultisig.NewLegacyAminoPubKey(2, pubkeys)
|
|
bz, err := legacy.Cdc.MarshalJSON(multisigKey)
|
|
require.NoError(t, err)
|
|
|
|
// Note the quotes around `"2"`. They are present because we are overriding
|
|
// the Amino JSON marshaling of LegacyAminoPubKey (using tmMultisig).
|
|
// Without the override, there would not be any quotes.
|
|
require.Contains(t, string(bz), "\"threshold\":\"2\"")
|
|
}
|
|
|
|
func TestAminoUnmarshalJSON(t *testing.T) {
|
|
// This is a real multisig from the Akash chain. It has been exported from
|
|
// v0.39, hence the `threshold` field as a string.
|
|
// We are testing that when unmarshaling this JSON into a LegacyAminoPubKey
|
|
// with amino, there's no error.
|
|
// ref: https://github.com/cosmos/cosmos-sdk/issues/8776
|
|
pkJSON := `{
|
|
"type": "tendermint/PubKeyMultisigThreshold",
|
|
"value": {
|
|
"pubkeys": [
|
|
{
|
|
"type": "tendermint/PubKeySecp256k1",
|
|
"value": "AzYxq2VNeD10TyABwOgV36OVWDIMn8AtI4OFA0uQX2MK"
|
|
},
|
|
{
|
|
"type": "tendermint/PubKeySecp256k1",
|
|
"value": "A39cdsrm00bTeQ3RVZVqjkH8MvIViO9o99c8iLiNO35h"
|
|
},
|
|
{
|
|
"type": "tendermint/PubKeySecp256k1",
|
|
"value": "A/uLLCZph8MkFg2tCxqSMGwFfPHdt1kkObmmrqy9aiYD"
|
|
},
|
|
{
|
|
"type": "tendermint/PubKeySecp256k1",
|
|
"value": "A4mOMhM5gPDtBAkAophjRs6uDGZm4tD4Dbok3ai4qJi8"
|
|
},
|
|
{
|
|
"type": "tendermint/PubKeySecp256k1",
|
|
"value": "A90icFucrjNNz2SAdJWMApfSQcARIqt+M2x++t6w5fFs"
|
|
}
|
|
],
|
|
"threshold": "3"
|
|
}
|
|
}`
|
|
|
|
cdc := codec.NewLegacyAmino()
|
|
cryptocodec.RegisterCrypto(cdc)
|
|
|
|
var pk cryptotypes.PubKey
|
|
err := cdc.UnmarshalJSON([]byte(pkJSON), &pk)
|
|
require.NoError(t, err)
|
|
lpk := pk.(*kmultisig.LegacyAminoPubKey)
|
|
require.Equal(t, uint32(3), lpk.Threshold)
|
|
require.Equal(t, 5, len(pk.(*kmultisig.LegacyAminoPubKey).PubKeys))
|
|
|
|
for _, key := range pk.(*kmultisig.LegacyAminoPubKey).PubKeys {
|
|
require.NotNil(t, key)
|
|
pk := secp256k1.PubKey{}
|
|
err := pk.Unmarshal(key.Value)
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
func TestProtoMarshalJSON(t *testing.T) {
|
|
require := require.New(t)
|
|
pubkeys := generatePubKeys(3)
|
|
msig := kmultisig.NewLegacyAminoPubKey(2, pubkeys)
|
|
|
|
registry := types.NewInterfaceRegistry()
|
|
cryptocodec.RegisterInterfaces(registry)
|
|
cdc := codec.NewProtoCodec(registry)
|
|
|
|
bz, err := cdc.MarshalInterfaceJSON(msig)
|
|
require.NoError(err)
|
|
|
|
var pk2 cryptotypes.PubKey
|
|
err = cdc.UnmarshalInterfaceJSON(bz, &pk2)
|
|
require.NoError(err)
|
|
require.True(pk2.Equals(msig))
|
|
|
|
// Test that we can correctly unmarshal key from keyring output
|
|
k, err := keyring.NewMultiRecord("my multisig", msig)
|
|
require.NoError(err)
|
|
ko, err := keyring.MkAccKeyOutput(k)
|
|
require.NoError(err)
|
|
require.Equal(ko.Address, sdk.AccAddress(pk2.Address()).String())
|
|
require.Equal(ko.PubKey, string(bz))
|
|
}
|