diff --git a/cli/cmdargs/parser.go b/cli/cmdargs/parser.go index 2661d5831..ab8f8a34b 100644 --- a/cli/cmdargs/parser.go +++ b/cli/cmdargs/parser.go @@ -199,10 +199,7 @@ func ParseParams(args []string, calledFromMain bool) (int, []smartcontract.Param // accounts from the provided wallet. func GetSignersAccounts(senderAcc *wallet.Account, wall *wallet.Wallet, signers []transaction.Signer, accScope transaction.WitnessScope) ([]actor.SignerAccount, error) { signersAccounts := make([]actor.SignerAccount, 0, len(signers)+1) - sender, err := address.StringToUint160(senderAcc.Address) - if err != nil { - return nil, err - } + sender := senderAcc.ScriptHash() signersAccounts = append(signersAccounts, actor.SignerAccount{ Signer: transaction.Signer{ Account: sender, diff --git a/cli/nep17_test.go b/cli/nep17_test.go index b318616d1..6a43f96e6 100644 --- a/cli/nep17_test.go +++ b/cli/nep17_test.go @@ -162,8 +162,7 @@ func TestNEP17Transfer(t *testing.T) { e.checkNextLine(t, `^Total fee:\s*(\d|\.)+`) e.checkTxPersisted(t) - sh, err := address.StringToUint160(w.Accounts[0].Address) - require.NoError(t, err) + sh := w.Accounts[0].ScriptHash() b, _ := e.Chain.GetGoverningTokenBalance(sh) require.Equal(t, big.NewInt(1), b) @@ -172,8 +171,6 @@ func TestNEP17Transfer(t *testing.T) { e.Run(t, append(args, "--force")...) e.checkTxPersisted(t) - sh, err := address.StringToUint160(w.Accounts[0].Address) - require.NoError(t, err) b, _ := e.Chain.GetGoverningTokenBalance(sh) require.Equal(t, big.NewInt(2), b) }) @@ -198,8 +195,6 @@ func TestNEP17Transfer(t *testing.T) { e.Run(t, args...) e.checkTxPersisted(t) - sh, err := address.StringToUint160(w.Accounts[0].Address) - require.NoError(t, err) b, _ := e.Chain.GetGoverningTokenBalance(sh) require.Equal(t, big.NewInt(3), b) diff --git a/cli/paramcontext/context.go b/cli/paramcontext/context.go index 85175d4d2..bd1b8d5d9 100644 --- a/cli/paramcontext/context.go +++ b/cli/paramcontext/context.go @@ -7,7 +7,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/smartcontract/context" "github.com/nspcc-dev/neo-go/pkg/wallet" ) @@ -21,11 +20,7 @@ func InitAndSave(net netmode.Magic, tx *transaction.Transaction, acc *wallet.Acc priv := acc.PrivateKey() pub := priv.PublicKey() sign := priv.SignHashable(uint32(net), tx) - h, err := address.StringToUint160(acc.Address) - if err != nil { - return fmt.Errorf("invalid address: %s", acc.Address) - } - if err := scCtx.AddSignature(h, acc.Contract, pub, sign); err != nil { + if err := scCtx.AddSignature(acc.ScriptHash(), acc.Contract, pub, sign); err != nil { return fmt.Errorf("can't add signature: %w", err) } } diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index 80623528b..94077b9ab 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -286,17 +286,12 @@ func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Conte } } for k, acc := range accounts { - addrHash, err := address.StringToUint160(acc.Address) - if err != nil { - return cli.NewExitError(fmt.Errorf("invalid account address: %w", err), 1) - } - if k != 0 { fmt.Fprintln(ctx.App.Writer) } fmt.Fprintf(ctx.App.Writer, "Account %s\n", acc.Address) - err = accHandler(ctx, c, addrHash, name, token, tokenID) + err = accHandler(ctx, c, acc.ScriptHash(), name, token, tokenID) if err != nil { return cli.NewExitError(err, 1) } diff --git a/pkg/core/native/native_test/neo_test.go b/pkg/core/native/native_test/neo_test.go index ef9e62433..011636ca9 100644 --- a/pkg/core/native/native_test/neo_test.go +++ b/pkg/core/native/native_test/neo_test.go @@ -166,7 +166,7 @@ func TestNEO_Vote(t *testing.T) { txes = append(txes, voteTx) } } - txes = append(txes, policyInvoker.PrepareInvoke(t, "blockAccount", candidates[len(candidates)-1].(neotest.SingleSigner).Account().PublicKey().GetScriptHash())) + txes = append(txes, policyInvoker.PrepareInvoke(t, "blockAccount", candidates[len(candidates)-1].(neotest.SingleSigner).Account().ScriptHash())) neoValidatorsInvoker.AddNewBlock(t, txes...) for _, tx := range txes { e.CheckHalt(t, tx.Hash(), stackitem.Make(true)) // luckily, both `transfer`, `registerCandidate` and `vote` return boolean values diff --git a/pkg/rpcclient/actor/actor_test.go b/pkg/rpcclient/actor/actor_test.go index 5ec1b4c4f..d621d2770 100644 --- a/pkg/rpcclient/actor/actor_test.go +++ b/pkg/rpcclient/actor/actor_test.go @@ -7,7 +7,6 @@ import ( "github.com/google/uuid" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" @@ -276,8 +275,5 @@ func TestSender(t *testing.T) { client, acc := testRPCAndAccount(t) a, err := NewSimple(client, acc) require.NoError(t, err) - - addr, err := address.StringToUint160(acc.Address) - require.NoError(t, err) - require.Equal(t, addr, a.Sender()) + require.Equal(t, acc.ScriptHash(), a.Sender()) } diff --git a/pkg/rpcclient/nep11.go b/pkg/rpcclient/nep11.go index 1c16990e9..723922759 100644 --- a/pkg/rpcclient/nep11.go +++ b/pkg/rpcclient/nep11.go @@ -6,7 +6,6 @@ import ( "github.com/google/uuid" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/smartcontract" @@ -88,13 +87,9 @@ func (c *Client) CreateNEP11TransferTx(acc *wallet.Account, tokenHash util.Uint1 if err != nil { return nil, fmt.Errorf("failed to create NEP-11 transfer script: %w", err) } - from, err := address.StringToUint160(acc.Address) - if err != nil { - return nil, fmt.Errorf("bad account address: %w", err) - } return c.CreateTxFromScript(script, acc, -1, gas, append([]SignerAccount{{ Signer: transaction.Signer{ - Account: from, + Account: acc.ScriptHash(), Scopes: transaction.CalledByEntry, }, Account: acc, @@ -148,11 +143,7 @@ func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID []byte) (util.Ui // versions. func (c *Client) TransferNEP11D(acc *wallet.Account, to util.Uint160, tokenHash util.Uint160, amount int64, tokenID []byte, data interface{}, gas int64, cosigners []SignerAccount) (util.Uint256, error) { - from, err := address.StringToUint160(acc.Address) - if err != nil { - return util.Uint256{}, fmt.Errorf("bad account address: %w", err) - } - tx, err := c.CreateNEP11TransferTx(acc, tokenHash, gas, cosigners, from, to, amount, tokenID, data) + tx, err := c.CreateNEP11TransferTx(acc, tokenHash, gas, cosigners, acc.ScriptHash(), to, amount, tokenID, data) if err != nil { return util.Uint256{}, err } diff --git a/pkg/rpcclient/nep17.go b/pkg/rpcclient/nep17.go index e1b988976..aa73a8c28 100644 --- a/pkg/rpcclient/nep17.go +++ b/pkg/rpcclient/nep17.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" @@ -96,10 +95,7 @@ func (c *Client) CreateNEP17TransferTx(acc *wallet.Account, to util.Uint160, // be removed in future versions. func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64, recipients []TransferTarget, cosigners []SignerAccount) (*transaction.Transaction, error) { - from, err := address.StringToUint160(acc.Address) - if err != nil { - return nil, fmt.Errorf("bad account address: %w", err) - } + from := acc.ScriptHash() b := smartcontract.NewBuilder() for i := range recipients { b.InvokeWithAssert(recipients[i].Token, "transfer", diff --git a/pkg/rpcclient/rpc.go b/pkg/rpcclient/rpc.go index 4de2a57c0..cacbc4ea9 100644 --- a/pkg/rpcclient/rpc.go +++ b/pkg/rpcclient/rpc.go @@ -827,10 +827,7 @@ func getSigners(sender *wallet.Account, cosigners []SignerAccount) ([]transactio signers []transaction.Signer accounts []*wallet.Account ) - from, err := address.StringToUint160(sender.Address) - if err != nil { - return nil, nil, fmt.Errorf("bad sender account address: %v", err) - } + from := sender.ScriptHash() s := transaction.Signer{ Account: from, Scopes: transaction.None, @@ -875,10 +872,7 @@ func (c *Client) SignAndPushP2PNotaryRequest(mainTx *transaction.Transaction, fa if err != nil { return nil, fmt.Errorf("failed to get native Notary hash: %w", err) } - from, err := address.StringToUint160(acc.Address) - if err != nil { - return nil, fmt.Errorf("bad account address: %v", err) - } + from := acc.ScriptHash() signers := []transaction.Signer{{Account: notaryHash}, {Account: from}} if fallbackSysFee < 0 { result, err := c.InvokeScript(fallbackScript, signers) diff --git a/pkg/services/notary/core_test.go b/pkg/services/notary/core_test.go index 448f06b14..844aeab92 100644 --- a/pkg/services/notary/core_test.go +++ b/pkg/services/notary/core_test.go @@ -175,7 +175,7 @@ func TestNotary(t *testing.T) { Scopes: transaction.None, }, { - Account: requester.PublicKey().GetScriptHash(), + Account: requester.ScriptHash(), Scopes: transaction.None, }, } @@ -721,9 +721,9 @@ func TestNotary(t *testing.T) { requester1, _ := wallet.NewAccount() requester2, _ := wallet.NewAccount() amount := int64(100_0000_0000) - gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), bc.GetNotaryContractScriptHash(), amount, []interface{}{requester1.PublicKey().GetScriptHash(), int64(bc.BlockHeight() + 50)}) + gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), bc.GetNotaryContractScriptHash(), amount, []interface{}{requester1.ScriptHash(), int64(bc.BlockHeight() + 50)}) e.CheckGASBalance(t, notaryHash, big.NewInt(amount)) - gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), bc.GetNotaryContractScriptHash(), amount, []interface{}{requester2.PublicKey().GetScriptHash(), int64(bc.BlockHeight() + 50)}) + gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), bc.GetNotaryContractScriptHash(), amount, []interface{}{requester2.ScriptHash(), int64(bc.BlockHeight() + 50)}) e.CheckGASBalance(t, notaryHash, big.NewInt(2*amount)) // create request for 2 standard signatures => main tx should be completed after the second request is added to the pool diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index dc7fb1119..b07b09455 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -1033,11 +1033,6 @@ func TestSignAndPushP2PNotaryRequest(t *testing.T) { }) require.NoError(t, c.Init()) - t.Run("bad account address", func(t *testing.T) { - _, err := c.SignAndPushP2PNotaryRequest(nil, nil, 0, 0, 0, &wallet.Account{Address: "not-an-addr"}) //nolint:staticcheck // SA1019: c.SignAndPushP2PNotaryRequest is deprecated - require.NotNil(t, err) - }) - t.Run("bad fallback script", func(t *testing.T) { _, err := c.SignAndPushP2PNotaryRequest(nil, []byte{byte(opcode.ASSERT)}, -1, 0, 0, acc) //nolint:staticcheck // SA1019: c.SignAndPushP2PNotaryRequest is deprecated require.NotNil(t, err) diff --git a/pkg/services/stateroot/network.go b/pkg/services/stateroot/network.go index e2eedcd8f..e91fb5100 100644 --- a/pkg/services/stateroot/network.go +++ b/pkg/services/stateroot/network.go @@ -90,7 +90,6 @@ func (s *service) trySendRoot(ir *incompleteRoot, acc *wallet.Account) { } func (s *service) sendValidatedRoot(r *state.MPTRoot, acc *wallet.Account) { - priv := acc.PrivateKey() w := io.NewBufBinWriter() m := NewMessage(RootT, r) m.EncodeBinary(w.BinWriter) @@ -98,13 +97,13 @@ func (s *service) sendValidatedRoot(r *state.MPTRoot, acc *wallet.Account) { Category: Category, ValidBlockStart: r.Index, ValidBlockEnd: r.Index + rootValidEndInc, - Sender: priv.GetScriptHash(), + Sender: acc.ScriptHash(), Data: w.Bytes(), Witness: transaction.Witness{ VerificationScript: acc.GetVerificationScript(), }, } - sig := priv.SignHashable(uint32(s.Network), ep) + sig := acc.PrivateKey().SignHashable(uint32(s.Network), ep) buf := io.NewBufBinWriter() emit.Bytes(buf.BinWriter, sig) ep.Witness.InvocationScript = buf.Bytes() diff --git a/pkg/services/stateroot/validators.go b/pkg/services/stateroot/validators.go index c0742d4f6..9bfe6e741 100644 --- a/pkg/services/stateroot/validators.go +++ b/pkg/services/stateroot/validators.go @@ -110,7 +110,7 @@ func (s *service) signAndSend(r *state.MPTRoot) error { Category: Category, ValidBlockStart: r.Index, ValidBlockEnd: r.Index + voteValidEndInc, - Sender: acc.PrivateKey().GetScriptHash(), + Sender: acc.ScriptHash(), Data: w.Bytes(), Witness: transaction.Witness{ VerificationScript: acc.GetVerificationScript(), diff --git a/pkg/wallet/account.go b/pkg/wallet/account.go index 15df77eac..bd436d6ab 100644 --- a/pkg/wallet/account.go +++ b/pkg/wallet/account.go @@ -20,6 +20,9 @@ type Account struct { // NEO private key. privateKey *keys.PrivateKey + // Script hash corresponding to the Address. + scriptHash util.Uint160 + // NEO public address. Address string `json:"address"` @@ -80,8 +83,6 @@ func (a *Account) SignTx(net netmode.Magic, t *transaction.Transaction) error { var ( haveAcc bool pos int - accHash util.Uint160 - err error ) if a.Locked { return errors.New("account is locked") @@ -89,12 +90,8 @@ func (a *Account) SignTx(net netmode.Magic, t *transaction.Transaction) error { if a.Contract == nil { return errors.New("account has no contract") } - accHash, err = address.StringToUint160(a.Address) - if err != nil { - return err - } for i := range t.Signers { - if t.Signers[i].Account.Equals(accHash) { + if t.Signers[i].Account.Equals(a.ScriptHash()) { haveAcc = true pos = i break @@ -184,6 +181,16 @@ func (a *Account) PublicKey() *keys.PublicKey { return a.privateKey.PublicKey() } +// ScriptHash returns the script hash (account) that the Account.Address is +// derived from. It never returns an error, so if this Account has an invalid +// Address you'll just get a zero script hash. +func (a *Account) ScriptHash() util.Uint160 { + if a.scriptHash.Equals(util.Uint160{}) { + a.scriptHash, _ = address.StringToUint160(a.Address) + } + return a.scriptHash +} + // Close cleans up the private key used by Account and disassociates it from // Account. The Account can no longer sign anything after this call, but Decrypt // can make it usable again. @@ -243,7 +250,8 @@ func (a *Account) ConvertMultisig(m int, pubs []*keys.PublicKey) error { return err } - a.Address = address.Uint160ToString(hash.Hash160(script)) + a.scriptHash = hash.Hash160(script) + a.Address = address.Uint160ToString(a.scriptHash) a.Contract = &Contract{ Script: script, Parameters: getContractParams(m), @@ -255,11 +263,11 @@ func (a *Account) ConvertMultisig(m int, pubs []*keys.PublicKey) error { // NewAccountFromPrivateKey creates a wallet from the given PrivateKey. func NewAccountFromPrivateKey(p *keys.PrivateKey) *Account { pubKey := p.PublicKey() - pubAddr := p.Address() a := &Account{ privateKey: p, - Address: pubAddr, + scriptHash: p.GetScriptHash(), + Address: p.Address(), Contract: &Contract{ Script: pubKey.GetVerificationScript(), Parameters: getContractParams(1), diff --git a/pkg/wallet/account_test.go b/pkg/wallet/account_test.go index 88118176e..714aa227e 100644 --- a/pkg/wallet/account_test.go +++ b/pkg/wallet/account_test.go @@ -9,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,6 +19,7 @@ func TestNewAccount(t *testing.T) { acc, err := NewAccount() require.NoError(t, err) require.NotNil(t, acc) + require.Equal(t, acc.Address, address.Uint160ToString(acc.ScriptHash())) } func TestDecryptAccount(t *testing.T) { diff --git a/pkg/wallet/wallet_test.go b/pkg/wallet/wallet_test.go index 7d155e9c4..9de064319 100644 --- a/pkg/wallet/wallet_test.go +++ b/pkg/wallet/wallet_test.go @@ -102,6 +102,7 @@ func TestSave(t *testing.T) { require.NoError(t, err) require.Equal(t, 2, len(w2.Accounts)) require.NoError(t, w2.Accounts[1].Decrypt("pass", w2.Scrypt)) + _ = w2.Accounts[1].ScriptHash() // openedWallet has it for acc 1. require.Equal(t, openedWallet.Accounts, w2.Accounts) }) }