feat(walletconnect)_: support the tx and personal signing from within the app or keycard
This commit is contained in:
parent
5555f98dd5
commit
5e2af9e4fa
|
@ -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{
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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], ¶ms); err != nil {
|
if err = json.Unmarshal(request.Params.Request.Params[0], ¶ms); 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue