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 }