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) {
account := Account{Wallet: true}
account := Account{Wallet: true, Type: AccountTypeGenerated}
require.True(t, account.IsWalletNonWatchOnlyAccount())
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.
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.

View File

@ -26,6 +26,7 @@ import (
"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/transfer"
"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/transactions"
@ -608,6 +609,18 @@ func (api *API) FetchChainIDForURL(ctx context.Context, rpcURL string) (*big.Int
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
func (api *API) WCPairSessionProposal(ctx context.Context, sessionProposalJSON string) (*wc.PairSessionResponse, error) {
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
func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string, hashedPassword string) (response *wc.SessionRequestResponse, err error) {
log.Debug("wallet.api.wc.SessionRequest", "request.len", len(sessionRequestJSON), "hashedPassword.len", len(hashedPassword))
func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string) (response *wc.SessionRequestResponse, err error) {
log.Debug("wallet.api.wc.SessionRequest", "request.len", len(sessionRequestJSON))
var request wc.SessionRequest
err = json.Unmarshal([]byte(sessionRequestJSON), &request)
@ -631,5 +644,5 @@ func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string,
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)
walletconnect := walletconnect.NewService(rpcClient.NetworkManager, accountsDB, transactor, gethManager, feed)
walletconnect := walletconnect.NewService(rpcClient.NetworkManager, accountsDB, transactor, gethManager, feed, config)
return &Service{
db: db,

View File

@ -1,9 +1,15 @@
package walletconnect
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math/big"
"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/types"
"github.com/status-im/status-go/transactions"
@ -58,39 +64,75 @@ func (n *sendTransactionParams) MarshalJSON() ([]byte, error) {
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 {
return nil, ErrorInvalidParamsCount
}
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
}
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 {
return nil, err
}
// TODO: export it as a JSON parsable type
chainID, err := parseCaip2ChainID(request.Params.ChainID)
if err != nil {
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 {
return nil, err
}
return &SessionRequestResponse{
SessionRequest: request,
Signed: hash.Bytes(),
SignedMessage: hash,
}, 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 {
return nil, ErrorInvalidParamsCount
}
@ -100,7 +142,12 @@ func (s *Service) personalSign(request SessionRequest, hashedPassword string) (r
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 {
return nil, err
}
@ -112,15 +159,11 @@ func (s *Service) personalSign(request SessionRequest, hashedPassword string) (r
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{
SessionRequest: request,
Signed: types.HexBytes(sig),
KeyUID: account.KeyUID,
Address: account.Address,
AddressPath: account.Path,
SignOnKeycard: kp.MigratedToKeycard(),
MesageToSign: types.HexBytes(hash),
}, nil
}

View File

@ -3,15 +3,25 @@ package walletconnect
import (
"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/log"
"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/params"
"github.com/status-im/status-go/rpc/network"
"github.com/status-im/status-go/transactions"
)
type txSigningDetails struct {
chainID uint64
from common.Address
txBeingSigned *ethTypes.Transaction
}
type Service struct {
networkManager *network.Manager
accountsDB *accounts.Database
@ -19,18 +29,38 @@ type Service struct {
transactor *transactions.Transactor
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{
networkManager: networkManager,
accountsDB: accountsDB,
eventFeed: eventFeed,
transactor: transactor,
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) {
namespace := Namespace{
Methods: []string{params.SendTransactionMethodName, params.PersonalSignMethodName},
@ -55,7 +85,7 @@ func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionRes
// Filter out non-own accounts
usableAccounts := make([]*accounts.Account, 0, 1)
for _, acc := range activeAccounts {
if !acc.IsOwnAccount() || acc.Operable != accounts.AccountFullyOperable {
if !acc.IsWalletAccountReadyForTransaction() {
continue
}
usableAccounts = append(usableAccounts, acc)
@ -73,14 +103,14 @@ func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionRes
}, 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
// want to cache the paired sessions
if request.Params.Request.Method == params.SendTransactionMethodName {
return s.sendTransaction(request, hashedPassword)
return s.buildTransaction(request)
} else if request.Params.Request.Method == params.PersonalSignMethodName {
return s.personalSign(request, hashedPassword)
return s.buildPersonalSingMessage(request)
}
// TODO #12434: respond async

View File

@ -88,8 +88,12 @@ type SessionRequest struct {
}
type SessionRequestResponse struct {
SessionRequest SessionRequest `json:"sessionRequest"`
Signed types.HexBytes `json:"signed"`
KeyUID string `json:"keyUid,omitempty"`
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) {