Abstract `accounts.Key` and geth `keystore`
This commit is contained in:
parent
608de7fa2d
commit
287e5cdf79
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
)
|
|
@ -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)]
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
15
vendor/github.com/status-im/status-go/eth-node/keystore/keystore.go
generated
vendored
Normal file
15
vendor/github.com/status-im/status-go/eth-node/keystore/keystore.go
generated
vendored
Normal 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")
|
||||||
|
)
|
329
vendor/github.com/status-im/status-go/eth-node/keystore/passphrase.go
generated
vendored
Normal file
329
vendor/github.com/status-im/status-go/eth-node/keystore/passphrase.go
generated
vendored
Normal 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)]
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
|
Loading…
Reference in New Issue