2019-07-24 18:59:15 +00:00
|
|
|
// +build e2e_test
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"C"
|
|
|
|
"encoding/json"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
|
|
mobile "github.com/status-im/status-go/mobile"
|
|
|
|
)
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/status-im/status-go/account/generator"
|
|
|
|
)
|
|
|
|
|
2019-07-26 09:33:38 +00:00
|
|
|
func checkMultiAccountErrorResponse(t *testing.T, respJSON *C.char, expectedError string) {
|
|
|
|
var e struct {
|
|
|
|
Error *string `json:"error,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(respJSON)), &e); err != nil {
|
|
|
|
t.Fatalf("error unmarshaling error response")
|
|
|
|
}
|
|
|
|
|
|
|
|
if e.Error == nil {
|
|
|
|
t.Fatalf("unexpected empty error. expected %s, got nil", expectedError)
|
|
|
|
}
|
|
|
|
|
|
|
|
if *e.Error != expectedError {
|
|
|
|
t.Fatalf("unexpected error. expected %s, got %+v", expectedError, *e.Error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-24 18:59:15 +00:00
|
|
|
func checkMultiAccountResponse(t *testing.T, respJSON *C.char, resp interface{}) {
|
|
|
|
var e struct {
|
2019-07-26 09:33:38 +00:00
|
|
|
Error *string `json:"error,omitempty"`
|
2019-07-24 18:59:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
json.Unmarshal([]byte(C.GoString(respJSON)), &e)
|
|
|
|
if e.Error != nil {
|
|
|
|
t.Errorf("unexpected response error: %s", *e.Error)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(respJSON)), resp); err != nil {
|
|
|
|
t.Fatalf("error unmarshaling response to expected struct: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-26 09:33:38 +00:00
|
|
|
func testMultiAccountGenerateDeriveStoreLoadReset(t *testing.T) bool { //nolint: gocyclo
|
2019-07-24 18:59:15 +00:00
|
|
|
// to make sure that we start with empty account (which might have gotten populated during previous tests)
|
|
|
|
if err := statusBackend.Logout(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
params := C.CString(`{
|
|
|
|
"n": 2,
|
|
|
|
"mnemonicPhraseLength": 24,
|
|
|
|
"bip39Passphrase": ""
|
|
|
|
}`)
|
|
|
|
|
|
|
|
// generate 2 random accounts
|
|
|
|
rawResp := MultiAccountGenerate(params)
|
|
|
|
var generateResp []generator.GeneratedAccountInfo
|
|
|
|
// check there's no error in the response
|
|
|
|
checkMultiAccountResponse(t, rawResp, &generateResp)
|
|
|
|
if len(generateResp) != 2 {
|
|
|
|
t.Errorf("expected 2 accounts created, got %d", len(generateResp))
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
bip44DerivationPath := "m/44'/60'/0'/0/0"
|
|
|
|
eip1581DerivationPath := "m/43'/60'/1581'/0'/0"
|
|
|
|
paths := []string{bip44DerivationPath, eip1581DerivationPath}
|
|
|
|
|
|
|
|
// derive 2 child accounts for each account without storing them
|
|
|
|
for i := 0; i < len(generateResp); i++ {
|
|
|
|
info := generateResp[i]
|
|
|
|
mnemonicLength := len(strings.Split(info.Mnemonic, " "))
|
|
|
|
|
|
|
|
if mnemonicLength != 24 {
|
|
|
|
t.Errorf("expected mnemonic to have 24 words, got %d", mnemonicLength)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-07-26 09:33:38 +00:00
|
|
|
if _, ok := testMultiAccountDeriveAddresses(t, info.ID, paths, false); !ok {
|
2019-07-24 18:59:15 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-26 09:33:38 +00:00
|
|
|
password := "multi-account-test-password"
|
|
|
|
|
2019-07-24 18:59:15 +00:00
|
|
|
// store 2 derived child accounts from the first account.
|
|
|
|
// after that all the generated account should be remove from memory.
|
2019-07-26 09:33:38 +00:00
|
|
|
addresses, ok := testMultiAccountStoreDerived(t, generateResp[0].ID, password, paths)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
loadedIDs := make([]string, 0)
|
|
|
|
|
|
|
|
// unlock and load all stored accounts.
|
|
|
|
for _, address := range addresses {
|
|
|
|
loadedID, ok := testMultiAccountLoadAccount(t, address, password)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
loadedIDs = append(loadedIDs, loadedID)
|
|
|
|
|
|
|
|
if _, ok := testMultiAccountDeriveAddresses(t, loadedID, paths, false); !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rawResp = MultiAccountReset()
|
|
|
|
|
|
|
|
// try again deriving addresses.
|
|
|
|
// it should fail because reset should remove all the accounts from memory.
|
|
|
|
for _, loadedID := range loadedIDs {
|
|
|
|
if _, ok := testMultiAccountDeriveAddresses(t, loadedID, paths, true); !ok {
|
|
|
|
t.Errorf("account is still in memory, expected Reset to remove all accounts")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func testMultiAccountImportMnemonicAndDerive(t *testing.T) bool { //nolint: gocyclo
|
|
|
|
// to make sure that we start with empty account (which might have gotten populated during previous tests)
|
|
|
|
if err := statusBackend.Logout(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
mnemonicPhrase := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
|
|
|
bip39Passphrase := "TREZOR"
|
|
|
|
params := mobile.MultiAccountImportMnemonicParams{
|
|
|
|
MnemonicPhrase: mnemonicPhrase,
|
|
|
|
Bip39Passphrase: bip39Passphrase,
|
|
|
|
}
|
|
|
|
|
|
|
|
paramsJSON, err := json.Marshal(¶ms)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error encoding MultiAccountImportMnemonicParams")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// import mnemonic
|
|
|
|
rawResp := MultiAccountImportMnemonic(C.CString(string(paramsJSON)))
|
|
|
|
var importResp generator.IdentifiedAccountInfo
|
|
|
|
// check the response doesn't have errors
|
|
|
|
checkMultiAccountResponse(t, rawResp, &importResp)
|
|
|
|
|
|
|
|
bip44DerivationPath := "m/44'/60'/0'/0/0"
|
|
|
|
expectedBip44Address := "0x9c32F71D4DB8Fb9e1A58B0a80dF79935e7256FA6"
|
|
|
|
addresses, ok := testMultiAccountDeriveAddresses(t, importResp.ID, []string{bip44DerivationPath}, false)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if addresses[bip44DerivationPath] != expectedBip44Address {
|
|
|
|
t.Errorf("unexpected address; expected %s, got %s", expectedBip44Address, addresses[bip44DerivationPath])
|
2019-07-24 18:59:15 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2019-07-26 09:33:38 +00:00
|
|
|
func testMultiAccountDeriveAddresses(t *testing.T, accountID string, paths []string, expectAccountNotFoundError bool) (map[string]string, bool) { //nolint: gocyclo
|
2019-07-24 18:59:15 +00:00
|
|
|
params := mobile.MultiAccountDeriveAddressesParams{
|
|
|
|
AccountID: accountID,
|
|
|
|
Paths: paths,
|
|
|
|
}
|
|
|
|
|
|
|
|
paramsJSON, err := json.Marshal(¶ms)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error encoding MultiAccountDeriveAddressesParams")
|
2019-07-26 09:33:38 +00:00
|
|
|
return nil, false
|
2019-07-24 18:59:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// derive addresses from account accountID
|
|
|
|
rawResp := MultiAccountDeriveAddresses(C.CString(string(paramsJSON)))
|
2019-07-26 09:33:38 +00:00
|
|
|
|
|
|
|
if expectAccountNotFoundError {
|
|
|
|
checkMultiAccountErrorResponse(t, rawResp, "account not found")
|
|
|
|
return nil, true
|
|
|
|
}
|
|
|
|
|
2019-07-24 18:59:15 +00:00
|
|
|
var deriveResp map[string]generator.AccountInfo
|
|
|
|
// check the response doesn't have errors
|
|
|
|
checkMultiAccountResponse(t, rawResp, &deriveResp)
|
2019-07-26 09:33:38 +00:00
|
|
|
if len(deriveResp) != len(paths) {
|
|
|
|
t.Errorf("expected %d derived accounts info, got %d", len(paths), len(deriveResp))
|
|
|
|
return nil, false
|
2019-07-24 18:59:15 +00:00
|
|
|
}
|
|
|
|
|
2019-07-26 09:33:38 +00:00
|
|
|
addresses := make(map[string]string)
|
|
|
|
|
2019-07-24 18:59:15 +00:00
|
|
|
// check that we have an address for each derivation path we used.
|
|
|
|
for _, path := range paths {
|
2019-07-26 09:33:38 +00:00
|
|
|
info, ok := deriveResp[path]
|
|
|
|
if !ok {
|
2019-07-24 18:59:15 +00:00
|
|
|
t.Errorf("results doesn't contain account info for path %s", path)
|
2019-07-26 09:33:38 +00:00
|
|
|
return nil, false
|
2019-07-24 18:59:15 +00:00
|
|
|
}
|
2019-07-26 09:33:38 +00:00
|
|
|
|
|
|
|
addresses[path] = info.Address
|
2019-07-24 18:59:15 +00:00
|
|
|
}
|
|
|
|
|
2019-07-26 09:33:38 +00:00
|
|
|
return addresses, true
|
2019-07-24 18:59:15 +00:00
|
|
|
}
|
|
|
|
|
2019-07-26 09:33:38 +00:00
|
|
|
func testMultiAccountStoreDerived(t *testing.T, accountID string, password string, paths []string) ([]string, bool) { //nolint: gocyclo
|
2019-07-24 18:59:15 +00:00
|
|
|
|
2019-07-26 09:33:38 +00:00
|
|
|
params := mobile.MultiAccountStoreDerivedAccountsParams{
|
2019-07-24 18:59:15 +00:00
|
|
|
MultiAccountDeriveAddressesParams: mobile.MultiAccountDeriveAddressesParams{
|
|
|
|
AccountID: accountID,
|
|
|
|
Paths: paths,
|
|
|
|
},
|
|
|
|
Password: password,
|
|
|
|
}
|
|
|
|
|
|
|
|
paramsJSON, err := json.Marshal(params)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error encoding MultiAccountStoreDerivedParams")
|
2019-07-26 09:33:38 +00:00
|
|
|
return nil, false
|
2019-07-24 18:59:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// store one child account for each derivation path.
|
2019-07-26 09:33:38 +00:00
|
|
|
rawResp := MultiAccountStoreDerivedAccounts(C.CString(string(paramsJSON)))
|
2019-07-24 18:59:15 +00:00
|
|
|
var storeResp map[string]generator.AccountInfo
|
|
|
|
|
|
|
|
// check that we don't have errors in the response
|
|
|
|
checkMultiAccountResponse(t, rawResp, &storeResp)
|
|
|
|
addresses := make([]string, 0)
|
|
|
|
for _, info := range storeResp {
|
|
|
|
addresses = append(addresses, info.Address)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(addresses) != 2 {
|
|
|
|
t.Errorf("expected 2 addresses, got %d", len(addresses))
|
2019-07-26 09:33:38 +00:00
|
|
|
return nil, false
|
2019-07-24 18:59:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// for each stored account, check that we can decrypt it with the password we used.
|
|
|
|
dir := statusBackend.StatusNode().Config().DataDir
|
|
|
|
for _, address := range addresses {
|
|
|
|
_, err = statusBackend.AccountManager().VerifyAccountPassword(dir, address, password)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("failed to verify password on stored derived account")
|
2019-07-26 09:33:38 +00:00
|
|
|
return nil, false
|
2019-07-24 18:59:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-26 09:33:38 +00:00
|
|
|
return addresses, true
|
2019-07-24 18:59:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func testMultiAccountGenerateAndDerive(t *testing.T) bool { //nolint: gocyclo
|
|
|
|
// to make sure that we start with empty account (which might have gotten populated during previous tests)
|
|
|
|
if err := statusBackend.Logout(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
paths := []string{"m/0", "m/1"}
|
|
|
|
params := mobile.MultiAccountGenerateAndDeriveAddressesParams{
|
|
|
|
MultiAccountGenerateParams: mobile.MultiAccountGenerateParams{
|
|
|
|
N: 2,
|
|
|
|
MnemonicPhraseLength: 12,
|
|
|
|
},
|
|
|
|
Paths: paths,
|
|
|
|
}
|
|
|
|
|
|
|
|
paramsJSON, err := json.Marshal(¶ms)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error encoding MultiAccountGenerateAndDeriveParams")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate 2 random accounts and derive 2 accounts from each one.
|
|
|
|
rawResp := MultiAccountGenerateAndDeriveAddresses(C.CString(string(paramsJSON)))
|
|
|
|
var generateResp []generator.GeneratedAndDerivedAccountInfo
|
|
|
|
// check there's no error in the response
|
|
|
|
checkMultiAccountResponse(t, rawResp, &generateResp)
|
|
|
|
if len(generateResp) != 2 {
|
|
|
|
t.Errorf("expected 2 accounts created, got %d", len(generateResp))
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// check that for each account we have the 2 derived addresses
|
|
|
|
for _, info := range generateResp {
|
|
|
|
for _, path := range paths {
|
|
|
|
if _, ok := info.Derived[path]; !ok {
|
|
|
|
t.Errorf("results doesn't contain account info for path %s", path)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func testMultiAccountImportStore(t *testing.T) bool { //nolint: gocyclo
|
|
|
|
// to make sure that we start with empty account (which might have gotten populated during previous tests)
|
|
|
|
if err := statusBackend.Logout(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err := crypto.GenerateKey()
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("failed generating key")
|
|
|
|
}
|
|
|
|
|
|
|
|
hex := fmt.Sprintf("%#x", crypto.FromECDSA(key))
|
|
|
|
importParams := mobile.MultiAccountImportPrivateKeyParams{
|
|
|
|
PrivateKey: hex,
|
|
|
|
}
|
|
|
|
|
|
|
|
paramsJSON, err := json.Marshal(&importParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error encoding MultiAccountImportPrivateKeyParams")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// import raw private key
|
|
|
|
rawResp := MultiAccountImportPrivateKey(C.CString(string(paramsJSON)))
|
|
|
|
var importResp generator.IdentifiedAccountInfo
|
|
|
|
// check the response doesn't have errors
|
|
|
|
checkMultiAccountResponse(t, rawResp, &importResp)
|
|
|
|
|
|
|
|
// prepare StoreAccount params
|
|
|
|
password := "test-multiaccount-imported-key-password"
|
|
|
|
storeParams := mobile.MultiAccountStoreAccountParams{
|
|
|
|
AccountID: importResp.ID,
|
|
|
|
Password: password,
|
|
|
|
}
|
|
|
|
|
|
|
|
paramsJSON, err = json.Marshal(storeParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error encoding MultiAccountStoreParams")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// store the imported private key
|
|
|
|
rawResp = MultiAccountStoreAccount(C.CString(string(paramsJSON)))
|
|
|
|
var storeResp generator.AccountInfo
|
|
|
|
// check the response doesn't have errors
|
|
|
|
checkMultiAccountResponse(t, rawResp, &storeResp)
|
|
|
|
|
|
|
|
dir := statusBackend.StatusNode().Config().DataDir
|
|
|
|
_, err = statusBackend.AccountManager().VerifyAccountPassword(dir, storeResp.Address, password)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("failed to verify password on stored derived account")
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
2019-07-26 09:33:38 +00:00
|
|
|
|
|
|
|
func testMultiAccountLoadAccount(t *testing.T, address string, password string) (string, bool) { //nolint: gocyclo
|
|
|
|
t.Log("loading account")
|
|
|
|
params := mobile.MultiAccountLoadAccountParams{
|
|
|
|
Address: address,
|
|
|
|
Password: password,
|
|
|
|
}
|
|
|
|
|
|
|
|
paramsJSON, err := json.Marshal(params)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error encoding MultiAccountLoadAccountParams")
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
|
|
|
// load the account in memory
|
|
|
|
rawResp := MultiAccountLoadAccount(C.CString(string(paramsJSON)))
|
|
|
|
var loadResp generator.IdentifiedAccountInfo
|
|
|
|
|
|
|
|
// check that we don't have errors in the response
|
|
|
|
checkMultiAccountResponse(t, rawResp, &loadResp)
|
|
|
|
|
|
|
|
return loadResp.ID, true
|
|
|
|
}
|