212 lines
5.3 KiB
Go
212 lines
5.3 KiB
Go
/*
|
|
Package auth contains generic Signable implementations that can be used
|
|
by your application or tests to handle authentication needs.
|
|
|
|
It currently supports transaction data as opaque bytes and either single
|
|
or multiple private key signatures using straightforward algorithms.
|
|
It currently does not support N-of-M key share signing of other more
|
|
complex algorithms (although it would be great to add them).
|
|
|
|
You can create them with NewSig() and NewMultiSig(), and they fulfill
|
|
the keys.Signable interface. You can then .Wrap() them to create
|
|
a basecoin.Tx.
|
|
*/
|
|
package auth
|
|
|
|
import (
|
|
crypto "github.com/tendermint/go-crypto"
|
|
"github.com/tendermint/go-crypto/keys"
|
|
"github.com/tendermint/go-wire/data"
|
|
|
|
"github.com/tendermint/basecoin"
|
|
"github.com/tendermint/basecoin/errors"
|
|
)
|
|
|
|
// nolint
|
|
const (
|
|
// for signatures
|
|
ByteSingleTx = 0x16
|
|
ByteMultiSig = 0x17
|
|
)
|
|
|
|
// nolint
|
|
const (
|
|
// for signatures
|
|
TypeSingleTx = NameSigs + "/one"
|
|
TypeMultiSig = NameSigs + "/multi"
|
|
)
|
|
|
|
// Signed holds one signature of the data
|
|
type Signed struct {
|
|
Sig crypto.Signature
|
|
Pubkey crypto.PubKey
|
|
}
|
|
|
|
// Empty returns true if there is not enough signature info
|
|
func (s Signed) Empty() bool {
|
|
return s.Sig.Empty() || s.Pubkey.Empty()
|
|
}
|
|
|
|
/**** Registration ****/
|
|
|
|
func init() {
|
|
basecoin.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 basecoin.Tx `json:"tx"`
|
|
Signed `json:"signature"`
|
|
}
|
|
|
|
var _ keys.Signable = &OneSig{}
|
|
var _ basecoin.TxLayer = &OneSig{}
|
|
|
|
// NewSig wraps the tx with a Signable that accepts exactly one signature
|
|
func NewSig(tx basecoin.Tx) *OneSig {
|
|
return &OneSig{Tx: tx}
|
|
}
|
|
|
|
//nolint
|
|
func (s *OneSig) Wrap() basecoin.Tx {
|
|
return basecoin.Tx{s}
|
|
}
|
|
func (s *OneSig) Next() basecoin.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.
|
|
//
|
|
// 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() {
|
|
return ErrTooManySignatures()
|
|
}
|
|
// set the value once we are happy
|
|
s.Signed = signed
|
|
return nil
|
|
}
|
|
|
|
// 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,
|
|
// including if there are no signatures
|
|
func (s *OneSig) Signers() ([]crypto.PubKey, error) {
|
|
if s.Empty() {
|
|
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 basecoin.Tx `json:"tx"`
|
|
Sigs []Signed `json:"signatures"`
|
|
}
|
|
|
|
var _ keys.Signable = &MultiSig{}
|
|
var _ basecoin.TxLayer = &MultiSig{}
|
|
|
|
// NewMulti wraps the tx with a Signable that accepts arbitrary numbers of signatures
|
|
func NewMulti(tx basecoin.Tx) *MultiSig {
|
|
return &MultiSig{Tx: tx}
|
|
}
|
|
|
|
// nolint
|
|
func (s *MultiSig) Wrap() basecoin.Tx {
|
|
return basecoin.Tx{s}
|
|
}
|
|
func (s *MultiSig) Next() basecoin.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 {
|
|
panic(err)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// Sign will add a signature and pubkey.
|
|
//
|
|
// 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 *MultiSig) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
|
|
signed := Signed{sig, pubkey}
|
|
if signed.Empty() {
|
|
return errors.ErrMissingSignature()
|
|
}
|
|
// set the value once we are happy
|
|
s.Sigs = append(s.Sigs, signed)
|
|
return nil
|
|
}
|
|
|
|
// 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,
|
|
// including if there are no signatures
|
|
func (s *MultiSig) Signers() ([]crypto.PubKey, error) {
|
|
if len(s.Sigs) == 0 {
|
|
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
|
|
}
|
|
|
|
// Sign - sign the transaction with private key
|
|
func Sign(tx keys.Signable, key crypto.PrivKey) error {
|
|
msg := tx.SignBytes()
|
|
pubkey := key.PubKey()
|
|
sig := key.Sign(msg)
|
|
return tx.Sign(pubkey, sig)
|
|
}
|