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) (*walletV2, error) { file, err := os.OpenFile(path, os.O_RDWR, os.ModeAppend) if err != nil { return nil, err } defer file.Close() wall := new(walletV2) return wall, 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 }