Start tx framework, testing sigs
This commit is contained in:
parent
bdd0463849
commit
24802303e0
|
@ -0,0 +1,38 @@
|
|||
package txs
|
||||
|
||||
import (
|
||||
"github.com/tendermint/basecoin"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
)
|
||||
|
||||
const (
|
||||
// for utils...
|
||||
ByteRaw = 0x1
|
||||
ByteFees = 0x2
|
||||
ByteMulti = 0x3
|
||||
|
||||
// for signatures
|
||||
ByteSig = 0x16
|
||||
ByteMultiSig = 0x17
|
||||
)
|
||||
|
||||
const (
|
||||
// for utils...
|
||||
TypeRaw = "raw"
|
||||
TypeFees = "fee"
|
||||
TypeMulti = "multi"
|
||||
|
||||
// for signatures
|
||||
TypeSig = "sig"
|
||||
TypeMultiSig = "multisig"
|
||||
)
|
||||
|
||||
// let's register data.Bytes as a "raw" tx, for tests or
|
||||
// other data we don't want to post....
|
||||
func init() {
|
||||
basecoin.TxMapper.RegisterImplementation(data.Bytes{}, TypeRaw, ByteRaw)
|
||||
}
|
||||
|
||||
func WrapBytes(d []byte) basecoin.Tx {
|
||||
return basecoin.Tx{data.Bytes(d)}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
package tx 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 txs
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
|
||||
"github.com/tendermint/basecoin"
|
||||
)
|
||||
|
||||
// Signed holds one signature of the data
|
||||
type Signed struct {
|
||||
Sig crypto.Signature
|
||||
Pubkey crypto.PubKey
|
||||
}
|
||||
|
||||
func (s Signed) Empty() bool {
|
||||
return s.Sig.Empty() || s.Pubkey.Empty()
|
||||
}
|
||||
|
||||
/**** Registration ****/
|
||||
|
||||
func init() {
|
||||
basecoin.TxMapper.
|
||||
RegisterImplementation(&OneSig{}, TypeSig, ByteSig).
|
||||
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{}
|
||||
|
||||
func NewSig(tx basecoin.Tx) *OneSig {
|
||||
return &OneSig{Tx: tx}
|
||||
}
|
||||
|
||||
func (s *OneSig) Wrap() basecoin.Tx {
|
||||
return basecoin.Tx{s}
|
||||
}
|
||||
|
||||
// TxBytes returns the full data with signatures
|
||||
func (s *OneSig) TxBytes() ([]byte, error) {
|
||||
return data.ToWire(s)
|
||||
}
|
||||
|
||||
// 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.New("Signature or Key missing")
|
||||
}
|
||||
if !s.Empty() {
|
||||
return errors.New("Transaction can only be signed once")
|
||||
}
|
||||
// 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.New("Never signed")
|
||||
}
|
||||
if !s.Pubkey.VerifyBytes(s.SignBytes(), s.Sig) {
|
||||
return nil, errors.New("Signature doesn't match")
|
||||
}
|
||||
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{}
|
||||
|
||||
func NewMulti(tx basecoin.Tx) *MultiSig {
|
||||
return &MultiSig{Tx: tx}
|
||||
}
|
||||
|
||||
func (s *MultiSig) Wrap() basecoin.Tx {
|
||||
return basecoin.Tx{s}
|
||||
}
|
||||
|
||||
// TxBytes returns the full data with signatures
|
||||
func (s *MultiSig) TxBytes() ([]byte, error) {
|
||||
return data.ToWire(s)
|
||||
}
|
||||
|
||||
// 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.New("Signature or Key missing")
|
||||
}
|
||||
// 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.New("Never signed")
|
||||
}
|
||||
// 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, errors.Errorf("Signature %d doesn't match (key: %X)", i, ms.Pubkey.Bytes())
|
||||
}
|
||||
keys[i] = ms.Pubkey
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
package txs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/basecoin"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/go-crypto/keys/cryptostore"
|
||||
"github.com/tendermint/go-crypto/keys/storage/memstorage"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
)
|
||||
|
||||
func checkSignBytes(t *testing.T, bytes []byte, expected string) {
|
||||
// load it back... unwrap the tx
|
||||
var preTx basecoin.Tx
|
||||
err := wire.ReadBinaryBytes(bytes, &preTx)
|
||||
require.Nil(t, err)
|
||||
|
||||
// now make sure this tx is data.Bytes with the info we want
|
||||
byt, ok := preTx.Unwrap().(data.Bytes)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, expected, string(byt))
|
||||
}
|
||||
|
||||
func TestOneSig(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
algo := crypto.NameEd25519
|
||||
cstore := cryptostore.New(
|
||||
cryptostore.SecretBox,
|
||||
memstorage.New(),
|
||||
)
|
||||
n, p := "foo", "bar"
|
||||
n2, p2 := "other", "thing"
|
||||
|
||||
acct, err := cstore.Create(n, p, algo)
|
||||
require.Nil(err, "%+v", err)
|
||||
acct2, err := cstore.Create(n2, p2, algo)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
cases := []struct {
|
||||
data string
|
||||
key keys.Info
|
||||
name, pass string
|
||||
}{
|
||||
{"first", acct, n, p},
|
||||
{"kehfkhefy8y", acct, n, p},
|
||||
{"second", acct2, n2, p2},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
inner := WrapBytes([]byte(tc.data))
|
||||
tx := NewSig(inner)
|
||||
// unsigned version
|
||||
_, err = tx.Signers()
|
||||
assert.NotNil(err)
|
||||
orig, err := tx.TxBytes()
|
||||
require.Nil(err, "%+v", err)
|
||||
data := tx.SignBytes()
|
||||
checkSignBytes(t, data, tc.data)
|
||||
|
||||
// sign it
|
||||
err = cstore.Sign(tc.name, tc.pass, tx)
|
||||
require.Nil(err, "%+v", err)
|
||||
// but not twice
|
||||
err = cstore.Sign(tc.name, tc.pass, tx)
|
||||
require.NotNil(err)
|
||||
|
||||
// make sure it is proper now
|
||||
sigs, err := tx.Signers()
|
||||
require.Nil(err, "%+v", err)
|
||||
if assert.Equal(1, len(sigs)) {
|
||||
// This must be refactored...
|
||||
assert.Equal(tc.key.PubKey, sigs[0])
|
||||
}
|
||||
// the tx bytes should change after this
|
||||
after, err := tx.TxBytes()
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.NotEqual(orig, after, "%X != %X", orig, after)
|
||||
|
||||
// sign bytes are the same
|
||||
data = tx.SignBytes()
|
||||
checkSignBytes(t, data, tc.data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiSig(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
algo := crypto.NameEd25519
|
||||
cstore := cryptostore.New(
|
||||
cryptostore.SecretBox,
|
||||
memstorage.New(),
|
||||
)
|
||||
n, p := "foo", "bar"
|
||||
n2, p2 := "other", "thing"
|
||||
|
||||
acct, err := cstore.Create(n, p, algo)
|
||||
require.Nil(err, "%+v", err)
|
||||
acct2, err := cstore.Create(n2, p2, algo)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
type signer struct {
|
||||
key keys.Info
|
||||
name, pass string
|
||||
}
|
||||
cases := []struct {
|
||||
data string
|
||||
signers []signer
|
||||
}{
|
||||
{"one", []signer{{acct, n, p}}},
|
||||
{"two", []signer{{acct2, n2, p2}}},
|
||||
{"both", []signer{{acct, n, p}, {acct2, n2, p2}}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
inner := WrapBytes([]byte(tc.data))
|
||||
tx := NewMulti(inner)
|
||||
// unsigned version
|
||||
_, err = tx.Signers()
|
||||
assert.NotNil(err)
|
||||
orig, err := tx.TxBytes()
|
||||
require.Nil(err, "%+v", err)
|
||||
data := tx.SignBytes()
|
||||
checkSignBytes(t, data, tc.data)
|
||||
|
||||
// sign it
|
||||
for _, s := range tc.signers {
|
||||
err = cstore.Sign(s.name, s.pass, tx)
|
||||
require.Nil(err, "%+v", err)
|
||||
}
|
||||
|
||||
// make sure it is proper now
|
||||
sigs, err := tx.Signers()
|
||||
require.Nil(err, "%+v", err)
|
||||
if assert.Equal(len(tc.signers), len(sigs)) {
|
||||
for i := range sigs {
|
||||
// This must be refactored...
|
||||
assert.Equal(tc.signers[i].key.PubKey, sigs[i])
|
||||
}
|
||||
}
|
||||
// the tx bytes should change after this
|
||||
after, err := tx.TxBytes()
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.NotEqual(orig, after, "%X != %X", orig, after)
|
||||
|
||||
// sign bytes are the same
|
||||
data = tx.SignBytes()
|
||||
checkSignBytes(t, data, tc.data)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue