Abstract `accounts.Key` and geth `keystore`

This commit is contained in:
Pedro Pombeiro 2019-12-19 19:27:27 +01:00 committed by Pedro Pombeiro
parent 608de7fa2d
commit 287e5cdf79
35 changed files with 1174 additions and 241 deletions

View File

@ -10,13 +10,11 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/pborman/uuid" "github.com/pborman/uuid"
"github.com/status-im/status-go/account/generator" "github.com/status-im/status-go/account/generator"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/keystore"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/extkeys" "github.com/status-im/status-go/extkeys"
) )
@ -37,8 +35,7 @@ var zeroAddress = types.Address{}
// Manager represents account manager interface. // Manager represents account manager interface.
type Manager struct { type Manager struct {
mu sync.RWMutex mu sync.RWMutex
keystore *keystore.KeyStore keystore types.KeyStore
manager *accounts.Manager
accountsGenerator *generator.Generator accountsGenerator *generator.Generator
onboarding *Onboarding onboarding *Onboarding
@ -48,46 +45,13 @@ type Manager struct {
watchAddresses []types.Address watchAddresses []types.Address
} }
// NewManager returns new node account manager. // GetKeystore is only used in tests
func NewManager() *Manager { func (m *Manager) GetKeystore() types.KeyStore {
m := &Manager{}
m.accountsGenerator = generator.New(m)
return m
}
// InitKeystore sets key manager and key store.
func (m *Manager) InitKeystore(keydir string) error {
m.mu.Lock()
defer m.mu.Unlock()
manager, err := makeAccountManager(keydir)
if err != nil {
return err
}
m.manager = manager
backends := manager.Backends(keystore.KeyStoreType)
if len(backends) == 0 {
return ErrAccountKeyStoreMissing
}
keyStore, ok := backends[0].(*keystore.KeyStore)
if !ok {
return ErrAccountKeyStoreMissing
}
m.keystore = keyStore
return nil
}
func (m *Manager) GetKeystore() *keystore.KeyStore {
m.mu.RLock() m.mu.RLock()
defer m.mu.RUnlock() defer m.mu.RUnlock()
return m.keystore return m.keystore
} }
func (m *Manager) GetManager() *accounts.Manager {
m.mu.RLock()
defer m.mu.RUnlock()
return m.manager
}
// AccountsGenerator returns accountsGenerator. // AccountsGenerator returns accountsGenerator.
func (m *Manager) AccountsGenerator() *generator.Generator { func (m *Manager) AccountsGenerator() *generator.Generator {
return m.accountsGenerator return m.accountsGenerator
@ -153,7 +117,7 @@ func (m *Manager) RecoverAccount(password, mnemonic string) (Info, error) {
// VerifyAccountPassword tries to decrypt a given account key file, with a provided password. // VerifyAccountPassword tries to decrypt a given account key file, with a provided password.
// If no error is returned, then account is considered verified. // If no error is returned, then account is considered verified.
func (m *Manager) VerifyAccountPassword(keyStoreDir, address, password string) (*keystore.Key, error) { func (m *Manager) VerifyAccountPassword(keyStoreDir, address, password string) (*types.Key, error) {
var err error var err error
var foundKeyFile []byte var foundKeyFile []byte
@ -202,7 +166,7 @@ func (m *Manager) VerifyAccountPassword(keyStoreDir, address, password string) (
} }
// avoid swap attack // avoid swap attack
if types.Address(key.Address) != addressObj { if key.Address != addressObj {
return nil, fmt.Errorf("account mismatch: have %s, want %s", key.Address.Hex(), addressObj.Hex()) return nil, fmt.Errorf("account mismatch: have %s, want %s", key.Address.Hex(), addressObj.Hex())
} }
@ -240,9 +204,9 @@ func (m *Manager) SetChatAccount(privKey *ecdsa.PrivateKey) {
address := crypto.PubkeyToAddress(privKey.PublicKey) address := crypto.PubkeyToAddress(privKey.PublicKey)
id := uuid.NewRandom() id := uuid.NewRandom()
key := &keystore.Key{ key := &types.Key{
Id: id, Id: id,
Address: common.Address(address), Address: address,
PrivateKey: privKey, PrivateKey: privKey,
} }
@ -302,9 +266,13 @@ func (m *Manager) ImportAccount(privateKey *ecdsa.PrivateKey, password string) (
account, err := m.keystore.ImportECDSA(privateKey, password) account, err := m.keystore.ImportECDSA(privateKey, password)
return types.Address(account.Address), err return account.Address, err
} }
// ImportSingleExtendedKey imports an extended key setting it in both the PrivateKey and ExtendedKey fields
// of the Key struct.
// ImportExtendedKey is used in older version of Status where PrivateKey is set to be the BIP44 key at index 0,
// and ExtendedKey is the extended key of the BIP44 key at index 1.
func (m *Manager) ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error) { func (m *Manager) ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error) {
if m.keystore == nil { if m.keystore == nil {
return "", "", ErrAccountKeyStoreMissing return "", "", ErrAccountKeyStoreMissing
@ -418,18 +386,17 @@ func (m *Manager) ImportOnboardingAccount(id string, password string) (Info, str
// AddressToDecryptedAccount tries to load decrypted key for a given account. // AddressToDecryptedAccount tries to load decrypted key for a given account.
// The running node, has a keystore directory which is loaded on start. Key file // The running node, has a keystore directory which is loaded on start. Key file
// for a given address is expected to be in that directory prior to node start. // for a given address is expected to be in that directory prior to node start.
func (m *Manager) AddressToDecryptedAccount(address, password string) (accounts.Account, *keystore.Key, error) { func (m *Manager) AddressToDecryptedAccount(address, password string) (types.Account, *types.Key, error) {
if m.keystore == nil { if m.keystore == nil {
return accounts.Account{}, nil, ErrAccountKeyStoreMissing return types.Account{}, nil, ErrAccountKeyStoreMissing
} }
account, err := ParseAccountString(address) account, err := ParseAccountString(address)
if err != nil { if err != nil {
return accounts.Account{}, nil, ErrAddressToAccountMappingFailure return types.Account{}, nil, ErrAddressToAccountMappingFailure
} }
var key *keystore.Key account, key, err := m.keystore.AccountDecryptedKey(account, password)
account, key, err = m.keystore.AccountDecryptedKey(account, password)
if err != nil { if err != nil {
err = fmt.Errorf("%s: %s", ErrAccountToKeyMappingFailure, err) err = fmt.Errorf("%s: %s", ErrAccountToKeyMappingFailure, err)
} }
@ -444,7 +411,7 @@ func (m *Manager) unlockExtendedKey(address, password string) (*SelectedExtKey,
} }
selectedExtendedKey := &SelectedExtKey{ selectedExtendedKey := &SelectedExtKey{
Address: types.Address(account.Address), Address: account.Address,
AccountKey: accountKey, AccountKey: accountKey,
} }

42
account/accounts_geth.go Normal file
View File

@ -0,0 +1,42 @@
// +build !nimbus
package account
import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/status-im/status-go/account/generator"
)
// GethManager represents account manager interface.
type GethManager struct {
Manager
manager *accounts.Manager
}
// NewManager returns new node account manager.
func NewManager() *GethManager {
m := &GethManager{}
m.accountsGenerator = generator.New(m)
return m
}
// InitKeystore sets key manager and key store.
func (m *GethManager) InitKeystore(keydir string) error {
m.mu.Lock()
defer m.mu.Unlock()
var err error
m.manager, err = makeAccountManager(keydir)
if err != nil {
return err
}
m.keystore, err = makeKeyStore(m.manager)
return err
}
func (m *GethManager) GetManager() *accounts.Manager {
m.mu.RLock()
defer m.mu.RUnlock()
return m.manager
}

View File

@ -0,0 +1,28 @@
// +build nimbus
package account
import (
"github.com/status-im/status-go/account/generator"
)
// NewManager returns new node account manager.
func NewManager() *Manager {
m := &Manager{}
m.accountsGenerator = generator.New(m)
return m
}
// InitKeystore sets key manager and key store.
func (m *Manager) InitKeystore(keydir string) error {
m.mu.Lock()
defer m.mu.Unlock()
// TODO: Wire with the Nimbus keystore
manager, err := makeAccountManager(keydir)
if err != nil {
return err
}
m.keystore, err = makeKeyStore(manager)
return err
}

View File

@ -87,7 +87,7 @@ func TestVerifyAccountPassword(t *testing.T) {
require.Fail(t, "no error reported, but account key is missing") require.Fail(t, "no error reported, but account key is missing")
} }
accountAddress := types.BytesToAddress(types.FromHex(testCase.address)) accountAddress := types.BytesToAddress(types.FromHex(testCase.address))
if types.Address(accountKey.Address) != accountAddress { if accountKey.Address != accountAddress {
require.Fail(t, "account mismatch: have %s, want %s", accountKey.Address.Hex(), accountAddress.Hex()) require.Fail(t, "account mismatch: have %s, want %s", accountKey.Address.Hex(), accountAddress.Hex())
} }
} }
@ -120,7 +120,7 @@ func TestManagerTestSuite(t *testing.T) {
type ManagerTestSuite struct { type ManagerTestSuite struct {
suite.Suite suite.Suite
testAccount testAccount
accManager *Manager accManager *GethManager
keydir string keydir string
} }

View File

@ -7,7 +7,6 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/pborman/uuid" "github.com/pborman/uuid"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
@ -25,7 +24,7 @@ var (
) )
type AccountManager interface { type AccountManager interface {
AddressToDecryptedAccount(address, password string) (accounts.Account, *keystore.Key, error) AddressToDecryptedAccount(address, password string) (types.Account, *types.Key, error)
ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error) ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error)
ImportAccount(privateKey *ecdsa.PrivateKey, password string) (types.Address, error) ImportAccount(privateKey *ecdsa.PrivateKey, password string) (types.Address, error)
} }

View File

@ -4,8 +4,8 @@ import (
"bytes" "bytes"
"errors" "errors"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/extkeys" "github.com/status-im/status-go/extkeys"
) )
@ -21,7 +21,7 @@ var (
// ValidateKeystoreExtendedKey validates the keystore keys, checking that // ValidateKeystoreExtendedKey validates the keystore keys, checking that
// ExtendedKey is the extended key of PrivateKey. // ExtendedKey is the extended key of PrivateKey.
func ValidateKeystoreExtendedKey(key *keystore.Key) error { func ValidateKeystoreExtendedKey(key *types.Key) error {
if key.ExtendedKey.IsZeroed() { if key.ExtendedKey.IsZeroed() {
return nil return nil
} }

View File

@ -3,7 +3,7 @@ package generator
import ( import (
"testing" "testing"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/extkeys" "github.com/status-im/status-go/extkeys"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -25,7 +25,7 @@ func TestValidateKeystoreExtendedKey(t *testing.T) {
extendedKey2 := generateTestKey(t) extendedKey2 := generateTestKey(t)
// new keystore file format // new keystore file format
key := &keystore.Key{ key := &types.Key{
PrivateKey: extendedKey1.ToECDSA(), PrivateKey: extendedKey1.ToECDSA(),
ExtendedKey: extendedKey1, ExtendedKey: extendedKey1,
} }
@ -33,14 +33,14 @@ func TestValidateKeystoreExtendedKey(t *testing.T) {
// old keystore file format where the extended key was // old keystore file format where the extended key was
// from another derivation path and not the same of the private key // from another derivation path and not the same of the private key
oldKey := &keystore.Key{ oldKey := &types.Key{
PrivateKey: extendedKey1.ToECDSA(), PrivateKey: extendedKey1.ToECDSA(),
ExtendedKey: extendedKey2, ExtendedKey: extendedKey2,
} }
assert.Error(t, ValidateKeystoreExtendedKey(oldKey)) assert.Error(t, ValidateKeystoreExtendedKey(oldKey))
// normal key where we don't have an extended key // normal key where we don't have an extended key
normalKey := &keystore.Key{ normalKey := &types.Key{
PrivateKey: extendedKey1.ToECDSA(), PrivateKey: extendedKey1.ToECDSA(),
ExtendedKey: nil, ExtendedKey: nil,
} }

View File

@ -1,26 +0,0 @@
package account
import (
"io/ioutil"
"os"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
)
// makeAccountManager creates ethereum accounts.Manager with single disk backend and lightweight kdf.
// If keydir is empty new temporary directory with go-ethereum-keystore will be intialized.
func makeAccountManager(keydir string) (manager *accounts.Manager, err error) {
if keydir == "" {
// There is no datadir.
keydir, err = ioutil.TempDir("", "go-ethereum-keystore")
}
if err != nil {
return nil, err
}
if err := os.MkdirAll(keydir, 0700); err != nil {
return nil, err
}
config := accounts.Config{InsecureUnlockAllowed: false}
return accounts.NewManager(&config, keystore.NewKeyStore(keydir, keystore.LightScryptN, keystore.LightScryptP)), nil
}

135
account/keystore_geth.go Normal file
View File

@ -0,0 +1,135 @@
// TODO: Make independent version for Nimbus
package account
import (
"crypto/ecdsa"
"errors"
"io/ioutil"
"os"
"strings"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/extkeys"
)
// makeAccountManager creates ethereum accounts.Manager with single disk backend and lightweight kdf.
// If keydir is empty new temporary directory with go-ethereum-keystore will be intialized.
func makeAccountManager(keydir string) (manager *accounts.Manager, err error) {
if keydir == "" {
// There is no datadir.
keydir, err = ioutil.TempDir("", "go-ethereum-keystore")
}
if err != nil {
return nil, err
}
if err := os.MkdirAll(keydir, 0700); err != nil {
return nil, err
}
config := accounts.Config{InsecureUnlockAllowed: false}
return accounts.NewManager(&config, keystore.NewKeyStore(keydir, keystore.LightScryptN, keystore.LightScryptP)), nil
}
func makeKeyStore(manager *accounts.Manager) (types.KeyStore, error) {
backends := manager.Backends(keystore.KeyStoreType)
if len(backends) == 0 {
return nil, ErrAccountKeyStoreMissing
}
keyStore, ok := backends[0].(*keystore.KeyStore)
if !ok {
return nil, ErrAccountKeyStoreMissing
}
return adaptGethKeyStore(keyStore), nil
}
type gethKeyStoreAdapter struct {
keystore *keystore.KeyStore
}
func adaptGethKeyStore(keystore *keystore.KeyStore) types.KeyStore {
return &gethKeyStoreAdapter{keystore: keystore}
}
func (k *gethKeyStoreAdapter) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (types.Account, error) {
gethAccount, err := k.keystore.ImportECDSA(priv, passphrase)
return accountFrom(gethAccount), err
}
func (k *gethKeyStoreAdapter) ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, passphrase string) (types.Account, error) {
gethAccount, err := k.keystore.ImportSingleExtendedKey(extKey, passphrase)
return accountFrom(gethAccount), err
}
func (k *gethKeyStoreAdapter) ImportExtendedKeyForPurpose(keyPurpose extkeys.KeyPurpose, extKey *extkeys.ExtendedKey, passphrase string) (types.Account, error) {
gethAccount, err := k.keystore.ImportExtendedKeyForPurpose(keyPurpose, extKey, passphrase)
return accountFrom(gethAccount), err
}
func (k *gethKeyStoreAdapter) AccountDecryptedKey(a types.Account, auth string) (types.Account, *types.Key, error) {
gethAccount, err := gethAccountFrom(a)
if err != nil {
return types.Account{}, nil, err
}
var gethKey *keystore.Key
gethAccount, gethKey, err = k.keystore.AccountDecryptedKey(gethAccount, auth)
return accountFrom(gethAccount), keyFrom(gethKey), err
}
func (k *gethKeyStoreAdapter) Delete(a types.Account, auth string) error {
gethAccount, err := gethAccountFrom(a)
if err != nil {
return err
}
return k.keystore.Delete(gethAccount, auth)
}
// parseGethURL converts a user supplied URL into the accounts specific structure.
func parseGethURL(url string) (accounts.URL, error) {
parts := strings.Split(url, "://")
if len(parts) != 2 || parts[0] == "" {
return accounts.URL{}, errors.New("protocol scheme missing")
}
return accounts.URL{
Scheme: parts[0],
Path: parts[1],
}, nil
}
func gethAccountFrom(account types.Account) (accounts.Account, error) {
var (
gethAccount accounts.Account
err error
)
gethAccount.Address = common.Address(account.Address)
if account.URL != "" {
gethAccount.URL, err = parseGethURL(account.URL)
}
return gethAccount, err
}
func accountFrom(gethAccount accounts.Account) types.Account {
return types.Account{
Address: types.Address(gethAccount.Address),
URL: gethAccount.URL.String(),
}
}
func keyFrom(k *keystore.Key) *types.Key {
if k == nil {
return nil
}
return &types.Key{
Id: k.Id,
Address: types.Address(k.Address),
PrivateKey: k.PrivateKey,
ExtendedKey: k.ExtendedKey,
SubAccountIndex: k.SubAccountIndex,
}
}

View File

@ -5,10 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
) )
@ -75,8 +71,8 @@ type Info struct {
// SelectedExtKey is a container for the selected (logged in) external account. // SelectedExtKey is a container for the selected (logged in) external account.
type SelectedExtKey struct { type SelectedExtKey struct {
Address types.Address Address types.Address
AccountKey *keystore.Key AccountKey *types.Key
SubAccounts []accounts.Account SubAccounts []types.Account
} }
// Hex dumps address of a given extended key as hex string. // Hex dumps address of a given extended key as hex string.
@ -88,25 +84,14 @@ func (k *SelectedExtKey) Hex() string {
return k.Address.Hex() return k.Address.Hex()
} }
// ParseAccountString parses hex encoded string and returns is as accounts.Account. // ParseAccountString parses hex encoded string and returns is as types.Account.
func ParseAccountString(account string) (accounts.Account, error) { func ParseAccountString(account string) (types.Account, error) {
// valid address, convert to account // valid address, convert to account
if types.IsHexAddress(account) { if types.IsHexAddress(account) {
return accounts.Account{Address: common.HexToAddress(account)}, nil return types.Account{Address: types.HexToAddress(account)}, nil
} }
return accounts.Account{}, ErrInvalidAccountAddressOrKey return types.Account{}, ErrInvalidAccountAddressOrKey
}
// GethFromAddress converts account address from string to common.Address.
// The function is useful to format "From" field of send transaction struct.
func GethFromAddress(accountAddress string) common.Address {
from, err := ParseAccountString(accountAddress)
if err != nil {
return common.Address{}
}
return from.Address
} }
// FromAddress converts account address from string to types.Address. // FromAddress converts account address from string to types.Address.
@ -117,12 +102,12 @@ func FromAddress(accountAddress string) types.Address {
return types.Address{} return types.Address{}
} }
return types.Address(from.Address) return from.Address
} }
// GethToAddress converts account address from string to *common.Address. // ToAddress converts account address from string to *common.Address.
// The function is useful to format "To" field of send transaction struct. // The function is useful to format "To" field of send transaction struct.
func GethToAddress(accountAddress string) *common.Address { func ToAddress(accountAddress string) *types.Address {
to, err := ParseAccountString(accountAddress) to, err := ParseAccountString(accountAddress)
if err != nil { if err != nil {
return nil return nil

View File

@ -4,9 +4,9 @@ package account
import ( import (
"testing" "testing"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/status-go/account/generator" "github.com/status-im/status-go/account/generator"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/extkeys" "github.com/status-im/status-go/extkeys"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
@ -21,13 +21,13 @@ func (suite *AccountUtilsTestSuite) SetupTest() {
suite.validKey = "0xF35E0325dad87e2661c4eF951d58727e6d583d5c" suite.validKey = "0xF35E0325dad87e2661c4eF951d58727e6d583d5c"
} }
func (suite *AccountUtilsTestSuite) TestGethToAddress() { func (suite *AccountUtilsTestSuite) TestToAddress() {
addr := GethToAddress(suite.validKey) addr := ToAddress(suite.validKey)
suite.Equal(suite.validKey, addr.String()) suite.Equal(suite.validKey, addr.String())
} }
func (suite *AccountUtilsTestSuite) TestGethToAddressInvalidAddress() { func (suite *AccountUtilsTestSuite) TestToAddressInvalidAddress() {
addr := GethToAddress("foobar") addr := ToAddress("foobar")
suite.Nil(addr) suite.Nil(addr)
} }
@ -46,21 +46,6 @@ func (suite *AccountUtilsTestSuite) TestFromAddress() {
} }
} }
func (suite *AccountUtilsTestSuite) TestGethFromAddress() {
var flagtests = []struct {
in string
out string
}{
{suite.validKey, suite.validKey},
{"foobar", "0x0000000000000000000000000000000000000000"},
}
for _, tt := range flagtests {
addr := GethFromAddress(tt.in)
suite.Equal(tt.out, addr.String())
}
}
func (suite *AccountUtilsTestSuite) TestHex() { func (suite *AccountUtilsTestSuite) TestHex() {
var addr *SelectedExtKey var addr *SelectedExtKey
cr, _ := crypto.GenerateKey() cr, _ := crypto.GenerateKey()
@ -70,7 +55,7 @@ func (suite *AccountUtilsTestSuite) TestHex() {
}{ }{
{&SelectedExtKey{ {&SelectedExtKey{
Address: FromAddress(suite.validKey), Address: FromAddress(suite.validKey),
AccountKey: &keystore.Key{PrivateKey: cr}, AccountKey: &types.Key{PrivateKey: cr},
}, suite.validKey}, }, suite.validKey},
{addr, "0x0"}, {addr, "0x0"},
} }

View File

@ -70,7 +70,7 @@ type GethStatusBackend struct {
personalAPI *personal.PublicAPI personalAPI *personal.PublicAPI
rpcFilters *rpcfilters.Service rpcFilters *rpcfilters.Service
multiaccountsDB *multiaccounts.Database multiaccountsDB *multiaccounts.Database
accountManager *account.Manager accountManager *account.GethManager
transactor *transactions.Transactor transactor *transactions.Transactor
connectionState connectionState connectionState connectionState
appState appState appState appState
@ -104,7 +104,7 @@ func (b *GethStatusBackend) StatusNode() *node.StatusNode {
} }
// AccountManager returns reference to account manager // AccountManager returns reference to account manager
func (b *GethStatusBackend) AccountManager() *account.Manager { func (b *GethStatusBackend) AccountManager() *account.GethManager {
return b.accountManager return b.accountManager
} }
@ -386,7 +386,7 @@ func (b *GethStatusBackend) subscriptionService() gethnode.ServiceConstructor {
func (b *GethStatusBackend) accountsService(accountsFeed *event.Feed) gethnode.ServiceConstructor { func (b *GethStatusBackend) accountsService(accountsFeed *event.Feed) gethnode.ServiceConstructor {
return func(*gethnode.ServiceContext) (gethnode.Service, error) { return func(*gethnode.ServiceContext) (gethnode.Service, error) {
return accountssvc.NewService(accounts.NewDB(b.appDB), b.multiaccountsDB, b.accountManager, accountsFeed), nil return accountssvc.NewService(accounts.NewDB(b.appDB), b.multiaccountsDB, &b.accountManager.Manager, accountsFeed), nil
} }
} }
@ -683,7 +683,7 @@ func (b *GethStatusBackend) getVerifiedWalletAccount(address, password string) (
} }
return &account.SelectedExtKey{ return &account.SelectedExtKey{
Address: types.Address(key.Address), Address: key.Address,
AccountKey: key, AccountKey: key,
}, nil }, nil
} }

View File

@ -0,0 +1,15 @@
// Imported from github.com/ethereum/go-ethereum/accounts/keystore/keystore.go
package keystore
import (
"errors"
)
const (
version = 3
)
var (
ErrDecrypt = errors.New("could not decrypt key with given password")
)

View File

@ -0,0 +1,329 @@
// Imported from github.com/ethereum/go-ethereum/accounts/keystore/passphrase.go
// and github.com/ethereum/go-ethereum/accounts/keystore/presale.go
package keystore
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/pborman/uuid"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/extkeys"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
)
const (
keyHeaderKDF = "scrypt"
)
type encryptedKeyJSONV3 struct {
Address string `json:"address"`
Crypto CryptoJSON `json:"crypto"`
Id string `json:"id"`
Version int `json:"version"`
ExtendedKey CryptoJSON `json:"extendedkey"`
SubAccountIndex uint32 `json:"subaccountindex"`
}
type encryptedKeyJSONV1 struct {
Address string `json:"address"`
Crypto CryptoJSON `json:"crypto"`
Id string `json:"id"`
Version string `json:"version"`
}
type CryptoJSON struct {
Cipher string `json:"cipher"`
CipherText string `json:"ciphertext"`
CipherParams cipherparamsJSON `json:"cipherparams"`
KDF string `json:"kdf"`
KDFParams map[string]interface{} `json:"kdfparams"`
MAC string `json:"mac"`
}
type cipherparamsJSON struct {
IV string `json:"iv"`
}
// DecryptKey decrypts a key from a json blob, returning the private key itself.
func DecryptKey(keyjson []byte, auth string) (*types.Key, error) {
// Parse the json into a simple map to fetch the key version
m := make(map[string]interface{})
if err := json.Unmarshal(keyjson, &m); err != nil {
return nil, err
}
// Depending on the version try to parse one way or another
var (
keyBytes, keyId []byte
err error
extKeyBytes []byte
extKey *extkeys.ExtendedKey
)
subAccountIndex, ok := m["subaccountindex"].(float64)
if !ok {
subAccountIndex = 0
}
if version, ok := m["version"].(string); ok && version == "1" {
k := new(encryptedKeyJSONV1)
if err := json.Unmarshal(keyjson, k); err != nil {
return nil, err
}
keyBytes, keyId, err = decryptKeyV1(k, auth)
if err != nil {
return nil, err
}
extKey, err = extkeys.NewKeyFromString(extkeys.EmptyExtendedKeyString)
} else {
k := new(encryptedKeyJSONV3)
if err := json.Unmarshal(keyjson, k); err != nil {
return nil, err
}
keyBytes, keyId, err = decryptKeyV3(k, auth)
if err != nil {
return nil, err
}
extKeyBytes, err = decryptExtendedKey(k, auth)
if err != nil {
return nil, err
}
extKey, err = extkeys.NewKeyFromString(string(extKeyBytes))
}
// Handle any decryption errors and return the key
if err != nil {
return nil, err
}
key := crypto.ToECDSAUnsafe(keyBytes)
return &types.Key{
Id: uuid.UUID(keyId),
Address: crypto.PubkeyToAddress(key.PublicKey),
PrivateKey: key,
ExtendedKey: extKey,
SubAccountIndex: uint32(subAccountIndex),
}, nil
}
func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
if cryptoJson.Cipher != "aes-128-ctr" {
return nil, fmt.Errorf("Cipher not supported: %v", cryptoJson.Cipher)
}
mac, err := hex.DecodeString(cryptoJson.MAC)
if err != nil {
return nil, err
}
iv, err := hex.DecodeString(cryptoJson.CipherParams.IV)
if err != nil {
return nil, err
}
cipherText, err := hex.DecodeString(cryptoJson.CipherText)
if err != nil {
return nil, err
}
derivedKey, err := getKDFKey(cryptoJson, auth)
if err != nil {
return nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, ErrDecrypt
}
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
if err != nil {
return nil, err
}
return plainText, err
}
func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
if keyProtected.Version != version {
return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
}
keyId = uuid.Parse(keyProtected.Id)
plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
if err != nil {
return nil, nil, err
}
return plainText, keyId, err
}
func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
keyId = uuid.Parse(keyProtected.Id)
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
if err != nil {
return nil, nil, err
}
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
if err != nil {
return nil, nil, err
}
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
if err != nil {
return nil, nil, err
}
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
if err != nil {
return nil, nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, nil, ErrDecrypt
}
plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv)
if err != nil {
return nil, nil, err
}
return plainText, keyId, err
}
func decryptExtendedKey(keyProtected *encryptedKeyJSONV3, auth string) (plainText []byte, err error) {
if len(keyProtected.ExtendedKey.CipherText) == 0 {
return []byte(extkeys.EmptyExtendedKeyString), nil
}
if keyProtected.Version != version {
return nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
}
if keyProtected.ExtendedKey.Cipher != "aes-128-ctr" {
return nil, fmt.Errorf("Cipher not supported: %v", keyProtected.ExtendedKey.Cipher)
}
mac, err := hex.DecodeString(keyProtected.ExtendedKey.MAC)
if err != nil {
return nil, err
}
iv, err := hex.DecodeString(keyProtected.ExtendedKey.CipherParams.IV)
if err != nil {
return nil, err
}
cipherText, err := hex.DecodeString(keyProtected.ExtendedKey.CipherText)
if err != nil {
return nil, err
}
derivedKey, err := getKDFKey(keyProtected.ExtendedKey, auth)
if err != nil {
return nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, ErrDecrypt
}
plainText, err = aesCTRXOR(derivedKey[:16], cipherText, iv)
if err != nil {
return nil, err
}
return plainText, err
}
func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
authArray := []byte(auth)
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
if err != nil {
return nil, err
}
dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
if cryptoJSON.KDF == keyHeaderKDF {
n := ensureInt(cryptoJSON.KDFParams["n"])
r := ensureInt(cryptoJSON.KDFParams["r"])
p := ensureInt(cryptoJSON.KDFParams["p"])
return scrypt.Key(authArray, salt, n, r, p, dkLen)
} else if cryptoJSON.KDF == "pbkdf2" {
c := ensureInt(cryptoJSON.KDFParams["c"])
prf := cryptoJSON.KDFParams["prf"].(string)
if prf != "hmac-sha256" {
return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf)
}
key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
return key, nil
}
return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF)
}
// TODO: can we do without this when unmarshalling dynamic JSON?
// why do integers in KDF params end up as float64 and not int after
// unmarshal?
func ensureInt(x interface{}) int {
res, ok := x.(int)
if !ok {
res = int(x.(float64))
}
return res
}
func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
// AES-128 is selected due to size of encryptKey.
aesBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(aesBlock, iv)
outText := make([]byte, len(inText))
stream.XORKeyStream(outText, inText)
return outText, err
}
func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) {
aesBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
decrypter := cipher.NewCBCDecrypter(aesBlock, iv)
paddedPlaintext := make([]byte, len(cipherText))
decrypter.CryptBlocks(paddedPlaintext, cipherText)
plaintext := pkcs7Unpad(paddedPlaintext)
if plaintext == nil {
return nil, ErrDecrypt
}
return plaintext, err
}
// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
func pkcs7Unpad(in []byte) []byte {
if len(in) == 0 {
return nil
}
padding := in[len(in)-1]
if int(padding) > len(in) || padding > aes.BlockSize {
return nil
} else if padding == 0 {
return nil
}
for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
if in[i] != padding {
return nil
}
}
return in[:len(in)-int(padding)]
}

View File

@ -0,0 +1,8 @@
package types
// Account represents an Ethereum account located at a specific location defined
// by the optional URL field.
type Account struct {
Address Address `json:"address"` // Ethereum account address derived from the key
URL string `json:"url"` // Optional resource locator within a backend
}

24
eth-node/types/key.go Normal file
View File

@ -0,0 +1,24 @@
package types
import (
"crypto/ecdsa"
"github.com/pborman/uuid"
"github.com/status-im/status-go/extkeys"
)
type Key struct {
Id uuid.UUID // Version 4 "random" for unique id not derived from key data
// to simplify lookups we also store the address
Address Address
// we only store privkey as pubkey/address can be derived from it
// privkey in this struct is always in plaintext
PrivateKey *ecdsa.PrivateKey
// ExtendedKey is the extended key of the PrivateKey itself, and it's used
// to derive child keys.
ExtendedKey *extkeys.ExtendedKey
// SubAccountIndex is DEPRECATED
// It was use in Status to keep track of the number of sub-account created
// before having multi-account support.
SubAccountIndex uint32
}

View File

@ -0,0 +1,26 @@
package types
import (
"crypto/ecdsa"
"github.com/status-im/status-go/extkeys"
)
type KeyStore interface {
// ImportAccount imports the account specified with privateKey.
ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (Account, error)
// ImportSingleExtendedKey imports an extended key setting it in both the PrivateKey and ExtendedKey fields
// of the Key struct.
// ImportExtendedKey is used in older version of Status where PrivateKey is set to be the BIP44 key at index 0,
// and ExtendedKey is the extended key of the BIP44 key at index 1.
ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, passphrase string) (Account, error)
// ImportExtendedKeyForPurpose stores ECDSA key (obtained from extended key) along with CKD#2 (root for sub-accounts)
// If key file is not found, it is created. Key is encrypted with the given passphrase.
// Deprecated: status-go is now using ImportSingleExtendedKey
ImportExtendedKeyForPurpose(keyPurpose extkeys.KeyPurpose, extKey *extkeys.ExtendedKey, passphrase string) (Account, error)
// AccountDecryptedKey returns decrypted key for account (provided that password is correct).
AccountDecryptedKey(a Account, auth string) (Account, *Key, error)
// Delete deletes the key matched by account if the passphrase is correct.
// If the account contains no filename, the address must match a unique key.
Delete(a Account, auth string) error
}

View File

@ -23,12 +23,11 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/keystore"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/signal" "github.com/status-im/status-go/signal"
@ -592,8 +591,8 @@ func testSendTransactionWithLogin(t *testing.T, feed *event.Feed) bool {
EnsureNodeSync(statusBackend.StatusNode().EnsureSync) EnsureNodeSync(statusBackend.StatusNode().EnsureSync)
args, err := json.Marshal(transactions.SendTxArgs{ args, err := json.Marshal(transactions.SendTxArgs{
From: account.GethFromAddress(TestConfig.Account1.WalletAddress), From: account.FromAddress(TestConfig.Account1.WalletAddress),
To: account.GethToAddress(TestConfig.Account2.WalletAddress), To: account.ToAddress(TestConfig.Account2.WalletAddress),
Value: (*hexutil.Big)(big.NewInt(1000000000000)), Value: (*hexutil.Big)(big.NewInt(1000000000000)),
}) })
if err != nil { if err != nil {
@ -625,8 +624,8 @@ func testSendTransactionInvalidPassword(t *testing.T, feed *event.Feed) bool {
EnsureNodeSync(statusBackend.StatusNode().EnsureSync) EnsureNodeSync(statusBackend.StatusNode().EnsureSync)
args, err := json.Marshal(transactions.SendTxArgs{ args, err := json.Marshal(transactions.SendTxArgs{
From: common.HexToAddress(acc.WalletAddress), From: types.HexToAddress(acc.WalletAddress),
To: account.GethToAddress(TestConfig.Account2.WalletAddress), To: account.ToAddress(TestConfig.Account2.WalletAddress),
Value: (*hexutil.Big)(big.NewInt(1000000000000)), Value: (*hexutil.Big)(big.NewInt(1000000000000)),
}) })
if err != nil { if err != nil {
@ -653,8 +652,8 @@ func testFailedTransaction(t *testing.T, feed *event.Feed) bool {
EnsureNodeSync(statusBackend.StatusNode().EnsureSync) EnsureNodeSync(statusBackend.StatusNode().EnsureSync)
args, err := json.Marshal(transactions.SendTxArgs{ args, err := json.Marshal(transactions.SendTxArgs{
From: *account.GethToAddress(TestConfig.Account1.WalletAddress), From: *account.ToAddress(TestConfig.Account1.WalletAddress),
To: account.GethToAddress(TestConfig.Account2.WalletAddress), To: account.ToAddress(TestConfig.Account2.WalletAddress),
Value: (*hexutil.Big)(big.NewInt(1000000000000)), Value: (*hexutil.Big)(big.NewInt(1000000000000)),
}) })
if err != nil { if err != nil {

View File

@ -6,10 +6,9 @@ package status
import ( import (
ecdsa "crypto/ecdsa" ecdsa "crypto/ecdsa"
accounts "github.com/ethereum/go-ethereum/accounts"
keystore "github.com/ethereum/go-ethereum/accounts/keystore"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
account "github.com/status-im/status-go/account" account "github.com/status-im/status-go/account"
types "github.com/status-im/status-go/eth-node/types"
reflect "reflect" reflect "reflect"
) )
@ -75,11 +74,11 @@ func (m *MockAccountManager) EXPECT() *MockAccountManagerMockRecorder {
} }
// AddressToDecryptedAccount mocks base method // AddressToDecryptedAccount mocks base method
func (m *MockAccountManager) AddressToDecryptedAccount(arg0, arg1 string) (accounts.Account, *keystore.Key, error) { func (m *MockAccountManager) AddressToDecryptedAccount(arg0, arg1 string) (types.Account, *types.Key, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddressToDecryptedAccount", arg0, arg1) ret := m.ctrl.Call(m, "AddressToDecryptedAccount", arg0, arg1)
ret0, _ := ret[0].(accounts.Account) ret0, _ := ret[0].(types.Account)
ret1, _ := ret[1].(*keystore.Key) ret1, _ := ret[1].(*types.Key)
ret2, _ := ret[2].(error) ret2, _ := ret[2].(error)
return ret0, ret1, ret2 return ret0, ret1, ret2
} }

View File

@ -6,8 +6,6 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
@ -46,10 +44,10 @@ var logintests = []struct {
expectedAddressKey: "addressKey", expectedAddressKey: "addressKey",
expectedError: nil, expectedError: nil,
prepareExpectations: func(s *StatusSuite) { prepareExpectations: func(s *StatusSuite) {
key := keystore.Key{ key := types.Key{
PrivateKey: &ecdsa.PrivateKey{}, PrivateKey: &ecdsa.PrivateKey{},
} }
s.am.EXPECT().AddressToDecryptedAccount("0x01", "password").Return(accounts.Account{}, &key, nil) s.am.EXPECT().AddressToDecryptedAccount("0x01", "password").Return(types.Account{}, &key, nil)
s.w.EXPECT().AddKeyPair(key.PrivateKey).Return("addressKey", nil) s.w.EXPECT().AddKeyPair(key.PrivateKey).Return("addressKey", nil)
loginParams := account.LoginParams{ loginParams := account.LoginParams{
@ -65,10 +63,10 @@ var logintests = []struct {
expectedAddressKey: "", expectedAddressKey: "",
expectedError: errors.New("foo"), expectedError: errors.New("foo"),
prepareExpectations: func(s *StatusSuite) { prepareExpectations: func(s *StatusSuite) {
key := keystore.Key{ key := types.Key{
PrivateKey: &ecdsa.PrivateKey{}, PrivateKey: &ecdsa.PrivateKey{},
} }
s.am.EXPECT().AddressToDecryptedAccount("0x01", "password").Return(accounts.Account{}, &key, errors.New("foo")) s.am.EXPECT().AddressToDecryptedAccount("0x01", "password").Return(types.Account{}, &key, errors.New("foo"))
}, },
}, },
{ {
@ -76,10 +74,10 @@ var logintests = []struct {
expectedAddressKey: "", expectedAddressKey: "",
expectedError: errors.New("foo"), expectedError: errors.New("foo"),
prepareExpectations: func(s *StatusSuite) { prepareExpectations: func(s *StatusSuite) {
key := keystore.Key{ key := types.Key{
PrivateKey: &ecdsa.PrivateKey{}, PrivateKey: &ecdsa.PrivateKey{},
} }
s.am.EXPECT().AddressToDecryptedAccount("0x01", "password").Return(accounts.Account{}, &key, nil) s.am.EXPECT().AddressToDecryptedAccount("0x01", "password").Return(types.Account{}, &key, nil)
s.w.EXPECT().AddKeyPair(key.PrivateKey).Return("", errors.New("foo")) s.w.EXPECT().AddKeyPair(key.PrivateKey).Return("", errors.New("foo"))
}, },
}, },
@ -88,10 +86,10 @@ var logintests = []struct {
expectedAddressKey: "", expectedAddressKey: "",
expectedError: errors.New("foo"), expectedError: errors.New("foo"),
prepareExpectations: func(s *StatusSuite) { prepareExpectations: func(s *StatusSuite) {
key := keystore.Key{ key := types.Key{
PrivateKey: &ecdsa.PrivateKey{}, PrivateKey: &ecdsa.PrivateKey{},
} }
s.am.EXPECT().AddressToDecryptedAccount("0x01", "password").Return(accounts.Account{}, &key, nil) s.am.EXPECT().AddressToDecryptedAccount("0x01", "password").Return(types.Account{}, &key, nil)
s.w.EXPECT().AddKeyPair(key.PrivateKey).Return("", nil) s.w.EXPECT().AddKeyPair(key.PrivateKey).Return("", nil)
loginParams := account.LoginParams{ loginParams := account.LoginParams{

View File

@ -3,12 +3,11 @@ package status
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/eth-node/types"
) )
// Make sure that Service implements node.Service interface. // Make sure that Service implements node.Service interface.
@ -21,7 +20,7 @@ type WhisperService interface {
// AccountManager interface to manage account actions // AccountManager interface to manage account actions
type AccountManager interface { type AccountManager interface {
AddressToDecryptedAccount(string, string) (accounts.Account, *keystore.Key, error) AddressToDecryptedAccount(string, string) (types.Account, *types.Key, error)
SelectAccount(account.LoginParams) error SelectAccount(account.LoginParams) error
CreateAccount(password string) (accountInfo account.Info, mnemonic string, err error) CreateAccount(password string) (accountInfo account.Info, mnemonic string, err error)
} }

View File

@ -121,7 +121,7 @@ func (s *TransactionsTestSuite) TestEmptyToFieldPreserved() {
utils.EnsureNodeSync(s.Backend.StatusNode().EnsureSync) utils.EnsureNodeSync(s.Backend.StatusNode().EnsureSync)
args := transactions.SendTxArgs{ args := transactions.SendTxArgs{
From: account.GethFromAddress(utils.TestConfig.Account1.WalletAddress), From: account.FromAddress(utils.TestConfig.Account1.WalletAddress),
} }
hash, err := s.Backend.SendTransaction(args, utils.TestConfig.Account1.Password) hash, err := s.Backend.SendTransaction(args, utils.TestConfig.Account1.Password)
@ -135,7 +135,7 @@ func (s *TransactionsTestSuite) TestSendContractTxCompat() {
utils.CheckTestSkipForNetworks(s.T(), params.MainNetworkID) utils.CheckTestSkipForNetworks(s.T(), params.MainNetworkID)
initFunc := func(byteCode []byte, args *transactions.SendTxArgs) { initFunc := func(byteCode []byte, args *transactions.SendTxArgs) {
args.Data = (hexutil.Bytes)(byteCode) args.Data = (types.HexBytes)(byteCode)
} }
s.testSendContractTx(initFunc, nil, "") s.testSendContractTx(initFunc, nil, "")
} }
@ -148,8 +148,8 @@ func (s *TransactionsTestSuite) TestSendContractTxCollision() {
// Scenario 1: Both fields are filled and have the same value, expect success // Scenario 1: Both fields are filled and have the same value, expect success
initFunc := func(byteCode []byte, args *transactions.SendTxArgs) { initFunc := func(byteCode []byte, args *transactions.SendTxArgs) {
args.Input = (hexutil.Bytes)(byteCode) args.Input = (types.HexBytes)(byteCode)
args.Data = (hexutil.Bytes)(byteCode) args.Data = (types.HexBytes)(byteCode)
} }
s.testSendContractTx(initFunc, nil, "") s.testSendContractTx(initFunc, nil, "")
@ -164,8 +164,8 @@ func (s *TransactionsTestSuite) TestSendContractTxCollision() {
} }
initFunc2 := func(byteCode []byte, args *transactions.SendTxArgs) { initFunc2 := func(byteCode []byte, args *transactions.SendTxArgs) {
args.Input = (hexutil.Bytes)(byteCode) args.Input = (types.HexBytes)(byteCode)
args.Data = (hexutil.Bytes)(inverted(byteCode)) args.Data = (types.HexBytes)(inverted(byteCode))
} }
s.testSendContractTx(initFunc2, transactions.ErrInvalidSendTxArgs, "expected error when invalid tx args are sent") s.testSendContractTx(initFunc2, transactions.ErrInvalidSendTxArgs, "expected error when invalid tx args are sent")
} }
@ -174,7 +174,7 @@ func (s *TransactionsTestSuite) TestSendContractTx() {
utils.CheckTestSkipForNetworks(s.T(), params.MainNetworkID) utils.CheckTestSkipForNetworks(s.T(), params.MainNetworkID)
initFunc := func(byteCode []byte, args *transactions.SendTxArgs) { initFunc := func(byteCode []byte, args *transactions.SendTxArgs) {
args.Input = (hexutil.Bytes)(byteCode) args.Input = (types.HexBytes)(byteCode)
} }
s.testSendContractTx(initFunc, nil, "") s.testSendContractTx(initFunc, nil, "")
} }
@ -199,7 +199,7 @@ func (s *TransactionsTestSuite) testSendContractTx(setInputAndDataValue initFunc
gas := uint64(params.DefaultGas) gas := uint64(params.DefaultGas)
args := transactions.SendTxArgs{ args := transactions.SendTxArgs{
From: account.GethFromAddress(utils.TestConfig.Account1.WalletAddress), From: account.FromAddress(utils.TestConfig.Account1.WalletAddress),
To: nil, // marker, contract creation is expected To: nil, // marker, contract creation is expected
//Value: (*hexutil.Big)(new(big.Int).Mul(big.NewInt(1), common.Ether)), //Value: (*hexutil.Big)(new(big.Int).Mul(big.NewInt(1), common.Ether)),
Gas: (*hexutil.Uint64)(&gas), Gas: (*hexutil.Uint64)(&gas),
@ -231,8 +231,8 @@ func (s *TransactionsTestSuite) TestSendEther() {
utils.EnsureNodeSync(s.Backend.StatusNode().EnsureSync) utils.EnsureNodeSync(s.Backend.StatusNode().EnsureSync)
hash, err := s.Backend.SendTransaction(transactions.SendTxArgs{ hash, err := s.Backend.SendTransaction(transactions.SendTxArgs{
From: account.GethFromAddress(utils.TestConfig.Account1.WalletAddress), From: account.FromAddress(utils.TestConfig.Account1.WalletAddress),
To: account.GethToAddress(utils.TestConfig.Account2.WalletAddress), To: account.ToAddress(utils.TestConfig.Account2.WalletAddress),
Value: (*hexutil.Big)(big.NewInt(1000000000000)), Value: (*hexutil.Big)(big.NewInt(1000000000000)),
}, utils.TestConfig.Account1.Password) }, utils.TestConfig.Account1.Password)
s.NoError(err) s.NoError(err)
@ -257,8 +257,8 @@ func (s *TransactionsTestSuite) TestSendEtherTxUpstream() {
defer s.LogoutAndStop() defer s.LogoutAndStop()
hash, err := s.Backend.SendTransaction(transactions.SendTxArgs{ hash, err := s.Backend.SendTransaction(transactions.SendTxArgs{
From: account.GethFromAddress(utils.TestConfig.Account1.WalletAddress), From: account.FromAddress(utils.TestConfig.Account1.WalletAddress),
To: account.GethToAddress(utils.TestConfig.Account2.WalletAddress), To: account.ToAddress(utils.TestConfig.Account2.WalletAddress),
GasPrice: (*hexutil.Big)(big.NewInt(28000000000)), GasPrice: (*hexutil.Big)(big.NewInt(28000000000)),
Value: (*hexutil.Big)(big.NewInt(1000000000000)), Value: (*hexutil.Big)(big.NewInt(1000000000000)),
}, utils.TestConfig.Account1.Password) }, utils.TestConfig.Account1.Password)

View File

@ -5,21 +5,21 @@ package transactions
import ( import (
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/status-im/status-go/eth-node/types"
) )
// AddrLocker provides locks for addresses // AddrLocker provides locks for addresses
type AddrLocker struct { type AddrLocker struct {
mu sync.Mutex mu sync.Mutex
locks map[common.Address]*sync.Mutex locks map[types.Address]*sync.Mutex
} }
// lock returns the lock of the given address. // lock returns the lock of the given address.
func (l *AddrLocker) lock(address common.Address) *sync.Mutex { func (l *AddrLocker) lock(address types.Address) *sync.Mutex {
l.mu.Lock() l.mu.Lock()
defer l.mu.Unlock() defer l.mu.Unlock()
if l.locks == nil { if l.locks == nil {
l.locks = make(map[common.Address]*sync.Mutex) l.locks = make(map[types.Address]*sync.Mutex)
} }
if _, ok := l.locks[address]; !ok { if _, ok := l.locks[address]; !ok {
l.locks[address] = new(sync.Mutex) l.locks[address] = new(sync.Mutex)
@ -30,11 +30,11 @@ func (l *AddrLocker) lock(address common.Address) *sync.Mutex {
// LockAddr locks an account's mutex. This is used to prevent another tx getting the // LockAddr locks an account's mutex. This is used to prevent another tx getting the
// same nonce until the lock is released. The mutex prevents the (an identical nonce) from // same nonce until the lock is released. The mutex prevents the (an identical nonce) from
// being read again during the time that the first transaction is being signed. // being read again during the time that the first transaction is being signed.
func (l *AddrLocker) LockAddr(address common.Address) { func (l *AddrLocker) LockAddr(address types.Address) {
l.lock(address).Lock() l.lock(address).Lock()
} }
// UnlockAddr unlocks the mutex of the given account. // UnlockAddr unlocks the mutex of the given account.
func (l *AddrLocker) UnlockAddr(address common.Address) { func (l *AddrLocker) UnlockAddr(address types.Address) {
l.lock(address).Unlock() l.lock(address).Unlock()
} }

View File

@ -7,8 +7,9 @@ import (
ethereum "github.com/ethereum/go-ethereum" ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" gethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc"
) )
@ -57,12 +58,12 @@ func (w *rpcWrapper) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uin
// //
// If the transaction was a contract creation use the TransactionReceipt method to get the // If the transaction was a contract creation use the TransactionReceipt method to get the
// contract address after the transaction has been mined. // contract address after the transaction has been mined.
func (w *rpcWrapper) SendTransaction(ctx context.Context, tx *types.Transaction) error { func (w *rpcWrapper) SendTransaction(ctx context.Context, tx *gethtypes.Transaction) error {
data, err := rlp.EncodeToBytes(tx) data, err := rlp.EncodeToBytes(tx)
if err != nil { if err != nil {
return err return err
} }
return w.rpcClient.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)) return w.rpcClient.CallContext(ctx, nil, "eth_sendRawTransaction", types.EncodeHex(data))
} }
func toCallArg(msg ethereum.CallMsg) interface{} { func toCallArg(msg ethereum.CallMsg) interface{} {
@ -71,7 +72,7 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
"to": msg.To, "to": msg.To,
} }
if len(msg.Data) > 0 { if len(msg.Data) > 0 {
arg["data"] = hexutil.Bytes(msg.Data) arg["data"] = types.HexBytes(msg.Data)
} }
if msg.Value != nil { if msg.Value != nil {
arg["value"] = (*hexutil.Big)(msg.Value) arg["value"] = (*hexutil.Big)(msg.Value)

View File

@ -10,12 +10,13 @@ import (
"time" "time"
ethereum "github.com/ethereum/go-ethereum" ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
gethtypes "github.com/ethereum/go-ethereum/core/types" gethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc"
) )
@ -170,9 +171,18 @@ func (t *Transactor) HashTransaction(args SendTxArgs) (validatedArgs SendTxArgs,
if args.Gas == nil { if args.Gas == nil {
ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
defer cancel() defer cancel()
var (
gethTo common.Address
gethToPtr *common.Address
)
if args.To != nil {
gethTo = common.Address(*args.To)
gethToPtr = &gethTo
}
gas, err = t.gasCalculator.EstimateGas(ctx, ethereum.CallMsg{ gas, err = t.gasCalculator.EstimateGas(ctx, ethereum.CallMsg{
From: args.From, From: common.Address(args.From),
To: args.To, To: gethToPtr,
GasPrice: gasPrice, GasPrice: gasPrice,
Value: value, Value: value,
Data: args.GetInput(), Data: args.GetInput(),
@ -238,7 +248,7 @@ func (t *Transactor) validateAndPropagate(selectedAccount *account.SelectedExtKe
}() }()
ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
defer cancel() defer cancel()
nonce, err = t.pendingNonceProvider.PendingNonceAt(ctx, args.From) nonce, err = t.pendingNonceProvider.PendingNonceAt(ctx, common.Address(args.From))
if err != nil { if err != nil {
return hash, err return hash, err
} }
@ -264,9 +274,18 @@ func (t *Transactor) validateAndPropagate(selectedAccount *account.SelectedExtKe
if args.Gas == nil { if args.Gas == nil {
ctx, cancel = context.WithTimeout(context.Background(), t.rpcCallTimeout) ctx, cancel = context.WithTimeout(context.Background(), t.rpcCallTimeout)
defer cancel() defer cancel()
var (
gethTo common.Address
gethToPtr *common.Address
)
if args.To != nil {
gethTo = common.Address(*args.To)
gethToPtr = &gethTo
}
gas, err = t.gasCalculator.EstimateGas(ctx, ethereum.CallMsg{ gas, err = t.gasCalculator.EstimateGas(ctx, ethereum.CallMsg{
From: args.From, From: common.Address(args.From),
To: args.To, To: gethToPtr,
GasPrice: gasPrice, GasPrice: gasPrice,
Value: value, Value: value,
Data: args.GetInput(), Data: args.GetInput(),
@ -282,14 +301,7 @@ func (t *Transactor) validateAndPropagate(selectedAccount *account.SelectedExtKe
gas = uint64(*args.Gas) gas = uint64(*args.Gas)
} }
var tx *gethtypes.Transaction tx := t.buildTransactionWithOverrides(nonce, value, gas, gasPrice, args)
if args.To != nil {
tx = gethtypes.NewTransaction(nonce, *args.To, value, gas, gasPrice, args.GetInput())
t.logNewTx(args, gas, gasPrice, value)
} else {
tx = gethtypes.NewContractCreation(nonce, value, gas, gasPrice, args.GetInput())
t.logNewContract(args, gas, gasPrice, value, nonce)
}
signedTx, err := gethtypes.SignTx(tx, gethtypes.NewEIP155Signer(chainID), selectedAccount.AccountKey.PrivateKey) signedTx, err := gethtypes.SignTx(tx, gethtypes.NewEIP155Signer(chainID), selectedAccount.AccountKey.PrivateKey)
if err != nil { if err != nil {
@ -310,10 +322,14 @@ func (t *Transactor) buildTransaction(args SendTxArgs) *gethtypes.Transaction {
gas := uint64(*args.Gas) gas := uint64(*args.Gas)
gasPrice := (*big.Int)(args.GasPrice) gasPrice := (*big.Int)(args.GasPrice)
return t.buildTransactionWithOverrides(nonce, value, gas, gasPrice, args)
}
func (t *Transactor) buildTransactionWithOverrides(nonce uint64, value *big.Int, gas uint64, gasPrice *big.Int, args SendTxArgs) *gethtypes.Transaction {
var tx *gethtypes.Transaction var tx *gethtypes.Transaction
if args.To != nil { if args.To != nil {
tx = gethtypes.NewTransaction(nonce, *args.To, value, gas, gasPrice, args.GetInput()) tx = gethtypes.NewTransaction(nonce, common.Address(*args.To), value, gas, gasPrice, args.GetInput())
t.logNewTx(args, gas, gasPrice, value) t.logNewTx(args, gas, gasPrice, value)
} else { } else {
tx = gethtypes.NewContractCreation(nonce, value, gas, gasPrice, args.GetInput()) tx = gethtypes.NewContractCreation(nonce, value, gas, gasPrice, args.GetInput())
@ -337,7 +353,7 @@ func (t *Transactor) getTransactionNonce(args SendTxArgs) (newNonce uint64, err
// get the remote nonce // get the remote nonce
ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
defer cancel() defer cancel()
remoteNonce, err = t.pendingNonceProvider.PendingNonceAt(ctx, args.From) remoteNonce, err = t.pendingNonceProvider.PendingNonceAt(ctx, common.Address(args.From))
if err != nil { if err != nil {
return newNonce, err return newNonce, err
} }

View File

@ -11,7 +11,6 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
@ -23,6 +22,7 @@ import (
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/contracts/ens/contract" "github.com/status-im/status-go/contracts/ens/contract"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc"
@ -104,7 +104,7 @@ func (s *TransactorSuite) setupTransactionPoolAPI(args SendTxArgs, returnNonce,
func (s *TransactorSuite) rlpEncodeTx(args SendTxArgs, config *params.NodeConfig, account *account.SelectedExtKey, nonce *hexutil.Uint64, gas hexutil.Uint64, gasPrice *big.Int) hexutil.Bytes { func (s *TransactorSuite) rlpEncodeTx(args SendTxArgs, config *params.NodeConfig, account *account.SelectedExtKey, nonce *hexutil.Uint64, gas hexutil.Uint64, gasPrice *big.Int) hexutil.Bytes {
newTx := gethtypes.NewTransaction( newTx := gethtypes.NewTransaction(
uint64(*nonce), uint64(*nonce),
*args.To, common.Address(*args.To),
args.Value.ToInt(), args.Value.ToInt(),
uint64(gas), uint64(gas),
gasPrice, gasPrice,
@ -122,7 +122,7 @@ func (s *TransactorSuite) TestGasValues() {
key, _ := gethcrypto.GenerateKey() key, _ := gethcrypto.GenerateKey()
selectedAccount := &account.SelectedExtKey{ selectedAccount := &account.SelectedExtKey{
Address: account.FromAddress(utils.TestConfig.Account1.WalletAddress), Address: account.FromAddress(utils.TestConfig.Account1.WalletAddress),
AccountKey: &keystore.Key{PrivateKey: key}, AccountKey: &types.Key{PrivateKey: key},
} }
testCases := []struct { testCases := []struct {
name string name string
@ -155,8 +155,8 @@ func (s *TransactorSuite) TestGasValues() {
s.T().Run(testCase.name, func(t *testing.T) { s.T().Run(testCase.name, func(t *testing.T) {
s.SetupTest() s.SetupTest()
args := SendTxArgs{ args := SendTxArgs{
From: account.GethFromAddress(utils.TestConfig.Account1.WalletAddress), From: account.FromAddress(utils.TestConfig.Account1.WalletAddress),
To: account.GethToAddress(utils.TestConfig.Account2.WalletAddress), To: account.ToAddress(utils.TestConfig.Account2.WalletAddress),
Gas: testCase.gas, Gas: testCase.gas,
GasPrice: testCase.gasPrice, GasPrice: testCase.gasPrice,
} }
@ -171,10 +171,10 @@ func (s *TransactorSuite) TestGasValues() {
func (s *TransactorSuite) TestArgsValidation() { func (s *TransactorSuite) TestArgsValidation() {
args := SendTxArgs{ args := SendTxArgs{
From: account.GethFromAddress(utils.TestConfig.Account1.WalletAddress), From: account.FromAddress(utils.TestConfig.Account1.WalletAddress),
To: account.GethToAddress(utils.TestConfig.Account2.WalletAddress), To: account.ToAddress(utils.TestConfig.Account2.WalletAddress),
Data: hexutil.Bytes([]byte{0x01, 0x02}), Data: types.HexBytes([]byte{0x01, 0x02}),
Input: hexutil.Bytes([]byte{0x02, 0x01}), Input: types.HexBytes([]byte{0x02, 0x01}),
} }
s.False(args.Valid()) s.False(args.Valid())
selectedAccount := &account.SelectedExtKey{ selectedAccount := &account.SelectedExtKey{
@ -186,8 +186,8 @@ func (s *TransactorSuite) TestArgsValidation() {
func (s *TransactorSuite) TestAccountMismatch() { func (s *TransactorSuite) TestAccountMismatch() {
args := SendTxArgs{ args := SendTxArgs{
From: account.GethFromAddress(utils.TestConfig.Account1.WalletAddress), From: account.FromAddress(utils.TestConfig.Account1.WalletAddress),
To: account.GethToAddress(utils.TestConfig.Account2.WalletAddress), To: account.ToAddress(utils.TestConfig.Account2.WalletAddress),
} }
var err error var err error
@ -216,14 +216,14 @@ func (s *TransactorSuite) TestLocalNonce() {
key, _ := gethcrypto.GenerateKey() key, _ := gethcrypto.GenerateKey()
selectedAccount := &account.SelectedExtKey{ selectedAccount := &account.SelectedExtKey{
Address: account.FromAddress(utils.TestConfig.Account1.WalletAddress), Address: account.FromAddress(utils.TestConfig.Account1.WalletAddress),
AccountKey: &keystore.Key{PrivateKey: key}, AccountKey: &types.Key{PrivateKey: key},
} }
nonce := hexutil.Uint64(0) nonce := hexutil.Uint64(0)
for i := 0; i < txCount; i++ { for i := 0; i < txCount; i++ {
args := SendTxArgs{ args := SendTxArgs{
From: account.GethFromAddress(utils.TestConfig.Account1.WalletAddress), From: account.FromAddress(utils.TestConfig.Account1.WalletAddress),
To: account.GethToAddress(utils.TestConfig.Account2.WalletAddress), To: account.ToAddress(utils.TestConfig.Account2.WalletAddress),
} }
s.setupTransactionPoolAPI(args, nonce, hexutil.Uint64(i), selectedAccount, nil) s.setupTransactionPoolAPI(args, nonce, hexutil.Uint64(i), selectedAccount, nil)
@ -235,8 +235,8 @@ func (s *TransactorSuite) TestLocalNonce() {
nonce = hexutil.Uint64(5) nonce = hexutil.Uint64(5)
args := SendTxArgs{ args := SendTxArgs{
From: account.GethFromAddress(utils.TestConfig.Account1.WalletAddress), From: account.FromAddress(utils.TestConfig.Account1.WalletAddress),
To: account.GethToAddress(utils.TestConfig.Account2.WalletAddress), To: account.ToAddress(utils.TestConfig.Account2.WalletAddress),
} }
s.setupTransactionPoolAPI(args, nonce, nonce, selectedAccount, nil) s.setupTransactionPoolAPI(args, nonce, nonce, selectedAccount, nil)
@ -250,8 +250,8 @@ func (s *TransactorSuite) TestLocalNonce() {
testErr := errors.New("test") testErr := errors.New("test")
s.txServiceMock.EXPECT().GetTransactionCount(gomock.Any(), gomock.Eq(common.Address(selectedAccount.Address)), gethrpc.PendingBlockNumber).Return(nil, testErr) s.txServiceMock.EXPECT().GetTransactionCount(gomock.Any(), gomock.Eq(common.Address(selectedAccount.Address)), gethrpc.PendingBlockNumber).Return(nil, testErr)
args = SendTxArgs{ args = SendTxArgs{
From: account.GethFromAddress(utils.TestConfig.Account1.WalletAddress), From: account.FromAddress(utils.TestConfig.Account1.WalletAddress),
To: account.GethToAddress(utils.TestConfig.Account2.WalletAddress), To: account.ToAddress(utils.TestConfig.Account2.WalletAddress),
} }
_, err = s.manager.SendTransaction(args, selectedAccount) _, err = s.manager.SendTransaction(args, selectedAccount)
@ -269,14 +269,14 @@ func (s *TransactorSuite) TestContractCreation() {
backend := backends.NewSimulatedBackend(genesis, math.MaxInt64) backend := backends.NewSimulatedBackend(genesis, math.MaxInt64)
selectedAccount := &account.SelectedExtKey{ selectedAccount := &account.SelectedExtKey{
Address: types.Address(testaddr), Address: types.Address(testaddr),
AccountKey: &keystore.Key{PrivateKey: key}, AccountKey: &types.Key{PrivateKey: key},
} }
s.manager.sender = backend s.manager.sender = backend
s.manager.gasCalculator = backend s.manager.gasCalculator = backend
s.manager.pendingNonceProvider = backend s.manager.pendingNonceProvider = backend
tx := SendTxArgs{ tx := SendTxArgs{
From: testaddr, From: types.Address(testaddr),
Input: hexutil.Bytes(common.FromHex(contract.ENSBin)), Input: types.FromHex(contract.ENSBin),
} }
hash, err := s.manager.SendTransaction(tx, selectedAccount) hash, err := s.manager.SendTransaction(tx, selectedAccount)
@ -288,9 +288,9 @@ func (s *TransactorSuite) TestContractCreation() {
} }
func (s *TransactorSuite) TestSendTransactionWithSignature() { func (s *TransactorSuite) TestSendTransactionWithSignature() {
privKey, err := gethcrypto.GenerateKey() privKey, err := crypto.GenerateKey()
s.Require().NoError(err) s.Require().NoError(err)
address := gethcrypto.PubkeyToAddress(privKey.PublicKey) address := crypto.PubkeyToAddress(privKey.PublicKey)
scenarios := []struct { scenarios := []struct {
localNonce hexutil.Uint64 localNonce hexutil.Uint64
@ -340,7 +340,7 @@ func (s *TransactorSuite) TestSendTransactionWithSignature() {
// simulate transaction signed externally // simulate transaction signed externally
signer := gethtypes.NewEIP155Signer(chainID) signer := gethtypes.NewEIP155Signer(chainID)
tx := gethtypes.NewTransaction(uint64(nonce), to, (*big.Int)(value), uint64(gas), (*big.Int)(gasPrice), data) tx := gethtypes.NewTransaction(uint64(nonce), common.Address(to), (*big.Int)(value), uint64(gas), (*big.Int)(gasPrice), data)
hash := signer.Hash(tx) hash := signer.Hash(tx)
sig, err := gethcrypto.Sign(hash[:], privKey) sig, err := gethcrypto.Sign(hash[:], privKey)
s.Require().NoError(err) s.Require().NoError(err)
@ -350,7 +350,7 @@ func (s *TransactorSuite) TestSendTransactionWithSignature() {
s.Require().NoError(err) s.Require().NoError(err)
s.txServiceMock.EXPECT(). s.txServiceMock.EXPECT().
GetTransactionCount(gomock.Any(), address, gethrpc.PendingBlockNumber). GetTransactionCount(gomock.Any(), common.Address(address), gethrpc.PendingBlockNumber).
Return(&scenario.localNonce, nil) Return(&scenario.localNonce, nil)
if !scenario.expectError { if !scenario.expectError {
@ -382,9 +382,9 @@ func (s *TransactorSuite) TestSendTransactionWithSignature_InvalidSignature() {
} }
func (s *TransactorSuite) TestHashTransaction() { func (s *TransactorSuite) TestHashTransaction() {
privKey, err := gethcrypto.GenerateKey() privKey, err := crypto.GenerateKey()
s.Require().NoError(err) s.Require().NoError(err)
address := gethcrypto.PubkeyToAddress(privKey.PublicKey) address := crypto.PubkeyToAddress(privKey.PublicKey)
remoteNonce := hexutil.Uint64(1) remoteNonce := hexutil.Uint64(1)
txNonce := hexutil.Uint64(0) txNonce := hexutil.Uint64(0)
@ -405,7 +405,7 @@ func (s *TransactorSuite) TestHashTransaction() {
} }
s.txServiceMock.EXPECT(). s.txServiceMock.EXPECT().
GetTransactionCount(gomock.Any(), address, gethrpc.PendingBlockNumber). GetTransactionCount(gomock.Any(), common.Address(address), gethrpc.PendingBlockNumber).
Return(&remoteNonce, nil) Return(&remoteNonce, nil)
newArgs, hash, err := s.manager.HashTransaction(args) newArgs, hash, err := s.manager.HashTransaction(args)

View File

@ -8,6 +8,7 @@ import (
ethereum "github.com/ethereum/go-ethereum" ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/eth-node/types"
) )
var ( var (
@ -36,8 +37,8 @@ type GasCalculator interface {
// This struct is based on go-ethereum's type in internal/ethapi/api.go, but we have freedom // This struct is based on go-ethereum's type in internal/ethapi/api.go, but we have freedom
// over the exact layout of this struct. // over the exact layout of this struct.
type SendTxArgs struct { type SendTxArgs struct {
From common.Address `json:"from"` From types.Address `json:"from"`
To *common.Address `json:"to"` To *types.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"` Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"` GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"` Value *hexutil.Big `json:"value"`
@ -45,8 +46,8 @@ type SendTxArgs struct {
// We keep both "input" and "data" for backward compatibility. // We keep both "input" and "data" for backward compatibility.
// "input" is a preferred field. // "input" is a preferred field.
// see `vendor/github.com/ethereum/go-ethereum/internal/ethapi/api.go:1107` // see `vendor/github.com/ethereum/go-ethereum/internal/ethapi/api.go:1107`
Input hexutil.Bytes `json:"input"` Input types.HexBytes `json:"input"`
Data hexutil.Bytes `json:"data"` Data types.HexBytes `json:"data"`
} }
// Valid checks whether this structure is filled in correctly. // Valid checks whether this structure is filled in correctly.
@ -61,7 +62,7 @@ func (args SendTxArgs) Valid() bool {
} }
// GetInput returns either Input or Data field's value dependent on what is filled. // GetInput returns either Input or Data field's value dependent on what is filled.
func (args SendTxArgs) GetInput() hexutil.Bytes { func (args SendTxArgs) GetInput() types.HexBytes {
if !isNilOrEmpty(args.Input) { if !isNilOrEmpty(args.Input) {
return args.Input return args.Input
} }
@ -69,6 +70,6 @@ func (args SendTxArgs) GetInput() hexutil.Bytes {
return args.Data return args.Data
} }
func isNilOrEmpty(bytes hexutil.Bytes) bool { func isNilOrEmpty(bytes types.HexBytes) bool {
return bytes == nil || len(bytes) == 0 return bytes == nil || len(bytes) == 0
} }

View File

@ -3,17 +3,17 @@ package transactions
import ( import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/status-im/status-go/eth-node/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestSendTxArgsValidity(t *testing.T) { func TestSendTxArgsValidity(t *testing.T) {
// 1. If only data fields is set, valid and return data // 1. If only data fields is set, valid and return data
bytes1 := hexutil.Bytes([]byte{0xAA, 0xBB, 0xCC, 0xDD}) bytes1 := types.HexBytes([]byte{0xAA, 0xBB, 0xCC, 0xDD})
bytes2 := hexutil.Bytes([]byte{0x00, 0x01, 0x02}) bytes2 := types.HexBytes([]byte{0x00, 0x01, 0x02})
bytesEmpty := hexutil.Bytes([]byte{}) bytesEmpty := types.HexBytes([]byte{})
doSendTxValidityTest(t, SendTxArgs{}, true, nil) doSendTxValidityTest(t, SendTxArgs{}, true, nil)
doSendTxValidityTest(t, SendTxArgs{Input: bytes1}, true, bytes1) doSendTxValidityTest(t, SendTxArgs{Input: bytes1}, true, bytes1)
@ -25,7 +25,7 @@ func TestSendTxArgsValidity(t *testing.T) {
doSendTxValidityTest(t, SendTxArgs{Input: bytesEmpty, Data: bytesEmpty}, true, bytesEmpty) doSendTxValidityTest(t, SendTxArgs{Input: bytesEmpty, Data: bytesEmpty}, true, bytesEmpty)
} }
func doSendTxValidityTest(t *testing.T, args SendTxArgs, expectValid bool, expectValue hexutil.Bytes) { func doSendTxValidityTest(t *testing.T, args SendTxArgs, expectValid bool, expectValue types.HexBytes) {
assert.Equal(t, expectValid, args.Valid(), "Valid() returned unexpected value") assert.Equal(t, expectValid, args.Valid(), "Valid() returned unexpected value")
if expectValid { if expectValid {
assert.Equal(t, expectValue, args.GetInput(), "GetInput() returned unexpected value") assert.Equal(t, expectValue, args.GetInput(), "GetInput() returned unexpected value")

View File

@ -0,0 +1,15 @@
// Imported from github.com/ethereum/go-ethereum/accounts/keystore/keystore.go
package keystore
import (
"errors"
)
const (
version = 3
)
var (
ErrDecrypt = errors.New("could not decrypt key with given password")
)

View File

@ -0,0 +1,329 @@
// Imported from github.com/ethereum/go-ethereum/accounts/keystore/passphrase.go
// and github.com/ethereum/go-ethereum/accounts/keystore/presale.go
package keystore
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/pborman/uuid"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/extkeys"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
)
const (
keyHeaderKDF = "scrypt"
)
type encryptedKeyJSONV3 struct {
Address string `json:"address"`
Crypto CryptoJSON `json:"crypto"`
Id string `json:"id"`
Version int `json:"version"`
ExtendedKey CryptoJSON `json:"extendedkey"`
SubAccountIndex uint32 `json:"subaccountindex"`
}
type encryptedKeyJSONV1 struct {
Address string `json:"address"`
Crypto CryptoJSON `json:"crypto"`
Id string `json:"id"`
Version string `json:"version"`
}
type CryptoJSON struct {
Cipher string `json:"cipher"`
CipherText string `json:"ciphertext"`
CipherParams cipherparamsJSON `json:"cipherparams"`
KDF string `json:"kdf"`
KDFParams map[string]interface{} `json:"kdfparams"`
MAC string `json:"mac"`
}
type cipherparamsJSON struct {
IV string `json:"iv"`
}
// DecryptKey decrypts a key from a json blob, returning the private key itself.
func DecryptKey(keyjson []byte, auth string) (*types.Key, error) {
// Parse the json into a simple map to fetch the key version
m := make(map[string]interface{})
if err := json.Unmarshal(keyjson, &m); err != nil {
return nil, err
}
// Depending on the version try to parse one way or another
var (
keyBytes, keyId []byte
err error
extKeyBytes []byte
extKey *extkeys.ExtendedKey
)
subAccountIndex, ok := m["subaccountindex"].(float64)
if !ok {
subAccountIndex = 0
}
if version, ok := m["version"].(string); ok && version == "1" {
k := new(encryptedKeyJSONV1)
if err := json.Unmarshal(keyjson, k); err != nil {
return nil, err
}
keyBytes, keyId, err = decryptKeyV1(k, auth)
if err != nil {
return nil, err
}
extKey, err = extkeys.NewKeyFromString(extkeys.EmptyExtendedKeyString)
} else {
k := new(encryptedKeyJSONV3)
if err := json.Unmarshal(keyjson, k); err != nil {
return nil, err
}
keyBytes, keyId, err = decryptKeyV3(k, auth)
if err != nil {
return nil, err
}
extKeyBytes, err = decryptExtendedKey(k, auth)
if err != nil {
return nil, err
}
extKey, err = extkeys.NewKeyFromString(string(extKeyBytes))
}
// Handle any decryption errors and return the key
if err != nil {
return nil, err
}
key := crypto.ToECDSAUnsafe(keyBytes)
return &types.Key{
Id: uuid.UUID(keyId),
Address: crypto.PubkeyToAddress(key.PublicKey),
PrivateKey: key,
ExtendedKey: extKey,
SubAccountIndex: uint32(subAccountIndex),
}, nil
}
func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
if cryptoJson.Cipher != "aes-128-ctr" {
return nil, fmt.Errorf("Cipher not supported: %v", cryptoJson.Cipher)
}
mac, err := hex.DecodeString(cryptoJson.MAC)
if err != nil {
return nil, err
}
iv, err := hex.DecodeString(cryptoJson.CipherParams.IV)
if err != nil {
return nil, err
}
cipherText, err := hex.DecodeString(cryptoJson.CipherText)
if err != nil {
return nil, err
}
derivedKey, err := getKDFKey(cryptoJson, auth)
if err != nil {
return nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, ErrDecrypt
}
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
if err != nil {
return nil, err
}
return plainText, err
}
func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
if keyProtected.Version != version {
return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
}
keyId = uuid.Parse(keyProtected.Id)
plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
if err != nil {
return nil, nil, err
}
return plainText, keyId, err
}
func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
keyId = uuid.Parse(keyProtected.Id)
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
if err != nil {
return nil, nil, err
}
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
if err != nil {
return nil, nil, err
}
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
if err != nil {
return nil, nil, err
}
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
if err != nil {
return nil, nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, nil, ErrDecrypt
}
plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv)
if err != nil {
return nil, nil, err
}
return plainText, keyId, err
}
func decryptExtendedKey(keyProtected *encryptedKeyJSONV3, auth string) (plainText []byte, err error) {
if len(keyProtected.ExtendedKey.CipherText) == 0 {
return []byte(extkeys.EmptyExtendedKeyString), nil
}
if keyProtected.Version != version {
return nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
}
if keyProtected.ExtendedKey.Cipher != "aes-128-ctr" {
return nil, fmt.Errorf("Cipher not supported: %v", keyProtected.ExtendedKey.Cipher)
}
mac, err := hex.DecodeString(keyProtected.ExtendedKey.MAC)
if err != nil {
return nil, err
}
iv, err := hex.DecodeString(keyProtected.ExtendedKey.CipherParams.IV)
if err != nil {
return nil, err
}
cipherText, err := hex.DecodeString(keyProtected.ExtendedKey.CipherText)
if err != nil {
return nil, err
}
derivedKey, err := getKDFKey(keyProtected.ExtendedKey, auth)
if err != nil {
return nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, ErrDecrypt
}
plainText, err = aesCTRXOR(derivedKey[:16], cipherText, iv)
if err != nil {
return nil, err
}
return plainText, err
}
func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
authArray := []byte(auth)
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
if err != nil {
return nil, err
}
dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
if cryptoJSON.KDF == keyHeaderKDF {
n := ensureInt(cryptoJSON.KDFParams["n"])
r := ensureInt(cryptoJSON.KDFParams["r"])
p := ensureInt(cryptoJSON.KDFParams["p"])
return scrypt.Key(authArray, salt, n, r, p, dkLen)
} else if cryptoJSON.KDF == "pbkdf2" {
c := ensureInt(cryptoJSON.KDFParams["c"])
prf := cryptoJSON.KDFParams["prf"].(string)
if prf != "hmac-sha256" {
return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf)
}
key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
return key, nil
}
return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF)
}
// TODO: can we do without this when unmarshalling dynamic JSON?
// why do integers in KDF params end up as float64 and not int after
// unmarshal?
func ensureInt(x interface{}) int {
res, ok := x.(int)
if !ok {
res = int(x.(float64))
}
return res
}
func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
// AES-128 is selected due to size of encryptKey.
aesBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(aesBlock, iv)
outText := make([]byte, len(inText))
stream.XORKeyStream(outText, inText)
return outText, err
}
func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) {
aesBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
decrypter := cipher.NewCBCDecrypter(aesBlock, iv)
paddedPlaintext := make([]byte, len(cipherText))
decrypter.CryptBlocks(paddedPlaintext, cipherText)
plaintext := pkcs7Unpad(paddedPlaintext)
if plaintext == nil {
return nil, ErrDecrypt
}
return plaintext, err
}
// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
func pkcs7Unpad(in []byte) []byte {
if len(in) == 0 {
return nil
}
padding := in[len(in)-1]
if int(padding) > len(in) || padding > aes.BlockSize {
return nil
} else if padding == 0 {
return nil
}
for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
if in[i] != padding {
return nil
}
}
return in[:len(in)-int(padding)]
}

View File

@ -0,0 +1,8 @@
package types
// Account represents an Ethereum account located at a specific location defined
// by the optional URL field.
type Account struct {
Address Address `json:"address"` // Ethereum account address derived from the key
URL string `json:"url"` // Optional resource locator within a backend
}

View File

@ -0,0 +1,24 @@
package types
import (
"crypto/ecdsa"
"github.com/pborman/uuid"
"github.com/status-im/status-go/extkeys"
)
type Key struct {
Id uuid.UUID // Version 4 "random" for unique id not derived from key data
// to simplify lookups we also store the address
Address Address
// we only store privkey as pubkey/address can be derived from it
// privkey in this struct is always in plaintext
PrivateKey *ecdsa.PrivateKey
// ExtendedKey is the extended key of the PrivateKey itself, and it's used
// to derive child keys.
ExtendedKey *extkeys.ExtendedKey
// SubAccountIndex is DEPRECATED
// It was use in Status to keep track of the number of sub-account created
// before having multi-account support.
SubAccountIndex uint32
}

View File

@ -0,0 +1,26 @@
package types
import (
"crypto/ecdsa"
"github.com/status-im/status-go/extkeys"
)
type KeyStore interface {
// ImportAccount imports the account specified with privateKey.
ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (Account, error)
// ImportSingleExtendedKey imports an extended key setting it in both the PrivateKey and ExtendedKey fields
// of the Key struct.
// ImportExtendedKey is used in older version of Status where PrivateKey is set to be the BIP44 key at index 0,
// and ExtendedKey is the extended key of the BIP44 key at index 1.
ImportSingleExtendedKey(extKey *extkeys.ExtendedKey, passphrase string) (Account, error)
// ImportExtendedKeyForPurpose stores ECDSA key (obtained from extended key) along with CKD#2 (root for sub-accounts)
// If key file is not found, it is created. Key is encrypted with the given passphrase.
// Deprecated: status-go is now using ImportSingleExtendedKey
ImportExtendedKeyForPurpose(keyPurpose extkeys.KeyPurpose, extKey *extkeys.ExtendedKey, passphrase string) (Account, error)
// AccountDecryptedKey returns decrypted key for account (provided that password is correct).
AccountDecryptedKey(a Account, auth string) (Account, *Key, error)
// Delete deletes the key matched by account if the passphrase is correct.
// If the account contains no filename, the address must match a unique key.
Delete(a Account, auth string) error
}

1
vendor/modules.txt vendored
View File

@ -374,6 +374,7 @@ github.com/status-im/status-go/eth-node/bridge/geth
github.com/status-im/status-go/eth-node/bridge/geth/ens github.com/status-im/status-go/eth-node/bridge/geth/ens
github.com/status-im/status-go/eth-node/crypto github.com/status-im/status-go/eth-node/crypto
github.com/status-im/status-go/eth-node/crypto/ecies github.com/status-im/status-go/eth-node/crypto/ecies
github.com/status-im/status-go/eth-node/keystore
github.com/status-im/status-go/eth-node/types github.com/status-im/status-go/eth-node/types
github.com/status-im/status-go/eth-node/types/ens github.com/status-im/status-go/eth-node/types/ens
# github.com/status-im/status-go/extkeys v1.0.0 => ./extkeys # github.com/status-im/status-go/extkeys v1.0.0 => ./extkeys

View File

@ -116,7 +116,7 @@ func TestPeerLimiterHandlerWithWhitelisting(t *testing.T) {
LimitPerSecIP: 1, LimitPerSecIP: 1,
LimitPerSecPeerID: 1, LimitPerSecPeerID: 1,
WhitelistedIPs: []string{"<nil>"}, // no IP is represented as <nil> string WhitelistedIPs: []string{"<nil>"}, // no IP is represented as <nil> string
WhitelistedPeerIDs: []enode.ID{enode.ID{0xaa, 0xbb, 0xcc}}, WhitelistedPeerIDs: []enode.ID{{0xaa, 0xbb, 0xcc}},
}, h) }, h)
p := &Peer{ p := &Peer{
peer: p2p.NewPeer(enode.ID{0xaa, 0xbb, 0xcc}, "test-peer", nil), peer: p2p.NewPeer(enode.ID{0xaa, 0xbb, 0xcc}, "test-peer", nil),