2016-06-15 19:50:35 +00:00
|
|
|
package main
|
|
|
|
|
2016-07-27 11:47:41 +00:00
|
|
|
/*
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
extern bool GethServiceSignalEvent( const char *jsonEvent );
|
|
|
|
*/
|
|
|
|
import "C"
|
|
|
|
|
2016-06-15 19:50:35 +00:00
|
|
|
import (
|
2016-08-21 06:45:59 +00:00
|
|
|
"encoding/json"
|
2016-06-20 15:21:45 +00:00
|
|
|
"errors"
|
2016-06-15 19:50:35 +00:00
|
|
|
"fmt"
|
|
|
|
|
2016-06-21 18:29:38 +00:00
|
|
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
2016-06-22 18:56:27 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2016-06-21 14:34:38 +00:00
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
2016-08-31 18:02:06 +00:00
|
|
|
"github.com/ethereum/go-ethereum/les/status"
|
2016-07-04 16:00:29 +00:00
|
|
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
2016-06-21 14:34:38 +00:00
|
|
|
errextra "github.com/pkg/errors"
|
2016-08-18 00:15:58 +00:00
|
|
|
"github.com/status-im/status-go/src/extkeys"
|
2016-06-15 19:50:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2016-08-21 06:45:59 +00:00
|
|
|
ErrInvalidGethNode = errors.New("no running node detected for account unlock")
|
|
|
|
ErrInvalidWhisperService = errors.New("whisper service is unavailable")
|
|
|
|
ErrInvalidAccountManager = errors.New("could not retrieve account manager")
|
|
|
|
ErrAddressToAccountMappingFailure = errors.New("cannot retreive a valid account for a given address")
|
|
|
|
ErrAccountToKeyMappingFailure = errors.New("cannot retreive a valid key for a given account")
|
2016-08-23 21:32:04 +00:00
|
|
|
ErrUnlockCalled = errors.New("no need to unlock accounts, login instead")
|
2016-08-21 06:45:59 +00:00
|
|
|
ErrWhisperIdentityInjectionFailure = errors.New("failed to inject identity into Whisper")
|
2016-08-29 00:31:16 +00:00
|
|
|
ErrWhisperClearIdentitiesFailure = errors.New("failed to clear whisper identities")
|
2016-08-23 21:32:04 +00:00
|
|
|
ErrWhisperNoIdentityFound = errors.New("failed to locate identity previously injected into Whisper")
|
|
|
|
ErrNoAccountSelected = errors.New("no account has been selected, please login")
|
2016-06-15 19:50:35 +00:00
|
|
|
)
|
|
|
|
|
2016-06-21 14:07:24 +00:00
|
|
|
// createAccount creates an internal geth account
|
2016-08-23 21:32:04 +00:00
|
|
|
// BIP44-compatible keys are generated: CKD#1 is stored as account key, CKD#2 stored as sub-account root
|
|
|
|
// Public key of CKD#1 is returned, with CKD#2 securely encoded into account key file (to be used for
|
|
|
|
// sub-account derivations)
|
|
|
|
func createAccount(password string) (address, pubKey, mnemonic string, err error) {
|
|
|
|
if currentNode == nil {
|
|
|
|
return "", "", "", ErrInvalidGethNode
|
|
|
|
}
|
2016-06-15 19:50:35 +00:00
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
if accountManager == nil {
|
|
|
|
return "", "", "", ErrInvalidAccountManager
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate mnemonic phrase
|
|
|
|
m := extkeys.NewMnemonic()
|
|
|
|
mnemonic, err = m.MnemonicPhrase(128, extkeys.EnglishLanguage)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", "", errextra.Wrap(err, "Can not create mnemonic seed")
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate extended master key (see BIP32)
|
|
|
|
extKey, err := extkeys.NewMaster(m.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
|
|
|
if err != nil {
|
|
|
|
return "", "", "", errextra.Wrap(err, "Can not create master extended key")
|
|
|
|
}
|
|
|
|
|
|
|
|
// import created key into account keystore
|
|
|
|
address, pubKey, err = importExtendedKey(extKey, password)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", "", err
|
|
|
|
}
|
2016-06-29 11:32:04 +00:00
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
return address, pubKey, mnemonic, nil
|
2016-08-18 00:15:58 +00:00
|
|
|
}
|
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
// createChildAccount creates sub-account for an account identified by parent address.
|
|
|
|
// CKD#2 is used as root for master accounts (when parentAddress is "").
|
|
|
|
// Otherwise (when parentAddress != ""), child is derived directly from parent.
|
|
|
|
func createChildAccount(parentAddress, password string) (address, pubKey string, err error) {
|
|
|
|
if currentNode == nil {
|
|
|
|
return "", "", ErrInvalidGethNode
|
|
|
|
}
|
2016-08-18 00:15:58 +00:00
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
if accountManager == nil {
|
|
|
|
return "", "", ErrInvalidAccountManager
|
|
|
|
}
|
|
|
|
|
|
|
|
if parentAddress == "" { // by default derive from currently selected account
|
|
|
|
parentAddress = selectedAddress
|
|
|
|
}
|
|
|
|
|
|
|
|
if parentAddress == "" {
|
|
|
|
return "", "", ErrNoAccountSelected
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure that given password can decrypt key associated with a given parent address
|
|
|
|
account, err := utils.MakeAddress(accountManager, parentAddress)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", ErrAddressToAccountMappingFailure
|
|
|
|
}
|
|
|
|
|
|
|
|
account, accountKey, err := accountManager.AccountDecryptedKey(account, password)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", fmt.Errorf("%s: %v", ErrAccountToKeyMappingFailure.Error(), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
parentKey, err := extkeys.NewKeyFromString(accountKey.ExtendedKey.String())
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// derive child key
|
|
|
|
childKey, err := parentKey.Child(accountKey.SubAccountIndex)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
accountManager.IncSubAccountIndex(account, password)
|
2016-08-18 00:15:58 +00:00
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
// import derived key into account keystore
|
|
|
|
address, pubKey, err = importExtendedKey(childKey, password)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2016-08-18 00:15:58 +00:00
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
return address, pubKey, nil
|
|
|
|
}
|
2016-08-21 06:45:59 +00:00
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
// recoverAccount re-creates master key using given details.
|
|
|
|
// Once master key is re-generated, it is inserted into keystore (if not already there).
|
|
|
|
func recoverAccount(password, mnemonic string) (address, pubKey string, err error) {
|
|
|
|
if currentNode == nil {
|
|
|
|
return "", "", ErrInvalidGethNode
|
|
|
|
}
|
2016-08-18 00:15:58 +00:00
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
if accountManager == nil {
|
|
|
|
return "", "", ErrInvalidAccountManager
|
|
|
|
}
|
2016-08-18 00:15:58 +00:00
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
// re-create extended key (see BIP32)
|
|
|
|
m := extkeys.NewMnemonic()
|
|
|
|
extKey, err := extkeys.NewMaster(m.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
|
|
|
if err != nil {
|
|
|
|
return "", "", errextra.Wrap(err, "Can not create master extended key")
|
|
|
|
}
|
2016-08-18 00:15:58 +00:00
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
// import re-created key into account keystore
|
|
|
|
address, pubKey, err = importExtendedKey(extKey, password)
|
|
|
|
if err != nil {
|
|
|
|
return
|
2016-08-18 00:15:58 +00:00
|
|
|
}
|
2016-06-15 19:50:35 +00:00
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
return address, pubKey, nil
|
2016-06-15 19:50:35 +00:00
|
|
|
}
|
2016-06-20 15:21:45 +00:00
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
// selectAccount selects current account, by verifying that address has corresponding account which can be decrypted
|
|
|
|
// using provided password. Once verification is done, decrypted key is injected into Whisper (as a single identity,
|
|
|
|
// all previous identities are removed).
|
2016-08-21 06:45:59 +00:00
|
|
|
func selectAccount(address, password string) error {
|
|
|
|
if currentNode == nil {
|
|
|
|
return ErrInvalidGethNode
|
|
|
|
}
|
2016-06-21 18:29:38 +00:00
|
|
|
|
2016-08-21 06:45:59 +00:00
|
|
|
if accountManager == nil {
|
|
|
|
return ErrInvalidAccountManager
|
|
|
|
}
|
|
|
|
account, err := utils.MakeAddress(accountManager, address)
|
|
|
|
if err != nil {
|
|
|
|
return ErrAddressToAccountMappingFailure
|
|
|
|
}
|
2016-06-21 18:29:38 +00:00
|
|
|
|
2016-08-21 06:45:59 +00:00
|
|
|
account, accountKey, err := accountManager.AccountDecryptedKey(account, password)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("%s: %v", ErrAccountToKeyMappingFailure.Error(), err)
|
|
|
|
}
|
2016-07-04 12:28:49 +00:00
|
|
|
|
2016-08-21 06:45:59 +00:00
|
|
|
if whisperService == nil {
|
|
|
|
return ErrInvalidWhisperService
|
2016-06-21 18:29:38 +00:00
|
|
|
}
|
2016-08-21 06:45:59 +00:00
|
|
|
if err := whisperService.InjectIdentity(accountKey.PrivateKey); err != nil {
|
|
|
|
return ErrWhisperIdentityInjectionFailure
|
|
|
|
}
|
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
// persist address for easier recovery of currently selected key (from Whisper)
|
|
|
|
selectedAddress = address
|
|
|
|
|
2016-08-21 06:45:59 +00:00
|
|
|
return nil
|
|
|
|
}
|
2016-06-21 18:29:38 +00:00
|
|
|
|
2016-08-29 00:31:16 +00:00
|
|
|
// logout clears whisper identities
|
|
|
|
func logout() error {
|
|
|
|
if currentNode == nil {
|
|
|
|
return ErrInvalidGethNode
|
|
|
|
}
|
|
|
|
if whisperService == nil {
|
|
|
|
return ErrInvalidWhisperService
|
|
|
|
}
|
|
|
|
|
|
|
|
err := whisperService.ClearIdentities()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("%s: %v", ErrWhisperClearIdentitiesFailure, err)
|
|
|
|
}
|
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
selectedAddress = ""
|
|
|
|
|
2016-08-29 00:31:16 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-21 06:45:59 +00:00
|
|
|
// unlockAccount unlocks an existing account for a certain duration and
|
|
|
|
// inject the account as a whisper identity if the account was created as
|
|
|
|
// a whisper enabled account
|
|
|
|
func unlockAccount(address, password string, seconds int) error {
|
|
|
|
if currentNode == nil {
|
|
|
|
return ErrInvalidGethNode
|
|
|
|
}
|
2016-06-21 18:29:38 +00:00
|
|
|
|
2016-08-21 06:45:59 +00:00
|
|
|
return ErrUnlockCalled
|
2016-06-21 18:29:38 +00:00
|
|
|
}
|
|
|
|
|
2016-08-23 21:32:04 +00:00
|
|
|
// importExtendedKey processes incoming extended key, extracts required info and creates corresponding account key.
|
|
|
|
// Once account key is formed, that key is put (if not already) into keystore i.e. key is *encoded* into key file.
|
|
|
|
func importExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error) {
|
|
|
|
// imports extended key, create key file (if necessary)
|
|
|
|
account, err := accountManager.ImportExtendedKey(extKey, password)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", errextra.Wrap(err, "Account manager could not create the account")
|
|
|
|
}
|
|
|
|
address = fmt.Sprintf("%x", account.Address)
|
|
|
|
|
|
|
|
// obtain public key to return
|
|
|
|
account, key, err := accountManager.AccountDecryptedKey(account, password)
|
|
|
|
if err != nil {
|
|
|
|
return address, "", errextra.Wrap(err, "Could not recover the key")
|
|
|
|
}
|
|
|
|
pubKey = common.ToHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-06-21 14:07:24 +00:00
|
|
|
// createAndStartNode creates a node entity and starts the
|
|
|
|
// node running locally
|
2016-07-01 13:23:39 +00:00
|
|
|
func createAndStartNode(inputDir string) error {
|
2016-06-20 15:21:45 +00:00
|
|
|
|
2016-07-01 13:23:39 +00:00
|
|
|
currentNode = MakeNode(inputDir)
|
2016-06-20 15:21:45 +00:00
|
|
|
if currentNode != nil {
|
2016-06-29 11:32:04 +00:00
|
|
|
RunNode(currentNode)
|
2016-06-20 15:21:45 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.New("Could not create the in-memory node object")
|
|
|
|
|
|
|
|
}
|
2016-07-04 16:00:29 +00:00
|
|
|
|
|
|
|
func doAddPeer(url string) (bool, error) {
|
|
|
|
server := currentNode.Server()
|
|
|
|
if server == nil {
|
|
|
|
return false, errors.New("node not started")
|
|
|
|
}
|
|
|
|
// Try to add the url as a static peer and return
|
|
|
|
node, err := discover.ParseNode(url)
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("invalid enode: %v", err)
|
|
|
|
}
|
|
|
|
server.AddPeer(node)
|
|
|
|
return true, nil
|
2016-07-27 11:47:41 +00:00
|
|
|
}
|
|
|
|
|
2016-08-31 18:02:06 +00:00
|
|
|
func onSendTransactionRequest(queuedTx status.QueuedTx) {
|
2016-07-27 11:47:41 +00:00
|
|
|
event := GethEvent{
|
|
|
|
Type: "sendTransactionQueued",
|
|
|
|
Event: SendTransactionEvent{
|
2016-08-31 18:02:06 +00:00
|
|
|
Id: string(queuedTx.Id),
|
2016-07-27 11:47:41 +00:00
|
|
|
Args: queuedTx.Args,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
body, _ := json.Marshal(&event)
|
|
|
|
C.GethServiceSignalEvent(C.CString(string(body)))
|
|
|
|
}
|
|
|
|
|
2016-08-31 18:02:06 +00:00
|
|
|
func completeTransaction(id, password string) (common.Hash, error) {
|
2016-07-27 11:47:41 +00:00
|
|
|
if currentNode != nil {
|
|
|
|
if lightEthereum != nil {
|
|
|
|
backend := lightEthereum.StatusBackend
|
2016-08-09 16:41:42 +00:00
|
|
|
|
2016-08-31 18:02:06 +00:00
|
|
|
return backend.CompleteQueuedTransaction(status.QueuedTxId(id), password)
|
2016-07-27 11:47:41 +00:00
|
|
|
}
|
2016-08-09 16:41:42 +00:00
|
|
|
|
|
|
|
return common.Hash{}, errors.New("can not retrieve LES service")
|
2016-07-27 11:47:41 +00:00
|
|
|
}
|
|
|
|
|
2016-08-09 16:41:42 +00:00
|
|
|
return common.Hash{}, errors.New("can not complete transaction: no running node detected")
|
2016-07-27 11:47:41 +00:00
|
|
|
}
|