package main // #include import "C" import ( "encoding/hex" "encoding/json" "errors" "fmt" "os" "unsafe" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/status-im/status-go/api" "github.com/status-im/status-go/exportlogs" "github.com/status-im/status-go/logutils" "github.com/status-im/status-go/multiaccounts" "github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/params" "github.com/status-im/status-go/profiling" "github.com/status-im/status-go/services/personal" "github.com/status-im/status-go/services/typeddata" "github.com/status-im/status-go/signal" "github.com/status-im/status-go/transactions" protocol "github.com/status-im/status-protocol-go" validator "gopkg.in/go-playground/validator.v9" ) // All general log messages in this package should be routed through this logger. var logger = log.New("package", "status-go/lib") // OpenAccounts opens database and returns accounts list. //export OpenAccounts func OpenAccounts(datadir *C.char) *C.char { statusBackend.UpdateRootDataDir(C.GoString(datadir)) err := statusBackend.OpenAccounts() if err != nil { return makeJSONResponse(err) } accs, err := statusBackend.GetAccounts() if err != nil { return makeJSONResponse(err) } data, err := json.Marshal(accs) if err != nil { return makeJSONResponse(err) } return C.CString(string(data)) } // ExtractGroupMembershipSignatures extract public keys from tuples of content/signature //export ExtractGroupMembershipSignatures func ExtractGroupMembershipSignatures(signaturePairsStr *C.char) *C.char { var signaturePairs [][2]string if err := json.Unmarshal([]byte(C.GoString(signaturePairsStr)), &signaturePairs); err != nil { return makeJSONResponse(err) } identities, err := statusBackend.ExtractGroupMembershipSignatures(signaturePairs) if err != nil { return makeJSONResponse(err) } data, err := json.Marshal(struct { Identities []string `json:"identities"` }{Identities: identities}) if err != nil { return makeJSONResponse(err) } return C.CString(string(data)) } // Sign signs a string containing group membership information //export SignGroupMembership func SignGroupMembership(content *C.char) *C.char { signature, err := statusBackend.SignGroupMembership(C.GoString(content)) if err != nil { return makeJSONResponse(err) } data, err := json.Marshal(struct { Signature string `json:"signature"` }{Signature: signature}) if err != nil { return makeJSONResponse(err) } return C.CString(string(data)) } // EnableInstallation enables an installation for multi-device sync. //export EnableInstallation func EnableInstallation(installationID *C.char) *C.char { err := statusBackend.EnableInstallation(C.GoString(installationID)) if err != nil { return makeJSONResponse(err) } data, err := json.Marshal(struct { Response string `json:"response"` }{Response: "ok"}) if err != nil { return makeJSONResponse(err) } return C.CString(string(data)) } // DisableInstallation disables an installation for multi-device sync. //export DisableInstallation func DisableInstallation(installationID *C.char) *C.char { err := statusBackend.DisableInstallation(C.GoString(installationID)) if err != nil { return makeJSONResponse(err) } data, err := json.Marshal(struct { Response string `json:"response"` }{Response: "ok"}) if err != nil { return makeJSONResponse(err) } return C.CString(string(data)) } //ValidateNodeConfig validates config for status node //export ValidateNodeConfig func ValidateNodeConfig(configJSON *C.char) *C.char { var resp APIDetailedResponse _, err := params.NewConfigFromJSON(C.GoString(configJSON)) // Convert errors to APIDetailedResponse switch err := err.(type) { case validator.ValidationErrors: resp = APIDetailedResponse{ Message: "validation: validation failed", FieldErrors: make([]APIFieldError, len(err)), } for i, ve := range err { resp.FieldErrors[i] = APIFieldError{ Parameter: ve.Namespace(), Errors: []APIError{ { Message: fmt.Sprintf("field validation failed on the '%s' tag", ve.Tag()), }, }, } } case error: resp = APIDetailedResponse{ Message: fmt.Sprintf("validation: %s", err.Error()), } case nil: resp = APIDetailedResponse{ Status: true, } } respJSON, err := json.Marshal(resp) if err != nil { return makeJSONResponse(err) } return C.CString(string(respJSON)) } //ResetChainData remove chain data from data directory //export ResetChainData func ResetChainData() *C.char { api.RunAsync(statusBackend.ResetChainData) return makeJSONResponse(nil) } //CallRPC calls public APIs via RPC //export CallRPC func CallRPC(inputJSON *C.char) *C.char { outputJSON, err := statusBackend.CallRPC(C.GoString(inputJSON)) if err != nil { return makeJSONResponse(err) } return C.CString(outputJSON) } //CallPrivateRPC calls both public and private APIs via RPC //export CallPrivateRPC func CallPrivateRPC(inputJSON *C.char) *C.char { outputJSON, err := statusBackend.CallPrivateRPC(C.GoString(inputJSON)) if err != nil { return makeJSONResponse(err) } return C.CString(outputJSON) } //CreateAccount is equivalent to creating an account from the command line, // just modified to handle the function arg passing //export CreateAccount func CreateAccount(password *C.char) *C.char { info, mnemonic, err := statusBackend.AccountManager().CreateAccount(C.GoString(password)) errString := "" if err != nil { fmt.Fprintln(os.Stderr, err) errString = err.Error() } out := AccountInfo{ Address: info.WalletAddress, PubKey: info.WalletPubKey, WalletAddress: info.WalletAddress, WalletPubKey: info.WalletPubKey, ChatAddress: info.ChatAddress, ChatPubKey: info.ChatPubKey, Mnemonic: mnemonic, Error: errString, } outBytes, _ := json.Marshal(out) return C.CString(string(outBytes)) } //RecoverAccount re-creates master key using given details //export RecoverAccount func RecoverAccount(password, mnemonic *C.char) *C.char { info, err := statusBackend.AccountManager().RecoverAccount(C.GoString(password), C.GoString(mnemonic)) errString := "" if err != nil { fmt.Fprintln(os.Stderr, err) errString = err.Error() } out := AccountInfo{ Address: info.WalletAddress, PubKey: info.WalletPubKey, WalletAddress: info.WalletAddress, WalletPubKey: info.WalletPubKey, ChatAddress: info.ChatAddress, ChatPubKey: info.ChatPubKey, Mnemonic: C.GoString(mnemonic), Error: errString, } outBytes, _ := json.Marshal(out) return C.CString(string(outBytes)) } // StartOnboarding initialize the onboarding with n random accounts //export StartOnboarding func StartOnboarding(n, mnemonicPhraseLength C.int) *C.char { out := struct { Accounts []OnboardingAccount `json:"accounts"` Error string `json:"error"` }{ Accounts: make([]OnboardingAccount, 0), } accounts, err := statusBackend.AccountManager().StartOnboarding(int(n), int(mnemonicPhraseLength)) if err != nil { fmt.Fprintln(os.Stderr, err) out.Error = err.Error() } if err == nil { for _, account := range accounts { out.Accounts = append(out.Accounts, OnboardingAccount{ ID: account.ID, Address: account.Info.WalletAddress, PubKey: account.Info.WalletPubKey, WalletAddress: account.Info.WalletAddress, WalletPubKey: account.Info.WalletPubKey, ChatAddress: account.Info.ChatAddress, ChatPubKey: account.Info.ChatPubKey, }) } } outBytes, _ := json.Marshal(out) return C.CString(string(outBytes)) } // ImportOnboardingAccount re-creates and imports an account created during onboarding. //export ImportOnboardingAccount func ImportOnboardingAccount(id, password *C.char) *C.char { info, mnemonic, err := statusBackend.AccountManager().ImportOnboardingAccount(C.GoString(id), C.GoString(password)) errString := "" if err != nil { fmt.Fprintln(os.Stderr, err) errString = err.Error() } out := AccountInfo{ Address: info.WalletAddress, PubKey: info.WalletPubKey, WalletAddress: info.WalletAddress, WalletPubKey: info.WalletPubKey, ChatAddress: info.ChatAddress, ChatPubKey: info.ChatPubKey, Mnemonic: mnemonic, Error: errString, } outBytes, _ := json.Marshal(out) return C.CString(string(outBytes)) } // RemoveOnboarding resets the current onboarding removing from memory all the generated keys. //export RemoveOnboarding func RemoveOnboarding() { statusBackend.AccountManager().RemoveOnboarding() } //VerifyAccountPassword verifies account password //export VerifyAccountPassword func VerifyAccountPassword(keyStoreDir, address, password *C.char) *C.char { _, err := statusBackend.AccountManager().VerifyAccountPassword(C.GoString(keyStoreDir), C.GoString(address), C.GoString(password)) return makeJSONResponse(err) } //StartNode - start Status node //export StartNode func StartNode(configJSON *C.char) *C.char { config, err := params.NewConfigFromJSON(C.GoString(configJSON)) if err != nil { return makeJSONResponse(err) } if err := logutils.OverrideRootLogWithConfig(config, false); err != nil { return makeJSONResponse(err) } api.RunAsync(func() error { return statusBackend.StartNode(config) }) return makeJSONResponse(nil) } //StopNode - stop status node //export StopNode func StopNode() *C.char { api.RunAsync(statusBackend.StopNode) return makeJSONResponse(nil) } //Login loads a key file (for a given address), tries to decrypt it using the password, to verify ownership // if verified, purges all the previous identities from Whisper, and injects verified key as shh identity //export Login func Login(accountData, password *C.char) *C.char { data, pass := C.GoString(accountData), C.GoString(password) var account multiaccounts.Account err := json.Unmarshal([]byte(data), &account) if err != nil { return makeJSONResponse(err) } api.RunAsync(func() error { return statusBackend.StartNodeWithAccount(account, pass) }) return makeJSONResponse(nil) } // SaveAccountAndLogin saves account in status-go database.. //export SaveAccountAndLogin func SaveAccountAndLogin(accountData, password, configJSON, subaccountData *C.char) *C.char { data, confJSON, subData := C.GoString(accountData), C.GoString(configJSON), C.GoString(subaccountData) var account multiaccounts.Account err := json.Unmarshal([]byte(data), &account) if err != nil { return makeJSONResponse(err) } conf := params.NodeConfig{} err = json.Unmarshal([]byte(confJSON), &conf) if err != nil { return makeJSONResponse(err) } var subaccs []accounts.Account err = json.Unmarshal([]byte(subData), &subaccs) if err != nil { return makeJSONResponse(err) } api.RunAsync(func() error { return statusBackend.StartNodeWithAccountAndConfig(account, C.GoString(password), &conf, subaccs) }) return makeJSONResponse(nil) } // InitKeystore initialize keystore before doing any operations with keys. //export InitKeystore func InitKeystore(keydir *C.char) *C.char { err := statusBackend.AccountManager().InitKeystore(C.GoString(keydir)) return makeJSONResponse(err) } // LoginWithKeycard initializes an account with a chat key and encryption key used for PFS. // It purges all the previous identities from Whisper, and injects the key as shh identity. //export LoginWithKeycard func LoginWithKeycard(chatKeyData, encryptionKeyData *C.char) *C.char { err := statusBackend.InjectChatAccount(C.GoString(chatKeyData), C.GoString(encryptionKeyData)) return makeJSONResponse(err) } //Logout is equivalent to clearing whisper identities //export Logout func Logout() *C.char { err := statusBackend.Logout() if err != nil { makeJSONResponse(err) } return makeJSONResponse(statusBackend.StopNode()) } // SignMessage unmarshals rpc params {data, address, password} and passes // them onto backend.SignMessage //export SignMessage func SignMessage(rpcParams *C.char) *C.char { var params personal.SignParams err := json.Unmarshal([]byte(C.GoString(rpcParams)), ¶ms) if err != nil { return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams)) } result, err := statusBackend.SignMessage(params) return C.CString(prepareJSONResponse(result.String(), err)) } // Recover unmarshals rpc params {signDataString, signedData} and passes // them onto backend. //export Recover func Recover(rpcParams *C.char) *C.char { var params personal.RecoverParams err := json.Unmarshal([]byte(C.GoString(rpcParams)), ¶ms) if err != nil { return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams)) } addr, err := statusBackend.Recover(params) return C.CString(prepareJSONResponse(addr.String(), err)) } // SendTransaction converts RPC args and calls backend.SendTransaction //export SendTransaction func SendTransaction(txArgsJSON, password *C.char) *C.char { var params transactions.SendTxArgs err := json.Unmarshal([]byte(C.GoString(txArgsJSON)), ¶ms) if err != nil { return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams)) } hash, err := statusBackend.SendTransaction(params, C.GoString(password)) code := codeUnknown if c, ok := errToCodeMap[err]; ok { code = c } return C.CString(prepareJSONResponseWithCode(hash.String(), err, code)) } // SendTransactionWithSignature converts RPC args and calls backend.SendTransactionWithSignature //export SendTransactionWithSignature func SendTransactionWithSignature(txArgsJSON, sigString *C.char) *C.char { var params transactions.SendTxArgs err := json.Unmarshal([]byte(C.GoString(txArgsJSON)), ¶ms) if err != nil { return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams)) } sig, err := hex.DecodeString(C.GoString(sigString)) if err != nil { return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams)) } hash, err := statusBackend.SendTransactionWithSignature(params, sig) code := codeUnknown if c, ok := errToCodeMap[err]; ok { code = c } return C.CString(prepareJSONResponseWithCode(hash.String(), err, code)) } // HashTransaction validate the transaction and returns new txArgs and the transaction hash. //export HashTransaction func HashTransaction(txArgsJSON *C.char) *C.char { var params transactions.SendTxArgs err := json.Unmarshal([]byte(C.GoString(txArgsJSON)), ¶ms) if err != nil { return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams)) } newTxArgs, hash, err := statusBackend.HashTransaction(params) code := codeUnknown if c, ok := errToCodeMap[err]; ok { code = c } result := struct { Transaction transactions.SendTxArgs `json:"transaction"` Hash common.Hash `json:"hash"` }{ Transaction: newTxArgs, Hash: hash, } return C.CString(prepareJSONResponseWithCode(result, err, code)) } // HashMessage calculates the hash of a message to be safely signed by the keycard // The hash is calulcated as // keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). // This gives context to the signed message and prevents signing of transactions. //export HashMessage func HashMessage(message *C.char) *C.char { hash, err := api.HashMessage(C.GoString(message)) code := codeUnknown if c, ok := errToCodeMap[err]; ok { code = c } return C.CString(prepareJSONResponseWithCode(fmt.Sprintf("0x%x", hash), err, code)) } // SignTypedData unmarshall data into TypedData, validate it and signs with selected account, // if password matches selected account. //export SignTypedData func SignTypedData(data, address, password *C.char) *C.char { var typed typeddata.TypedData err := json.Unmarshal([]byte(C.GoString(data)), &typed) if err != nil { return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams)) } if err := typed.Validate(); err != nil { return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams)) } result, err := statusBackend.SignTypedData(typed, C.GoString(address), C.GoString(password)) return C.CString(prepareJSONResponse(result.String(), err)) } // HashTypedData unmarshalls data into TypedData, validates it and hashes it. //export HashTypedData func HashTypedData(data *C.char) *C.char { var typed typeddata.TypedData err := json.Unmarshal([]byte(C.GoString(data)), &typed) if err != nil { return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams)) } if err := typed.Validate(); err != nil { return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams)) } result, err := statusBackend.HashTypedData(typed) return C.CString(prepareJSONResponse(result.String(), err)) } //StartCPUProfile runs pprof for cpu //export StartCPUProfile func StartCPUProfile(dataDir *C.char) *C.char { err := profiling.StartCPUProfile(C.GoString(dataDir)) return makeJSONResponse(err) } //StopCPUProfiling stops pprof for cpu //export StopCPUProfiling func StopCPUProfiling() *C.char { //nolint: deadcode err := profiling.StopCPUProfile() return makeJSONResponse(err) } //WriteHeapProfile starts pprof for heap //export WriteHeapProfile func WriteHeapProfile(dataDir *C.char) *C.char { //nolint: deadcode err := profiling.WriteHeapFile(C.GoString(dataDir)) return makeJSONResponse(err) } func makeJSONResponse(err error) *C.char { errString := "" if err != nil { fmt.Fprintln(os.Stderr, err) errString = err.Error() } out := APIResponse{ Error: errString, } outBytes, _ := json.Marshal(out) return C.CString(string(outBytes)) } // SendDataNotification sends push notifications by given tokens. // dataPayloadJSON is a JSON string that looks like this: // { // "data": { // "msg-v2": { // "from": "0x2cea3bd5", // hash of sender (first 10 characters/4 bytes of sha3 hash) // "to": "0xb1f89744", // hash of recipient (first 10 characters/4 bytes of sha3 hash) // "id": "0x872653ad", // message ID hash (first 10 characters/4 bytes of sha3 hash) // } // } // } //export SendDataNotification func SendDataNotification(dataPayloadJSON, tokensArray *C.char) (outCBytes *C.char) { var ( err error outBytes []byte ) errString := "" defer func() { out := SendDataNotificationResult{ Status: err == nil, Error: errString, } outBytes, err = json.Marshal(out) if err != nil { logger.Error("failed to marshal SendDataNotification output", "error", err) outCBytes = makeJSONResponse(err) return } outCBytes = C.CString(string(outBytes)) }() tokens, err := ParseJSONArray(C.GoString(tokensArray)) if err != nil { errString = err.Error() return } err = statusBackend.SendDataNotification(C.GoString(dataPayloadJSON), tokens...) if err != nil { errString = err.Error() return } return } // UpdateMailservers updates mail servers in status backend. //export UpdateMailservers func UpdateMailservers(data *C.char) *C.char { var enodes []string err := json.Unmarshal([]byte(C.GoString(data)), &enodes) if err != nil { return makeJSONResponse(err) } err = statusBackend.UpdateMailservers(enodes) return makeJSONResponse(err) } // AddPeer adds an enode as a peer. //export AddPeer func AddPeer(enode *C.char) *C.char { err := statusBackend.StatusNode().AddPeer(C.GoString(enode)) return makeJSONResponse(err) } // ConnectionChange handles network state changes as reported // by ReactNative (see https://facebook.github.io/react-native/docs/netinfo.html) //export ConnectionChange func ConnectionChange(typ *C.char, expensive C.int) { statusBackend.ConnectionChange(C.GoString(typ), expensive == 1) } // AppStateChange handles app state changes (background/foreground). //export AppStateChange func AppStateChange(state *C.char) { statusBackend.AppStateChange(C.GoString(state)) } // SetSignalEventCallback setup geth callback to notify about new signal //export SetSignalEventCallback func SetSignalEventCallback(cb unsafe.Pointer) { signal.SetSignalEventCallback(cb) } // ExportNodeLogs reads current node log and returns content to a caller. //export ExportNodeLogs func ExportNodeLogs() *C.char { node := statusBackend.StatusNode() if node == nil { return makeJSONResponse(errors.New("node is not running")) } config := node.Config() if config == nil { return makeJSONResponse(errors.New("config and log file are not available")) } data, err := json.Marshal(exportlogs.ExportFromBaseFile(config.LogFile)) if err != nil { return makeJSONResponse(fmt.Errorf("error marshalling to json: %v", err)) } return C.CString(string(data)) } // ChaosModeUpdate changes the URL of the upstream RPC client. //export ChaosModeUpdate func ChaosModeUpdate(on C.int) *C.char { node := statusBackend.StatusNode() if node == nil { return makeJSONResponse(errors.New("node is not running")) } err := node.ChaosModeCheckRPCClientsUpstreamURL(on == 1) return makeJSONResponse(err) } // GetNodesFromContract returns a list of nodes from a contract //export GetNodesFromContract func GetNodesFromContract(rpcEndpoint *C.char, contractAddress *C.char) *C.char { nodes, err := statusBackend.GetNodesFromContract( C.GoString(rpcEndpoint), C.GoString(contractAddress), ) if err != nil { return makeJSONResponse(err) } data, err := json.Marshal(struct { Nodes []string `json:"result"` }{Nodes: nodes}) if err != nil { return makeJSONResponse(err) } return C.CString(string(data)) } // SignHash exposes vanilla ECDSA signing required for Swarm messages //export SignHash func SignHash(hexEncodedHash *C.char) *C.char { hexEncodedSignature, err := statusBackend.SignHash( C.GoString(hexEncodedHash), ) if err != nil { return makeJSONResponse(err) } return C.CString(hexEncodedSignature) } // GenerateAlias returns a 3 random name words given the pk string 0x prefixed //export GenerateAlias func GenerateAlias(pk string) *C.char { // We ignore any error, empty string is considered an error name, _ := protocol.GenerateAlias(pk) return C.CString(name) } // Identicon returns the base64 identicon //export Identicon func Identicon(pk string) *C.char { // We ignore any error, empty string is considered an error identicon, _ := protocol.Identicon(pk) return C.CString(identicon) }