From 6961c7e5d1f2a187bc5f77ac0056ce5ec5bf7ec6 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 9 Jun 2020 10:13:49 +0200 Subject: [PATCH] crypto: removal of multisig (#4988) ## Description deprecation & removal of multisig. This key was only used in the sdk and now it has been added there Closes: #4715 Closes: #2163 --- CHANGELOG_PENDING.md | 2 + UPGRADING.md | 4 + crypto/encoding/amino/amino.go | 4 - crypto/encoding/amino/encode_test.go | 3 - crypto/multisig/bitarray/compact_bit_array.go | 233 ------------------ .../bitarray/compact_bit_array_test.go | 202 --------------- crypto/multisig/codec.go | 30 --- crypto/multisig/multisignature.go | 77 ------ crypto/multisig/threshold_pubkey.go | 96 -------- crypto/multisig/threshold_pubkey_test.go | 181 -------------- 10 files changed, 6 insertions(+), 826 deletions(-) delete mode 100644 crypto/multisig/bitarray/compact_bit_array.go delete mode 100644 crypto/multisig/bitarray/compact_bit_array_test.go delete mode 100644 crypto/multisig/codec.go delete mode 100644 crypto/multisig/multisignature.go delete mode 100644 crypto/multisig/threshold_pubkey.go delete mode 100644 crypto/multisig/threshold_pubkey_test.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 69db2c8e3..de693aa06 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -23,6 +23,8 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [consensus] \#4582 HeightVoteSet: `round` is now int32 - [privval] \#4582 `round` in private_validator_state.json is no longer a string in json it is now a number. - [crypto] \#4940 All keys have become `[]byte` instead of `[]byte`. The byte method no longer returns the marshaled value but just the `[]byte` form of the data. + - [crypto] \4988 Removal of key type multisig + - The key has been moved to the Cosmos-SDK (https://github.com/cosmos/cosmos-sdk/blob/master/crypto/types/multisig/multisignature.go) - [crypto] \#4941 Remove suffixes from all keys. - ed25519: type `PrivKeyEd25519` is now `PrivKey` - ed25519: type `PubKeyEd25519` is now `PubKey` diff --git a/UPGRADING.md b/UPGRADING.md index e364a617d..72276edda 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -11,6 +11,10 @@ a newer version of Tendermint Core. - `KV.Pair` has been replaced with `abci.EventAttribute`. This allows applications to indicate if a msg should be indexed at runtime. Previously this was only possible if the node operator decided to index specific or all messages on startup of the node, now the application can indicate which msgs should be indexed. +### Crypto + +- `Multsig` & `PubKeyMultisigThreshold` have been moved to the [Cosmos-SDK](https://github.com/cosmos/cosmos-sdk). (https://github.com/cosmos/cosmos-sdk/blob/master/crypto/types/multisig/multisignature.go) + ## v0.33.4 ### Go API diff --git a/crypto/encoding/amino/amino.go b/crypto/encoding/amino/amino.go index 3da6078ee..a2e60e7ca 100644 --- a/crypto/encoding/amino/amino.go +++ b/crypto/encoding/amino/amino.go @@ -7,7 +7,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/multisig" "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/crypto/sr25519" ) @@ -34,7 +33,6 @@ func init() { nameTable[reflect.TypeOf(ed25519.PubKey{})] = ed25519.PubKeyAminoName nameTable[reflect.TypeOf(sr25519.PubKey{})] = sr25519.PubKeyAminoName nameTable[reflect.TypeOf(secp256k1.PubKey{})] = secp256k1.PubKeyAminoName - nameTable[reflect.TypeOf(multisig.PubKey{})] = multisig.PubKeyAminoRoute } // PubkeyAminoName returns the amino route of a pubkey @@ -55,8 +53,6 @@ func RegisterAmino(cdc *amino.Codec) { sr25519.PubKeyAminoName, nil) cdc.RegisterConcrete(secp256k1.PubKey{}, secp256k1.PubKeyAminoName, nil) - cdc.RegisterConcrete(multisig.PubKey{}, - multisig.PubKeyAminoRoute, nil) cdc.RegisterInterface((*crypto.PrivKey)(nil), nil) cdc.RegisterConcrete(ed25519.PrivKey{}, diff --git a/crypto/encoding/amino/encode_test.go b/crypto/encoding/amino/encode_test.go index 18908369a..8e5e44c3a 100644 --- a/crypto/encoding/amino/encode_test.go +++ b/crypto/encoding/amino/encode_test.go @@ -11,7 +11,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/multisig" "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/crypto/sr25519" tmjson "github.com/tendermint/tendermint/libs/json" @@ -139,7 +138,6 @@ func TestPubkeyAminoName(t *testing.T) { {ed25519.PubKey{}, ed25519.PubKeyAminoName, true}, {sr25519.PubKey{}, sr25519.PubKeyAminoName, true}, {secp256k1.PubKey{}, secp256k1.PubKeyAminoName, true}, - {multisig.PubKey{}, multisig.PubKeyAminoRoute, true}, } for i, tc := range tests { got, found := PubkeyAminoName(cdc, tc.key) @@ -220,5 +218,4 @@ func TestRegisterKeyType(t *testing.T) { nameTable[reflect.TypeOf(ed25519.PubKey{})] = ed25519.PubKeyAminoName nameTable[reflect.TypeOf(sr25519.PubKey{})] = sr25519.PubKeyAminoName nameTable[reflect.TypeOf(secp256k1.PubKey{})] = secp256k1.PubKeyAminoName - nameTable[reflect.TypeOf(multisig.PubKey{})] = multisig.PubKeyAminoRoute } diff --git a/crypto/multisig/bitarray/compact_bit_array.go b/crypto/multisig/bitarray/compact_bit_array.go deleted file mode 100644 index 890a4c9c7..000000000 --- a/crypto/multisig/bitarray/compact_bit_array.go +++ /dev/null @@ -1,233 +0,0 @@ -package bitarray - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "regexp" - "strings" -) - -// CompactBitArray is an implementation of a space efficient bit array. -// This is used to ensure that the encoded data takes up a minimal amount of -// space after amino encoding. -// This is not thread safe, and is not intended for concurrent usage. -type CompactBitArray struct { - ExtraBitsStored byte `json:"extra_bits"` // The number of extra bits in elems. - Elems []byte `json:"bits"` -} - -// NewCompactBitArray returns a new compact bit array. -// It returns nil if the number of bits is zero. -func NewCompactBitArray(bits int) *CompactBitArray { - if bits <= 0 { - return nil - } - return &CompactBitArray{ - ExtraBitsStored: byte(bits % 8), - Elems: make([]byte, (bits+7)/8), - } -} - -// Size returns the number of bits in the bitarray -func (bA *CompactBitArray) Size() int { - if bA == nil { - return 0 - } else if bA.ExtraBitsStored == byte(0) { - return len(bA.Elems) * 8 - } - // num_bits = 8*num_full_bytes + overflow_in_last_byte - // num_full_bytes = (len(bA.Elems)-1) - return (len(bA.Elems)-1)*8 + int(bA.ExtraBitsStored) -} - -// GetIndex returns the bit at index i within the bit array. -// The behavior is undefined if i >= bA.Size() -func (bA *CompactBitArray) GetIndex(i int) bool { - if bA == nil { - return false - } - if i >= bA.Size() { - return false - } - return bA.Elems[i>>3]&(uint8(1)< 0 -} - -// SetIndex sets the bit at index i within the bit array. -// The behavior is undefined if i >= bA.Size() -func (bA *CompactBitArray) SetIndex(i int, v bool) bool { - if bA == nil { - return false - } - if i >= bA.Size() { - return false - } - if v { - bA.Elems[i>>3] |= (uint8(1) << uint8(7-(i%8))) - } else { - bA.Elems[i>>3] &= ^(uint8(1) << uint8(7-(i%8))) - } - return true -} - -// NumTrueBitsBefore returns the number of bits set to true before the -// given index. e.g. if bA = _XX__XX, NumOfTrueBitsBefore(4) = 2, since -// there are two bits set to true before index 4. -func (bA *CompactBitArray) NumTrueBitsBefore(index int) int { - numTrueValues := 0 - for i := 0; i < index; i++ { - if bA.GetIndex(i) { - numTrueValues++ - } - } - return numTrueValues -} - -// Copy returns a copy of the provided bit array. -func (bA *CompactBitArray) Copy() *CompactBitArray { - if bA == nil { - return nil - } - c := make([]byte, len(bA.Elems)) - copy(c, bA.Elems) - return &CompactBitArray{ - ExtraBitsStored: bA.ExtraBitsStored, - Elems: c, - } -} - -// String returns a string representation of CompactBitArray: BA{}, -// where is a sequence of 'x' (1) and '_' (0). -// The includes spaces and newlines to help people. -// For a simple sequence of 'x' and '_' characters with no spaces or newlines, -// see the MarshalJSON() method. -// Example: "BA{_x_}" or "nil-BitArray" for nil. -func (bA *CompactBitArray) String() string { - return bA.StringIndented("") -} - -// StringIndented returns the same thing as String(), but applies the indent -// at every 10th bit, and twice at every 50th bit. -func (bA *CompactBitArray) StringIndented(indent string) string { - if bA == nil { - return "nil-BitArray" - } - lines := []string{} - bits := "" - size := bA.Size() - for i := 0; i < size; i++ { - if bA.GetIndex(i) { - bits += "x" - } else { - bits += "_" - } - if i%100 == 99 { - lines = append(lines, bits) - bits = "" - } - if i%10 == 9 { - bits += indent - } - if i%50 == 49 { - bits += indent - } - } - if len(bits) > 0 { - lines = append(lines, bits) - } - return fmt.Sprintf("BA{%v:%v}", size, strings.Join(lines, indent)) -} - -// MarshalJSON implements json.Marshaler interface by marshaling bit array -// using a custom format: a string of '-' or 'x' where 'x' denotes the 1 bit. -func (bA *CompactBitArray) MarshalJSON() ([]byte, error) { - if bA == nil { - return []byte("null"), nil - } - - bits := `"` - size := bA.Size() - for i := 0; i < size; i++ { - if bA.GetIndex(i) { - bits += `x` - } else { - bits += `_` - } - } - bits += `"` - return []byte(bits), nil -} - -var bitArrayJSONRegexp = regexp.MustCompile(`\A"([_x]*)"\z`) - -// UnmarshalJSON implements json.Unmarshaler interface by unmarshaling a custom -// JSON description. -func (bA *CompactBitArray) UnmarshalJSON(bz []byte) error { - b := string(bz) - if b == "null" { - // This is required e.g. for encoding/json when decoding - // into a pointer with pre-allocated BitArray. - bA.ExtraBitsStored = 0 - bA.Elems = nil - return nil - } - - // Validate 'b'. - match := bitArrayJSONRegexp.FindStringSubmatch(b) - if match == nil { - return fmt.Errorf("bitArray in JSON should be a string of format %q but got %s", bitArrayJSONRegexp.String(), b) - } - bits := match[1] - - // Construct new CompactBitArray and copy over. - numBits := len(bits) - bA2 := NewCompactBitArray(numBits) - for i := 0; i < numBits; i++ { - if bits[i] == 'x' { - bA2.SetIndex(i, true) - } - } - *bA = *bA2 - return nil -} - -// CompactMarshal is a space efficient encoding for CompactBitArray. -// It is not amino compatible. -func (bA *CompactBitArray) CompactMarshal() []byte { - size := bA.Size() - if size <= 0 { - return []byte("null") - } - bz := make([]byte, 0, size/8) - // length prefix number of bits, not number of bytes. This difference - // takes 3-4 bits in encoding, as opposed to instead encoding the number of - // bytes (saving 3-4 bits) and including the offset as a full byte. - bz = appendUvarint(bz, uint64(size)) - bz = append(bz, bA.Elems...) - return bz -} - -// CompactUnmarshal is a space efficient decoding for CompactBitArray. -// It is not amino compatible. -func CompactUnmarshal(bz []byte) (*CompactBitArray, error) { - if len(bz) < 2 { - return nil, errors.New("compact bit array: invalid compact unmarshal size") - } else if bytes.Equal(bz, []byte("null")) { - return NewCompactBitArray(0), nil - } - size, n := binary.Uvarint(bz) - bz = bz[n:] - if len(bz) != int(size+7)/8 { - return nil, errors.New("compact bit array: invalid compact unmarshal size") - } - - bA := &CompactBitArray{byte(int(size % 8)), bz} - return bA, nil -} - -func appendUvarint(b []byte, x uint64) []byte { - var a [binary.MaxVarintLen64]byte - n := binary.PutUvarint(a[:], x) - return append(b, a[:n]...) -} diff --git a/crypto/multisig/bitarray/compact_bit_array_test.go b/crypto/multisig/bitarray/compact_bit_array_test.go deleted file mode 100644 index f086dc877..000000000 --- a/crypto/multisig/bitarray/compact_bit_array_test.go +++ /dev/null @@ -1,202 +0,0 @@ -package bitarray - -import ( - "encoding/json" - "math/rand" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - tmrand "github.com/tendermint/tendermint/libs/rand" -) - -func randCompactBitArray(bits int) (*CompactBitArray, []byte) { - numBytes := (bits + 7) / 8 - src := tmrand.Bytes((bits + 7) / 8) - bA := NewCompactBitArray(bits) - - for i := 0; i < numBytes-1; i++ { - for j := uint8(0); j < 8; j++ { - bA.SetIndex(i*8+int(j), src[i]&(uint8(1)<<(8-j)) > 0) - } - } - // Set remaining bits - for i := uint8(0); i < 8-bA.ExtraBitsStored; i++ { - bA.SetIndex(numBytes*8+int(i), src[numBytes-1]&(uint8(1)<<(8-i)) > 0) - } - return bA, src -} - -func TestNewBitArrayNeverCrashesOnNegatives(t *testing.T) { - bitList := []int{-127, -128, -1 << 31} - for _, bits := range bitList { - bA := NewCompactBitArray(bits) - require.Nil(t, bA) - } -} - -func TestJSONMarshalUnmarshal(t *testing.T) { - - bA1 := NewCompactBitArray(0) - bA2 := NewCompactBitArray(1) - - bA3 := NewCompactBitArray(1) - bA3.SetIndex(0, true) - - bA4 := NewCompactBitArray(5) - bA4.SetIndex(0, true) - bA4.SetIndex(1, true) - - bA5 := NewCompactBitArray(9) - bA5.SetIndex(0, true) - bA5.SetIndex(1, true) - bA5.SetIndex(8, true) - - bA6 := NewCompactBitArray(16) - bA6.SetIndex(0, true) - bA6.SetIndex(1, true) - bA6.SetIndex(8, false) - bA6.SetIndex(15, true) - - testCases := []struct { - bA *CompactBitArray - marshalledBA string - }{ - {nil, `null`}, - {bA1, `null`}, - {bA2, `"_"`}, - {bA3, `"x"`}, - {bA4, `"xx___"`}, - {bA5, `"xx______x"`}, - {bA6, `"xx_____________x"`}, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.bA.String(), func(t *testing.T) { - bz, err := json.Marshal(tc.bA) - require.NoError(t, err) - - assert.Equal(t, tc.marshalledBA, string(bz)) - - var unmarshalledBA *CompactBitArray - err = json.Unmarshal(bz, &unmarshalledBA) - require.NoError(t, err) - - if tc.bA == nil { - require.Nil(t, unmarshalledBA) - } else { - require.NotNil(t, unmarshalledBA) - assert.EqualValues(t, tc.bA.Elems, unmarshalledBA.Elems) - if assert.EqualValues(t, tc.bA.String(), unmarshalledBA.String()) { - assert.EqualValues(t, tc.bA.Elems, unmarshalledBA.Elems) - } - } - }) - } -} - -func TestCompactMarshalUnmarshal(t *testing.T) { - bA1 := NewCompactBitArray(0) - bA2 := NewCompactBitArray(1) - - bA3 := NewCompactBitArray(1) - bA3.SetIndex(0, true) - - bA4 := NewCompactBitArray(5) - bA4.SetIndex(0, true) - bA4.SetIndex(1, true) - - bA5 := NewCompactBitArray(9) - bA5.SetIndex(0, true) - bA5.SetIndex(1, true) - bA5.SetIndex(8, true) - - bA6 := NewCompactBitArray(16) - bA6.SetIndex(0, true) - bA6.SetIndex(1, true) - bA6.SetIndex(8, false) - bA6.SetIndex(15, true) - - testCases := []struct { - bA *CompactBitArray - marshalledBA []byte - }{ - {nil, []byte("null")}, - {bA1, []byte("null")}, - {bA2, []byte{byte(1), byte(0)}}, - {bA3, []byte{byte(1), byte(128)}}, - {bA4, []byte{byte(5), byte(192)}}, - {bA5, []byte{byte(9), byte(192), byte(128)}}, - {bA6, []byte{byte(16), byte(192), byte(1)}}, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.bA.String(), func(t *testing.T) { - bz := tc.bA.CompactMarshal() - - assert.Equal(t, tc.marshalledBA, bz) - - unmarshalledBA, err := CompactUnmarshal(bz) - require.NoError(t, err) - if tc.bA == nil { - require.Nil(t, unmarshalledBA) - } else { - require.NotNil(t, unmarshalledBA) - assert.EqualValues(t, tc.bA.Elems, unmarshalledBA.Elems) - if assert.EqualValues(t, tc.bA.String(), unmarshalledBA.String()) { - assert.EqualValues(t, tc.bA.Elems, unmarshalledBA.Elems) - } - } - }) - } -} - -func TestCompactBitArrayNumOfTrueBitsBefore(t *testing.T) { - testCases := []struct { - marshalledBA string - bAIndex []int - trueValueIndex []int - }{ - {`"_____"`, []int{0, 1, 2, 3, 4}, []int{0, 0, 0, 0, 0}}, - {`"x"`, []int{0}, []int{0}}, - {`"_x"`, []int{1}, []int{0}}, - {`"x___xxxx"`, []int{0, 4, 5, 6, 7}, []int{0, 1, 2, 3, 4}}, - {`"__x_xx_x__x_x___"`, []int{2, 4, 5, 7, 10, 12}, []int{0, 1, 2, 3, 4, 5}}, - {`"______________xx"`, []int{14, 15}, []int{0, 1}}, - } - for tcIndex, tc := range testCases { - tc := tc - tcIndex := tcIndex - t.Run(tc.marshalledBA, func(t *testing.T) { - var bA *CompactBitArray - err := json.Unmarshal([]byte(tc.marshalledBA), &bA) - require.NoError(t, err) - - for i := 0; i < len(tc.bAIndex); i++ { - - require.Equal(t, tc.trueValueIndex[i], bA.NumTrueBitsBefore(tc.bAIndex[i]), "tc %d, i %d", tcIndex, i) - } - }) - } -} - -func TestCompactBitArrayGetSetIndex(t *testing.T) { - r := rand.New(rand.NewSource(100)) - numTests := 10 - numBitsPerArr := 100 - for i := 0; i < numTests; i++ { - bits := r.Intn(1000) - bA, _ := randCompactBitArray(bits) - - for j := 0; j < numBitsPerArr; j++ { - copy := bA.Copy() - index := r.Intn(bits) - val := (r.Int63() % 2) == 0 - bA.SetIndex(index, val) - require.Equal(t, val, bA.GetIndex(index), "bA.SetIndex(%d, %v) failed on bit array: %s", index, val, copy) - } - } -} diff --git a/crypto/multisig/codec.go b/crypto/multisig/codec.go deleted file mode 100644 index f56cfed28..000000000 --- a/crypto/multisig/codec.go +++ /dev/null @@ -1,30 +0,0 @@ -package multisig - -import ( - amino "github.com/tendermint/go-amino" - - "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/secp256k1" - "github.com/tendermint/tendermint/crypto/sr25519" -) - -// TODO: Figure out API for others to either add their own pubkey types, or -// to make verify / marshal accept a cdc. -const ( - PubKeyAminoRoute = "tendermint/PubKeyMultisigThreshold" -) - -var cdc = amino.NewCodec() - -func init() { - cdc.RegisterInterface((*crypto.PubKey)(nil), nil) - cdc.RegisterConcrete(PubKey{}, - PubKeyAminoRoute, nil) - cdc.RegisterConcrete(ed25519.PubKey{}, - ed25519.PubKeyAminoName, nil) - cdc.RegisterConcrete(sr25519.PubKey{}, - sr25519.PubKeyAminoName, nil) - cdc.RegisterConcrete(secp256k1.PubKey{}, - secp256k1.PubKeyAminoName, nil) -} diff --git a/crypto/multisig/multisignature.go b/crypto/multisig/multisignature.go deleted file mode 100644 index 1e3bef4e1..000000000 --- a/crypto/multisig/multisignature.go +++ /dev/null @@ -1,77 +0,0 @@ -package multisig - -import ( - "fmt" - "strings" - - "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/multisig/bitarray" -) - -// Multisignature is used to represent the signature object used in the multisigs. -// Sigs is a list of signatures, sorted by corresponding index. -type Multisignature struct { - BitArray *bitarray.CompactBitArray - Sigs [][]byte -} - -// NewMultisig returns a new Multisignature of size n. -func NewMultisig(n int) *Multisignature { - // Default the signature list to have a capacity of two, since we can - // expect that most multisigs will require multiple signers. - return &Multisignature{bitarray.NewCompactBitArray(n), make([][]byte, 0, 2)} -} - -// GetIndex returns the index of pk in keys. Returns -1 if not found -func getIndex(pk crypto.PubKey, keys []crypto.PubKey) int { - for i := 0; i < len(keys); i++ { - if pk.Equals(keys[i]) { - return i - } - } - return -1 -} - -// AddSignature adds a signature to the multisig, at the corresponding index. -// If the signature already exists, replace it. -func (mSig *Multisignature) AddSignature(sig []byte, index int) { - newSigIndex := mSig.BitArray.NumTrueBitsBefore(index) - // Signature already exists, just replace the value there - if mSig.BitArray.GetIndex(index) { - mSig.Sigs[newSigIndex] = sig - return - } - mSig.BitArray.SetIndex(index, true) - // Optimization if the index is the greatest index - if newSigIndex == len(mSig.Sigs) { - mSig.Sigs = append(mSig.Sigs, sig) - return - } - // Expand slice by one with a dummy element, move all elements after i - // over by one, then place the new signature in that gap. - mSig.Sigs = append(mSig.Sigs, make([]byte, 0)) - copy(mSig.Sigs[newSigIndex+1:], mSig.Sigs[newSigIndex:]) - mSig.Sigs[newSigIndex] = sig -} - -// AddSignatureFromPubKey adds a signature to the multisig, at the index in -// keys corresponding to the provided pubkey. -func (mSig *Multisignature) AddSignatureFromPubKey(sig []byte, pubkey crypto.PubKey, keys []crypto.PubKey) error { - index := getIndex(pubkey, keys) - if index == -1 { - keysStr := make([]string, len(keys)) - for i, k := range keys { - keysStr[i] = fmt.Sprintf("%X", k.Bytes()) - } - - return fmt.Errorf("provided key %X doesn't exist in pubkeys: \n%s", pubkey.Bytes(), strings.Join(keysStr, "\n")) - } - - mSig.AddSignature(sig, index) - return nil -} - -// Marshal the multisignature with amino -func (mSig *Multisignature) Marshal() []byte { - return cdc.MustMarshalBinaryBare(mSig) -} diff --git a/crypto/multisig/threshold_pubkey.go b/crypto/multisig/threshold_pubkey.go deleted file mode 100644 index 3e0deb83b..000000000 --- a/crypto/multisig/threshold_pubkey.go +++ /dev/null @@ -1,96 +0,0 @@ -package multisig - -import ( - "github.com/tendermint/tendermint/crypto" -) - -// PubKey implements a K of N threshold multisig. -type PubKey struct { - K uint `json:"threshold"` - PubKeys []crypto.PubKey `json:"pubkeys"` -} - -var _ crypto.PubKey = PubKey{} - -// NewPubKeyMultisigThreshold returns a new PubKeyMultisigThreshold. -// Panics if len(pubkeys) < k or 0 >= k. -func NewPubKeyMultisigThreshold(k int, pubkeys []crypto.PubKey) crypto.PubKey { - if k <= 0 { - panic("threshold k of n multisignature: k <= 0") - } - if len(pubkeys) < k { - panic("threshold k of n multisignature: len(pubkeys) < k") - } - for _, pubkey := range pubkeys { - if pubkey == nil { - panic("nil pubkey") - } - } - return PubKey{uint(k), pubkeys} -} - -// VerifyBytes expects sig to be an amino encoded version of a MultiSignature. -// Returns true iff the multisignature contains k or more signatures -// for the correct corresponding keys, -// and all signatures are valid. (Not just k of the signatures) -// The multisig uses a bitarray, so multiple signatures for the same key is not -// a concern. -func (pk PubKey) VerifyBytes(msg []byte, marshalledSig []byte) bool { - var sig Multisignature - err := cdc.UnmarshalBinaryBare(marshalledSig, &sig) - if err != nil { - return false - } - size := sig.BitArray.Size() - // ensure bit array is the correct size - if len(pk.PubKeys) != size { - return false - } - // ensure size of signature list - if len(sig.Sigs) < int(pk.K) || len(sig.Sigs) > size { - return false - } - // ensure at least k signatures are set - if sig.BitArray.NumTrueBitsBefore(size) < int(pk.K) { - return false - } - // index in the list of signatures which we are concerned with. - sigIndex := 0 - for i := 0; i < size; i++ { - if sig.BitArray.GetIndex(i) { - if !pk.PubKeys[i].VerifyBytes(msg, sig.Sigs[sigIndex]) { - return false - } - sigIndex++ - } - } - return true -} - -// Bytes returns the amino encoded version of the PubKey -func (pk PubKey) Bytes() []byte { - return cdc.MustMarshalBinaryBare(pk) -} - -// Address returns tmhash(PubKey.Bytes()) -func (pk PubKey) Address() crypto.Address { - return crypto.AddressHash(pk.Bytes()) -} - -// Equals returns true iff pk and other both have the same number of keys, and -// all constituent keys are the same, and in the same order. -func (pk PubKey) Equals(other crypto.PubKey) bool { - otherKey, sameType := other.(PubKey) - if !sameType { - return false - } - if pk.K != otherKey.K || len(pk.PubKeys) != len(otherKey.PubKeys) { - return false - } - for i := 0; i < len(pk.PubKeys); i++ { - if !pk.PubKeys[i].Equals(otherKey.PubKeys[i]) { - return false - } - } - return true -} diff --git a/crypto/multisig/threshold_pubkey_test.go b/crypto/multisig/threshold_pubkey_test.go deleted file mode 100644 index a3aebe6f0..000000000 --- a/crypto/multisig/threshold_pubkey_test.go +++ /dev/null @@ -1,181 +0,0 @@ -package multisig - -import ( - "math/rand" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/secp256k1" - "github.com/tendermint/tendermint/crypto/sr25519" -) - -// This tests multisig functionality, but it expects the first k signatures to be valid -// TODO: Adapt it to give more flexibility about first k signatures being valid -func TestThresholdMultisigValidCases(t *testing.T) { - pkSet1, sigSet1 := generatePubKeysAndSignatures(5, []byte{1, 2, 3, 4}) - cases := []struct { - msg []byte - k int - pubkeys []crypto.PubKey - signingIndices []int - // signatures should be the same size as signingIndices. - signatures [][]byte - passAfterKSignatures []bool - }{ - { - msg: []byte{1, 2, 3, 4}, - k: 2, - pubkeys: pkSet1, - signingIndices: []int{0, 3, 1}, - signatures: sigSet1, - passAfterKSignatures: []bool{false}, - }, - } - for tcIndex, tc := range cases { - multisigKey := NewPubKeyMultisigThreshold(tc.k, tc.pubkeys) - multisignature := NewMultisig(len(tc.pubkeys)) - - for i := 0; i < tc.k-1; i++ { - signingIndex := tc.signingIndices[i] - require.NoError( - t, - multisignature.AddSignatureFromPubKey(tc.signatures[signingIndex], tc.pubkeys[signingIndex], tc.pubkeys), - ) - require.False( - t, - multisigKey.VerifyBytes(tc.msg, multisignature.Marshal()), - "multisig passed when i < k, tc %d, i %d", tcIndex, i, - ) - require.NoError( - t, - multisignature.AddSignatureFromPubKey(tc.signatures[signingIndex], tc.pubkeys[signingIndex], tc.pubkeys), - ) - require.Equal( - t, - i+1, - len(multisignature.Sigs), - "adding a signature for the same pubkey twice increased signature count by 2, tc %d", tcIndex, - ) - } - - require.False( - t, - multisigKey.VerifyBytes(tc.msg, multisignature.Marshal()), - "multisig passed with k - 1 sigs, tc %d", tcIndex, - ) - require.NoError( - t, - multisignature.AddSignatureFromPubKey( - tc.signatures[tc.signingIndices[tc.k]], - tc.pubkeys[tc.signingIndices[tc.k]], - tc.pubkeys, - ), - ) - require.True( - t, - multisigKey.VerifyBytes(tc.msg, multisignature.Marshal()), - "multisig failed after k good signatures, tc %d", tcIndex, - ) - - for i := tc.k + 1; i < len(tc.signingIndices); i++ { - signingIndex := tc.signingIndices[i] - - require.NoError( - t, - multisignature.AddSignatureFromPubKey(tc.signatures[signingIndex], tc.pubkeys[signingIndex], tc.pubkeys), - ) - require.Equal( - t, - tc.passAfterKSignatures[i-tc.k-1], - multisigKey.VerifyBytes(tc.msg, multisignature.Marshal()), - "multisig didn't verify as expected after k sigs, tc %d, i %d", tcIndex, i, - ) - require.NoError( - t, - multisignature.AddSignatureFromPubKey(tc.signatures[signingIndex], tc.pubkeys[signingIndex], tc.pubkeys), - ) - require.Equal( - t, - i+1, - len(multisignature.Sigs), - "adding a signature for the same pubkey twice increased signature count by 2, tc %d", tcIndex, - ) - } - } -} - -// TODO: Fully replace this test with table driven tests -func TestThresholdMultisigDuplicateSignatures(t *testing.T) { - msg := []byte{1, 2, 3, 4, 5} - pubkeys, sigs := generatePubKeysAndSignatures(5, msg) - multisigKey := NewPubKeyMultisigThreshold(2, pubkeys) - multisignature := NewMultisig(5) - require.False(t, multisigKey.VerifyBytes(msg, multisignature.Marshal())) - multisignature.AddSignatureFromPubKey(sigs[0], pubkeys[0], pubkeys) - // Add second signature manually - multisignature.Sigs = append(multisignature.Sigs, sigs[0]) - require.False(t, multisigKey.VerifyBytes(msg, multisignature.Marshal())) -} - -// TODO: Fully replace this test with table driven tests -func TestMultiSigPubKeyEquality(t *testing.T) { - msg := []byte{1, 2, 3, 4} - pubkeys, _ := generatePubKeysAndSignatures(5, msg) - multisigKey := NewPubKeyMultisigThreshold(2, pubkeys) - var unmarshalledMultisig PubKey - cdc.MustUnmarshalBinaryBare(multisigKey.Bytes(), &unmarshalledMultisig) - require.True(t, multisigKey.Equals(unmarshalledMultisig)) - - // Ensure that reordering pubkeys is treated as a different pubkey - pubkeysCpy := make([]crypto.PubKey, 5) - copy(pubkeysCpy, pubkeys) - pubkeysCpy[4] = pubkeys[3] - pubkeysCpy[3] = pubkeys[4] - multisigKey2 := NewPubKeyMultisigThreshold(2, pubkeysCpy) - require.False(t, multisigKey.Equals(multisigKey2)) -} - -func TestAddress(t *testing.T) { - msg := []byte{1, 2, 3, 4} - pubkeys, _ := generatePubKeysAndSignatures(5, msg) - multisigKey := NewPubKeyMultisigThreshold(2, pubkeys) - require.Len(t, multisigKey.Address().Bytes(), 20) -} - -func TestPubKeyMultisigThresholdAminoToIface(t *testing.T) { - msg := []byte{1, 2, 3, 4} - pubkeys, _ := generatePubKeysAndSignatures(5, msg) - multisigKey := NewPubKeyMultisigThreshold(2, pubkeys) - - ab, err := cdc.MarshalBinaryLengthPrefixed(multisigKey) - require.NoError(t, err) - // like other crypto.Pubkey implementations (e.g. ed25519.PubKey), - // PubKey should be deserializable into a crypto.PubKey: - var pubKey crypto.PubKey - err = cdc.UnmarshalBinaryLengthPrefixed(ab, &pubKey) - require.NoError(t, err) - - require.Equal(t, multisigKey, pubKey) -} - -func generatePubKeysAndSignatures(n int, msg []byte) (pubkeys []crypto.PubKey, signatures [][]byte) { - pubkeys = make([]crypto.PubKey, n) - signatures = make([][]byte, n) - for i := 0; i < n; i++ { - var privkey crypto.PrivKey - switch rand.Int63() % 3 { - case 0: - privkey = ed25519.GenPrivKey() - case 1: - privkey = secp256k1.GenPrivKey() - case 2: - privkey = sr25519.GenPrivKey() - } - pubkeys[i] = privkey.PubKey() - signatures[i], _ = privkey.Sign(msg) - } - return -}