## Description deprecation & removal of multisig. This key was only used in the sdk and now it has been added there Closes: #4715 Closes: #2163pull/4991/head
@ -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)<<uint8(7-(i%8))) > 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{<bit-string>}, | |||
// where <bit-string> is a sequence of 'x' (1) and '_' (0). | |||
// The <bit-string> 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]...) | |||
} |
@ -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) | |||
} | |||
} | |||
} |
@ -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) | |||
} |
@ -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) | |||
} |
@ -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 | |||
} |
@ -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 | |||
} |