Revamped signature tx building blocks
This commit is contained in:
parent
cce6e6cb33
commit
8e442d5ded
|
@ -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))
|
|
||||||
data := s.SignBytes()
|
|
||||||
for i := range s.Sigs {
|
|
||||||
ms := s.Sigs[i]
|
|
||||||
if !ms.Pubkey.VerifyBytes(data, ms.Sig) {
|
|
||||||
return nil, ErrInvalidSignature()
|
|
||||||
}
|
|
||||||
keys[i] = ms.Pubkey
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys, nil
|
l := len(*s)
|
||||||
|
res = make([]crypto.PubKey, l)
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
res[i], err = (*s)[i].signer(signBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign - sign the transaction with private key
|
// Sign - sign the given data with private key and store
|
||||||
func Sign(tx keys.Signable, key crypto.PrivKey) error {
|
// the result in the credentil
|
||||||
msg := tx.SignBytes()
|
func Sign(msg []byte, key crypto.PrivKey, cred Credential) error {
|
||||||
pubkey := key.PubKey()
|
pubkey := key.PubKey()
|
||||||
sig := key.Sign(msg)
|
sig := key.Sign(msg)
|
||||||
return tx.Sign(pubkey, sig)
|
return cred.Sign(pubkey, sig)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue