github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/wallet/account_test.go (about)

     1  package wallet
     2  
     3  import (
     4  	"encoding/json"
     5  	"testing"
     6  
     7  	"github.com/nspcc-dev/neo-go/internal/keytestcases"
     8  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
     9  	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
    10  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    11  	"github.com/nspcc-dev/neo-go/pkg/encoding/address"
    12  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func TestNewAccount(t *testing.T) {
    18  	acc, err := NewAccount()
    19  	require.NoError(t, err)
    20  	require.NotNil(t, acc)
    21  	require.Equal(t, acc.Address, address.Uint160ToString(acc.ScriptHash()))
    22  }
    23  
    24  func TestDecryptAccount(t *testing.T) {
    25  	for _, testCase := range keytestcases.Arr {
    26  		acc := &Account{EncryptedWIF: testCase.EncryptedWif}
    27  		assert.Nil(t, acc.PrivateKey())
    28  		err := acc.Decrypt(testCase.Passphrase, keys.NEP2ScryptParams())
    29  		if testCase.Invalid {
    30  			assert.Error(t, err)
    31  			continue
    32  		}
    33  
    34  		assert.NoError(t, err)
    35  		assert.NotNil(t, acc.PrivateKey())
    36  		assert.Equal(t, testCase.PrivateKey, acc.privateKey.String())
    37  	}
    38  	// No encrypted key.
    39  	acc := &Account{}
    40  	require.Error(t, acc.Decrypt("qwerty", keys.NEP2ScryptParams()))
    41  }
    42  
    43  func TestNewFromWif(t *testing.T) {
    44  	for _, testCase := range keytestcases.Arr {
    45  		acc, err := NewAccountFromWIF(testCase.Wif)
    46  		if testCase.Invalid {
    47  			assert.Error(t, err)
    48  			continue
    49  		}
    50  
    51  		assert.NoError(t, err)
    52  		compareFields(t, testCase, acc)
    53  	}
    54  }
    55  
    56  func TestNewAccountFromEncryptedWIF(t *testing.T) {
    57  	for _, tc := range keytestcases.Arr {
    58  		acc, err := NewAccountFromEncryptedWIF(tc.EncryptedWif, tc.Passphrase, keys.NEP2ScryptParams())
    59  		if tc.Invalid {
    60  			assert.Error(t, err)
    61  			continue
    62  		}
    63  
    64  		assert.NoError(t, err)
    65  		compareFields(t, tc, acc)
    66  	}
    67  }
    68  
    69  func TestContract_MarshalJSON(t *testing.T) {
    70  	var c Contract
    71  
    72  	data := []byte(`{"script":"AQI=","parameters":[{"name":"name0", "type":"Signature"}],"deployed":false}`)
    73  	require.NoError(t, json.Unmarshal(data, &c))
    74  	require.Equal(t, []byte{1, 2}, c.Script)
    75  
    76  	result, err := json.Marshal(c)
    77  	require.NoError(t, err)
    78  	require.JSONEq(t, string(data), string(result))
    79  
    80  	data = []byte(`1`)
    81  	require.Error(t, json.Unmarshal(data, &c))
    82  
    83  	data = []byte(`{"script":"ERROR","parameters":[1],"deployed":false}`)
    84  	require.Error(t, json.Unmarshal(data, &c))
    85  }
    86  
    87  func TestContractSignTx(t *testing.T) {
    88  	acc, err := NewAccount()
    89  	require.NoError(t, err)
    90  	require.True(t, acc.CanSign())
    91  
    92  	accNoContr := *acc
    93  	accNoContr.Contract = nil
    94  	tx := &transaction.Transaction{
    95  		Script: []byte{1, 2, 3},
    96  		Signers: []transaction.Signer{{
    97  			Account: acc.Contract.ScriptHash(),
    98  			Scopes:  transaction.CalledByEntry,
    99  		}},
   100  	}
   101  	require.Error(t, accNoContr.SignTx(0, tx))
   102  
   103  	acc2, err := NewAccount()
   104  	require.NoError(t, err)
   105  	require.True(t, acc2.CanSign())
   106  
   107  	require.Error(t, acc2.SignTx(0, tx))
   108  
   109  	pubs := keys.PublicKeys{acc.PublicKey(), acc2.PublicKey()}
   110  	multiS, err := smartcontract.CreateDefaultMultiSigRedeemScript(pubs)
   111  	require.NoError(t, err)
   112  	multiAcc := NewAccountFromPrivateKey(acc.privateKey)
   113  	require.NoError(t, multiAcc.ConvertMultisig(2, pubs))
   114  	multiAcc2 := NewAccountFromPrivateKey(acc2.privateKey)
   115  	require.NoError(t, multiAcc2.ConvertMultisig(2, pubs))
   116  
   117  	tx = &transaction.Transaction{
   118  		Script: []byte{1, 2, 3},
   119  		Signers: []transaction.Signer{{
   120  			Account: acc2.Contract.ScriptHash(),
   121  			Scopes:  transaction.CalledByEntry,
   122  		}, {
   123  			Account: acc.Contract.ScriptHash(),
   124  			Scopes:  transaction.None,
   125  		}, {
   126  			Account: hash.Hash160(multiS),
   127  			Scopes:  transaction.None,
   128  		}},
   129  	}
   130  	require.Error(t, acc.SignTx(0, tx)) // Can't append, no witness for acc2.
   131  
   132  	require.NoError(t, acc2.SignTx(0, tx)) // Append script for acc2.
   133  	require.Equal(t, 1, len(tx.Scripts))
   134  	require.Equal(t, 66, len(tx.Scripts[0].InvocationScript))
   135  
   136  	require.NoError(t, acc2.SignTx(0, tx)) // Sign again, effectively a no-op.
   137  	require.Equal(t, 1, len(tx.Scripts))
   138  	require.Equal(t, 66, len(tx.Scripts[0].InvocationScript))
   139  
   140  	acc2.Locked = true
   141  	require.False(t, acc2.CanSign())
   142  	require.Error(t, acc2.SignTx(0, tx))     // Locked account.
   143  	require.Nil(t, acc2.PublicKey())         // Locked account.
   144  	require.Nil(t, acc2.SignHashable(0, tx)) // Locked account.
   145  
   146  	acc2.Locked = false
   147  	acc2.Close()
   148  	require.False(t, acc2.CanSign())
   149  	require.Error(t, acc2.SignTx(0, tx)) // No private key.
   150  	acc2.Close()                         // No-op.
   151  	require.False(t, acc2.CanSign())
   152  
   153  	tx.Scripts = append(tx.Scripts, transaction.Witness{
   154  		VerificationScript: acc.Contract.Script,
   155  	})
   156  	require.NoError(t, acc.SignTx(0, tx)) // Add invocation script for existing witness.
   157  	require.Equal(t, 66, len(tx.Scripts[1].InvocationScript))
   158  	require.NotNil(t, acc.SignHashable(0, tx)) // Works via Hashable too.
   159  
   160  	require.NoError(t, multiAcc.SignTx(0, tx))
   161  	require.Equal(t, 3, len(tx.Scripts))
   162  	require.Equal(t, 66, len(tx.Scripts[2].InvocationScript))
   163  
   164  	require.NoError(t, multiAcc2.SignTx(0, tx)) // Append to existing script.
   165  	require.Equal(t, 3, len(tx.Scripts))
   166  	require.Equal(t, 132, len(tx.Scripts[2].InvocationScript))
   167  }
   168  
   169  func TestContract_ScriptHash(t *testing.T) {
   170  	script := []byte{0, 1, 2, 3}
   171  	c := &Contract{Script: script}
   172  
   173  	require.Equal(t, hash.Hash160(script), c.ScriptHash())
   174  }
   175  
   176  func TestAccount_ConvertMultisig(t *testing.T) {
   177  	// test is based on a wallet1_solo.json accounts from neo-local
   178  	a, err := NewAccountFromWIF("KxyjQ8eUa4FHt3Gvioyt1Wz29cTUrE4eTqX3yFSk1YFCsPL8uNsY")
   179  	require.NoError(t, err)
   180  
   181  	hexs := []string{
   182  		"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2", // <- this is our key
   183  		"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e",
   184  		"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62",
   185  		"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699",
   186  	}
   187  
   188  	t.Run("locked", func(t *testing.T) {
   189  		a.Locked = true
   190  		pubs := convertPubs(t, hexs)
   191  		require.Error(t, a.ConvertMultisig(1, pubs))
   192  		a.Locked = false
   193  	})
   194  	t.Run("no private key", func(t *testing.T) {
   195  		pk := a.privateKey
   196  		a.privateKey = nil
   197  		pubs := convertPubs(t, hexs)
   198  		require.Error(t, a.ConvertMultisig(0, pubs))
   199  		a.privateKey = pk
   200  	})
   201  	t.Run("invalid number of signatures", func(t *testing.T) {
   202  		pubs := convertPubs(t, hexs)
   203  		require.Error(t, a.ConvertMultisig(0, pubs))
   204  	})
   205  
   206  	t.Run("account key is missing from multisig", func(t *testing.T) {
   207  		pubs := convertPubs(t, hexs[1:])
   208  		require.Error(t, a.ConvertMultisig(1, pubs))
   209  	})
   210  
   211  	t.Run("1/1 multisig", func(t *testing.T) {
   212  		pubs := convertPubs(t, hexs[:1])
   213  		require.NoError(t, a.ConvertMultisig(1, pubs))
   214  		require.Equal(t, "NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP", a.Address)
   215  	})
   216  
   217  	t.Run("3/4 multisig", func(t *testing.T) {
   218  		pubs := convertPubs(t, hexs)
   219  		require.NoError(t, a.ConvertMultisig(3, pubs))
   220  		require.Equal(t, "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq", a.Address)
   221  	})
   222  }
   223  
   224  func convertPubs(t *testing.T, hexKeys []string) []*keys.PublicKey {
   225  	pubs := make([]*keys.PublicKey, len(hexKeys))
   226  	for i := range pubs {
   227  		var err error
   228  		pubs[i], err = keys.NewPublicKeyFromString(hexKeys[i])
   229  		require.NoError(t, err)
   230  	}
   231  	return pubs
   232  }
   233  
   234  func compareFields(t *testing.T, tk keytestcases.Ktype, acc *Account) {
   235  	want, have := tk.Address, acc.Address
   236  	require.Equalf(t, want, have, "expected address %s got %s", want, have)
   237  	want, have = tk.Wif, acc.privateKey.WIF()
   238  	require.Equalf(t, want, have, "expected wif %s got %s", want, have)
   239  	want, have = tk.PublicKey, acc.PublicKey().StringCompressed()
   240  	require.Equalf(t, want, have, "expected pub key %s got %s", want, have)
   241  	want, have = tk.PrivateKey, acc.privateKey.String()
   242  	require.Equalf(t, want, have, "expected priv key %s got %s", want, have)
   243  }