Threshold Multisignature implementationpull/2205/head
@ -0,0 +1,69 @@ | |||
package multisig | |||
import ( | |||
"errors" | |||
"github.com/tendermint/tendermint/crypto" | |||
) | |||
// 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 *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{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 { | |||
return errors.New("provided key didn't exist in pubkeys") | |||
} | |||
mSig.AddSignature(sig, index) | |||
return nil | |||
} | |||
// Marshal the multisignature with amino | |||
func (mSig *Multisignature) Marshal() []byte { | |||
return cdc.MustMarshalBinaryBare(mSig) | |||
} |
@ -0,0 +1,92 @@ | |||
package multisig | |||
import ( | |||
"github.com/tendermint/tendermint/crypto" | |||
"github.com/tendermint/tendermint/crypto/tmhash" | |||
) | |||
// ThresholdMultiSignaturePubKey implements a K of N threshold multisig. | |||
type ThresholdMultiSignaturePubKey struct { | |||
K uint `json:"threshold"` | |||
Pubkeys []crypto.PubKey `json:"pubkeys"` | |||
} | |||
var _ crypto.PubKey = &ThresholdMultiSignaturePubKey{} | |||
// NewThresholdMultiSignaturePubKey returns a new ThresholdMultiSignaturePubKey. | |||
// Panics if len(pubkeys) < k or 0 >= k. | |||
func NewThresholdMultiSignaturePubKey(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") | |||
} | |||
return &ThresholdMultiSignaturePubKey{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 *ThresholdMultiSignaturePubKey) 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 ThresholdMultiSignaturePubKey | |||
func (pk *ThresholdMultiSignaturePubKey) Bytes() []byte { | |||
return cdc.MustMarshalBinaryBare(pk) | |||
} | |||
// Address returns tmhash(ThresholdMultiSignaturePubKey.Bytes()) | |||
func (pk *ThresholdMultiSignaturePubKey) Address() crypto.Address { | |||
return crypto.Address(tmhash.Sum(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 *ThresholdMultiSignaturePubKey) Equals(other crypto.PubKey) bool { | |||
otherKey, sameType := other.(*ThresholdMultiSignaturePubKey) | |||
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 | |||
} |
@ -0,0 +1,112 @@ | |||
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" | |||
) | |||
// 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 := NewThresholdMultiSignaturePubKey(tc.k, tc.pubkeys) | |||
multisignature := NewMultisig(len(tc.pubkeys)) | |||
for i := 0; i < tc.k-1; i++ { | |||
signingIndex := tc.signingIndices[i] | |||
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) | |||
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) | |||
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] | |||
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) | |||
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 := NewThresholdMultiSignaturePubKey(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 := NewThresholdMultiSignaturePubKey(2, pubkeys) | |||
var unmarshalledMultisig *ThresholdMultiSignaturePubKey | |||
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 := NewThresholdMultiSignaturePubKey(2, pubkeysCpy) | |||
require.False(t, multisigKey.Equals(multisigKey2)) | |||
} | |||
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 | |||
if rand.Int63()%2 == 0 { | |||
privkey = ed25519.GenPrivKey() | |||
} else { | |||
privkey = secp256k1.GenPrivKey() | |||
} | |||
pubkeys[i] = privkey.PubKey() | |||
signatures[i], _ = privkey.Sign(msg) | |||
} | |||
return | |||
} |
@ -0,0 +1,26 @@ | |||
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" | |||
) | |||
// TODO: Figure out API for others to either add their own pubkey types, or | |||
// to make verify / marshal accept a cdc. | |||
const ( | |||
ThresholdPubkeyAminoRoute = "tendermint/PubkeyThresholdMultisig" | |||
) | |||
var cdc = amino.NewCodec() | |||
func init() { | |||
cdc.RegisterInterface((*crypto.PubKey)(nil), nil) | |||
cdc.RegisterConcrete(ThresholdMultiSignaturePubKey{}, | |||
ThresholdPubkeyAminoRoute, nil) | |||
cdc.RegisterConcrete(ed25519.PubKeyEd25519{}, | |||
ed25519.Ed25519PubKeyAminoRoute, nil) | |||
cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{}, | |||
secp256k1.Secp256k1PubKeyAminoRoute, nil) | |||
} |