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