@ -0,0 +1,60 @@ | |||
package multisig | |||
import "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. | |||
func (mSig *Multisignature) AddSignature(sig []byte, index int) { | |||
i := mSig.BitArray.trueIndex(index) | |||
// Signature already exists, just replace the value there | |||
if mSig.BitArray.GetIndex(index) { | |||
mSig.Sigs[i] = sig | |||
return | |||
} | |||
mSig.BitArray.SetIndex(index, true) | |||
// Optimization if the index is the greatest index | |||
if i > 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[i+1:], mSig.Sigs[i:]) | |||
mSig.Sigs[i] = 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) { | |||
index := GetIndex(pubkey, keys) | |||
mSig.AddSignature(sig, index) | |||
} | |||
// Marshal the multisignature with amino | |||
func (mSig *Multisignature) Marshal() []byte { | |||
return cdc.MustMarshalBinary(mSig) | |||
} |
@ -0,0 +1,78 @@ | |||
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. | |||
func NewThresholdMultiSignaturePubKey(k int, pubkeys []crypto.PubKey) crypto.PubKey { | |||
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.UnmarshalBinary(marshalledSig, &sig) | |||
if err != nil { | |||
return false | |||
} | |||
size := sig.BitArray.Size() | |||
if len(sig.Sigs) < int(pk.K) || len(pk.Pubkeys) != size { | |||
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.MustMarshalBinary(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 { | |||
if otherKey, ok := other.(*ThresholdMultiSignaturePubKey); ok { | |||
if pk.K != otherKey.K { | |||
return false | |||
} | |||
for i := uint(0); i < pk.K; i++ { | |||
if !pk.Pubkeys[i].Equals(otherKey.Pubkeys[i]) { | |||
return false | |||
} | |||
} | |||
return true | |||
} | |||
return false | |||
} |
@ -0,0 +1,73 @@ | |||
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" | |||
) | |||
func TestThresholdMultisig(t *testing.T) { | |||
msg := []byte{1, 2, 3, 4} | |||
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) | |||
require.False(t, multisigKey.VerifyBytes(msg, multisignature.Marshal())) | |||
// Make sure adding the same signature twice doesn't make the signature pass | |||
multisignature.AddSignatureFromPubkey(sigs[0], pubkeys[0], pubkeys) | |||
require.False(t, multisigKey.VerifyBytes(msg, multisignature.Marshal())) | |||
// Adding two signatures should make it pass, as k = 2 | |||
multisignature.AddSignatureFromPubkey(sigs[3], pubkeys[3], pubkeys) | |||
require.True(t, multisigKey.VerifyBytes(msg, multisignature.Marshal())) | |||
// Adding a third invalid signature should make verification fail. | |||
multisignature.AddSignatureFromPubkey(sigs[0], pubkeys[4], pubkeys) | |||
require.False(t, multisigKey.VerifyBytes(msg, multisignature.Marshal())) | |||
// try adding the invalid signature one signature before | |||
// first reset the multisig | |||
multisignature.BitArray.SetIndex(4, false) | |||
multisignature.Sigs = multisignature.Sigs[:2] | |||
multisignature.AddSignatureFromPubkey(sigs[0], pubkeys[2], pubkeys) | |||
require.False(t, multisigKey.VerifyBytes(msg, multisignature.Marshal())) | |||
} | |||
func TestMultiSigPubkeyEquality(t *testing.T) { | |||
msg := []byte{1, 2, 3, 4} | |||
pubkeys, _ := generatePubKeysAndSignatures(5, msg) | |||
multisigKey := NewThresholdMultiSignaturePubKey(2, pubkeys) | |||
var unmarshalledMultisig *ThresholdMultiSignaturePubKey | |||
cdc.MustUnmarshalBinary(multisigKey.Bytes(), &unmarshalledMultisig) | |||
require.Equal(t, multisigKey, 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.NotEqual(t, multisigKey, 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/ThresholdMultisigPubkey" | |||
) | |||
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) | |||
} |