neoneo-go/pkg/wallet/account.go

261 lines
6.7 KiB
Go
Raw Normal View History

package wallet
import (
"bytes"
"errors"
"fmt"
"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/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/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
)
// Account represents a NEO account. It holds the private and the public key
// along with some metadata.
type Account struct {
// NEO private key.
privateKey *keys.PrivateKey
// NEO public key.
publicKey []byte
// NEO public address.
Address string `json:"address"`
// Encrypted WIF of the account also known as the key.
EncryptedWIF string `json:"key"`
// Label is a label the user had made for this account.
Label string `json:"label"`
2019-10-22 14:56:03 +00:00
// Contract is a Contract object which describes the details of the contract.
// This field can be null (for watch-only address).
Contract *Contract `json:"contract"`
// Indicates whether the account is locked by the user.
// the client shouldn't spend the funds in a locked account.
Locked bool `json:"lock"`
// Indicates whether the account is the default change account.
Default bool `json:"isDefault"`
}
// Contract represents a subset of the smartcontract to embed in the
// Account so it's NEP-6 compliant.
type Contract struct {
// Script of the contract deployed on the blockchain.
Script []byte `json:"script"`
// A list of parameters used deploying this contract.
Parameters []ContractParam `json:"parameters"`
// Indicates whether the contract has been deployed to the blockchain.
Deployed bool `json:"deployed"`
}
// ContractParam is a descriptor of a contract parameter
// containing type and optional name.
type ContractParam struct {
Name string `json:"name"`
Type smartcontract.ParamType `json:"type"`
}
// ScriptHash returns the hash of contract's script.
func (c Contract) ScriptHash() util.Uint160 {
return hash.Hash160(c.Script)
}
// NewAccount creates a new Account with a random generated PrivateKey.
func NewAccount() (*Account, error) {
priv, err := keys.NewPrivateKey()
if err != nil {
return nil, err
}
return NewAccountFromPrivateKey(priv), nil
}
// SignTx signs transaction t and updates it's Witnesses.
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")
}
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) {
haveAcc = true
pos = i
break
}
}
if !haveAcc {
return errors.New("transaction is not signed by this account")
}
if len(t.Scripts) < pos {
return errors.New("transaction is not yet signed by the previous signer")
}
if len(t.Scripts) == pos {
t.Scripts = append(t.Scripts, transaction.Witness{
VerificationScript: a.Contract.Script, // Can be nil for deployed contract.
})
}
if len(a.Contract.Parameters) == 0 {
return nil
}
if a.privateKey == nil {
return errors.New("account key is not available (need to decrypt?)")
}
sign := a.privateKey.SignHashable(uint32(net), t)
invoc := append([]byte{byte(opcode.PUSHDATA1), 64}, sign...)
if len(a.Contract.Parameters) == 1 {
t.Scripts[pos].InvocationScript = invoc
} else {
t.Scripts[pos].InvocationScript = append(t.Scripts[pos].InvocationScript, invoc...)
}
return nil
2022-08-24 15:50:31 +00:00
}
// CanSign returns true when account is not locked and has a decrypted private
// key inside, so it's ready to create real signatures.
func (a *Account) CanSign() bool {
return !a.Locked && a.privateKey != nil
}
// GetVerificationScript returns account's verification script.
func (a *Account) GetVerificationScript() []byte {
if a.Contract != nil {
return a.Contract.Script
}
return a.PrivateKey().PublicKey().GetVerificationScript()
}
// Decrypt decrypts the EncryptedWIF with the given passphrase returning error
// if anything goes wrong.
func (a *Account) Decrypt(passphrase string, scrypt keys.ScryptParams) error {
var err error
if a.EncryptedWIF == "" {
return errors.New("no encrypted wif in the account")
}
a.privateKey, err = keys.NEP2Decrypt(a.EncryptedWIF, passphrase, scrypt)
if err != nil {
return err
}
a.publicKey = a.privateKey.PublicKey().Bytes()
return nil
}
// Encrypt encrypts the wallet's PrivateKey with the given passphrase
// under the NEP-2 standard.
func (a *Account) Encrypt(passphrase string, scrypt keys.ScryptParams) error {
wif, err := keys.NEP2Encrypt(a.privateKey, passphrase, scrypt)
if err != nil {
return err
}
a.EncryptedWIF = wif
return nil
}
2019-11-15 10:32:40 +00:00
// PrivateKey returns private key corresponding to the account.
func (a *Account) PrivateKey() *keys.PrivateKey {
return a.privateKey
}
// NewAccountFromWIF creates a new Account from the given WIF.
func NewAccountFromWIF(wif string) (*Account, error) {
privKey, err := keys.NewPrivateKeyFromWIF(wif)
if err != nil {
return nil, err
}
return NewAccountFromPrivateKey(privKey), nil
}
// NewAccountFromEncryptedWIF creates a new Account from the given encrypted WIF.
func NewAccountFromEncryptedWIF(wif string, pass string, scrypt keys.ScryptParams) (*Account, error) {
priv, err := keys.NEP2Decrypt(wif, pass, scrypt)
if err != nil {
return nil, err
}
a := NewAccountFromPrivateKey(priv)
a.EncryptedWIF = wif
return a, nil
}
// ConvertMultisig sets a's contract to multisig contract with m sufficient signatures.
func (a *Account) ConvertMultisig(m int, pubs []*keys.PublicKey) error {
var found bool
for i := range pubs {
if bytes.Equal(a.publicKey, pubs[i].Bytes()) {
found = true
break
}
}
if !found {
return errors.New("own public key was not found among multisig keys")
}
script, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
if err != nil {
return err
}
a.Address = address.Uint160ToString(hash.Hash160(script))
a.Contract = &Contract{
Script: script,
Parameters: getContractParams(m),
}
return nil
}
// NewAccountFromPrivateKey creates a wallet from the given PrivateKey.
func NewAccountFromPrivateKey(p *keys.PrivateKey) *Account {
pubKey := p.PublicKey()
pubAddr := p.Address()
a := &Account{
publicKey: pubKey.Bytes(),
privateKey: p,
Address: pubAddr,
Contract: &Contract{
Script: pubKey.GetVerificationScript(),
Parameters: getContractParams(1),
},
}
return a
}
func getContractParams(n int) []ContractParam {
params := make([]ContractParam, n)
for i := range params {
params[i].Name = fmt.Sprintf("parameter%d", i)
params[i].Type = smartcontract.SignatureType
}
return params
}