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

     1  package wallet
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/nspcc-dev/neo-go/pkg/config/netmode"
     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/io"
    13  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    14  	"github.com/nspcc-dev/neo-go/pkg/util"
    15  	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
    16  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    17  )
    18  
    19  // Account represents a NEO account. It holds the private and the public key
    20  // along with some metadata.
    21  type Account struct {
    22  	// NEO private key.
    23  	privateKey *keys.PrivateKey
    24  
    25  	// Script hash corresponding to the Address.
    26  	scriptHash util.Uint160
    27  
    28  	// NEO public address.
    29  	Address string `json:"address"`
    30  
    31  	// Encrypted WIF of the account also known as the key.
    32  	EncryptedWIF string `json:"key"`
    33  
    34  	// Label is a label the user had made for this account.
    35  	Label string `json:"label"`
    36  
    37  	// Contract is a Contract object which describes the details of the contract.
    38  	// This field can be null (for watch-only address).
    39  	Contract *Contract `json:"contract"`
    40  
    41  	// Indicates whether the account is locked by the user.
    42  	// the client shouldn't spend the funds in a locked account.
    43  	Locked bool `json:"lock"`
    44  
    45  	// Indicates whether the account is the default change account.
    46  	Default bool `json:"isDefault"`
    47  }
    48  
    49  // Contract represents a subset of the smartcontract to embed in the
    50  // Account so it's NEP-6 compliant.
    51  type Contract struct {
    52  	// Script of the contract deployed on the blockchain.
    53  	Script []byte `json:"script"`
    54  
    55  	// A list of parameters used deploying this contract.
    56  	Parameters []ContractParam `json:"parameters"`
    57  
    58  	// Indicates whether the contract has been deployed to the blockchain.
    59  	Deployed bool `json:"deployed"`
    60  
    61  	// InvocationBuilder returns invocation script for deployed contracts.
    62  	// In case contract is not deployed or has 0 arguments, this field is ignored.
    63  	// It might be executed on a partially formed tx, and is primarily needed to properly
    64  	// calculate network fee for complex contract signers.
    65  	InvocationBuilder func(tx *transaction.Transaction) ([]byte, error) `json:"-"`
    66  }
    67  
    68  // ContractParam is a descriptor of a contract parameter
    69  // containing type and optional name.
    70  type ContractParam struct {
    71  	Name string                  `json:"name"`
    72  	Type smartcontract.ParamType `json:"type"`
    73  }
    74  
    75  // ScriptHash returns the hash of contract's script.
    76  func (c Contract) ScriptHash() util.Uint160 {
    77  	return hash.Hash160(c.Script)
    78  }
    79  
    80  // NewAccount creates a new Account with a random generated PrivateKey.
    81  func NewAccount() (*Account, error) {
    82  	priv, err := keys.NewPrivateKey()
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	return NewAccountFromPrivateKey(priv), nil
    87  }
    88  
    89  // NewContractAccount creates a contract account belonging to some deployed contract.
    90  // SignTx can be called on this account with no error and will create invocation script,
    91  // which puts provided arguments on stack for use in `verify`.
    92  func NewContractAccount(hash util.Uint160, args ...any) *Account {
    93  	return &Account{
    94  		Address: address.Uint160ToString(hash),
    95  		Contract: &Contract{
    96  			Parameters: make([]ContractParam, len(args)),
    97  			Deployed:   true,
    98  			InvocationBuilder: func(tx *transaction.Transaction) ([]byte, error) {
    99  				w := io.NewBufBinWriter()
   100  				for i := range args {
   101  					emit.Any(w.BinWriter, args[i])
   102  				}
   103  				if w.Err != nil {
   104  					return nil, w.Err
   105  				}
   106  				return w.Bytes(), nil
   107  			},
   108  		},
   109  	}
   110  }
   111  
   112  // SignTx signs transaction t and updates it's Witnesses.
   113  func (a *Account) SignTx(net netmode.Magic, t *transaction.Transaction) error {
   114  	var (
   115  		haveAcc bool
   116  		pos     int
   117  	)
   118  	if a.Locked {
   119  		return errors.New("account is locked")
   120  	}
   121  	if a.Contract == nil {
   122  		return errors.New("account has no contract")
   123  	}
   124  	for i := range t.Signers {
   125  		if t.Signers[i].Account.Equals(a.ScriptHash()) {
   126  			haveAcc = true
   127  			pos = i
   128  			break
   129  		}
   130  	}
   131  	if !haveAcc {
   132  		return errors.New("transaction is not signed by this account")
   133  	}
   134  	if len(t.Scripts) < pos {
   135  		return errors.New("transaction is not yet signed by the previous signer")
   136  	}
   137  	if len(t.Scripts) == pos {
   138  		t.Scripts = append(t.Scripts, transaction.Witness{
   139  			VerificationScript: a.Contract.Script, // Can be nil for deployed contract.
   140  		})
   141  	}
   142  	if a.Contract.Deployed && a.Contract.InvocationBuilder != nil {
   143  		invoc, err := a.Contract.InvocationBuilder(t)
   144  		t.Scripts[pos].InvocationScript = invoc
   145  		return err
   146  	}
   147  	if len(a.Contract.Parameters) == 0 {
   148  		return nil
   149  	}
   150  	if a.privateKey == nil {
   151  		return errors.New("account key is not available (need to decrypt?)")
   152  	}
   153  	sign := a.privateKey.SignHashable(uint32(net), t)
   154  
   155  	invoc := append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, sign...)
   156  	if len(a.Contract.Parameters) == 1 {
   157  		t.Scripts[pos].InvocationScript = invoc
   158  	} else {
   159  		t.Scripts[pos].InvocationScript = append(t.Scripts[pos].InvocationScript, invoc...)
   160  	}
   161  
   162  	return nil
   163  }
   164  
   165  // SignHashable signs the given Hashable item and returns the signature. If this
   166  // account can't sign (CanSign() returns false) nil is returned.
   167  func (a *Account) SignHashable(net netmode.Magic, item hash.Hashable) []byte {
   168  	if !a.CanSign() {
   169  		return nil
   170  	}
   171  	return a.privateKey.SignHashable(uint32(net), item)
   172  }
   173  
   174  // CanSign returns true when account is not locked and has a decrypted private
   175  // key inside, so it's ready to create real signatures.
   176  func (a *Account) CanSign() bool {
   177  	return !a.Locked && a.privateKey != nil
   178  }
   179  
   180  // GetVerificationScript returns account's verification script.
   181  func (a *Account) GetVerificationScript() []byte {
   182  	if a.Contract != nil {
   183  		return a.Contract.Script
   184  	}
   185  	return a.privateKey.PublicKey().GetVerificationScript()
   186  }
   187  
   188  // Decrypt decrypts the EncryptedWIF with the given passphrase returning error
   189  // if anything goes wrong. After the decryption Account can be used to sign
   190  // things unless it's locked. Don't decrypt the key unless you want to sign
   191  // something and don't forget to call Close after use for maximum safety.
   192  func (a *Account) Decrypt(passphrase string, scrypt keys.ScryptParams) error {
   193  	var err error
   194  
   195  	if a.EncryptedWIF == "" {
   196  		return errors.New("no encrypted wif in the account")
   197  	}
   198  	a.privateKey, err = keys.NEP2Decrypt(a.EncryptedWIF, passphrase, scrypt)
   199  	if err != nil {
   200  		return err
   201  	}
   202  
   203  	return nil
   204  }
   205  
   206  // Encrypt encrypts the wallet's PrivateKey with the given passphrase
   207  // under the NEP-2 standard.
   208  func (a *Account) Encrypt(passphrase string, scrypt keys.ScryptParams) error {
   209  	wif, err := keys.NEP2Encrypt(a.privateKey, passphrase, scrypt)
   210  	if err != nil {
   211  		return err
   212  	}
   213  	a.EncryptedWIF = wif
   214  	return nil
   215  }
   216  
   217  // PrivateKey returns private key corresponding to the account if it's unlocked.
   218  // Please be very careful when using it, do not copy its contents and do not
   219  // keep a pointer to it unless you absolutely need to. Most of the time you can
   220  // use other methods (PublicKey, ScriptHash, SignHashable) depending on your
   221  // needs and it'll be safer this way.
   222  func (a *Account) PrivateKey() *keys.PrivateKey {
   223  	return a.privateKey
   224  }
   225  
   226  // PublicKey returns the public key associated with the private key corresponding to
   227  // the account. It can return nil if account is locked (use CanSign to check).
   228  func (a *Account) PublicKey() *keys.PublicKey {
   229  	if !a.CanSign() {
   230  		return nil
   231  	}
   232  	return a.privateKey.PublicKey()
   233  }
   234  
   235  // ScriptHash returns the script hash (account) that the Account.Address is
   236  // derived from. It never returns an error, so if this Account has an invalid
   237  // Address you'll just get a zero script hash.
   238  func (a *Account) ScriptHash() util.Uint160 {
   239  	if a.scriptHash.Equals(util.Uint160{}) {
   240  		a.scriptHash, _ = address.StringToUint160(a.Address)
   241  	}
   242  	return a.scriptHash
   243  }
   244  
   245  // Close cleans up the private key used by Account and disassociates it from
   246  // Account. The Account can no longer sign anything after this call, but Decrypt
   247  // can make it usable again.
   248  func (a *Account) Close() {
   249  	if a.privateKey == nil {
   250  		return
   251  	}
   252  	a.privateKey.Destroy()
   253  	a.privateKey = nil
   254  }
   255  
   256  // NewAccountFromWIF creates a new Account from the given WIF.
   257  func NewAccountFromWIF(wif string) (*Account, error) {
   258  	privKey, err := keys.NewPrivateKeyFromWIF(wif)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  	return NewAccountFromPrivateKey(privKey), nil
   263  }
   264  
   265  // NewAccountFromEncryptedWIF creates a new Account from the given encrypted WIF.
   266  func NewAccountFromEncryptedWIF(wif string, pass string, scrypt keys.ScryptParams) (*Account, error) {
   267  	priv, err := keys.NEP2Decrypt(wif, pass, scrypt)
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  
   272  	a := NewAccountFromPrivateKey(priv)
   273  	a.EncryptedWIF = wif
   274  
   275  	return a, nil
   276  }
   277  
   278  // ConvertMultisig sets a's contract to multisig contract with m sufficient signatures.
   279  func (a *Account) ConvertMultisig(m int, pubs []*keys.PublicKey) error {
   280  	if a.Locked {
   281  		return errors.New("account is locked")
   282  	}
   283  	if a.privateKey == nil {
   284  		return errors.New("account key is not available (need to decrypt?)")
   285  	}
   286  	accKey := a.privateKey.PublicKey()
   287  	return a.ConvertMultisigEncrypted(accKey, m, pubs)
   288  }
   289  
   290  // ConvertMultisigEncrypted sets a's contract to an encrypted multisig contract
   291  // with m sufficient signatures. The encrypted private key is not modified and
   292  // remains the same.
   293  func (a *Account) ConvertMultisigEncrypted(accKey *keys.PublicKey, m int, pubs []*keys.PublicKey) error {
   294  	var found bool
   295  	for i := range pubs {
   296  		if accKey.Equal(pubs[i]) {
   297  			found = true
   298  			break
   299  		}
   300  	}
   301  
   302  	if !found {
   303  		return errors.New("own public key was not found among multisig keys")
   304  	}
   305  
   306  	script, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
   307  	if err != nil {
   308  		return err
   309  	}
   310  
   311  	a.scriptHash = hash.Hash160(script)
   312  	a.Address = address.Uint160ToString(a.scriptHash)
   313  	a.Contract = &Contract{
   314  		Script:     script,
   315  		Parameters: getContractParams(m),
   316  	}
   317  
   318  	return nil
   319  }
   320  
   321  // NewAccountFromPrivateKey creates a wallet from the given PrivateKey.
   322  func NewAccountFromPrivateKey(p *keys.PrivateKey) *Account {
   323  	pubKey := p.PublicKey()
   324  
   325  	a := &Account{
   326  		privateKey: p,
   327  		scriptHash: p.GetScriptHash(),
   328  		Address:    p.Address(),
   329  		Contract: &Contract{
   330  			Script:     pubKey.GetVerificationScript(),
   331  			Parameters: getContractParams(1),
   332  		},
   333  	}
   334  
   335  	return a
   336  }
   337  
   338  func getContractParams(n int) []ContractParam {
   339  	params := make([]ContractParam, n)
   340  	for i := range params {
   341  		params[i].Name = fmt.Sprintf("parameter%d", i)
   342  		params[i].Type = smartcontract.SignatureType
   343  	}
   344  
   345  	return params
   346  }