- package keys_test
-
- import (
- "fmt"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- dbm "github.com/tendermint/tmlibs/db"
-
- "github.com/tendermint/go-crypto"
- "github.com/tendermint/go-crypto/keys"
- "github.com/tendermint/go-crypto/keys/words"
- )
-
- // TestKeyManagement makes sure we can manipulate these keys well
- func TestKeyManagement(t *testing.T) {
-
- // make the storage with reasonable defaults
- cstore := keys.New(
- dbm.NewMemDB(),
- words.MustLoadCodec("english"),
- )
-
- algo := keys.AlgoEd25519
- n1, n2, n3 := "personal", "business", "other"
- p1, p2 := "1234", "really-secure!@#$"
-
- // Check empty state
- l, err := cstore.List()
- require.Nil(t, err)
- assert.Empty(t, l)
-
- // create some keys
- _, err = cstore.Get(n1)
- assert.NotNil(t, err)
- i, _, err := cstore.CreateMnemonic(n1, p1, algo)
- require.Equal(t, n1, i.GetName())
- require.Nil(t, err)
- _, _, err = cstore.CreateMnemonic(n2, p2, algo)
- require.Nil(t, err)
-
- // we can get these keys
- i2, err := cstore.Get(n2)
- assert.NoError(t, err)
- _, err = cstore.Get(n3)
- assert.NotNil(t, err)
-
- // list shows them in order
- keyS, err := cstore.List()
- require.Nil(t, err)
- require.Equal(t, 2, len(keyS))
- // note these are in alphabetical order
- assert.Equal(t, n2, keyS[0].GetName())
- assert.Equal(t, n1, keyS[1].GetName())
- assert.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey())
-
- // deleting a key removes it
- err = cstore.Delete("bad name", "foo")
- require.NotNil(t, err)
- err = cstore.Delete(n1, p1)
- require.Nil(t, err)
- keyS, err = cstore.List()
- require.Nil(t, err)
- assert.Equal(t, 1, len(keyS))
- _, err = cstore.Get(n1)
- assert.NotNil(t, err)
-
- // create an offline key
- o1 := "offline"
- priv1 := crypto.GenPrivKeyEd25519()
- pub1, err := priv1.PubKey()
- require.Nil(t, err)
- i, err = cstore.CreateOffline(o1, pub1)
- require.Nil(t, err)
- require.Equal(t, pub1, i.GetPubKey())
- require.Equal(t, o1, i.GetName())
- keyS, err = cstore.List()
- require.Equal(t, 2, len(keyS))
-
- // delete the offline key
- err = cstore.Delete(o1, "no")
- require.NotNil(t, err)
- err = cstore.Delete(o1, "yes")
- require.Nil(t, err)
- keyS, err = cstore.List()
- require.Equal(t, 1, len(keyS))
-
- // make sure that it only signs with the right password
- // tx := mock.NewSig([]byte("mytransactiondata"))
- // err = cstore.Sign(n2, p1, tx)
- // assert.NotNil(t, err)
- // err = cstore.Sign(n2, p2, tx)
- // assert.Nil(t, err, "%+v", err)
- // sigs, err := tx.Signers()
- // assert.Nil(t, err, "%+v", err)
- // if assert.Equal(t, 1, len(sigs)) {
- // assert.Equal(t, i2.PubKey, sigs[0])
- // }
- }
-
- // TestSignVerify does some detailed checks on how we sign and validate
- // signatures
- func TestSignVerify(t *testing.T) {
-
- // make the storage with reasonable defaults
- cstore := keys.New(
- dbm.NewMemDB(),
- words.MustLoadCodec("english"),
- )
- algo := keys.AlgoSecp256k1
-
- n1, n2, n3 := "some dude", "a dudette", "dude-ish"
- p1, p2, p3 := "1234", "foobar", "foobar"
-
- // create two users and get their info
- i1, _, err := cstore.CreateMnemonic(n1, p1, algo)
- require.Nil(t, err)
-
- i2, _, err := cstore.CreateMnemonic(n2, p2, algo)
- require.Nil(t, err)
-
- // Import a public key
- armor, err := cstore.ExportPubKey(n2)
- require.Nil(t, err)
- cstore.ImportPubKey(n3, armor)
- _, err = cstore.Get(n3)
- require.Nil(t, err)
-
- // let's try to sign some messages
- d1 := []byte("my first message")
- d2 := []byte("some other important info!")
- d3 := []byte("feels like I forgot something...")
-
- // try signing both data with both keys...
- s11, pub1, err := cstore.Sign(n1, p1, d1)
- require.Nil(t, err)
- require.Equal(t, i1.GetPubKey(), pub1)
-
- s12, pub1, err := cstore.Sign(n1, p1, d2)
- require.Nil(t, err)
- require.Equal(t, i1.GetPubKey(), pub1)
-
- s21, pub2, err := cstore.Sign(n2, p2, d1)
- require.Nil(t, err)
- require.Equal(t, i2.GetPubKey(), pub2)
-
- s22, pub2, err := cstore.Sign(n2, p2, d2)
- require.Nil(t, err)
- require.Equal(t, i2.GetPubKey(), pub2)
-
- // let's try to validate and make sure it only works when everything is proper
- cases := []struct {
- key crypto.PubKey
- data []byte
- sig crypto.Signature
- valid bool
- }{
- // proper matches
- {i1.GetPubKey(), d1, s11, true},
- // change data, pubkey, or signature leads to fail
- {i1.GetPubKey(), d2, s11, false},
- {i2.GetPubKey(), d1, s11, false},
- {i1.GetPubKey(), d1, s21, false},
- // make sure other successes
- {i1.GetPubKey(), d2, s12, true},
- {i2.GetPubKey(), d1, s21, true},
- {i2.GetPubKey(), d2, s22, true},
- }
-
- for i, tc := range cases {
- valid := tc.key.VerifyBytes(tc.data, tc.sig)
- assert.Equal(t, tc.valid, valid, "%d", i)
- }
-
- // Now try to sign data with a secret-less key
- _, _, err = cstore.Sign(n3, p3, d3)
- assert.NotNil(t, err)
- }
-
- /*
- // TestSignWithLedger makes sure we have ledger compatibility with
- // the crypto store.
- //
- // This test will only succeed with a ledger attached to the computer
- // and the cosmos app open
- func TestSignWithLedger(t *testing.T) {
- if os.Getenv("WITH_LEDGER") == "" {
- t.Skip("Set WITH_LEDGER to run code on real ledger")
- }
-
- // make the storage with reasonable defaults
- cstore := keys.New(
- dbm.NewMemDB(),
- words.MustLoadCodec("english"),
- )
- n := "nano-s"
- p := "hard2hack"
-
- // create a nano user
- c, _, err := cstore.Create(n, p, nano.KeyLedgerEd25519)
- require.Nil(t, err, "%+v", err)
- assert.Equal(t, c.Key, n)
- _, ok := c.PubKey.Unwrap().(nano.PubKeyLedgerEd25519)
- require.True(t, ok)
-
- // make sure we can get it back
- info, err := cstore.Get(n)
- require.Nil(t, err, "%+v", err)
- assert.Equal(t, info.Key, n)
- key := info.PubKey
- require.False(t ,key.Empty())
- require.True(t, key.Equals(c.PubKey))
-
- // let's try to sign some messages
- d1 := []byte("welcome to cosmos")
- d2 := []byte("please turn on the app")
-
- // try signing both data with the ledger...
- s1, pub, err := cstore.Sign(n, p, d1)
- require.Nil(t, err)
- require.Equal(t, info.PubKey, pub)
-
- s2, pub, err := cstore.Sign(n, p, d2)
- require.Nil(t, err)
- require.Equal(t, info.PubKey, pub)
-
- // now, let's check those signatures work
- assert.True(t, key.VerifyBytes(d1, s1))
- assert.True(t, key.VerifyBytes(d2, s2))
- // and mismatched signatures don't
- assert.False(t, key.VerifyBytes(d1, s2))
- }
- */
-
- func assertPassword(t *testing.T, cstore keys.Keybase, name, pass, badpass string) {
- err := cstore.Update(name, badpass, pass)
- assert.NotNil(t, err)
- err = cstore.Update(name, pass, pass)
- assert.Nil(t, err, "%+v", err)
- }
-
- // TestExportImport tests exporting and importing keys.
- func TestExportImport(t *testing.T) {
-
- // make the storage with reasonable defaults
- db := dbm.NewMemDB()
- cstore := keys.New(
- db,
- words.MustLoadCodec("english"),
- )
-
- info, _, err := cstore.CreateMnemonic("john", "passphrase", keys.AlgoEd25519)
- assert.NoError(t, err)
- assert.Equal(t, info.GetName(), "john")
- addr := info.GetPubKey().Address()
-
- john, err := cstore.Get("john")
- assert.NoError(t, err)
- assert.Equal(t, john.GetName(), "john")
- assert.Equal(t, john.GetPubKey().Address(), addr)
-
- armor, err := cstore.Export("john")
- assert.NoError(t, err)
-
- err = cstore.Import("john2", armor)
- assert.NoError(t, err)
-
- john2, err := cstore.Get("john2")
- assert.NoError(t, err)
-
- assert.Equal(t, john.GetPubKey().Address(), addr)
- assert.Equal(t, john.GetName(), "john")
- assert.Equal(t, john, john2)
- }
-
- func TestExportImportPubKey(t *testing.T) {
- // make the storage with reasonable defaults
- db := dbm.NewMemDB()
- cstore := keys.New(
- db,
- words.MustLoadCodec("english"),
- )
-
- // Create a private-public key pair and ensure consistency
- info, _, err := cstore.CreateMnemonic("john", "passphrase", keys.AlgoEd25519)
- assert.NoError(t, err)
- assert.Equal(t, info.GetName(), "john")
- addr := info.GetPubKey().Address()
- john, err := cstore.Get("john")
- assert.NoError(t, err)
- assert.Equal(t, john.GetName(), "john")
- assert.Equal(t, john.GetPubKey().Address(), addr)
-
- // Export the public key only
- armor, err := cstore.ExportPubKey("john")
- assert.NoError(t, err)
- // Import it under a different name
- err = cstore.ImportPubKey("john-pubkey-only", armor)
- assert.NoError(t, err)
- // Ensure consistency
- john2, err := cstore.Get("john-pubkey-only")
- assert.NoError(t, err)
- // Compare the public keys
- assert.True(t, john.GetPubKey().Equals(john2.GetPubKey()))
- // Ensure the original key hasn't changed
- john, err = cstore.Get("john")
- assert.NoError(t, err)
- assert.Equal(t, john.GetPubKey().Address(), addr)
- assert.Equal(t, john.GetName(), "john")
-
- // Ensure keys cannot be overwritten
- err = cstore.ImportPubKey("john-pubkey-only", armor)
- assert.NotNil(t, err)
- }
-
- // TestAdvancedKeyManagement verifies update, import, export functionality
- func TestAdvancedKeyManagement(t *testing.T) {
-
- // make the storage with reasonable defaults
- cstore := keys.New(
- dbm.NewMemDB(),
- words.MustLoadCodec("english"),
- )
-
- algo := keys.AlgoSecp256k1
- n1, n2 := "old-name", "new name"
- p1, p2 := "1234", "foobar"
-
- // make sure key works with initial password
- _, _, err := cstore.CreateMnemonic(n1, p1, algo)
- require.Nil(t, err, "%+v", err)
- assertPassword(t, cstore, n1, p1, p2)
-
- // update password requires the existing password
- err = cstore.Update(n1, "jkkgkg", p2)
- assert.NotNil(t, err)
- assertPassword(t, cstore, n1, p1, p2)
-
- // then it changes the password when correct
- err = cstore.Update(n1, p1, p2)
- assert.NoError(t, err)
- // p2 is now the proper one!
- assertPassword(t, cstore, n1, p2, p1)
-
- // exporting requires the proper name and passphrase
- _, err = cstore.Export(n1 + ".notreal")
- assert.NotNil(t, err)
- _, err = cstore.Export(" " + n1)
- assert.NotNil(t, err)
- _, err = cstore.Export(n1 + " ")
- assert.NotNil(t, err)
- _, err = cstore.Export("")
- assert.NotNil(t, err)
- exported, err := cstore.Export(n1)
- require.Nil(t, err, "%+v", err)
-
- // import succeeds
- err = cstore.Import(n2, exported)
- assert.NoError(t, err)
-
- // second import fails
- err = cstore.Import(n2, exported)
- assert.NotNil(t, err)
- }
-
- // TestSeedPhrase verifies restoring from a seed phrase
- func TestSeedPhrase(t *testing.T) {
-
- // make the storage with reasonable defaults
- cstore := keys.New(
- dbm.NewMemDB(),
- words.MustLoadCodec("english"),
- )
-
- algo := keys.AlgoEd25519
- n1, n2 := "lost-key", "found-again"
- p1, p2 := "1234", "foobar"
-
- // make sure key works with initial password
- info, seed, err := cstore.CreateMnemonic(n1, p1, algo)
- require.Nil(t, err, "%+v", err)
- assert.Equal(t, n1, info.GetName())
- assert.NotEmpty(t, seed)
-
- // now, let us delete this key
- err = cstore.Delete(n1, p1)
- require.Nil(t, err, "%+v", err)
- _, err = cstore.Get(n1)
- require.NotNil(t, err)
-
- // let us re-create it from the seed-phrase
- newInfo, err := cstore.Recover(n2, p2, seed)
- require.Nil(t, err, "%+v", err)
- assert.Equal(t, n2, newInfo.GetName())
- assert.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
- assert.Equal(t, info.GetPubKey(), newInfo.GetPubKey())
- }
-
- func ExampleNew() {
- // Select the encryption and storage for your cryptostore
- cstore := keys.New(
- dbm.NewMemDB(),
- words.MustLoadCodec("english"),
- )
- ed := keys.AlgoEd25519
- sec := keys.AlgoSecp256k1
-
- // Add keys and see they return in alphabetical order
- bob, _, err := cstore.CreateMnemonic("Bob", "friend", ed)
- if err != nil {
- // this should never happen
- fmt.Println(err)
- } else {
- // return info here just like in List
- fmt.Println(bob.GetName())
- }
- cstore.CreateMnemonic("Alice", "secret", sec)
- cstore.CreateMnemonic("Carl", "mitm", ed)
- info, _ := cstore.List()
- for _, i := range info {
- fmt.Println(i.GetName())
- }
-
- // We need to use passphrase to generate a signature
- tx := []byte("deadbeef")
- sig, pub, err := cstore.Sign("Bob", "friend", tx)
- if err != nil {
- fmt.Println("don't accept real passphrase")
- }
-
- // and we can validate the signature with publically available info
- binfo, _ := cstore.Get("Bob")
- if !binfo.GetPubKey().Equals(bob.GetPubKey()) {
- fmt.Println("Get and Create return different keys")
- }
-
- if pub.Equals(binfo.GetPubKey()) {
- fmt.Println("signed by Bob")
- }
- if !pub.VerifyBytes(tx, sig) {
- fmt.Println("invalid signature")
- }
-
- // Output:
- // Bob
- // Alice
- // Bob
- // Carl
- // signed by Bob
- }
|