feat(walletconnect)_: support the tx and personal signing from within the app or keycard

This commit is contained in:
Sale Djenic 2023-11-17 16:28:37 +01:00 committed by saledjenic
parent 5555f98dd5
commit 5e2af9e4fa
8 changed files with 122 additions and 32 deletions

View File

@ -1 +1 @@
0.171.15 0.171.16

View File

@ -10,7 +10,7 @@ import (
) )
func TestIsOwnAccount(t *testing.T) { func TestIsOwnAccount(t *testing.T) {
account := Account{Wallet: true} account := Account{Wallet: true, Type: AccountTypeGenerated}
require.True(t, account.IsWalletNonWatchOnlyAccount()) require.True(t, account.IsWalletNonWatchOnlyAccount())
account = Account{ account = Account{

View File

@ -116,7 +116,7 @@ const (
// Returns true if an account is a wallet account that logged in user has a control over, otherwise returns false. // Returns true if an account is a wallet account that logged in user has a control over, otherwise returns false.
func (a *Account) IsWalletNonWatchOnlyAccount() bool { func (a *Account) IsWalletNonWatchOnlyAccount() bool {
return !a.Chat && a.Type != AccountTypeWatch return !a.Chat && len(a.Type) > 0 && a.Type != AccountTypeWatch
} }
// Returns true if an account is a wallet account that is ready for sending transactions, otherwise returns false. // Returns true if an account is a wallet account that is ready for sending transactions, otherwise returns false.

View File

@ -26,6 +26,7 @@ import (
"github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/transfer" "github.com/status-im/status-go/services/wallet/transfer"
"github.com/status-im/status-go/services/wallet/walletconnect"
wc "github.com/status-im/status-go/services/wallet/walletconnect" wc "github.com/status-im/status-go/services/wallet/walletconnect"
"github.com/status-im/status-go/services/wallet/walletevent" "github.com/status-im/status-go/services/wallet/walletevent"
"github.com/status-im/status-go/transactions" "github.com/status-im/status-go/transactions"
@ -608,6 +609,18 @@ func (api *API) FetchChainIDForURL(ctx context.Context, rpcURL string) (*big.Int
return client.ChainID(ctx) return client.ChainID(ctx)
} }
func (api *API) WCSignMessage(ctx context.Context, message types.HexBytes, address common.Address, password string) (string, error) {
log.Debug("wallet.api.wc.SignMessage", "message", message, "address", address, "password", password)
return api.s.walletConnect.SignMessage(message, address, password)
}
func (api *API) WCSendTransaction(signature string) (response *walletconnect.SessionRequestResponse, err error) {
log.Debug("wallet.api.wc.SendTransaction", "signature", signature)
return api.s.walletConnect.SendTransaction(signature)
}
// WCPairSessionProposal responds to "session_proposal" event // WCPairSessionProposal responds to "session_proposal" event
func (api *API) WCPairSessionProposal(ctx context.Context, sessionProposalJSON string) (*wc.PairSessionResponse, error) { func (api *API) WCPairSessionProposal(ctx context.Context, sessionProposalJSON string) (*wc.PairSessionResponse, error) {
log.Debug("wallet.api.wc.PairSessionProposal", "proposal.len", len(sessionProposalJSON)) log.Debug("wallet.api.wc.PairSessionProposal", "proposal.len", len(sessionProposalJSON))
@ -622,8 +635,8 @@ func (api *API) WCPairSessionProposal(ctx context.Context, sessionProposalJSON s
} }
// WCSessionRequest responds to "session_request" event // WCSessionRequest responds to "session_request" event
func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string, hashedPassword string) (response *wc.SessionRequestResponse, err error) { func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string) (response *wc.SessionRequestResponse, err error) {
log.Debug("wallet.api.wc.SessionRequest", "request.len", len(sessionRequestJSON), "hashedPassword.len", len(hashedPassword)) log.Debug("wallet.api.wc.SessionRequest", "request.len", len(sessionRequestJSON))
var request wc.SessionRequest var request wc.SessionRequest
err = json.Unmarshal([]byte(sessionRequestJSON), &request) err = json.Unmarshal([]byte(sessionRequestJSON), &request)
@ -631,5 +644,5 @@ func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string,
return nil, err return nil, err
} }
return api.s.walletConnect.SessionRequest(request, hashedPassword) return api.s.walletConnect.SessionRequest(request)
} }

View File

@ -137,7 +137,7 @@ func NewService(
activity := activity.NewService(db, tokenManager, collectiblesManager, feed) activity := activity.NewService(db, tokenManager, collectiblesManager, feed)
walletconnect := walletconnect.NewService(rpcClient.NetworkManager, accountsDB, transactor, gethManager, feed) walletconnect := walletconnect.NewService(rpcClient.NetworkManager, accountsDB, transactor, gethManager, feed, config)
return &Service{ return &Service{
db: db, db: db,

View File

@ -1,9 +1,15 @@
package walletconnect package walletconnect
import ( import (
"encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt"
"math/big"
"strings" "strings"
"github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"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/eth-node/types"
"github.com/status-im/status-go/transactions" "github.com/status-im/status-go/transactions"
@ -58,39 +64,75 @@ func (n *sendTransactionParams) MarshalJSON() ([]byte, error) {
return json.Marshal(n.SendTxArgs) return json.Marshal(n.SendTxArgs)
} }
func (s *Service) sendTransaction(request SessionRequest, hashedPassword string) (response *SessionRequestResponse, err error) { func (s *Service) buildTransaction(request SessionRequest) (response *SessionRequestResponse, err error) {
if len(request.Params.Request.Params) != 1 { if len(request.Params.Request.Params) != 1 {
return nil, ErrorInvalidParamsCount return nil, ErrorInvalidParamsCount
} }
var params sendTransactionParams var params sendTransactionParams
if err := json.Unmarshal(request.Params.Request.Params[0], &params); err != nil { if err = json.Unmarshal(request.Params.Request.Params[0], &params); err != nil {
return nil, err return nil, err
} }
acc, err := s.gethManager.GetVerifiedWalletAccount(s.accountsDB, params.From.Hex(), hashedPassword) account, err := s.accountsDB.GetAccountByAddress(params.From)
if err != nil {
return nil, fmt.Errorf("failed to get active account: %w", err)
}
kp, err := s.accountsDB.GetKeypairByKeyUID(account.KeyUID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO: export it as a JSON parsable type
chainID, err := parseCaip2ChainID(request.Params.ChainID) chainID, err := parseCaip2ChainID(request.Params.ChainID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
hash, err := s.transactor.SendTransactionWithChainID(chainID, params.SendTxArgs, acc) // In this case we can ignore `unlock` function received from `ValidateAndBuildTransaction` cause `Nonce`
// will be always set by the initiator of this transaction (by the dapp).
// Though we will need sort out completely that part since Nonce kept in the local cache is not the most recent one,
// instead of that we should always ask network what's the most recent known Nonce for the account.
// Logged issue to handle that: https://github.com/status-im/status-go/issues/4335
txBeingSigned, _, err := s.transactor.ValidateAndBuildTransaction(chainID, params.SendTxArgs)
if err != nil {
return nil, err
}
s.txSignDetails = &txSigningDetails{
from: common.Address(account.Address),
chainID: chainID,
txBeingSigned: txBeingSigned,
}
signer := ethTypes.NewLondonSigner(new(big.Int).SetUint64(s.txSignDetails.chainID))
return &SessionRequestResponse{
KeyUID: account.KeyUID,
Address: account.Address,
AddressPath: account.Path,
SignOnKeycard: kp.MigratedToKeycard(),
MesageToSign: signer.Hash(s.txSignDetails.txBeingSigned),
}, nil
}
func (s *Service) sendTransaction(signature string) (response *SessionRequestResponse, err error) {
if s.txSignDetails.txBeingSigned == nil {
return response, errors.New("no tx to sign")
}
signatureBytes, _ := hex.DecodeString(signature)
hash, err := s.transactor.SendBuiltTransactionWithSignature(s.txSignDetails.chainID, s.txSignDetails.txBeingSigned, signatureBytes)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &SessionRequestResponse{ return &SessionRequestResponse{
SessionRequest: request, SignedMessage: hash,
Signed: hash.Bytes(),
}, nil }, nil
} }
func (s *Service) personalSign(request SessionRequest, hashedPassword string) (response *SessionRequestResponse, err error) { func (s *Service) buildPersonalSingMessage(request SessionRequest) (response *SessionRequestResponse, err error) {
if len(request.Params.Request.Params) != 2 { if len(request.Params.Request.Params) != 2 {
return nil, ErrorInvalidParamsCount return nil, ErrorInvalidParamsCount
} }
@ -100,7 +142,12 @@ func (s *Service) personalSign(request SessionRequest, hashedPassword string) (r
return nil, err return nil, err
} }
acc, err := s.gethManager.GetVerifiedWalletAccount(s.accountsDB, address.Hex(), hashedPassword) account, err := s.accountsDB.GetAccountByAddress(address)
if err != nil {
return nil, fmt.Errorf("failed to get active account: %w", err)
}
kp, err := s.accountsDB.GetKeypairByKeyUID(account.KeyUID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -112,15 +159,11 @@ func (s *Service) personalSign(request SessionRequest, hashedPassword string) (r
hash := crypto.TextHash(dBytes) hash := crypto.TextHash(dBytes)
sig, err := crypto.Sign(hash, acc.AccountKey.PrivateKey)
if err != nil {
return nil, err
}
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
return &SessionRequestResponse{ return &SessionRequestResponse{
SessionRequest: request, KeyUID: account.KeyUID,
Signed: types.HexBytes(sig), Address: account.Address,
AddressPath: account.Path,
SignOnKeycard: kp.MigratedToKeycard(),
MesageToSign: types.HexBytes(hash),
}, nil }, nil
} }

View File

@ -3,15 +3,25 @@ package walletconnect
import ( import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"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/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc/network" "github.com/status-im/status-go/rpc/network"
"github.com/status-im/status-go/transactions" "github.com/status-im/status-go/transactions"
) )
type txSigningDetails struct {
chainID uint64
from common.Address
txBeingSigned *ethTypes.Transaction
}
type Service struct { type Service struct {
networkManager *network.Manager networkManager *network.Manager
accountsDB *accounts.Database accountsDB *accounts.Database
@ -19,18 +29,38 @@ type Service struct {
transactor *transactions.Transactor transactor *transactions.Transactor
gethManager *account.GethManager gethManager *account.GethManager
config *params.NodeConfig
txSignDetails *txSigningDetails
} }
func NewService(networkManager *network.Manager, accountsDB *accounts.Database, transactor *transactions.Transactor, gethManager *account.GethManager, eventFeed *event.Feed) *Service { func NewService(networkManager *network.Manager, accountsDB *accounts.Database, transactor *transactions.Transactor,
gethManager *account.GethManager, eventFeed *event.Feed, config *params.NodeConfig) *Service {
return &Service{ return &Service{
networkManager: networkManager, networkManager: networkManager,
accountsDB: accountsDB, accountsDB: accountsDB,
eventFeed: eventFeed, eventFeed: eventFeed,
transactor: transactor, transactor: transactor,
gethManager: gethManager, gethManager: gethManager,
config: config,
} }
} }
func (s *Service) SignMessage(message types.HexBytes, address common.Address, password string) (string, error) {
selectedAccount, err := s.gethManager.VerifyAccountPassword(s.config.KeyStoreDir, address.Hex(), password)
if err != nil {
return "", err
}
signature, err := crypto.Sign(message[:], selectedAccount.PrivateKey)
return types.EncodeHex(signature), err
}
func (s *Service) SendTransaction(signature string) (response *SessionRequestResponse, err error) {
return s.sendTransaction(signature)
}
func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionResponse, error) { func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionResponse, error) {
namespace := Namespace{ namespace := Namespace{
Methods: []string{params.SendTransactionMethodName, params.PersonalSignMethodName}, Methods: []string{params.SendTransactionMethodName, params.PersonalSignMethodName},
@ -55,7 +85,7 @@ func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionRes
// Filter out non-own accounts // Filter out non-own accounts
usableAccounts := make([]*accounts.Account, 0, 1) usableAccounts := make([]*accounts.Account, 0, 1)
for _, acc := range activeAccounts { for _, acc := range activeAccounts {
if !acc.IsOwnAccount() || acc.Operable != accounts.AccountFullyOperable { if !acc.IsWalletAccountReadyForTransaction() {
continue continue
} }
usableAccounts = append(usableAccounts, acc) usableAccounts = append(usableAccounts, acc)
@ -73,14 +103,14 @@ func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionRes
}, nil }, nil
} }
func (s *Service) SessionRequest(request SessionRequest, hashedPassword string) (response *SessionRequestResponse, err error) { func (s *Service) SessionRequest(request SessionRequest) (response *SessionRequestResponse, err error) {
// TODO #12434: should we check topic for validity? It might make sense if we // TODO #12434: should we check topic for validity? It might make sense if we
// want to cache the paired sessions // want to cache the paired sessions
if request.Params.Request.Method == params.SendTransactionMethodName { if request.Params.Request.Method == params.SendTransactionMethodName {
return s.sendTransaction(request, hashedPassword) return s.buildTransaction(request)
} else if request.Params.Request.Method == params.PersonalSignMethodName { } else if request.Params.Request.Method == params.PersonalSignMethodName {
return s.personalSign(request, hashedPassword) return s.buildPersonalSingMessage(request)
} }
// TODO #12434: respond async // TODO #12434: respond async

View File

@ -88,8 +88,12 @@ type SessionRequest struct {
} }
type SessionRequestResponse struct { type SessionRequestResponse struct {
SessionRequest SessionRequest `json:"sessionRequest"` KeyUID string `json:"keyUid,omitempty"`
Signed types.HexBytes `json:"signed"` Address types.Address `json:"address,omitempty"`
AddressPath string `json:"addressPath,omitempty"`
SignOnKeycard bool `json:"signOnKeycard,omitempty"`
MesageToSign interface{} `json:"messageToSign,omitempty"`
SignedMessage interface{} `json:"signedMessage,omitempty"`
} }
func sessionProposalToSupportedChain(caipChains []string, supportsChain func(uint64) bool) (chains []uint64, eipChains []string) { func sessionProposalToSupportedChain(caipChains []string, supportsChain func(uint64) bool) (chains []uint64, eipChains []string) {