mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-30 05:01:44 +00:00
Move tx from light-client and add tests
This commit is contained in:
10
tx/docs.go
Normal file
10
tx/docs.go
Normal 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
tx/multi.go
Normal file
67
tx/multi.go
Normal 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
tx/multi_test.go
Normal file
77
tx/multi_test.go
Normal 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
tx/one.go
Normal file
58
tx/one.go
Normal 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
tx/one_test.go
Normal file
73
tx/one_test.go
Normal 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
tx/reader.go
Normal file
76
tx/reader.go
Normal 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
tx/reader_test.go
Normal file
70
tx/reader_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user