github.com/Finschia/finschia-sdk@v0.48.1/crypto/keys/multisig/multisig_test.go (about) 1 package multisig_test 2 3 import ( 4 "strings" 5 "testing" 6 7 "github.com/stretchr/testify/require" 8 9 "github.com/Finschia/finschia-sdk/codec" 10 "github.com/Finschia/finschia-sdk/codec/legacy" 11 "github.com/Finschia/finschia-sdk/codec/types" 12 cryptocodec "github.com/Finschia/finschia-sdk/crypto/codec" 13 "github.com/Finschia/finschia-sdk/crypto/keyring" 14 kmultisig "github.com/Finschia/finschia-sdk/crypto/keys/multisig" 15 "github.com/Finschia/finschia-sdk/crypto/keys/secp256k1" 16 cryptotypes "github.com/Finschia/finschia-sdk/crypto/types" 17 "github.com/Finschia/finschia-sdk/crypto/types/multisig" 18 "github.com/Finschia/finschia-sdk/simapp" 19 sdk "github.com/Finschia/finschia-sdk/types" 20 "github.com/Finschia/finschia-sdk/types/tx/signing" 21 "github.com/Finschia/finschia-sdk/x/auth/legacy/legacytx" 22 ) 23 24 func TestNewMultiSig(t *testing.T) { 25 require := require.New(t) 26 pk1 := secp256k1.GenPrivKey().PubKey() 27 pks := []cryptotypes.PubKey{pk1, pk1} 28 29 require.NotNil(kmultisig.NewLegacyAminoPubKey(1, pks), 30 "Should support not unique public keys") 31 } 32 33 func TestAddress(t *testing.T) { 34 pubKeys := generatePubKeys(5) 35 multisigKey := kmultisig.NewLegacyAminoPubKey(2, pubKeys) 36 37 require.Len(t, multisigKey.Address(), 20) 38 } 39 40 func TestEquals(t *testing.T) { 41 pubKey1 := secp256k1.GenPrivKey().PubKey() 42 pubKey2 := secp256k1.GenPrivKey().PubKey() 43 44 multisigKey := kmultisig.NewLegacyAminoPubKey(1, []cryptotypes.PubKey{pubKey1, pubKey2}) 45 otherMultisigKey := kmultisig.NewLegacyAminoPubKey(1, []cryptotypes.PubKey{pubKey1, multisigKey}) 46 47 testCases := []struct { 48 msg string 49 other cryptotypes.PubKey 50 expectEq bool 51 }{ 52 { 53 "equals with proto pub key", 54 &kmultisig.LegacyAminoPubKey{Threshold: 1, PubKeys: multisigKey.PubKeys}, 55 true, 56 }, 57 { 58 "different threshold", 59 &kmultisig.LegacyAminoPubKey{Threshold: 2, PubKeys: multisigKey.PubKeys}, 60 false, 61 }, 62 { 63 "different pub keys length", 64 &kmultisig.LegacyAminoPubKey{Threshold: 1, PubKeys: []*types.Any{multisigKey.PubKeys[0]}}, 65 false, 66 }, 67 { 68 "different pub keys", 69 otherMultisigKey, 70 false, 71 }, 72 { 73 "different types", 74 secp256k1.GenPrivKey().PubKey(), 75 false, 76 }, 77 { 78 "ensure that reordering pubkeys is treated as a different pubkey", 79 reorderPubKey(multisigKey), 80 false, 81 }, 82 } 83 84 for _, tc := range testCases { 85 t.Run(tc.msg, func(t *testing.T) { 86 eq := multisigKey.Equals(tc.other) 87 require.Equal(t, eq, tc.expectEq) 88 }) 89 } 90 } 91 92 func TestVerifyMultisignature(t *testing.T) { 93 var ( 94 pk multisig.PubKey 95 sig *signing.MultiSignatureData 96 ) 97 msg := []byte{1, 2, 3, 4} 98 signBytesFn := func(mode signing.SignMode) ([]byte, error) { return msg, nil } 99 100 testCases := []struct { 101 msg string 102 malleate func(*require.Assertions) 103 expectPass bool 104 }{ 105 { 106 "nested multisignature", 107 func(require *require.Assertions) { 108 genPk, genSig := generateNestedMultiSignature(3, msg) 109 sig = genSig 110 pk = genPk 111 }, 112 true, 113 }, 114 { 115 "wrong size for sig bit array", 116 func(require *require.Assertions) { 117 pubKeys := generatePubKeys(3) 118 pk = kmultisig.NewLegacyAminoPubKey(3, pubKeys) 119 sig = multisig.NewMultisig(1) 120 }, 121 false, 122 }, 123 { 124 "single signature data, expects the first k signatures to be valid", 125 func(require *require.Assertions) { 126 k := 2 127 signingIndices := []int{0, 3, 1} 128 pubKeys, sigs := generatePubKeysAndSignatures(5, msg) 129 pk = kmultisig.NewLegacyAminoPubKey(k, pubKeys) 130 sig = multisig.NewMultisig(len(pubKeys)) 131 signBytesFn := func(mode signing.SignMode) ([]byte, error) { return msg, nil } 132 133 for i := 0; i < k-1; i++ { 134 signingIndex := signingIndices[i] 135 require.NoError( 136 multisig.AddSignatureFromPubKey(sig, sigs[signingIndex], pubKeys[signingIndex], pubKeys), 137 ) 138 require.Error( 139 pk.VerifyMultisignature(signBytesFn, sig), 140 "multisig passed when i < k, i %d", i, 141 ) 142 require.NoError( 143 multisig.AddSignatureFromPubKey(sig, sigs[signingIndex], pubKeys[signingIndex], pubKeys), 144 ) 145 require.Equal( 146 i+1, 147 len(sig.Signatures), 148 "adding a signature for the same pubkey twice increased signature count by 2, index %d", i, 149 ) 150 } 151 require.Error( 152 pk.VerifyMultisignature(signBytesFn, sig), 153 "multisig passed with k - 1 sigs", 154 ) 155 require.NoError( 156 multisig.AddSignatureFromPubKey( 157 sig, 158 sigs[signingIndices[k]], 159 pubKeys[signingIndices[k]], 160 pubKeys, 161 ), 162 ) 163 require.NoError( 164 pk.VerifyMultisignature(signBytesFn, sig), 165 "multisig failed after k good signatures", 166 ) 167 }, 168 true, 169 }, 170 { 171 "duplicate signatures", 172 func(require *require.Assertions) { 173 pubKeys, sigs := generatePubKeysAndSignatures(5, msg) 174 pk = kmultisig.NewLegacyAminoPubKey(2, pubKeys) 175 sig = multisig.NewMultisig(5) 176 177 require.Error(pk.VerifyMultisignature(signBytesFn, sig)) 178 multisig.AddSignatureFromPubKey(sig, sigs[0], pubKeys[0], pubKeys) 179 // Add second signature manually 180 sig.Signatures = append(sig.Signatures, sigs[0]) 181 }, 182 false, 183 }, 184 { 185 "duplicated key", 186 func(require *require.Assertions) { 187 // here we test an edge case where we create a multi sig with two same 188 // keys. It should work. 189 pubkeys, sigs := generatePubKeysAndSignatures(3, msg) 190 pubkeys[1] = pubkeys[0] 191 pk = kmultisig.NewLegacyAminoPubKey(2, pubkeys) 192 sig = multisig.NewMultisig(len(pubkeys)) 193 multisig.AddSignature(sig, sigs[0], 0) 194 multisig.AddSignature(sig, sigs[0], 1) 195 }, 196 true, 197 }, 198 { 199 "same key used twice", 200 func(require *require.Assertions) { 201 pubkeys, sigs := generatePubKeysAndSignatures(3, msg) 202 pk = kmultisig.NewLegacyAminoPubKey(2, pubkeys) 203 sig = multisig.NewMultisig(len(pubkeys)) 204 multisig.AddSignature(sig, sigs[0], 0) 205 multisig.AddSignature(sig, sigs[0], 1) 206 }, 207 false, 208 }, 209 { 210 "unable to verify signature", 211 func(require *require.Assertions) { 212 pubKeys := generatePubKeys(2) 213 _, sigs := generatePubKeysAndSignatures(2, msg) 214 pk = kmultisig.NewLegacyAminoPubKey(2, pubKeys) 215 sig = multisig.NewMultisig(2) 216 multisig.AddSignatureFromPubKey(sig, sigs[0], pubKeys[0], pubKeys) 217 multisig.AddSignatureFromPubKey(sig, sigs[1], pubKeys[1], pubKeys) 218 }, 219 false, 220 }, 221 } 222 223 for _, tc := range testCases { 224 t.Run(tc.msg, func(t *testing.T) { 225 tc.malleate(require.New(t)) 226 err := pk.VerifyMultisignature(signBytesFn, sig) 227 if tc.expectPass { 228 require.NoError(t, err) 229 } else { 230 require.Error(t, err) 231 } 232 }) 233 } 234 } 235 236 func TestAddSignatureFromPubKeyNilCheck(t *testing.T) { 237 pkSet, sigs := generatePubKeysAndSignatures(5, []byte{1, 2, 3, 4}) 238 multisignature := multisig.NewMultisig(5) 239 240 // verify no error is returned with all non-nil values 241 err := multisig.AddSignatureFromPubKey(multisignature, sigs[0], pkSet[0], pkSet) 242 require.NoError(t, err) 243 // verify error is returned when key value is nil 244 err = multisig.AddSignatureFromPubKey(multisignature, sigs[0], pkSet[0], nil) 245 require.Error(t, err) 246 // verify error is returned when pubkey value is nil 247 err = multisig.AddSignatureFromPubKey(multisignature, sigs[0], nil, pkSet) 248 require.Error(t, err) 249 // verify error is returned when signature value is nil 250 err = multisig.AddSignatureFromPubKey(multisignature, nil, pkSet[0], pkSet) 251 require.Error(t, err) 252 // verify error is returned when multisignature value is nil 253 err = multisig.AddSignatureFromPubKey(nil, sigs[0], pkSet[0], pkSet) 254 require.Error(t, err) 255 } 256 257 func TestMultiSigMigration(t *testing.T) { 258 msg := []byte{1, 2, 3, 4} 259 pkSet, sigs := generatePubKeysAndSignatures(2, msg) 260 multisignature := multisig.NewMultisig(2) 261 262 multisigKey := kmultisig.NewLegacyAminoPubKey(2, pkSet) 263 signBytesFn := func(mode signing.SignMode) ([]byte, error) { return msg, nil } 264 265 cdc := codec.NewLegacyAmino() 266 267 require.NoError(t, multisig.AddSignatureFromPubKey(multisignature, sigs[0], pkSet[0], pkSet)) 268 269 // create a StdSignature for msg, and convert it to sigV2 270 sig := legacytx.StdSignature{PubKey: pkSet[1], Signature: sigs[1].(*signing.SingleSignatureData).Signature} 271 sigV2, err := legacytx.StdSignatureToSignatureV2(cdc, sig) 272 require.NoError(t, multisig.AddSignatureV2(multisignature, sigV2, pkSet)) 273 274 require.NoError(t, err) 275 require.NotNil(t, sigV2) 276 277 require.NoError(t, multisigKey.VerifyMultisignature(signBytesFn, multisignature)) 278 } 279 280 func TestPubKeyMultisigThresholdAminoToIface(t *testing.T) { 281 pubkeys := generatePubKeys(5) 282 multisigKey := kmultisig.NewLegacyAminoPubKey(2, pubkeys) 283 284 ab, err := legacy.Cdc.MarshalLengthPrefixed(multisigKey) 285 require.NoError(t, err) 286 // like other cryptotypes.Pubkey implementations (e.g. ed25519.PubKey), 287 // LegacyAminoPubKey should be deserializable into a cryptotypes.LegacyAminoPubKey: 288 var pubKey kmultisig.LegacyAminoPubKey 289 err = legacy.Cdc.UnmarshalLengthPrefixed(ab, &pubKey) 290 require.NoError(t, err) 291 292 require.Equal(t, multisigKey.Equals(&pubKey), true) 293 } 294 295 func generatePubKeys(n int) []cryptotypes.PubKey { 296 pks := make([]cryptotypes.PubKey, n) 297 for i := 0; i < n; i++ { 298 pks[i] = secp256k1.GenPrivKey().PubKey() 299 } 300 return pks 301 } 302 303 func generatePubKeysAndSignatures(n int, msg []byte) (pubKeys []cryptotypes.PubKey, signatures []signing.SignatureData) { 304 pubKeys = make([]cryptotypes.PubKey, n) 305 signatures = make([]signing.SignatureData, n) 306 307 for i := 0; i < n; i++ { 308 privkey := secp256k1.GenPrivKey() 309 pubKeys[i] = privkey.PubKey() 310 311 sig, _ := privkey.Sign(msg) 312 signatures[i] = &signing.SingleSignatureData{Signature: sig} 313 } 314 return 315 } 316 317 func generateNestedMultiSignature(n int, msg []byte) (multisig.PubKey, *signing.MultiSignatureData) { 318 pubKeys := make([]cryptotypes.PubKey, n) 319 signatures := make([]signing.SignatureData, n) 320 bitArray := cryptotypes.NewCompactBitArray(n) 321 for i := 0; i < n; i++ { 322 nestedPks, nestedSigs := generatePubKeysAndSignatures(5, msg) 323 nestedBitArray := cryptotypes.NewCompactBitArray(5) 324 for j := 0; j < 5; j++ { 325 nestedBitArray.SetIndex(j, true) 326 } 327 nestedSig := &signing.MultiSignatureData{ 328 BitArray: nestedBitArray, 329 Signatures: nestedSigs, 330 } 331 signatures[i] = nestedSig 332 pubKeys[i] = kmultisig.NewLegacyAminoPubKey(5, nestedPks) 333 bitArray.SetIndex(i, true) 334 } 335 return kmultisig.NewLegacyAminoPubKey(n, pubKeys), &signing.MultiSignatureData{ 336 BitArray: bitArray, 337 Signatures: signatures, 338 } 339 } 340 341 func reorderPubKey(pk *kmultisig.LegacyAminoPubKey) (other *kmultisig.LegacyAminoPubKey) { 342 pubkeysCpy := make([]*types.Any, len(pk.PubKeys)) 343 copy(pubkeysCpy, pk.PubKeys) 344 pubkeysCpy[0] = pk.PubKeys[1] 345 pubkeysCpy[1] = pk.PubKeys[0] 346 other = &kmultisig.LegacyAminoPubKey{Threshold: 2, PubKeys: pubkeysCpy} 347 return 348 } 349 350 func TestDisplay(t *testing.T) { 351 require := require.New(t) 352 pubKeys := generatePubKeys(3) 353 msig := kmultisig.NewLegacyAminoPubKey(2, pubKeys) 354 355 // LegacyAminoPubKey wraps PubKeys into Amino (for serialization) and Any String method doesn't work. 356 require.PanicsWithValue("reflect.Value.Interface: cannot return value obtained from unexported field or method", 357 func() { require.Empty(msig.String()) }, 358 ) 359 ccfg := simapp.MakeTestEncodingConfig() 360 bz, err := ccfg.Marshaler.MarshalInterfaceJSON(msig) 361 require.NoError(err) 362 expectedPrefix := `{"@type":"/cosmos.crypto.multisig.LegacyAminoPubKey","threshold":2,"public_keys":[{"@type":"/cosmos.crypto.secp256k1.PubKey"` 363 require.True(strings.HasPrefix(string(bz), expectedPrefix)) 364 // Example output: 365 // {"@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"}]} 366 } 367 368 func TestAminoBinary(t *testing.T) { 369 pubkeys := generatePubKeys(2) 370 msig := kmultisig.NewLegacyAminoPubKey(2, pubkeys) 371 372 // Do a round-trip key->bytes->key. 373 bz, err := legacy.Cdc.Marshal(msig) 374 require.NoError(t, err) 375 var newMsig cryptotypes.PubKey 376 err = legacy.Cdc.Unmarshal(bz, &newMsig) 377 require.NoError(t, err) 378 require.Equal(t, msig.Threshold, newMsig.(*kmultisig.LegacyAminoPubKey).Threshold) 379 } 380 381 func TestAminoMarshalJSON(t *testing.T) { 382 pubkeys := generatePubKeys(2) 383 multisigKey := kmultisig.NewLegacyAminoPubKey(2, pubkeys) 384 bz, err := legacy.Cdc.MarshalJSON(multisigKey) 385 require.NoError(t, err) 386 387 // Note the quotes around `"2"`. They are present because we are overriding 388 // the Amino JSON marshaling of LegacyAminoPubKey (using tmMultisig). 389 // Without the override, there would not be any quotes. 390 require.Contains(t, string(bz), "\"threshold\":\"2\"") 391 } 392 393 func TestAminoUnmarshalJSON(t *testing.T) { 394 // This is a real multisig from the Akash chain. It has been exported from 395 // v0.39, hence the `threshold` field as a string. 396 // We are testing that when unmarshaling this JSON into a LegacyAminoPubKey 397 // with amino, there's no error. 398 // ref: https://github.com/cosmos/cosmos-sdk/issues/8776 399 pkJSON := `{ 400 "type": "tendermint/PubKeyMultisigThreshold", 401 "value": { 402 "pubkeys": [ 403 { 404 "type": "tendermint/PubKeySecp256k1", 405 "value": "AzYxq2VNeD10TyABwOgV36OVWDIMn8AtI4OFA0uQX2MK" 406 }, 407 { 408 "type": "tendermint/PubKeySecp256k1", 409 "value": "A39cdsrm00bTeQ3RVZVqjkH8MvIViO9o99c8iLiNO35h" 410 }, 411 { 412 "type": "tendermint/PubKeySecp256k1", 413 "value": "A/uLLCZph8MkFg2tCxqSMGwFfPHdt1kkObmmrqy9aiYD" 414 }, 415 { 416 "type": "tendermint/PubKeySecp256k1", 417 "value": "A4mOMhM5gPDtBAkAophjRs6uDGZm4tD4Dbok3ai4qJi8" 418 }, 419 { 420 "type": "tendermint/PubKeySecp256k1", 421 "value": "A90icFucrjNNz2SAdJWMApfSQcARIqt+M2x++t6w5fFs" 422 } 423 ], 424 "threshold": "3" 425 } 426 }` 427 428 cdc := codec.NewLegacyAmino() 429 cryptocodec.RegisterCrypto(cdc) 430 431 var pk cryptotypes.PubKey 432 err := cdc.UnmarshalJSON([]byte(pkJSON), &pk) 433 require.NoError(t, err) 434 lpk := pk.(*kmultisig.LegacyAminoPubKey) 435 require.Equal(t, uint32(3), lpk.Threshold) 436 } 437 438 func TestProtoMarshalJSON(t *testing.T) { 439 require := require.New(t) 440 pubkeys := generatePubKeys(3) 441 msig := kmultisig.NewLegacyAminoPubKey(2, pubkeys) 442 443 registry := types.NewInterfaceRegistry() 444 cryptocodec.RegisterInterfaces(registry) 445 cdc := codec.NewProtoCodec(registry) 446 447 bz, err := cdc.MarshalInterfaceJSON(msig) 448 require.NoError(err) 449 450 var pk2 cryptotypes.PubKey 451 err = cdc.UnmarshalInterfaceJSON(bz, &pk2) 452 require.NoError(err) 453 require.True(pk2.Equals(msig)) 454 455 // Test that we can correctly unmarshal key from keyring output 456 457 info, err := keyring.NewMultiInfo("my multisig", msig) 458 require.NoError(err) 459 ko, err := keyring.MkAccKeyOutput(info) 460 require.NoError(err) 461 require.Equal(ko.Address, sdk.AccAddress(pk2.Address()).String()) 462 require.Equal(ko.PubKey, string(bz)) 463 }