Browse Source

Move tx from light-client and add tests

pull/1782/head
Ethan Frey 8 years ago
parent
commit
ae55713864
7 changed files with 431 additions and 0 deletions
  1. +10
    -0
      tx/docs.go
  2. +67
    -0
      tx/multi.go
  3. +77
    -0
      tx/multi_test.go
  4. +58
    -0
      tx/one.go
  5. +73
    -0
      tx/one_test.go
  6. +76
    -0
      tx/reader.go
  7. +70
    -0
      tx/reader_test.go

+ 10
- 0
tx/docs.go View File

@ -0,0 +1,10 @@
/*
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)
*/
package tx

+ 67
- 0
tx/multi.go View File

@ -0,0 +1,67 @@
package tx
import (
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
data "github.com/tendermint/go-data"
)
// MultiSig lets us wrap arbitrary data with a go-crypto signature
//
// TODO: rethink how we want to integrate this with KeyStore so it makes
// more sense (particularly the verify method)
type MultiSig struct {
Data data.Bytes
Sigs []Signed
}
type Signed struct {
Sig crypto.SignatureS
Pubkey crypto.PubKeyS
}
var _ SigInner = &MultiSig{}
func NewMulti(data []byte) Sig {
return Sig{&MultiSig{Data: data}}
}
// SignBytes returns the original data passed into `NewSig`
func (s *MultiSig) SignBytes() []byte {
return s.Data
}
// 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 {
if pubkey == nil || sig == nil {
return errors.New("Signature or Key missing")
}
// set the value once we are happy
x := Signed{crypto.SignatureS{sig}, crypto.PubKeyS{pubkey}}
s.Sigs = append(s.Sigs, x)
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")
}
keys := make([]crypto.PubKey, len(s.Sigs))
for i := range s.Sigs {
ms := s.Sigs[i]
if !ms.Pubkey.VerifyBytes(s.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
}

+ 77
- 0
tx/multi_test.go View File

@ -0,0 +1,77 @@
package tx
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-keys"
"github.com/tendermint/go-keys/cryptostore"
"github.com/tendermint/go-keys/storage/memstorage"
)
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 {
tx := NewMulti([]byte(tc.data))
// unsigned version
_, err = tx.Signers()
assert.NotNil(err)
orig, err := tx.TxBytes()
require.Nil(err, "%+v", err)
data := tx.SignBytes()
assert.Equal(tc.data, string(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()
assert.Equal(tc.data, string(data))
}
}

+ 58
- 0
tx/one.go View File

@ -0,0 +1,58 @@
package tx
import (
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
data "github.com/tendermint/go-data"
)
// OneSig lets us wrap arbitrary data with a go-crypto signature
//
// TODO: rethink how we want to integrate this with KeyStore so it makes
// more sense (particularly the verify method)
type OneSig struct {
Data data.Bytes
Signed
}
var _ SigInner = &OneSig{}
func New(data []byte) Sig {
return WrapSig(&OneSig{Data: data})
}
// SignBytes returns the original data passed into `NewSig`
func (s *OneSig) SignBytes() []byte {
return s.Data
}
// 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 {
if pubkey == nil || sig == nil {
return errors.New("Signature or Key missing")
}
if !s.Sig.Empty() {
return errors.New("Transaction can only be signed once")
}
// set the value once we are happy
s.Pubkey = crypto.PubKeyS{pubkey}
s.Sig = crypto.SignatureS{sig}
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.Pubkey.Empty() || s.Sig.Empty() {
return nil, errors.New("Never signed")
}
if !s.Pubkey.VerifyBytes(s.Data, s.Sig) {
return nil, errors.New("Signature doesn't match")
}
return []crypto.PubKey{s.Pubkey}, nil
}

+ 73
- 0
tx/one_test.go View File

@ -0,0 +1,73 @@
package tx
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-keys"
"github.com/tendermint/go-keys/cryptostore"
"github.com/tendermint/go-keys/storage/memstorage"
)
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 {
tx := New([]byte(tc.data))
// unsigned version
_, err = tx.Signers()
assert.NotNil(err)
orig, err := tx.TxBytes()
require.Nil(err, "%+v", err)
data := tx.SignBytes()
assert.Equal(tc.data, string(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()
assert.Equal(tc.data, string(data))
}
}

+ 76
- 0
tx/reader.go View File

@ -0,0 +1,76 @@
package tx
import (
crypto "github.com/tendermint/go-crypto"
data "github.com/tendermint/go-data"
keys "github.com/tendermint/go-keys"
)
const (
typeOneSig = byte(0x01)
typeMultiSig = byte(0x02)
nameOneSig = "sig"
nameMultiSig = "multi"
)
var _ keys.Signable = Sig{}
var TxMapper data.Mapper
func init() {
TxMapper = data.NewMapper(Sig{}).
RegisterInterface(&OneSig{}, nameOneSig, typeOneSig).
RegisterInterface(&MultiSig{}, nameMultiSig, typeMultiSig)
}
/*
DO NOT USE this interface.
It is public by necessity but should never be used directly
outside of this package.
Only use Sig, never SigInner
*/
type SigInner interface {
SignBytes() []byte
Sign(pubkey crypto.PubKey, sig crypto.Signature) error
Signers() ([]crypto.PubKey, error)
}
// Sig is what is exported, and handles serialization
type Sig struct {
SigInner
}
// TxBytes
func (s Sig) TxBytes() ([]byte, error) {
return data.ToWire(s)
}
// WrapSig goes from concrete implementation to "interface" struct
func WrapSig(pk SigInner) Sig {
if wrap, ok := pk.(Sig); ok {
pk = wrap.Unwrap()
}
return Sig{pk}
}
// Unwrap recovers the concrete interface safely (regardless of levels of embeds)
func (p Sig) Unwrap() SigInner {
pk := p.SigInner
for wrap, ok := pk.(Sig); ok; wrap, ok = pk.(Sig) {
pk = wrap.SigInner
}
return pk
}
func (p Sig) MarshalJSON() ([]byte, error) {
return TxMapper.ToJSON(p.Unwrap())
}
func (p *Sig) UnmarshalJSON(data []byte) (err error) {
parsed, err := TxMapper.FromJSON(data)
if err == nil && parsed != nil {
p.SigInner = parsed.(SigInner)
}
return
}

+ 70
- 0
tx/reader_test.go View File

@ -0,0 +1,70 @@
package tx
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
data "github.com/tendermint/go-data"
"github.com/tendermint/go-keys/cryptostore"
"github.com/tendermint/go-keys/storage/memstorage"
)
func TestReader(t *testing.T) {
assert, require := assert.New(t), require.New(t)
algo := crypto.NameEd25519
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
)
type sigs struct{ name, pass string }
u := sigs{"alice", "1234"}
u2 := sigs{"bob", "foobar"}
_, err := cstore.Create(u.name, u.pass, algo)
require.Nil(err, "%+v", err)
_, err = cstore.Create(u2.name, u2.pass, algo)
require.Nil(err, "%+v", err)
cases := []struct {
tx Sig
sigs []sigs
}{
{New([]byte("first")), nil},
{New([]byte("second")), []sigs{u}},
{New([]byte("other")), []sigs{u2}},
{NewMulti([]byte("m-first")), nil},
{NewMulti([]byte("m-second")), []sigs{u}},
{NewMulti([]byte("m-other")), []sigs{u, u2}},
}
for _, tc := range cases {
tx := tc.tx
// make sure json serialization and loading works w/o sigs
var pre Sig
pjs, err := data.ToJSON(tx)
require.Nil(err, "%+v", err)
err = data.FromJSON(pjs, &pre)
require.Nil(err, "%+v", err)
assert.Equal(tx, pre)
for _, s := range tc.sigs {
err = cstore.Sign(s.name, s.pass, tx)
require.Nil(err, "%+v", err)
}
var post Sig
sjs, err := data.ToJSON(tx)
require.Nil(err, "%+v", err)
err = data.FromJSON(sjs, &post)
require.Nil(err, "%+v\n%s", err, string(sjs))
assert.Equal(tx, post)
if len(tc.sigs) > 0 {
assert.NotEqual(pjs, sjs, "%s\n ------ %s", string(pjs), string(sjs))
}
}
}

Loading…
Cancel
Save