Start tx framework, testing sigs

This commit is contained in:
Ethan Frey 2017-05-17 21:20:08 +02:00
parent bdd0463849
commit 24802303e0
3 changed files with 365 additions and 0 deletions

38
txs/base.go Normal file
View File

@ -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)}
}

171
txs/sigs.go Normal file
View File

@ -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
}

156
txs/sigs_test.go Normal file
View File

@ -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)
}
}