neo-go/cli/wallet/legacy.go

176 lines
4.7 KiB
Go

package wallet
import (
"crypto/elliptic"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"os"
"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/wallet"
)
type (
walletV2 struct {
Version string `json:"version"`
Accounts []accountV2 `json:"accounts"`
Scrypt keys.ScryptParams `json:"scrypt"`
Extra wallet.Extra `json:"extra"`
}
accountV2 struct {
Address string `json:"address"`
EncryptedWIF string `json:"key"`
Label string `json:"label"`
Contract *struct {
Script string `json:"script"`
Parameters []wallet.ContractParam `json:"parameters"`
Deployed bool `json:"deployed"`
} `json:"contract"`
Locked bool `json:"lock"`
Default bool `json:"isdefault"`
}
)
// newWalletV2FromFile reads a NEO2 wallet from the file.
// This should be used read-only, no operations are supported on the returned wallet.
func newWalletV2FromFile(path string, configPath string) (*walletV2, *string, error) {
if len(path) != 0 && len(configPath) != 0 {
return nil, nil, errConflictingWalletFlags
}
if len(path) == 0 && len(configPath) == 0 {
return nil, nil, errNoPath
}
var pass *string
if len(configPath) != 0 {
cfg, err := ReadWalletConfig(configPath)
if err != nil {
return nil, nil, err
}
path = cfg.Path
pass = &cfg.Password
}
file, err := os.OpenFile(path, os.O_RDWR, os.ModeAppend)
if err != nil {
return nil, nil, err
}
defer file.Close()
wall := new(walletV2)
return wall, pass, json.NewDecoder(file).Decode(wall)
}
const simpleSigLen = 35
func (a *accountV2) convert(pass string, scrypt keys.ScryptParams) (*wallet.Account, error) {
address.Prefix = address.NEO2Prefix
priv, err := keys.NEP2Decrypt(a.EncryptedWIF, pass, scrypt)
if err != nil {
return nil, err
}
address.Prefix = address.NEO3Prefix
newAcc := wallet.NewAccountFromPrivateKey(priv)
if a.Contract != nil {
script, err := hex.DecodeString(a.Contract.Script)
if err != nil {
return nil, err
}
// If it is a simple signature script, a newAcc does already have it.
if len(script) != simpleSigLen {
nsigs, pubs, ok := parseMultisigContract(script)
if !ok {
return nil, errors.New("invalid multisig contract")
}
script, err := smartcontract.CreateMultiSigRedeemScript(nsigs, pubs)
if err != nil {
return nil, errors.New("can't create new multisig contract")
}
newAcc.Contract.Script = script
newAcc.Contract.Parameters = a.Contract.Parameters
newAcc.Contract.Deployed = a.Contract.Deployed
}
}
newAcc.Address = address.Uint160ToString(newAcc.Contract.ScriptHash())
newAcc.Default = a.Default
newAcc.Label = a.Label
newAcc.Locked = a.Locked
return newAcc, newAcc.Encrypt(pass, scrypt)
}
const (
opPush1 = 0x51
opPush16 = 0x60
opPushBytes1 = 0x01
opPushBytes2 = 0x02
opPushBytes33 = 0x21
opCheckMultisig = 0xAE
opRet = 0x66
)
func getNumOfThingsFromInstr(script []byte) (int, int, bool) {
var op = script[0]
switch {
case opPush1 <= op && op <= opPush16:
return int(op-opPush1) + 1, 1, true
case op == opPushBytes1 && len(script) >= 2:
return int(script[1]), 2, true
case op == opPushBytes2 && len(script) >= 3:
return int(binary.LittleEndian.Uint16(script[1:])), 3, true
default:
return 0, 0, false
}
}
const minMultisigLen = 37
// parseMultisigContract accepts a multisig verification script from NEO2
// and returns a list of public keys in the same order as in the script.
func parseMultisigContract(script []byte) (int, keys.PublicKeys, bool) {
// It should contain at least 1 public key.
if len(script) < minMultisigLen {
return 0, nil, false
}
nsigs, offset, ok := getNumOfThingsFromInstr(script)
if !ok {
return 0, nil, false
}
var pubs [][]byte
var nkeys int
for offset < len(script) && script[offset] == opPushBytes33 {
if len(script[offset:]) < 34 {
return 0, nil, false
}
pubs = append(pubs, script[offset+1:offset+34])
nkeys++
offset += 34
}
if nkeys < nsigs || offset >= len(script) {
return 0, nil, false
}
nkeys2, off, ok := getNumOfThingsFromInstr(script[offset:])
if !ok || nkeys2 != nkeys {
return 0, nil, false
}
end := script[offset+off:]
switch {
case len(end) == 1 && end[0] == opCheckMultisig:
case len(end) == 2 && end[0] == opCheckMultisig && end[1] == opRet:
default:
return 0, nil, false
}
ret := make(keys.PublicKeys, len(pubs))
for i := range pubs {
pub, err := keys.NewPublicKeyFromBytes(pubs[i], elliptic.P256())
if err != nil {
return 0, nil, false
}
ret[i] = pub
}
return nsigs, ret, true
}