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

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/gnolang/gno/tm2/pkg/commands"
    12  	"github.com/gnolang/gno/tm2/pkg/crypto/keys"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func TestAdd_Base_Add(t *testing.T) {
    18  	t.Parallel()
    19  
    20  	t.Run("valid key addition, generated mnemonic", func(t *testing.T) {
    21  		t.Parallel()
    22  
    23  		var (
    24  			kbHome      = t.TempDir()
    25  			baseOptions = BaseOptions{
    26  				InsecurePasswordStdin: true,
    27  				Home:                  kbHome,
    28  			}
    29  
    30  			keyName = "key-name"
    31  		)
    32  
    33  		ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
    34  		defer cancelFn()
    35  
    36  		io := commands.NewTestIO()
    37  		io.SetIn(strings.NewReader("test1234\ntest1234\n"))
    38  
    39  		// Create the command
    40  		cmd := NewRootCmdWithBaseConfig(io, baseOptions)
    41  
    42  		args := []string{
    43  			"add",
    44  			"--insecure-password-stdin",
    45  			"--home",
    46  			kbHome,
    47  			keyName,
    48  		}
    49  
    50  		require.NoError(t, cmd.ParseAndRun(ctx, args))
    51  
    52  		// Check the keybase
    53  		kb, err := keys.NewKeyBaseFromDir(kbHome)
    54  		require.NoError(t, err)
    55  
    56  		original, err := kb.GetByName(keyName)
    57  		require.NoError(t, err)
    58  		require.NotNil(t, original)
    59  	})
    60  
    61  	t.Run("valid key addition, overwrite", func(t *testing.T) {
    62  		t.Parallel()
    63  
    64  		var (
    65  			kbHome      = t.TempDir()
    66  			baseOptions = BaseOptions{
    67  				InsecurePasswordStdin: true,
    68  				Home:                  kbHome,
    69  			}
    70  
    71  			keyName = "key-name"
    72  		)
    73  
    74  		ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
    75  		defer cancelFn()
    76  
    77  		io := commands.NewTestIO()
    78  		io.SetIn(strings.NewReader("test1234\ntest1234\n"))
    79  
    80  		// Create the command
    81  		cmd := NewRootCmdWithBaseConfig(io, baseOptions)
    82  
    83  		args := []string{
    84  			"add",
    85  			"--insecure-password-stdin",
    86  			"--home",
    87  			kbHome,
    88  			keyName,
    89  		}
    90  
    91  		require.NoError(t, cmd.ParseAndRun(ctx, args))
    92  
    93  		// Check the keybase
    94  		kb, err := keys.NewKeyBaseFromDir(kbHome)
    95  		require.NoError(t, err)
    96  
    97  		original, err := kb.GetByName(keyName)
    98  		require.NoError(t, err)
    99  
   100  		io.SetIn(strings.NewReader("y\ntest1234\ntest1234\n"))
   101  
   102  		cmd = NewRootCmdWithBaseConfig(io, baseOptions)
   103  		require.NoError(t, cmd.ParseAndRun(ctx, args))
   104  
   105  		newKey, err := kb.GetByName(keyName)
   106  		require.NoError(t, err)
   107  
   108  		// Make sure the different key is generated and overwritten
   109  		assert.NotEqual(t, original.GetAddress(), newKey.GetAddress())
   110  	})
   111  
   112  	t.Run("valid key addition, provided mnemonic", func(t *testing.T) {
   113  		t.Parallel()
   114  
   115  		var (
   116  			kbHome      = t.TempDir()
   117  			mnemonic    = generateTestMnemonic(t)
   118  			baseOptions = BaseOptions{
   119  				InsecurePasswordStdin: true,
   120  				Home:                  kbHome,
   121  			}
   122  
   123  			keyName = "key-name"
   124  		)
   125  
   126  		ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
   127  		defer cancelFn()
   128  
   129  		io := commands.NewTestIO()
   130  		io.SetIn(strings.NewReader("test1234" + "\n" + "test1234" + "\n" + mnemonic + "\n"))
   131  
   132  		// Create the command
   133  		cmd := NewRootCmdWithBaseConfig(io, baseOptions)
   134  
   135  		args := []string{
   136  			"add",
   137  			"--insecure-password-stdin",
   138  			"--home",
   139  			kbHome,
   140  			"--recover",
   141  			keyName,
   142  		}
   143  
   144  		require.NoError(t, cmd.ParseAndRun(ctx, args))
   145  		// Check the keybase
   146  		kb, err := keys.NewKeyBaseFromDir(kbHome)
   147  		require.NoError(t, err)
   148  
   149  		key, err := kb.GetByName(keyName)
   150  		require.NoError(t, err)
   151  		require.NotNil(t, key)
   152  
   153  		// Get the account
   154  		accounts := generateAccounts(mnemonic, []string{"44'/118'/0'/0/0"})
   155  
   156  		assert.Equal(t, accounts[0].String(), key.GetAddress().String())
   157  	})
   158  
   159  	t.Run("no overwrite permission", func(t *testing.T) {
   160  		t.Parallel()
   161  
   162  		var (
   163  			kbHome      = t.TempDir()
   164  			baseOptions = BaseOptions{
   165  				InsecurePasswordStdin: true,
   166  				Home:                  kbHome,
   167  			}
   168  
   169  			keyName = "key-name"
   170  		)
   171  
   172  		ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
   173  		defer cancelFn()
   174  
   175  		io := commands.NewTestIO()
   176  		io.SetIn(strings.NewReader("test1234\ntest1234\n"))
   177  
   178  		// Create the command
   179  		cmd := NewRootCmdWithBaseConfig(io, baseOptions)
   180  
   181  		args := []string{
   182  			"add",
   183  			"--insecure-password-stdin",
   184  			"--home",
   185  			kbHome,
   186  			keyName,
   187  		}
   188  
   189  		require.NoError(t, cmd.ParseAndRun(ctx, args))
   190  
   191  		// Check the keybase
   192  		kb, err := keys.NewKeyBaseFromDir(kbHome)
   193  		require.NoError(t, err)
   194  
   195  		original, err := kb.GetByName(keyName)
   196  		require.NoError(t, err)
   197  
   198  		io.SetIn(strings.NewReader("n\ntest1234\ntest1234\n"))
   199  
   200  		// Confirm overwrite
   201  		cmd = NewRootCmdWithBaseConfig(io, baseOptions)
   202  		require.ErrorIs(t, cmd.ParseAndRun(ctx, args), errOverwriteAborted)
   203  
   204  		newKey, err := kb.GetByName(keyName)
   205  		require.NoError(t, err)
   206  
   207  		// Make sure the key is not overwritten
   208  		assert.Equal(t, original.GetAddress(), newKey.GetAddress())
   209  	})
   210  }
   211  
   212  func generateDerivationPaths(count int) []string {
   213  	paths := make([]string, count)
   214  
   215  	for i := 0; i < count; i++ {
   216  		paths[i] = fmt.Sprintf("44'/118'/0'/0/%d", i)
   217  	}
   218  
   219  	return paths
   220  }
   221  
   222  func TestAdd_Derive(t *testing.T) {
   223  	t.Parallel()
   224  
   225  	t.Run("valid address derivation", func(t *testing.T) {
   226  		t.Parallel()
   227  
   228  		var (
   229  			kbHome   = t.TempDir()
   230  			mnemonic = generateTestMnemonic(t)
   231  			paths    = generateDerivationPaths(10)
   232  
   233  			baseOptions = BaseOptions{
   234  				InsecurePasswordStdin: true,
   235  				Home:                  kbHome,
   236  			}
   237  
   238  			dummyPass = "dummy-pass"
   239  		)
   240  
   241  		ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
   242  		defer cancelFn()
   243  
   244  		mockOut := bytes.NewBufferString("")
   245  
   246  		io := commands.NewTestIO()
   247  		io.SetIn(strings.NewReader(dummyPass + "\n" + dummyPass + "\n" + mnemonic + "\n"))
   248  		io.SetOut(commands.WriteNopCloser(mockOut))
   249  
   250  		// Create the command
   251  		cmd := NewRootCmdWithBaseConfig(io, baseOptions)
   252  
   253  		args := []string{
   254  			"add",
   255  			"--insecure-password-stdin",
   256  			"--home",
   257  			kbHome,
   258  			"--recover",
   259  			"example-key",
   260  		}
   261  
   262  		for _, path := range paths {
   263  			args = append(
   264  				args, []string{
   265  					"--derivation-path",
   266  					path,
   267  				}...,
   268  			)
   269  		}
   270  
   271  		require.NoError(t, cmd.ParseAndRun(ctx, args))
   272  
   273  		// Verify the addresses are derived correctly
   274  		expectedAccounts := generateAccounts(
   275  			mnemonic,
   276  			paths,
   277  		)
   278  
   279  		// Grab the output
   280  		deriveOutput := mockOut.String()
   281  
   282  		for _, expectedAccount := range expectedAccounts {
   283  			assert.Contains(t, deriveOutput, expectedAccount.String())
   284  		}
   285  	})
   286  
   287  	t.Run("malformed derivation path", func(t *testing.T) {
   288  		t.Parallel()
   289  
   290  		var (
   291  			kbHome      = t.TempDir()
   292  			mnemonic    = generateTestMnemonic(t)
   293  			dummyPass   = "dummy-pass"
   294  			baseOptions = BaseOptions{
   295  				InsecurePasswordStdin: true,
   296  				Home:                  kbHome,
   297  			}
   298  		)
   299  
   300  		ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
   301  		defer cancelFn()
   302  
   303  		mockOut := bytes.NewBufferString("")
   304  
   305  		io := commands.NewTestIO()
   306  		io.SetIn(strings.NewReader(dummyPass + "\n" + dummyPass + "\n" + mnemonic + "\n"))
   307  		io.SetOut(commands.WriteNopCloser(mockOut))
   308  
   309  		// Create the command
   310  		cmd := NewRootCmdWithBaseConfig(io, baseOptions)
   311  
   312  		args := []string{
   313  			"add",
   314  			"--insecure-password-stdin",
   315  			"--home",
   316  			kbHome,
   317  			"--recover",
   318  			"example-key",
   319  			"--derivation-path",
   320  			"malformed path",
   321  		}
   322  
   323  		require.ErrorIs(t, cmd.ParseAndRun(ctx, args), errInvalidDerivationPath)
   324  	})
   325  
   326  	t.Run("invalid derivation path", func(t *testing.T) {
   327  		t.Parallel()
   328  
   329  		var (
   330  			kbHome      = t.TempDir()
   331  			mnemonic    = generateTestMnemonic(t)
   332  			dummyPass   = "dummy-pass"
   333  			baseOptions = BaseOptions{
   334  				InsecurePasswordStdin: true,
   335  				Home:                  kbHome,
   336  			}
   337  		)
   338  
   339  		ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
   340  		defer cancelFn()
   341  
   342  		mockOut := bytes.NewBufferString("")
   343  
   344  		io := commands.NewTestIO()
   345  		io.SetIn(strings.NewReader(dummyPass + "\n" + dummyPass + "\n" + mnemonic + "\n"))
   346  		io.SetOut(commands.WriteNopCloser(mockOut))
   347  
   348  		// Create the command
   349  		cmd := NewRootCmdWithBaseConfig(io, baseOptions)
   350  
   351  		args := []string{
   352  			"add",
   353  			"--insecure-password-stdin",
   354  			"--home",
   355  			kbHome,
   356  			"--recover",
   357  			"example-key",
   358  			"--derivation-path",
   359  			"44'/500'/0'/0/0", // invalid coin type
   360  		}
   361  
   362  		require.ErrorIs(t, cmd.ParseAndRun(ctx, args), errInvalidDerivationPath)
   363  	})
   364  }