github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/crypto/keys/keybase_test.go (about)

     1  // nolint: goconst
     2  package keys
     3  
     4  import (
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/fibonacci-chain/fbc/libs/tendermint/crypto"
    12  	"github.com/fibonacci-chain/fbc/libs/tendermint/crypto/ed25519"
    13  	"github.com/fibonacci-chain/fbc/libs/tendermint/crypto/secp256k1"
    14  
    15  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys/hd"
    16  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys/mintkey"
    17  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    18  )
    19  
    20  func init() {
    21  	mintkey.BcryptSecurityParameter = 1
    22  }
    23  
    24  const (
    25  	nums   = "1234"
    26  	foobar = "foobar"
    27  )
    28  
    29  func TestLanguage(t *testing.T) {
    30  	kb := NewInMemory()
    31  	_, _, err := kb.CreateMnemonic("something", Japanese, "no_pass", Secp256k1, "")
    32  	assert.Error(t, err)
    33  	assert.Equal(t, "unsupported language: only english is supported", err.Error())
    34  }
    35  
    36  func TestCreateAccountInvalidMnemonic(t *testing.T) {
    37  	kb := NewInMemory()
    38  	_, err := kb.CreateAccount(
    39  		"some_account",
    40  		"malarkey pair crucial catch public canyon evil outer stage ten gym tornado",
    41  		"", "", CreateHDPath(0, 0).String(), Secp256k1)
    42  	assert.Error(t, err)
    43  	assert.Equal(t, "Invalid mnemonic", err.Error())
    44  }
    45  
    46  func TestCreateLedgerUnsupportedAlgo(t *testing.T) {
    47  	kb := NewInMemory()
    48  
    49  	supportedLedgerAlgos := kb.SupportedAlgosLedger()
    50  	for _, supportedAlgo := range supportedLedgerAlgos {
    51  		if Ed25519 == supportedAlgo {
    52  			assert.FailNow(t, "Was not an unsupported algorithm")
    53  		}
    54  	}
    55  
    56  	_, err := kb.CreateLedger("some_account", Ed25519, "cosmos", 0, 1)
    57  	assert.Error(t, err)
    58  	assert.Equal(t, "unsupported signing algo", err.Error())
    59  }
    60  
    61  func TestCreateLedger(t *testing.T) {
    62  	kb := NewInMemory(WithSupportedAlgosLedger([]SigningAlgo{Secp256k1, Ed25519}))
    63  
    64  	// test_cover and test_unit will result in different answers
    65  	// test_cover does not compile some dependencies so ledger is disabled
    66  	// test_unit may add a ledger mock
    67  	// both cases are acceptable
    68  	supportedLedgerAlgos := kb.SupportedAlgosLedger()
    69  	secpSupported := false
    70  	edSupported := false
    71  	for _, supportedAlgo := range supportedLedgerAlgos {
    72  		secpSupported = secpSupported || (supportedAlgo == Secp256k1)
    73  		edSupported = edSupported || (supportedAlgo == Ed25519)
    74  	}
    75  	assert.True(t, secpSupported)
    76  	assert.True(t, edSupported)
    77  
    78  	ledger, err := kb.CreateLedger("some_account", Secp256k1, "cosmos", 3, 1)
    79  
    80  	if err != nil {
    81  		assert.Error(t, err)
    82  		assert.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error())
    83  		assert.Nil(t, ledger)
    84  		t.Skip("ledger nano S: support for ledger devices is not available in this executable")
    85  		return
    86  	}
    87  
    88  	// The mock is available, check that the address is correct
    89  	pubKey := ledger.GetPubKey()
    90  	pk, err := sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey)
    91  	assert.NoError(t, err)
    92  	assert.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk)
    93  
    94  	// Check that restoring the key gets the same results
    95  	restoredKey, err := kb.Get("some_account")
    96  	assert.NoError(t, err)
    97  	assert.NotNil(t, restoredKey)
    98  	assert.Equal(t, "some_account", restoredKey.GetName())
    99  	assert.Equal(t, TypeLedger, restoredKey.GetType())
   100  	pubKey = restoredKey.GetPubKey()
   101  	pk, err = sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey)
   102  	assert.NoError(t, err)
   103  	assert.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk)
   104  
   105  	path, err := restoredKey.GetPath()
   106  	assert.NoError(t, err)
   107  	assert.Equal(t, "m/44'/118'/3'/0/1", path.String())
   108  }
   109  
   110  // TestKeyManagement makes sure we can manipulate these keys well
   111  func TestKeyManagement(t *testing.T) {
   112  	// make the storage with reasonable defaults
   113  	cstore := NewInMemory(WithSupportedAlgos([]SigningAlgo{Secp256k1, Sr25519}))
   114  
   115  	// Test modified supported algos
   116  	supportedAlgos := cstore.SupportedAlgos()
   117  	secpSupported := false
   118  	edSupported := false
   119  	srSupported := false
   120  	for _, supportedAlgo := range supportedAlgos {
   121  		secpSupported = secpSupported || (supportedAlgo == Secp256k1)
   122  		edSupported = edSupported || (supportedAlgo == Ed25519)
   123  		srSupported = srSupported || (supportedAlgo == Sr25519)
   124  	}
   125  	assert.True(t, secpSupported)
   126  	assert.False(t, edSupported)
   127  	assert.True(t, srSupported)
   128  
   129  	algo := Secp256k1
   130  	n1, n2, n3 := "personal", "business", "other"
   131  	p1, p2 := nums, "really-secure!@#$"
   132  
   133  	// Check empty state
   134  	l, err := cstore.List()
   135  	require.Nil(t, err)
   136  	assert.Empty(t, l)
   137  
   138  	_, _, err = cstore.CreateMnemonic(n1, English, p1, Ed25519, "")
   139  	require.Error(t, err, "ed25519 keys are currently not supported by keybase")
   140  
   141  	// create some keys
   142  	_, err = cstore.Get(n1)
   143  	require.Error(t, err)
   144  	i, _, err := cstore.CreateMnemonic(n1, English, p1, algo, "")
   145  
   146  	require.NoError(t, err)
   147  	require.Equal(t, n1, i.GetName())
   148  	_, _, err = cstore.CreateMnemonic(n2, English, p2, algo, "")
   149  	require.NoError(t, err)
   150  
   151  	// we can get these keys
   152  	i2, err := cstore.Get(n2)
   153  	require.NoError(t, err)
   154  	_, err = cstore.Get(n3)
   155  	require.NotNil(t, err)
   156  	_, err = cstore.GetByAddress(accAddr(i2))
   157  	require.NoError(t, err)
   158  	addr, err := sdk.AccAddressFromBech32("cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t")
   159  	require.NoError(t, err)
   160  	_, err = cstore.GetByAddress(addr)
   161  	require.NotNil(t, err)
   162  
   163  	// list shows them in order
   164  	keyS, err := cstore.List()
   165  	require.NoError(t, err)
   166  	require.Equal(t, 2, len(keyS))
   167  	// note these are in alphabetical order
   168  	require.Equal(t, n2, keyS[0].GetName())
   169  	require.Equal(t, n1, keyS[1].GetName())
   170  	require.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey())
   171  
   172  	// deleting a key removes it
   173  	err = cstore.Delete("bad name", "foo", false)
   174  	require.NotNil(t, err)
   175  	err = cstore.Delete(n1, p1, false)
   176  	require.NoError(t, err)
   177  	keyS, err = cstore.List()
   178  	require.NoError(t, err)
   179  	require.Equal(t, 1, len(keyS))
   180  	_, err = cstore.Get(n1)
   181  	require.Error(t, err)
   182  
   183  	// create an offline key
   184  	o1 := "offline"
   185  	priv1 := ed25519.GenPrivKey()
   186  	pub1 := priv1.PubKey()
   187  	i, err = cstore.CreateOffline(o1, pub1, algo)
   188  	require.Nil(t, err)
   189  	require.Equal(t, pub1, i.GetPubKey())
   190  	require.Equal(t, o1, i.GetName())
   191  	iOffline := i.(*offlineInfo)
   192  	require.Equal(t, algo, iOffline.GetAlgo())
   193  	keyS, err = cstore.List()
   194  	require.NoError(t, err)
   195  	require.Equal(t, 2, len(keyS))
   196  
   197  	// delete the offline key
   198  	err = cstore.Delete(o1, "", false)
   199  	require.NoError(t, err)
   200  	keyS, err = cstore.List()
   201  	require.NoError(t, err)
   202  	require.Equal(t, 1, len(keyS))
   203  
   204  	// addr cache gets nuked - and test skip flag
   205  	err = cstore.Delete(n2, "", true)
   206  	require.NoError(t, err)
   207  }
   208  
   209  // TestSignVerify does some detailed checks on how we sign and validate
   210  // signatures
   211  func TestSignVerify(t *testing.T) {
   212  	cstore := NewInMemory()
   213  	algo := Secp256k1
   214  
   215  	n1, n2, n3 := "some dude", "a dudette", "dude-ish"
   216  	p1, p2, p3 := nums, foobar, foobar
   217  
   218  	// create two users and get their info
   219  	i1, _, err := cstore.CreateMnemonic(n1, English, p1, algo, "")
   220  	require.Nil(t, err)
   221  
   222  	i2, _, err := cstore.CreateMnemonic(n2, English, p2, algo, "")
   223  	require.Nil(t, err)
   224  
   225  	// Import a public key
   226  	armor, err := cstore.ExportPubKey(n2)
   227  	require.Nil(t, err)
   228  	require.NoError(t, cstore.ImportPubKey(n3, armor))
   229  	i3, err := cstore.Get(n3)
   230  	require.NoError(t, err)
   231  	require.Equal(t, i3.GetName(), n3)
   232  
   233  	// let's try to sign some messages
   234  	d1 := []byte("my first message")
   235  	d2 := []byte("some other important info!")
   236  	d3 := []byte("feels like I forgot something...")
   237  
   238  	// try signing both data with both ..
   239  	s11, pub1, err := cstore.Sign(n1, p1, d1)
   240  	require.Nil(t, err)
   241  	require.Equal(t, i1.GetPubKey(), pub1)
   242  
   243  	s12, pub1, err := cstore.Sign(n1, p1, d2)
   244  	require.Nil(t, err)
   245  	require.Equal(t, i1.GetPubKey(), pub1)
   246  
   247  	s21, pub2, err := cstore.Sign(n2, p2, d1)
   248  	require.Nil(t, err)
   249  	require.Equal(t, i2.GetPubKey(), pub2)
   250  
   251  	s22, pub2, err := cstore.Sign(n2, p2, d2)
   252  	require.Nil(t, err)
   253  	require.Equal(t, i2.GetPubKey(), pub2)
   254  
   255  	// let's try to validate and make sure it only works when everything is proper
   256  	cases := []struct {
   257  		key   crypto.PubKey
   258  		data  []byte
   259  		sig   []byte
   260  		valid bool
   261  	}{
   262  		// proper matches
   263  		{i1.GetPubKey(), d1, s11, true},
   264  		// change data, pubkey, or signature leads to fail
   265  		{i1.GetPubKey(), d2, s11, false},
   266  		{i2.GetPubKey(), d1, s11, false},
   267  		{i1.GetPubKey(), d1, s21, false},
   268  		// make sure other successes
   269  		{i1.GetPubKey(), d2, s12, true},
   270  		{i2.GetPubKey(), d1, s21, true},
   271  		{i2.GetPubKey(), d2, s22, true},
   272  	}
   273  
   274  	for i, tc := range cases {
   275  		valid := tc.key.VerifyBytes(tc.data, tc.sig)
   276  		require.Equal(t, tc.valid, valid, "%d", i)
   277  	}
   278  
   279  	// Now try to sign data with a secret-less key
   280  	_, _, err = cstore.Sign(n3, p3, d3)
   281  	require.NotNil(t, err)
   282  }
   283  
   284  func assertPassword(t *testing.T, cstore Keybase, name, pass, badpass string) {
   285  	getNewpass := func() (string, error) { return pass, nil }
   286  	err := cstore.Update(name, badpass, getNewpass)
   287  	require.NotNil(t, err)
   288  	err = cstore.Update(name, pass, getNewpass)
   289  	require.Nil(t, err, "%+v", err)
   290  }
   291  
   292  // TestExportImport tests exporting and importing
   293  func TestExportImport(t *testing.T) {
   294  	// make the storage with reasonable defaults
   295  	cstore := NewInMemory()
   296  
   297  	info, _, err := cstore.CreateMnemonic("john", English, "secretcpw", Secp256k1, "")
   298  	require.NoError(t, err)
   299  	require.Equal(t, info.GetName(), "john")
   300  
   301  	john, err := cstore.Get("john")
   302  	require.NoError(t, err)
   303  	require.Equal(t, info.GetName(), "john")
   304  	johnAddr := info.GetPubKey().Address()
   305  
   306  	armor, err := cstore.Export("john")
   307  	require.NoError(t, err)
   308  
   309  	err = cstore.Import("john2", armor)
   310  	require.NoError(t, err)
   311  
   312  	john2, err := cstore.Get("john2")
   313  	require.NoError(t, err)
   314  
   315  	require.Equal(t, john.GetPubKey().Address(), johnAddr)
   316  	require.Equal(t, john.GetName(), "john")
   317  	require.Equal(t, john, john2)
   318  }
   319  
   320  func TestExportImportPubKey(t *testing.T) {
   321  	// make the storage with reasonable defaults
   322  	cstore := NewInMemory()
   323  
   324  	// CreateMnemonic a private-public key pair and ensure consistency
   325  	notPasswd := "n9y25ah7"
   326  	info, _, err := cstore.CreateMnemonic("john", English, notPasswd, Secp256k1, "")
   327  	require.Nil(t, err)
   328  	require.NotEqual(t, info, "")
   329  	require.Equal(t, info.GetName(), "john")
   330  	addr := info.GetPubKey().Address()
   331  	john, err := cstore.Get("john")
   332  	require.NoError(t, err)
   333  	require.Equal(t, john.GetName(), "john")
   334  	require.Equal(t, john.GetPubKey().Address(), addr)
   335  
   336  	// Export the public key only
   337  	armor, err := cstore.ExportPubKey("john")
   338  	require.NoError(t, err)
   339  	// Import it under a different name
   340  	err = cstore.ImportPubKey("john-pubkey-only", armor)
   341  	require.NoError(t, err)
   342  	// Ensure consistency
   343  	john2, err := cstore.Get("john-pubkey-only")
   344  	require.NoError(t, err)
   345  	// Compare the public keys
   346  	require.True(t, john.GetPubKey().Equals(john2.GetPubKey()))
   347  	// Ensure the original key hasn't changed
   348  	john, err = cstore.Get("john")
   349  	require.NoError(t, err)
   350  	require.Equal(t, john.GetPubKey().Address(), addr)
   351  	require.Equal(t, john.GetName(), "john")
   352  
   353  	// Ensure keys cannot be overwritten
   354  	err = cstore.ImportPubKey("john-pubkey-only", armor)
   355  	require.NotNil(t, err)
   356  }
   357  
   358  // TestAdvancedKeyManagement verifies update, import, export functionality
   359  func TestAdvancedKeyManagement(t *testing.T) {
   360  	// make the storage with reasonable defaults
   361  	cstore := NewInMemory()
   362  
   363  	algo := Secp256k1
   364  	n1, n2 := "old-name", "new name"
   365  	p1, p2 := nums, foobar
   366  
   367  	// make sure key works with initial password
   368  	_, _, err := cstore.CreateMnemonic(n1, English, p1, algo, "")
   369  	require.Nil(t, err, "%+v", err)
   370  	assertPassword(t, cstore, n1, p1, p2)
   371  
   372  	// update password requires the existing password
   373  	getNewpass := func() (string, error) { return p2, nil }
   374  	err = cstore.Update(n1, "jkkgkg", getNewpass)
   375  	require.NotNil(t, err)
   376  	assertPassword(t, cstore, n1, p1, p2)
   377  
   378  	// then it changes the password when correct
   379  	err = cstore.Update(n1, p1, getNewpass)
   380  	require.NoError(t, err)
   381  	// p2 is now the proper one!
   382  	assertPassword(t, cstore, n1, p2, p1)
   383  
   384  	// exporting requires the proper name and passphrase
   385  	_, err = cstore.Export(n1 + ".notreal")
   386  	require.NotNil(t, err)
   387  	_, err = cstore.Export(" " + n1)
   388  	require.NotNil(t, err)
   389  	_, err = cstore.Export(n1 + " ")
   390  	require.NotNil(t, err)
   391  	_, err = cstore.Export("")
   392  	require.NotNil(t, err)
   393  	exported, err := cstore.Export(n1)
   394  	require.Nil(t, err, "%+v", err)
   395  
   396  	// import succeeds
   397  	err = cstore.Import(n2, exported)
   398  	require.NoError(t, err)
   399  
   400  	// second import fails
   401  	err = cstore.Import(n2, exported)
   402  	require.NotNil(t, err)
   403  }
   404  
   405  // TestSeedPhrase verifies restoring from a seed phrase
   406  func TestSeedPhrase(t *testing.T) {
   407  
   408  	// make the storage with reasonable defaults
   409  	cstore := NewInMemory()
   410  
   411  	algo := Secp256k1
   412  	n1, n2 := "lost-key", "found-again"
   413  	p1, p2 := nums, foobar
   414  
   415  	// make sure key works with initial password
   416  	info, mnemonic, err := cstore.CreateMnemonic(n1, English, p1, algo, "")
   417  	require.Nil(t, err, "%+v", err)
   418  	require.Equal(t, n1, info.GetName())
   419  	assert.NotEmpty(t, mnemonic)
   420  
   421  	// now, let us delete this key
   422  	err = cstore.Delete(n1, p1, false)
   423  	require.Nil(t, err, "%+v", err)
   424  	_, err = cstore.Get(n1)
   425  	require.NotNil(t, err)
   426  
   427  	// let us re-create it from the mnemonic-phrase
   428  	params := *hd.NewFundraiserParams(0, sdk.CoinType, 0)
   429  	hdPath := params.String()
   430  	newInfo, err := cstore.CreateAccount(n2, mnemonic, DefaultBIP39Passphrase, p2, hdPath, Secp256k1)
   431  	require.NoError(t, err)
   432  	require.Equal(t, n2, newInfo.GetName())
   433  	require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
   434  	require.Equal(t, info.GetPubKey(), newInfo.GetPubKey())
   435  }
   436  
   437  func TestCreateHDPath(t *testing.T) {
   438  	type args struct {
   439  		account uint32
   440  		index   uint32
   441  	}
   442  	tests := []struct {
   443  		name string
   444  		args args
   445  		want hd.BIP44Params
   446  	}{
   447  		{"m/44'/0'/0'/0/0", args{0, 0}, hd.BIP44Params{CoinType: 118, Purpose: 44}},
   448  		{"m/44'/114'/0'/0/0", args{0, 0}, hd.BIP44Params{Purpose: 44, CoinType: 118, Account: 0, AddressIndex: 0}},
   449  		{"m/44'/114'/1'/1/0", args{1, 1}, hd.BIP44Params{Purpose: 44, CoinType: 118, Account: 1, AddressIndex: 1}},
   450  	}
   451  	for _, tt := range tests {
   452  		tt := tt
   453  		t.Run(tt.name, func(t *testing.T) {
   454  			tt := tt
   455  			require.Equal(t, tt.want, *CreateHDPath(tt.args.account, tt.args.index))
   456  		})
   457  	}
   458  }
   459  
   460  func ExampleNew() {
   461  	// Select the encryption and storage for your cryptostore
   462  	customKeyGenFunc := func(bz []byte, algo SigningAlgo) (crypto.PrivKey, error) {
   463  		var bzArr [32]byte
   464  		copy(bzArr[:], bz)
   465  		return secp256k1.PrivKeySecp256k1(bzArr), nil
   466  	}
   467  	cstore := NewInMemory(WithKeygenFunc(customKeyGenFunc))
   468  
   469  	sec := Secp256k1
   470  
   471  	// Add keys and see they return in alphabetical order
   472  	bob, _, err := cstore.CreateMnemonic("Bob", English, "friend", sec, "")
   473  	if err != nil {
   474  		// this should never happen
   475  		fmt.Println(err)
   476  	} else {
   477  		// return info here just like in List
   478  		fmt.Println(bob.GetName())
   479  	}
   480  	_, _, _ = cstore.CreateMnemonic("Alice", English, "secret", sec, "")
   481  	_, _, _ = cstore.CreateMnemonic("Carl", English, "mitm", sec, "")
   482  	info, _ := cstore.List()
   483  	for _, i := range info {
   484  		fmt.Println(i.GetName())
   485  	}
   486  
   487  	// We need to use passphrase to generate a signature
   488  	tx := []byte("deadbeef")
   489  	sig, pub, err := cstore.Sign("Bob", "friend", tx)
   490  	if err != nil {
   491  		fmt.Println("don't accept real passphrase")
   492  	}
   493  
   494  	// and we can validate the signature with publicly available info
   495  	binfo, _ := cstore.Get("Bob")
   496  	if !binfo.GetPubKey().Equals(bob.GetPubKey()) {
   497  		fmt.Println("Get and Create return different keys")
   498  	}
   499  
   500  	if pub.Equals(binfo.GetPubKey()) {
   501  		fmt.Println("signed by Bob")
   502  	}
   503  	if !pub.VerifyBytes(tx, sig) {
   504  		fmt.Println("invalid signature")
   505  	}
   506  
   507  	// Output:
   508  	// Bob
   509  	// Alice
   510  	// Bob
   511  	// Carl
   512  	// signed by Bob
   513  }
   514  
   515  func accAddr(info Info) sdk.AccAddress {
   516  	return (sdk.AccAddress)(info.GetPubKey().Address())
   517  }