github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/crypto/keys/keybase_test.go (about)

     1  package keys
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/gnolang/gno/tm2/pkg/crypto"
    11  	"github.com/gnolang/gno/tm2/pkg/crypto/ed25519"
    12  	"github.com/gnolang/gno/tm2/pkg/crypto/keys/keyerror"
    13  )
    14  
    15  func TestCreateAccountInvalidMnemonic(t *testing.T) {
    16  	t.Parallel()
    17  
    18  	kb := NewInMemory()
    19  	_, err := kb.CreateAccount(
    20  		"some_account",
    21  		"malarkey pair crucial catch public canyon evil outer stage ten gym tornado",
    22  		"", "", 0, 1)
    23  	assert.Error(t, err)
    24  	assert.Equal(t, "invalid mnemonic", err.Error())
    25  }
    26  
    27  // TestKeyManagement makes sure we can manipulate these keys well
    28  func TestKeyManagement(t *testing.T) {
    29  	t.Parallel()
    30  
    31  	// make the storage with reasonable defaults
    32  	cstore := NewInMemory()
    33  
    34  	n1, n2, n3 := "personal", "business", "other"
    35  	p1, p2 := "1234", "really-secure!@#$"
    36  	mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake`
    37  	mn2 := `lecture salt about avocado smooth height escape general arch head barrel clutch dismiss supply doctor project cat truck fruit abuse gorilla symbol portion glare`
    38  	bip39Passphrase := ""
    39  
    40  	// Check empty state
    41  	l, err := cstore.List()
    42  	require.Nil(t, err)
    43  	assert.Empty(t, l)
    44  
    45  	// create some keys
    46  	has, err := cstore.HasByName(n1)
    47  	require.NoError(t, err)
    48  	require.False(t, has)
    49  	i, err := cstore.CreateAccount(n1, mn1, bip39Passphrase, p1, 0, 0)
    50  	require.NoError(t, err)
    51  	require.Equal(t, n1, i.GetName())
    52  	_, err = cstore.CreateAccount(n2, mn2, bip39Passphrase, p2, 0, 0)
    53  	require.NoError(t, err)
    54  
    55  	// we can get these keys
    56  	i2, err := cstore.GetByName(n2)
    57  	require.NoError(t, err)
    58  	has, err = cstore.HasByName(n3)
    59  	require.NoError(t, err)
    60  	require.False(t, has)
    61  	has, err = cstore.HasByAddress(toAddr(i2))
    62  	require.NoError(t, err)
    63  	require.True(t, has)
    64  	// Also check with HasByNameOrAddress
    65  	has, err = cstore.HasByNameOrAddress(crypto.AddressToBech32(toAddr(i2)))
    66  	require.NoError(t, err)
    67  	require.True(t, has)
    68  	addr, err := crypto.AddressFromBech32("g1frtkxv37nq7arvyz5p0mtjqq7hwuvd4dnt892p")
    69  	require.NoError(t, err)
    70  	_, err = cstore.GetByAddress(addr)
    71  	require.NotNil(t, err)
    72  	require.True(t, keyerror.IsErrKeyNotFound(err))
    73  
    74  	// list shows them in order
    75  	keyS, err := cstore.List()
    76  	require.NoError(t, err)
    77  	require.Equal(t, 2, len(keyS))
    78  	// note these are in alphabetical order
    79  	require.Equal(t, n2, keyS[0].GetName())
    80  	require.Equal(t, n1, keyS[1].GetName())
    81  	require.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey())
    82  
    83  	// deleting a key removes it
    84  	err = cstore.Delete("bad name", "foo", false)
    85  	require.NotNil(t, err)
    86  	err = cstore.Delete(n1, p1, false)
    87  	require.NoError(t, err)
    88  	keyS, err = cstore.List()
    89  	require.NoError(t, err)
    90  	require.Equal(t, 1, len(keyS))
    91  	has, err = cstore.HasByName(n1)
    92  	require.NoError(t, err)
    93  	require.False(t, has)
    94  
    95  	// create an offline key
    96  	o1 := "offline"
    97  	priv1 := ed25519.GenPrivKey()
    98  	pub1 := priv1.PubKey()
    99  	i, err = cstore.CreateOffline(o1, pub1)
   100  	require.Nil(t, err)
   101  	require.Equal(t, pub1, i.GetPubKey())
   102  	require.Equal(t, o1, i.GetName())
   103  	keyS, err = cstore.List()
   104  	require.NoError(t, err)
   105  	require.Equal(t, 2, len(keyS))
   106  
   107  	// delete the offline key
   108  	err = cstore.Delete(o1, "", false)
   109  	require.NoError(t, err)
   110  	keyS, err = cstore.List()
   111  	require.NoError(t, err)
   112  	require.Equal(t, 1, len(keyS))
   113  
   114  	// addr cache gets nuked - and test skip flag
   115  	err = cstore.Delete(n2, "", true)
   116  	require.NoError(t, err)
   117  }
   118  
   119  // TestSignVerify does some detailed checks on how we sign and validate
   120  // signatures
   121  func TestSignVerify(t *testing.T) {
   122  	t.Parallel()
   123  
   124  	cstore := NewInMemory()
   125  
   126  	n1, n2, n3 := "some dude", "a dudette", "dude-ish"
   127  	p1, p2, p3 := "1234", "foobar", "foobar"
   128  	mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake`
   129  	mn2 := `lecture salt about avocado smooth height escape general arch head barrel clutch dismiss supply doctor project cat truck fruit abuse gorilla symbol portion glare`
   130  	bip39Passphrase := ""
   131  
   132  	// create two users and get their info
   133  	i1, err := cstore.CreateAccount(n1, mn1, bip39Passphrase, p1, 0, 0)
   134  	require.Nil(t, err)
   135  
   136  	i2, err := cstore.CreateAccount(n2, mn2, bip39Passphrase, p2, 0, 0)
   137  	require.Nil(t, err)
   138  
   139  	// Import a public key
   140  	armor, err := cstore.ExportPubKey(n2)
   141  	require.Nil(t, err)
   142  	cstore.ImportPubKey(n3, armor)
   143  	i3, err := cstore.GetByName(n3)
   144  	require.NoError(t, err)
   145  	require.Equal(t, i3.GetName(), n3)
   146  
   147  	// let's try to sign some messages
   148  	d1 := []byte("my first message")
   149  	d2 := []byte("some other important info!")
   150  	d3 := []byte("feels like I forgot something...")
   151  
   152  	// try signing both data with both ..
   153  	s11, pub1, err := cstore.Sign(n1, p1, d1)
   154  	require.Nil(t, err)
   155  	require.Equal(t, i1.GetPubKey(), pub1)
   156  
   157  	s12, pub1, err := cstore.Sign(n1, p1, d2)
   158  	require.Nil(t, err)
   159  	require.Equal(t, i1.GetPubKey(), pub1)
   160  
   161  	s21, pub2, err := cstore.Sign(n2, p2, d1)
   162  	require.Nil(t, err)
   163  	require.Equal(t, i2.GetPubKey(), pub2)
   164  
   165  	s22, pub2, err := cstore.Sign(n2, p2, d2)
   166  	require.Nil(t, err)
   167  	require.Equal(t, i2.GetPubKey(), pub2)
   168  
   169  	// let's try to validate and make sure it only works when everything is proper
   170  	cases := []struct {
   171  		key   crypto.PubKey
   172  		data  []byte
   173  		sig   []byte
   174  		valid bool
   175  	}{
   176  		// proper matches
   177  		{i1.GetPubKey(), d1, s11, true},
   178  		// change data, pubkey, or signature leads to fail
   179  		{i1.GetPubKey(), d2, s11, false},
   180  		{i2.GetPubKey(), d1, s11, false},
   181  		{i1.GetPubKey(), d1, s21, false},
   182  		// make sure other successes
   183  		{i1.GetPubKey(), d2, s12, true},
   184  		{i2.GetPubKey(), d1, s21, true},
   185  		{i2.GetPubKey(), d2, s22, true},
   186  	}
   187  
   188  	for i, tc := range cases {
   189  		valid := tc.key.VerifyBytes(tc.data, tc.sig)
   190  		require.Equal(t, tc.valid, valid, "%d", i)
   191  	}
   192  
   193  	// Now try to sign data with a secret-less key
   194  	_, _, err = cstore.Sign(n3, p3, d3)
   195  	require.NotNil(t, err)
   196  }
   197  
   198  func assertPassword(t *testing.T, cstore Keybase, name, pass, badpass string) {
   199  	t.Helper()
   200  
   201  	getNewpass := func() (string, error) { return pass, nil }
   202  	err := cstore.Update(name, badpass, getNewpass)
   203  	require.NotNil(t, err)
   204  	err = cstore.Update(name, pass, getNewpass)
   205  	require.Nil(t, err, "%+v", err)
   206  }
   207  
   208  // TestExportImport tests exporting and importing
   209  func TestExportImport(t *testing.T) {
   210  	t.Parallel()
   211  
   212  	// make the storage with reasonable defaults
   213  	cstore := NewInMemory()
   214  
   215  	mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake`
   216  	bip39Passphrase := ""
   217  
   218  	info, err := cstore.CreateAccount("john", mn1, bip39Passphrase, "secretcpw", 0, 0)
   219  	require.NoError(t, err)
   220  	require.Equal(t, info.GetName(), "john")
   221  
   222  	john, err := cstore.GetByName("john")
   223  	require.NoError(t, err)
   224  	require.Equal(t, info.GetName(), "john")
   225  	johnAddr := info.GetPubKey().Address()
   226  
   227  	armor, err := cstore.Export("john")
   228  	require.NoError(t, err)
   229  
   230  	err = cstore.Import("john2", armor)
   231  	require.NoError(t, err)
   232  
   233  	john2, err := cstore.GetByName("john2")
   234  	require.NoError(t, err)
   235  
   236  	require.Equal(t, john.GetPubKey().Address(), johnAddr)
   237  	require.Equal(t, john.GetName(), "john")
   238  	require.Equal(t, john, john2)
   239  }
   240  
   241  func TestExportImportPubKey(t *testing.T) {
   242  	t.Parallel()
   243  
   244  	// make the storage with reasonable defaults
   245  	cstore := NewInMemory()
   246  
   247  	// CreateAccount a private-public key pair and ensure consistency
   248  	mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake`
   249  	bip39Passphrase := ""
   250  	notPasswd := "n9y25ah7"
   251  	info, err := cstore.CreateAccount("john", mn1, bip39Passphrase, notPasswd, 0, 0)
   252  	require.Nil(t, err)
   253  	require.NotEqual(t, info, "")
   254  	require.Equal(t, info.GetName(), "john")
   255  	addr := info.GetPubKey().Address()
   256  	john, err := cstore.GetByName("john")
   257  	require.NoError(t, err)
   258  	require.Equal(t, john.GetName(), "john")
   259  	require.Equal(t, john.GetPubKey().Address(), addr)
   260  
   261  	// Export the public key only
   262  	armor, err := cstore.ExportPubKey("john")
   263  	require.NoError(t, err)
   264  	// Import it under a different name
   265  	err = cstore.ImportPubKey("john-pubkey-only", armor)
   266  	require.NoError(t, err)
   267  	// Ensure consistency
   268  	john2, err := cstore.GetByName("john-pubkey-only")
   269  	require.NoError(t, err)
   270  	// Compare the public keys
   271  	require.True(t, john.GetPubKey().Equals(john2.GetPubKey()))
   272  	// Ensure the original key hasn't changed
   273  	john, err = cstore.GetByName("john")
   274  	require.NoError(t, err)
   275  	require.Equal(t, john.GetPubKey().Address(), addr)
   276  	require.Equal(t, john.GetName(), "john")
   277  
   278  	// Ensure keys cannot be overwritten
   279  	err = cstore.ImportPubKey("john-pubkey-only", armor)
   280  	require.NotNil(t, err)
   281  }
   282  
   283  // TestAdvancedKeyManagement verifies update, import, export functionality
   284  func TestAdvancedKeyManagement(t *testing.T) {
   285  	t.Parallel()
   286  
   287  	// make the storage with reasonable defaults
   288  	cstore := NewInMemory()
   289  
   290  	n1, n2 := "old-name", "new name"
   291  	p1, p2 := "1234", "foobar"
   292  	mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake`
   293  	bip39Passphrase := ""
   294  
   295  	// make sure key works with initial password
   296  	_, err := cstore.CreateAccount(n1, mn1, bip39Passphrase, p1, 0, 0)
   297  	require.Nil(t, err, "%+v", err)
   298  	assertPassword(t, cstore, n1, p1, p2)
   299  
   300  	// update password requires the existing password
   301  	getNewpass := func() (string, error) { return p2, nil }
   302  	err = cstore.Update(n1, "jkkgkg", getNewpass)
   303  	require.NotNil(t, err)
   304  	assertPassword(t, cstore, n1, p1, p2)
   305  
   306  	// then it changes the password when correct
   307  	err = cstore.Update(n1, p1, getNewpass)
   308  	require.NoError(t, err)
   309  	// p2 is now the proper one!
   310  	assertPassword(t, cstore, n1, p2, p1)
   311  
   312  	// exporting requires the proper name and passphrase
   313  	_, err = cstore.Export(n1 + ".notreal")
   314  	require.NotNil(t, err)
   315  	_, err = cstore.Export(" " + n1)
   316  	require.NotNil(t, err)
   317  	_, err = cstore.Export(n1 + " ")
   318  	require.NotNil(t, err)
   319  	_, err = cstore.Export("")
   320  	require.NotNil(t, err)
   321  	exported, err := cstore.Export(n1)
   322  	require.Nil(t, err, "%+v", err)
   323  
   324  	// import succeeds
   325  	err = cstore.Import(n2, exported)
   326  	require.NoError(t, err)
   327  
   328  	// second import fails
   329  	err = cstore.Import(n2, exported)
   330  	require.NotNil(t, err)
   331  }
   332  
   333  // TestSeedPhrase verifies restoring from a seed phrase
   334  func TestSeedPhrase(t *testing.T) {
   335  	t.Parallel()
   336  
   337  	// make the storage with reasonable defaults
   338  	cstore := NewInMemory()
   339  
   340  	n1 := "lost-key"
   341  	p1 := "1234"
   342  	mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake`
   343  	bip39Passphrase := ""
   344  
   345  	// make sure key works with initial password
   346  	info, err := cstore.CreateAccount(n1, mn1, bip39Passphrase, p1, 0, 0)
   347  	require.Nil(t, err, "%+v", err)
   348  	require.Equal(t, n1, info.GetName())
   349  
   350  	// now, let us delete this key
   351  	err = cstore.Delete(n1, p1, false)
   352  	require.Nil(t, err, "%+v", err)
   353  	has, err := cstore.HasByName(n1)
   354  	require.NoError(t, err)
   355  	require.False(t, has)
   356  }
   357  
   358  func ExampleNew() {
   359  	// Select the encryption and storage for your cryptostore
   360  	cstore := NewInMemory()
   361  
   362  	mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake`
   363  	mn2 := `lecture salt about avocado smooth height escape general arch head barrel clutch dismiss supply doctor project cat truck fruit abuse gorilla symbol portion glare`
   364  	mn3 := `jar nest rug lion shallow spring abuse west gravity skin project comic again dirt pelican better galaxy click hold lottery swap solution census own`
   365  	bip39Passphrase := ""
   366  
   367  	// Add keys and see they return in alphabetical order
   368  	bob, err := cstore.CreateAccount("Bob", mn1, bip39Passphrase, "friend", 0, 0)
   369  	if err != nil {
   370  		// this should never happen
   371  		fmt.Println(err)
   372  	} else {
   373  		// return info here just like in List
   374  		fmt.Println(bob.GetName())
   375  	}
   376  	_, _ = cstore.CreateAccount("Alice", mn2, bip39Passphrase, "secret", 0, 0)
   377  	_, _ = cstore.CreateAccount("Carl", mn3, bip39Passphrase, "mitm", 0, 0)
   378  	info, _ := cstore.List()
   379  	for _, i := range info {
   380  		fmt.Println(i.GetName())
   381  	}
   382  
   383  	// We need to use passphrase to generate a signature
   384  	tx := []byte("deadbeef")
   385  	sig, pub, err := cstore.Sign("Bob", "friend", tx)
   386  	if err != nil {
   387  		fmt.Println("don't accept real passphrase")
   388  	}
   389  
   390  	// and we can validate the signature with publicly available info
   391  	binfo, _ := cstore.GetByName("Bob")
   392  	if !binfo.GetPubKey().Equals(bob.GetPubKey()) {
   393  		fmt.Println("Get and Create return different keys")
   394  	}
   395  
   396  	if pub.Equals(binfo.GetPubKey()) {
   397  		fmt.Println("signed by Bob")
   398  	}
   399  	if !pub.VerifyBytes(tx, sig) {
   400  		fmt.Println("invalid signature")
   401  	}
   402  
   403  	// Output:
   404  	// Bob
   405  	// Alice
   406  	// Bob
   407  	// Carl
   408  	// signed by Bob
   409  }
   410  
   411  func toAddr(info Info) crypto.Address {
   412  	return info.GetPubKey().Address()
   413  }