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) | |||||
} |