Revamped signature tx building blocks

This commit is contained in:
Ethan Frey 2017-10-24 15:59:04 +02:00
parent cce6e6cb33
commit 8e442d5ded
1 changed files with 98 additions and 153 deletions

View File

@ -1,5 +1,5 @@
/* /*
Package auth contains generic Signable implementations that can be used Package auth contains generic Credential implementations that can be used
by your application or tests to handle authentication needs. by your application or tests to handle authentication needs.
It currently supports transaction data as opaque bytes and either single It currently supports transaction data as opaque bytes and either single
@ -7,205 +7,150 @@ or multiple private key signatures using straightforward algorithms.
It currently does not support N-of-M key share signing of other more It currently does not support N-of-M key share signing of other more
complex algorithms (although it would be great to add them). complex algorithms (although it would be great to add them).
You can create them with NewSig() and NewMultiSig(), and they fulfill This can be embedded in another structure along with the data to be
the keys.Signable interface. You can then .Wrap() them to create signed and easily allow you to build a custom keys.Signable implementation.
a sdk.Tx. Please see example usage of Credential.
*/ */
package auth package auth
import ( import (
crypto "github.com/tendermint/go-crypto" crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-wire/data"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/errors" "github.com/cosmos/cosmos-sdk/errors"
) )
// nolint //////////////////////////////////////////
const ( // Interface
// for signatures
ByteSingleTx = 0x16
ByteMultiSig = 0x17
)
// nolint // Credential can be combined with message data
const ( // to create a keys.Signable
// for signatures type Credential interface {
TypeSingleTx = NameSigs + "/one" Empty() bool
TypeMultiSig = NameSigs + "/multi" Sign(pubkey crypto.PubKey, sig crypto.Signature) error
) Signers(signBytes []byte) ([]crypto.PubKey, error)
}
// Signed holds one signature of the data /////////////////////////////////////////
type Signed struct { // NamedSig - one signature
// NamedSig holds one signature of the data
type NamedSig struct {
Sig crypto.Signature Sig crypto.Signature
Pubkey crypto.PubKey Pubkey crypto.PubKey
} }
var _ Credential = &NamedSig{}
// Empty returns true if there is not enough signature info // Empty returns true if there is not enough signature info
func (s Signed) Empty() bool { func (s *NamedSig) Empty() bool {
return s.Sig.Empty() || s.Pubkey.Empty() return s.Sig.Empty() || s.Pubkey.Empty()
} }
/**** Registration ****/
func init() {
sdk.TxMapper.
RegisterImplementation(&OneSig{}, TypeSingleTx, ByteSingleTx).
RegisterImplementation(&MultiSig{}, TypeMultiSig, ByteMultiSig)
}
/**** One Sig ****/
// OneSig lets us wrap arbitrary data with a go-crypto signature
type OneSig struct {
Tx sdk.Tx `json:"tx"`
Signed `json:"signature"`
}
var _ keys.Signable = &OneSig{}
var _ sdk.TxLayer = &OneSig{}
// NewSig wraps the tx with a Signable that accepts exactly one signature
func NewSig(tx sdk.Tx) *OneSig {
return &OneSig{Tx: tx}
}
//nolint
func (s *OneSig) Wrap() sdk.Tx {
return sdk.Tx{s}
}
func (s *OneSig) Next() sdk.Tx {
return s.Tx
}
func (s *OneSig) ValidateBasic() error {
return s.Tx.ValidateBasic()
}
// TxBytes returns the full data with signatures
func (s *OneSig) TxBytes() ([]byte, error) {
return data.ToWire(s.Wrap())
}
// SignBytes returns the original data passed into `NewSig`
func (s *OneSig) SignBytes() []byte {
res, err := data.ToWire(s.Tx)
if err != nil {
panic(err)
}
return res
}
// Sign will add a signature and pubkey. // Sign will add a signature and pubkey.
// func (s *NamedSig) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
// Depending on the Signable, one may be able to call this multiple times for multisig
// Returns error if called with invalid data or too many times
func (s *OneSig) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
signed := Signed{sig, pubkey}
if signed.Empty() {
return errors.ErrMissingSignature()
}
if !s.Empty() { if !s.Empty() {
return ErrTooManySignatures() return ErrTooManySignatures()
} }
// set the value once we are happy s.Sig = sig
s.Signed = signed s.Pubkey = pubkey
if s.Empty() {
return errors.ErrMissingSignature()
}
return nil return nil
} }
// Signers will return the public key(s) that signed if the signature // signer will return a pubkey and a possible error.
// building block to combine
func (s *NamedSig) signer(signBytes []byte) (crypto.PubKey, error) {
key := s.Pubkey
if s.Empty() {
return key, errors.ErrMissingSignature()
}
if !s.Pubkey.VerifyBytes(signBytes, s.Sig) {
return key, ErrInvalidSignature()
}
return key, nil
}
// Signers will return the public key that signed if the signature
// is valid, or an error if there is any issue with the signature, // is valid, or an error if there is any issue with the signature,
// including if there are no signatures // including if there are no signatures
func (s *OneSig) Signers() ([]crypto.PubKey, error) { func (s *NamedSig) Signers(signBytes []byte) ([]crypto.PubKey, error) {
if s.Empty() { key, err := s.signer(signBytes)
return nil, errors.ErrMissingSignature()
}
if !s.Pubkey.VerifyBytes(s.SignBytes(), s.Sig) {
return nil, ErrInvalidSignature()
}
return []crypto.PubKey{s.Pubkey}, nil
}
/**** MultiSig ****/
// MultiSig lets us wrap arbitrary data with a go-crypto signature
type MultiSig struct {
Tx sdk.Tx `json:"tx"`
Sigs []Signed `json:"signatures"`
}
var _ keys.Signable = &MultiSig{}
var _ sdk.TxLayer = &MultiSig{}
// NewMulti wraps the tx with a Signable that accepts arbitrary numbers of signatures
func NewMulti(tx sdk.Tx) *MultiSig {
return &MultiSig{Tx: tx}
}
// nolint
func (s *MultiSig) Wrap() sdk.Tx {
return sdk.Tx{s}
}
func (s *MultiSig) Next() sdk.Tx {
return s.Tx
}
func (s *MultiSig) ValidateBasic() error {
return s.Tx.ValidateBasic()
}
// TxBytes returns the full data with signatures
func (s *MultiSig) TxBytes() ([]byte, error) {
return data.ToWire(s.Wrap())
}
// SignBytes returns the original data passed into `NewSig`
func (s *MultiSig) SignBytes() []byte {
res, err := data.ToWire(s.Tx)
if err != nil { if err != nil {
panic(err) return nil, err
} }
return res return []crypto.PubKey{key}, nil
}
// // TxBytes returns the full data with signatures
// func (s *OneSig) TxBytes() ([]byte, error) {
// return data.ToWire(s.Wrap())
// }
// // SignBytes returns the original data passed into `NewSig`
// func (s *OneSig) SignBytes() []byte {
// res, err := data.ToWire(s.Tx)
// if err != nil {
// panic(err)
// }
// return res
// }
/////////////////////////////////////////
// NamedSigs - multiple signatures
// NamedSigs is a list of signatures
// and fulfils the same interface as NamedSig
type NamedSigs []NamedSig
var _ Credential = &NamedSigs{}
// Empty returns true iff no signatures were ever added
func (s *NamedSigs) Empty() bool {
return len(*s) == 0
} }
// Sign will add a signature and pubkey. // Sign will add a signature and pubkey.
// //
// Depending on the Signable, one may be able to call this multiple times for multisig // Depending on the Signable, one may be able to call this multiple times for multisig
// Returns error if called with invalid data or too many times // Returns error if called with invalid data or too many times
func (s *MultiSig) Sign(pubkey crypto.PubKey, sig crypto.Signature) error { func (s *NamedSigs) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
signed := Signed{sig, pubkey} // optimize for success case - append and store signature
if signed.Empty() { l := len(*s)
return errors.ErrMissingSignature() *s = append(*s, NamedSig{})
err := (*s)[l].Sign(pubkey, sig)
// if there is an error, remove from the list
if err != nil {
*s = (*s)[:l]
} }
// set the value once we are happy return err
s.Sigs = append(s.Sigs, signed)
return nil
} }
// Signers will return the public key(s) that signed if the signature // Signers will return the public key(s) that signed if the signature
// is valid, or an error if there is any issue with the signature, // is valid, or an error if there is any issue with the signature,
// including if there are no signatures // including if there are no signatures
func (s *MultiSig) Signers() ([]crypto.PubKey, error) { func (s *NamedSigs) Signers(signBytes []byte) (res []crypto.PubKey, err error) {
if len(s.Sigs) == 0 { if s.Empty() {
return nil, errors.ErrMissingSignature() return nil, errors.ErrMissingSignature()
} }
// verify all the signatures before returning them
keys := make([]crypto.PubKey, len(s.Sigs)) l := len(*s)
data := s.SignBytes() res = make([]crypto.PubKey, l)
for i := range s.Sigs { for i := 0; i < l; i++ {
ms := s.Sigs[i] res[i], err = (*s)[i].signer(signBytes)
if !ms.Pubkey.VerifyBytes(data, ms.Sig) { if err != nil {
return nil, ErrInvalidSignature() return nil, err
} }
keys[i] = ms.Pubkey }
return res, nil
} }
return keys, nil // Sign - sign the given data with private key and store
} // the result in the credentil
func Sign(msg []byte, key crypto.PrivKey, cred Credential) error {
// Sign - sign the transaction with private key
func Sign(tx keys.Signable, key crypto.PrivKey) error {
msg := tx.SignBytes()
pubkey := key.PubKey() pubkey := key.PubKey()
sig := key.Sign(msg) sig := key.Sign(msg)
return tx.Sign(pubkey, sig) return cred.Sign(pubkey, sig)
} }