Add methods to sign and recover messages/signatures to AccountManager

Also, make AccountManager a dependency of Messenger.
This is needed for community token permissions as we'll need a way to access wallet accounts
and sign messages when sending requests to join a community.

The APIs have been mostly taken from GethStatusBackend and personal service.
This commit is contained in:
Pascal Precht 2023-03-10 13:53:40 +01:00 committed by Follow the white rabbit
parent 612efb9b89
commit 6859a1d3b7
10 changed files with 139 additions and 8 deletions

View File

@ -1,6 +1,7 @@
package account
import (
"context"
"crypto/ecdsa"
"encoding/json"
"errors"
@ -10,17 +11,22 @@ import (
"path/filepath"
"strings"
"sync"
"time"
"github.com/google/uuid"
gethkeystore "github.com/ethereum/go-ethereum/accounts/keystore"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"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/keystore"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/extkeys"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
)
// errors
@ -32,6 +38,7 @@ var (
ErrOnboardingNotStarted = errors.New("onboarding must be started before choosing an account")
ErrOnboardingAccountNotFound = errors.New("cannot find onboarding account with the given id")
ErrAccountKeyStoreMissing = errors.New("account key store is not set")
ErrInvalidPersonalSignAccount = errors.New("invalid account as only the selected one can generate a signature")
)
type ErrCannotLocateKeyFile struct {
@ -44,11 +51,24 @@ func (e ErrCannotLocateKeyFile) Error() string {
var zeroAddress = types.Address{}
type SignParams struct {
Data interface{} `json:"data"`
Address string `json:"account"`
Password string `json:"password"`
}
type RecoverParams struct {
Message string `json:"message"`
Signature string `json:"signature"`
}
// Manager represents account manager interface.
type Manager struct {
mu sync.RWMutex
Keydir string
keystore types.KeyStore
mu sync.RWMutex
rpcClient *rpc.Client
rpcTimeout time.Duration
Keydir string
keystore types.KeyStore
accountsGenerator *generator.Generator
onboarding *Onboarding
@ -612,3 +632,96 @@ func (m *Manager) ReEncryptKeyStoreDir(keyDirPath, oldPass, newPass string) erro
func (m *Manager) DeleteAccount(address types.Address, password string) error {
return m.keystore.Delete(types.Account{Address: address}, password)
}
func (m *Manager) GetVerifiedWalletAccount(db *accounts.Database, address, password string) (*SelectedExtKey, error) {
exists, err := db.AddressExists(types.HexToAddress(address))
if err != nil {
return nil, err
}
if !exists {
return nil, errors.New("account doesn't exist")
}
key, err := m.VerifyAccountPassword(m.Keydir, address, password)
if _, ok := err.(*ErrCannotLocateKeyFile); ok {
key, err = m.generatePartialAccountKey(db, address, password)
if err != nil {
return nil, err
}
}
if err != nil {
return nil, err
}
return &SelectedExtKey{
Address: key.Address,
AccountKey: key,
}, nil
}
func (m *Manager) generatePartialAccountKey(db *accounts.Database, address string, password string) (*types.Key, error) {
dbPath, err := db.GetPath(types.HexToAddress(address))
path := "m/" + dbPath[strings.LastIndex(dbPath, "/")+1:]
if err != nil {
return nil, err
}
rootAddress, err := db.GetWalletRootAddress()
if err != nil {
return nil, err
}
info, err := m.AccountsGenerator().LoadAccount(rootAddress.Hex(), password)
if err != nil {
return nil, err
}
masterID := info.ID
accInfosMap, err := m.AccountsGenerator().StoreDerivedAccounts(masterID, password, []string{path})
if err != nil {
return nil, err
}
_, key, err := m.AddressToDecryptedAccount(accInfosMap[path].Address, password)
if err != nil {
return nil, err
}
return key, nil
}
func (m *Manager) Recover(rpcParams RecoverParams) (addr types.Address, err error) {
ctx, cancel := context.WithTimeout(context.Background(), m.rpcTimeout)
defer cancel()
var gethAddr gethcommon.Address
err = m.rpcClient.CallContextIgnoringLocalHandlers(
ctx,
&gethAddr,
m.rpcClient.UpstreamChainID,
params.PersonalRecoverMethodName,
rpcParams.Message, rpcParams.Signature)
addr = types.Address(gethAddr)
return
}
func (m *Manager) Sign(rpcParams SignParams, verifiedAccount *SelectedExtKey) (result types.HexBytes, err error) {
if !strings.EqualFold(rpcParams.Address, verifiedAccount.Address.Hex()) {
err = ErrInvalidPersonalSignAccount
return
}
ctx, cancel := context.WithTimeout(context.Background(), m.rpcTimeout)
defer cancel()
var gethResult hexutil.Bytes
err = m.rpcClient.CallContextIgnoringLocalHandlers(
ctx,
&gethResult,
m.rpcClient.UpstreamChainID,
params.PersonalSignMethodName,
rpcParams.Data, rpcParams.Address, rpcParams.Password)
result = types.HexBytes(gethResult)
return
}

View File

@ -1,9 +1,12 @@
package account
import (
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/status-im/status-go/account/generator"
"github.com/status-im/status-go/rpc"
)
// GethManager represents account manager interface.
@ -20,6 +23,11 @@ func NewGethManager() *GethManager {
return m
}
func (m *GethManager) SetRPCClient(rpcClient *rpc.Client, rpcTimeout time.Duration) {
m.Manager.rpcClient = rpcClient
m.Manager.rpcTimeout = rpcTimeout
}
// InitKeystore sets key manager and key store.
func (m *GethManager) InitKeystore(keydir string) error {
m.mu.Lock()

View File

@ -960,7 +960,7 @@ func (b *GethStatusBackend) startNode(config *params.NodeConfig) (err error) {
}); err != nil {
return
}
b.accountManager.SetRPCClient(b.statusNode.RPCClient(), rpc.DefaultCallTimeout)
signal.SendNodeStarted()
b.transactor.SetNetworkID(config.NetworkID)
@ -1456,7 +1456,7 @@ func (b *GethStatusBackend) injectAccountsIntoWakuService(w types.WakuKeyManager
}
if st != nil {
if err := st.InitProtocol(b.statusNode.GethNode().Config().Name, identity, b.appDB, b.statusNode.HTTPServer(), b.multiaccountsDB, acc, logutils.ZapLogger()); err != nil {
if err := st.InitProtocol(b.statusNode.GethNode().Config().Name, identity, b.appDB, b.statusNode.HTTPServer(), b.multiaccountsDB, acc, b.accountManager, logutils.ZapLogger()); err != nil {
return err
}
// Set initial connection state

View File

@ -226,6 +226,7 @@ func main() {
gethbridge.NewNodeBridge(backend.StatusNode().GethNode(), backend.StatusNode().WakuService(), backend.StatusNode().WakuV2Service()),
installationID.String(),
nil,
backend.AccountManager(),
options...,
)
if err != nil {

View File

@ -86,6 +86,7 @@ func (s *MessengerCommunitiesSuite) newMessengerWithOptions(shh types.Waku, priv
&testNode{shh: shh},
uuid.New().String(),
nil,
nil,
options...,
)
s.Require().NoError(err)

View File

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/appmetrics"
"github.com/status-im/status-go/connection"
@ -106,6 +107,7 @@ type Messenger struct {
pushNotificationClient *pushnotificationclient.Client
pushNotificationServer *pushnotificationserver.Server
communitiesManager *communities.Manager
accountsManager *account.GethManager
logger *zap.Logger
outputCSV bool
@ -238,6 +240,7 @@ func NewMessenger(
node types.Node,
installationID string,
peerStore *mailservers.PeerStore,
accountsManager *account.GethManager,
opts ...Option,
) (*Messenger, error) {
var messenger *Messenger
@ -434,6 +437,7 @@ func NewMessenger(
pushNotificationClient: pushNotificationClient,
pushNotificationServer: pushNotificationServer,
communitiesManager: communitiesManager,
accountsManager: accountsManager,
ensVerifier: ensVerifier,
featureFlags: c.featureFlags,
systemMessagesTranslations: c.systemMessagesTranslations,

View File

@ -100,6 +100,7 @@ func newMessengerWithKey(shh types.Waku, privateKey *ecdsa.PrivateKey, logger *z
&testNode{shh: shh},
uuid.New().String(),
nil,
nil,
options...,
)
if err != nil {

View File

@ -113,6 +113,7 @@ func (s *MessengerSyncSettingsSuite) newMessengerWithOptions(shh types.Waku, pri
&testNode{shh: shh},
uuid.New().String(),
nil,
nil,
options...,
)
s.Require().NoError(err)

View File

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/enode"
gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/connection"
"github.com/status-im/status-go/db"
coretypes "github.com/status-im/status-go/eth-node/core/types"
@ -107,7 +108,7 @@ func (s *Service) GetPeer(rawURL string) (*enode.Node, error) {
return enode.ParseV4(rawURL)
}
func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, db *sql.DB, httpServer *server.MediaServer, multiAccountDb *multiaccounts.Database, acc *multiaccounts.Account, logger *zap.Logger) error {
func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, db *sql.DB, httpServer *server.MediaServer, multiAccountDb *multiaccounts.Database, acc *multiaccounts.Account, accountManager *account.GethManager, logger *zap.Logger) error {
var err error
if !s.config.ShhextConfig.PFSEnabled {
return nil
@ -157,6 +158,7 @@ func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, db *
s.n,
s.config.ShhextConfig.InstallationID,
s.peerStore,
accountManager,
options...,
)
if err != nil {

View File

@ -137,7 +137,7 @@ func TestInitProtocol(t *testing.T) {
acc := &multiaccounts.Account{KeyUID: "0xdeadbeef"}
err = service.InitProtocol("Test", privateKey, sqlDB, nil, multiAccounts, acc, zap.NewNop())
err = service.InitProtocol("Test", privateKey, sqlDB, nil, multiAccounts, acc, nil, zap.NewNop())
require.NoError(t, err)
}
@ -202,7 +202,7 @@ func (s *ShhExtSuite) createAndAddNode() {
acc := &multiaccounts.Account{KeyUID: "0xdeadbeef"}
err = service.InitProtocol("Test", privateKey, sqlDB, nil, multiAccounts, acc, zap.NewNop())
err = service.InitProtocol("Test", privateKey, sqlDB, nil, multiAccounts, acc, nil, zap.NewNop())
s.NoError(err)
stack.RegisterLifecycle(service)