Skip to content

Commit

Permalink
Add SignatureV2 infrastructure (#6373)
Browse files Browse the repository at this point in the history
* Updte tx generator

* Add sigv2, PublicKeyCodec

* revert changes

* revert changes

* updates

* Updates

* Integrate multisig support

* Undo move

* remove func

* undo

* godocs

* godocs, cleanup

* Cleanup

* godocs, tests

* lint

* Re-enable VerifyBytes

* Address comments

* Fix imports

* Update crypto/types/multisig/multisignature.go

* Add test for MultiSignatureData

* Add nested multisig case

* Add test for AddSignatureV2

* Add changelog

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: sahith-narahari <sahithnarahari@gmail.com>
  • Loading branch information
3 people committed Jun 12, 2020
1 parent 7871910 commit 60f7edf
Show file tree
Hide file tree
Showing 25 changed files with 750 additions and 284 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ be used to retrieve the actual proposal `Content`. Also the `NewMsgSubmitProposa
* (types) [\#6327](https://github.com/cosmos/cosmos-sdk/pull/6327) `sdk.Msg` now inherits `proto.Message`, as a result all `sdk.Msg` types now use pointer semantics.
* (codec) [\#6330](https://github.com/cosmos/cosmos-sdk/pull/6330) `codec.RegisterCrypto` has been moved to the `crypto/codec` package and the global `codec.Cdc` Amino instance has been deprecated and moved to the `codec/legacy_global` package.
* (x/ibc) [\#6374](https://github.com/cosmos/cosmos-sdk/pull/6374) `VerifyMembership` and `VerifyNonMembership` now take a `specs []string` argument to specify the proof format used for verification. Most SDK chains can simply use `commitmenttypes.GetSDKSpecs()` for this argument.
* (crypto/types/multisig) [\#6373](https://github.com/cosmos/cosmos-sdk/pull/6373) `multisig.Multisignature` has been renamed to `AminoMultisignature`

### Features

Expand Down
16 changes: 8 additions & 8 deletions crypto/types/multisig/codec.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package multisig

import (
amino "github.com/tendermint/go-amino"
"github.com/tendermint/go-amino"

"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
Expand All @@ -10,21 +10,21 @@ import (
)

// TODO: Figure out API for others to either add their own pubkey types, or
// to make verify / marshal accept a cdc.
// to make verify / marshal accept a Cdc.
const (
PubKeyAminoRoute = "tendermint/PubKeyMultisigThreshold"
)

var cdc = amino.NewCodec()
var Cdc = amino.NewCodec()

func init() {
cdc.RegisterInterface((*crypto.PubKey)(nil), nil)
cdc.RegisterConcrete(PubKeyMultisigThreshold{},
Cdc.RegisterInterface((*crypto.PubKey)(nil), nil)
Cdc.RegisterConcrete(PubKeyMultisigThreshold{},
PubKeyAminoRoute, nil)
cdc.RegisterConcrete(ed25519.PubKeyEd25519{},
Cdc.RegisterConcrete(ed25519.PubKeyEd25519{},
ed25519.PubKeyAminoName, nil)
cdc.RegisterConcrete(sr25519.PubKeySr25519{},
Cdc.RegisterConcrete(sr25519.PubKeySr25519{},
sr25519.PubKeyAminoName, nil)
cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{},
Cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{},
secp256k1.PubKeyAminoName, nil)
}
50 changes: 31 additions & 19 deletions crypto/types/multisig/multisignature.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@ import (
"github.com/tendermint/tendermint/crypto"

"github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
)

// Multisignature is used to represent the signature object used in the multisigs.
// AminoMultisignature is used to represent amino multi-signatures for StdTx's.
// It is assumed that all signatures were made with SIGN_MODE_LEGACY_AMINO_JSON.
// Sigs is a list of signatures, sorted by corresponding index.
type Multisignature struct {
type AminoMultisignature struct {
BitArray *types.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{types.NewCompactBitArray(n), make([][]byte, 0, 2)}
// NewMultisig returns a new MultiSignatureData
func NewMultisig(n int) *signing.MultiSignatureData {
return &signing.MultiSignatureData{
BitArray: types.NewCompactBitArray(n),
Signatures: make([]signing.SignatureData, 0, n),
}
}

// GetIndex returns the index of pk in keys. Returns -1 if not found
Expand All @@ -35,29 +38,39 @@ func getIndex(pk crypto.PubKey, keys []crypto.PubKey) int {

// 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) {
func AddSignature(mSig *signing.MultiSignatureData, sig signing.SignatureData, index int) {
newSigIndex := mSig.BitArray.NumTrueBitsBefore(index)
// Signature already exists, just replace the value there
if mSig.BitArray.GetIndex(index) {
mSig.Sigs[newSigIndex] = sig
mSig.Signatures[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)
if newSigIndex == len(mSig.Signatures) {
mSig.Signatures = append(mSig.Signatures, 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
mSig.Signatures = append(mSig.Signatures, &signing.SingleSignatureData{})
copy(mSig.Signatures[newSigIndex+1:], mSig.Signatures[newSigIndex:])
mSig.Signatures[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 {
func AddSignatureFromPubKey(mSig *signing.MultiSignatureData, sig signing.SignatureData, pubkey crypto.PubKey, keys []crypto.PubKey) error {
if mSig == nil {
return fmt.Errorf("value of mSig is nil %v", mSig)
}
if sig == nil {
return fmt.Errorf("value of sig is nil %v", sig)
}

if pubkey == nil || keys == nil {
return fmt.Errorf("pubkey or keys can't be nil %v %v", pubkey, keys)
}
index := getIndex(pubkey, keys)
if index == -1 {
keysStr := make([]string, len(keys))
Expand All @@ -68,11 +81,10 @@ func (mSig *Multisignature) AddSignatureFromPubKey(sig []byte, pubkey crypto.Pub
return fmt.Errorf("provided key %X doesn't exist in pubkeys: \n%s", pubkey.Bytes(), strings.Join(keysStr, "\n"))
}

mSig.AddSignature(sig, index)
AddSignature(mSig, sig, index)
return nil
}

// Marshal the multisignature with amino
func (mSig *Multisignature) Marshal() []byte {
return cdc.MustMarshalBinaryBare(mSig)
func AddSignatureV2(mSig *signing.MultiSignatureData, sig signing.SignatureV2, keys []crypto.PubKey) error {
return AddSignatureFromPubKey(mSig, sig.Data, sig.PubKey, keys)
}
25 changes: 25 additions & 0 deletions crypto/types/multisig/pubkey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package multisig

import (
"github.com/tendermint/tendermint/crypto"

"github.com/cosmos/cosmos-sdk/types/tx/signing"
)

// PubKey defines a type which supports multi-signature verification via MultiSignatureData
// which supports multiple SignMode's.
type PubKey interface {
crypto.PubKey

// VerifyMultisignature verifies the provide multi-signature represented by MultiSignatureData
// using getSignBytes to retrieve the sign bytes to verify against for the provided mode.
VerifyMultisignature(getSignBytes GetSignBytesFunc, sig *signing.MultiSignatureData) error

// GetPubKeys returns the crypto.PubKey's nested within the multi-sig PubKey
GetPubKeys() []crypto.PubKey
}

// GetSignBytesFunc defines a function type which returns sign bytes for a given SignMode or an error.
// It will generally be implemented as a closure which wraps whatever signable object signatures are
// being verified against.
type GetSignBytesFunc func(mode signing.SignMode) ([]byte, error)
72 changes: 66 additions & 6 deletions crypto/types/multisig/threshold_pubkey.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package multisig

import (
"fmt"

"github.com/tendermint/tendermint/crypto"

"github.com/cosmos/cosmos-sdk/types/tx/signing"
)

// PubKeyMultisigThreshold implements a K of N threshold multisig.
Expand All @@ -10,11 +14,11 @@ type PubKeyMultisigThreshold struct {
PubKeys []crypto.PubKey `json:"pubkeys"`
}

var _ crypto.PubKey = PubKeyMultisigThreshold{}
var _ PubKey = PubKeyMultisigThreshold{}

// NewPubKeyMultisigThreshold returns a new PubKeyMultisigThreshold.
// Panics if len(pubkeys) < k or 0 >= k.
func NewPubKeyMultisigThreshold(k int, pubkeys []crypto.PubKey) crypto.PubKey {
func NewPubKeyMultisigThreshold(k int, pubkeys []crypto.PubKey) PubKey {
if k <= 0 {
panic("threshold k of n multisignature: k <= 0")
}
Expand All @@ -35,9 +39,12 @@ func NewPubKeyMultisigThreshold(k int, pubkeys []crypto.PubKey) crypto.PubKey {
// 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.
//
// NOTE: VerifyMultisignature should preferred to VerifyBytes which only works
// with amino multisignatures.
func (pk PubKeyMultisigThreshold) VerifyBytes(msg []byte, marshalledSig []byte) bool {
var sig Multisignature
err := cdc.UnmarshalBinaryBare(marshalledSig, &sig)
var sig AminoMultisignature
err := Cdc.UnmarshalBinaryBare(marshalledSig, &sig)
if err != nil {
return false
}
Expand Down Expand Up @@ -67,12 +74,65 @@ func (pk PubKeyMultisigThreshold) VerifyBytes(msg []byte, marshalledSig []byte)
return true
}

// VerifyMultisignature implements the PubKey.VerifyMultisignature method
func (pk PubKeyMultisigThreshold) VerifyMultisignature(getSignBytes GetSignBytesFunc, sig *signing.MultiSignatureData) error {
bitarray := sig.BitArray
sigs := sig.Signatures
size := bitarray.Size()
// ensure bit array is the correct size
if len(pk.PubKeys) != size {
return fmt.Errorf("bit array size is incorrect %d", len(pk.PubKeys))
}
// ensure size of signature list
if len(sigs) < int(pk.K) || len(sigs) > size {
return fmt.Errorf("signature size is incorrect %d", len(sigs))
}
// ensure at least k signatures are set
if bitarray.NumTrueBitsBefore(size) < int(pk.K) {
return fmt.Errorf("minimum number of signatures not set, have %d, expected %d", bitarray.NumTrueBitsBefore(size), int(pk.K))
}
// index in the list of signatures which we are concerned with.
sigIndex := 0
for i := 0; i < size; i++ {
if bitarray.GetIndex(i) {
si := sig.Signatures[sigIndex]
switch si := si.(type) {
case *signing.SingleSignatureData:
msg, err := getSignBytes(si.SignMode)
if err != nil {
return err
}
if !pk.PubKeys[i].VerifyBytes(msg, si.Signature) {
return err
}
case *signing.MultiSignatureData:
nestedMultisigPk, ok := pk.PubKeys[i].(PubKey)
if !ok {
return fmt.Errorf("unable to parse pubkey of index %d", i)
}
if err := nestedMultisigPk.VerifyMultisignature(getSignBytes, si); err != nil {
return err
}
default:
return fmt.Errorf("improper signature data type for index %d", sigIndex)
}
sigIndex++
}
}
return nil
}

// GetPubKeys implements the PubKey.GetPubKeys method
func (pk PubKeyMultisigThreshold) GetPubKeys() []crypto.PubKey {
return pk.PubKeys
}

// Bytes returns the amino encoded version of the PubKeyMultisigThreshold
func (pk PubKeyMultisigThreshold) Bytes() []byte {
return cdc.MustMarshalBinaryBare(pk)
return Cdc.MustMarshalBinaryBare(pk)
}

// Address returns tmhash(PubKey.Bytes())
// Address returns tmhash(PubKeyMultisigThreshold.Bytes())
func (pk PubKeyMultisigThreshold) Address() crypto.Address {
return crypto.AddressHash(pk.Bytes())
}
Expand Down
Loading

0 comments on commit 60f7edf

Please sign in to comment.