mirror of
https://github.com/fluencelabs/tendermint
synced 2025-05-15 08:01:19 +00:00
crypto: Threshold multisig implementation
This commit is contained in:
parent
21448bcf4f
commit
e7dd76c28d
@ -69,6 +69,21 @@ func (bA *CompactBitArray) SetIndex(i int, v bool) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// trueIndex returns the location of the given index, among the
|
||||||
|
// values in the bit array that are set to true.
|
||||||
|
// e.g. if bA = _XX_X_X, trueIndex(4) = 2, since
|
||||||
|
// the value at index 4 of the bit array is the third
|
||||||
|
// value that is true. (And it is 0-indexed)
|
||||||
|
func (bA *CompactBitArray) trueIndex(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.
|
// Copy returns a copy of the provided bit array.
|
||||||
func (bA *CompactBitArray) Copy() *CompactBitArray {
|
func (bA *CompactBitArray) Copy() *CompactBitArray {
|
||||||
if bA == nil {
|
if bA == nil {
|
||||||
|
@ -150,6 +150,32 @@ func TestCompactMarshalUnmarshal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompactBitArrayTrueIndex(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 {
|
||||||
|
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.trueIndex(tc.bAIndex[i]), "tc %d, i %d", tcIndex, i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCompactBitArrayGetSetIndex(t *testing.T) {
|
func TestCompactBitArrayGetSetIndex(t *testing.T) {
|
||||||
r := rand.New(rand.NewSource(100))
|
r := rand.New(rand.NewSource(100))
|
||||||
numTests := 10
|
numTests := 10
|
||||||
|
60
crypto/multisig/multisignature.go
Normal file
60
crypto/multisig/multisignature.go
Normal file
@ -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)
|
||||||
|
}
|
78
crypto/multisig/threshold_multisig.go
Normal file
78
crypto/multisig/threshold_multisig.go
Normal file
@ -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
|
||||||
|
}
|
73
crypto/multisig/threshold_multisig_test.go
Normal file
73
crypto/multisig/threshold_multisig_test.go
Normal file
@ -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
|
||||||
|
}
|
26
crypto/multisig/wire.go
Normal file
26
crypto/multisig/wire.go
Normal file
@ -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)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user