fix: keycard signing integration into wallet connect flows

Fixes: #15957
This commit is contained in:
Sale Djenic 2024-08-09 16:00:28 +02:00 committed by saledjenic
parent 03e75e9532
commit 20f30a52fd
11 changed files with 407 additions and 259 deletions

View File

@ -175,7 +175,7 @@ proc newModule*(
result.collectibleDetailsController = collectible_detailsc.newController(int32(backend_collectibles.CollectiblesRequestID.WalletAccount), networkService, events) result.collectibleDetailsController = collectible_detailsc.newController(int32(backend_collectibles.CollectiblesRequestID.WalletAccount), networkService, events)
result.filter = initFilter(result.controller) result.filter = initFilter(result.controller)
result.walletConnectService = wc_service.newService(result.events, result.threadpool, settingsService, transactionService) result.walletConnectService = wc_service.newService(result.events, result.threadpool, settingsService, transactionService, keycardService)
result.walletConnectController = wc_controller.newController(result.walletConnectService, walletAccountService) result.walletConnectController = wc_controller.newController(result.walletConnectService, walletAccountService)
result.dappsConnectorService = connector_service.newService(result.events) result.dappsConnectorService = connector_service.newService(result.events)

View File

@ -1,6 +1,8 @@
import NimQml import NimQml
import chronicles, times, json import chronicles, times, json
import app/global/global_singleton
import app_service/common/utils
import app_service/service/wallet_connect/service as wallet_connect_service import app_service/service/wallet_connect/service as wallet_connect_service
import app_service/service/wallet_account/service as wallet_account_service import app_service/service/wallet_account/service as wallet_account_service
@ -9,6 +11,8 @@ import helpers
logScope: logScope:
topics = "wallet-connect-controller" topics = "wallet-connect-controller"
type
SigningCallbackFn* = proc(topic: string, id: string, keyUid: string, address: string, signature: string)
QtObject: QtObject:
type type
@ -27,6 +31,10 @@ QtObject:
result.QObject.setup result.QObject.setup
## signals emitted by this controller
proc userAuthenticationResult*(self: Controller, topic: string, id: string, error: bool, password: string, pin: string, payload: string) {.signal.}
proc signingResultReceived*(self: Controller, topic: string, id: string, data: string) {.signal.}
proc addWalletConnectSession*(self: Controller, session_json: string): bool {.slot.} = proc addWalletConnectSession*(self: Controller, session_json: string): bool {.slot.} =
return self.service.addSession(session_json) return self.service.addSession(session_json)
@ -41,7 +49,7 @@ QtObject:
# Emits signal dappsListReceived with the list of dApps # Emits signal dappsListReceived with the list of dApps
proc getDapps*(self: Controller): bool {.slot.} = proc getDapps*(self: Controller): bool {.slot.} =
let res = self.service.getDapps() let res = self.service.getDapps()
if res == "": if res.len == 0:
return false return false
else: else:
self.dappsListReceived(res) self.dappsListReceived(res)
@ -61,32 +69,128 @@ QtObject:
self.activeSessionsReceived(resultStr) self.activeSessionsReceived(resultStr)
return true return true
proc userAuthenticationResult*(self: Controller, topic: string, id: string, error: bool, password: string, pin: string, payload: string) {.signal.}
# Beware, it will fail if an authentication is already in progress # Beware, it will fail if an authentication is already in progress
proc authenticateUser*(self: Controller, topic: string, id: string, address: string, payload: string): bool {.slot.} = proc authenticateUser*(self: Controller, topic: string, id: string, address: string, payload: string): bool {.slot.} =
let acc = self.walletAccountService.getAccountByAddress(address) let keypair = self.walletAccountService.getKeypairByAccountAddress(address)
if acc.keyUid == "": if keypair.isNil:
return false return false
var keyUid = singletonInstance.userProfile.getKeyUid()
return self.service.authenticateUser(acc.keyUid, proc(password: string, pin: string, success: bool) = if keypair.migratedToKeycard():
self.userAuthenticationResult(topic, id, success, password, pin, payload) keyUid = keypair.keyUid
return self.service.authenticateUser(keyUid, proc(receivedKeyUid: string, password: string, pin: string) =
if receivedKeyUid.len == 0 or receivedKeyUid != keyUid or password.len == 0:
self.userAuthenticationResult(topic, id, false, "", "", "")
return
self.userAuthenticationResult(topic, id, true, password, pin, payload)
) )
proc signMessageUnsafe*(self: Controller, address: string, password: string, message: string): string {.slot.} = proc signOnKeycard(self: Controller, address: string): bool =
return self.service.signMessageUnsafe(address, password, message) let keypair = self.walletAccountService.getKeypairByAccountAddress(address)
if keypair.isNil:
raise newException(CatchableError, "cannot resolve keypair for address: " & address)
return keypair.migratedToKeycard()
proc signMessage*(self: Controller, address: string, password: string, message: string): string {.slot.} = proc preparePassword(self: Controller, password: string): string =
return self.service.signMessage(address, password, message) if singletonInstance.userProfile.getIsKeycardUser():
return password
return hashPassword(password)
proc safeSignTypedData*(self: Controller, address: string, password: string, typedDataJson: string, chainId: int, legacy: bool): string {.slot.} = proc signMessageWithCallback(self: Controller, topic: string, id: string, address: string, message: string, password: string,
return self.service.safeSignTypedData(address, password, typedDataJson, chainId, legacy) pin: string, callback: SigningCallbackFn) =
var res = ""
try:
if message.len == 0:
raise newException(CatchableError, "message is empty")
if self.signOnKeycard(address):
let acc = self.walletAccountService.getAccountByAddress(address)
if acc.isNil:
raise newException(CatchableError, "cannot resolve account for address: " & address)
if not self.service.runSigningOnKeycard(
acc.keyUid,
acc.path,
singletonInstance.utils.removeHexPrefix(message),
pin,
proc(keyUid: string, signature: string) =
if keyUid.len == 0 or keyUid != acc.keyUid or signature.len == 0:
raise newException(CatchableError, "keycard signing failed")
callback(topic, id, keyUid, address, signature)
):
raise newException(CatchableError, "runSigningOnKeycard failed")
debug "signMessageWithCallback: signing on keycard started successfully"
return
let finalPassword = self.preparePassword(password)
res = self.service.signMessage(address, finalPassword, message)
except Exception as e:
error "signMessageWithCallback failed: ", msg=e.msg
callback(topic, id, "", address, res)
proc signTransaction*(self: Controller, address: string, chainId: int, password: string, txJson: string): string {.slot.} = proc signMessage*(self: Controller, topic: string, id: string, address: string, message: string, password: string, pin: string) {.slot.} =
return self.service.signTransaction(address, chainId, password, txJson) var res = ""
try:
if message.len == 0:
raise newException(CatchableError, "message is empty")
let hashedMessage = self.service.hashMessageEIP191(message)
if hashedMessage.len == 0:
raise newException(CatchableError, "hashMessageEIP191 failed")
self.signMessageWithCallback(topic, id, address, hashedMessage, password, pin,
proc (topic: string, id: string, keyUid: string, address: string, signature: string) =
self.signingResultReceived(topic, id, signature)
)
except Exception as e:
error "signMessage failed: ", msg=e.msg
self.signingResultReceived(topic, id, res)
proc sendTransaction*(self: Controller, address: string, chainId: int, password: string, txJson: string): string {.slot.} = proc signMessageUnsafe*(self: Controller, topic: string, id: string, address: string, message: string, password: string, pin: string) {.slot.} =
return self.service.sendTransaction(address, chainId, password, txJson) self.signMessage(topic, id, address, message, password, pin)
proc safeSignTypedData*(self: Controller, topic: string, id: string, address: string, typedDataJson: string, chainId: int, legacy: bool,
password: string, pin: string): string {.slot.} =
var res = ""
try:
var dataToSign = ""
if legacy:
dataToSign = self.service.hashTypedData(typedDataJson)
else:
dataToSign = self.service.hashTypedDataV4(typedDataJson)
if dataToSign.len == 0:
raise newException(CatchableError, "hashTypedData failed")
self.signMessageWithCallback(topic, id, address, dataToSign, password, pin,
proc (topic: string, id: string, keyUid: string, address: string, signature: string) =
self.signingResultReceived(topic, id, signature)
)
except Exception as e:
error "safeSignTypedData failed: ", msg=e.msg
self.signingResultReceived(topic, id, res)
proc signTransaction*(self: Controller, topic: string, id: string, address: string, chainId: int, txJson: string, password: string, pin: string) {.slot.} =
var res = ""
try:
let (txHash, txData) = self.service.buildTransaction(chainId, txJson)
if txHash.len == 0 or txData.isNil:
raise newException(CatchableError, "building transaction failed")
self.signMessageWithCallback(topic, id, address, txHash, password, pin,
proc (topic: string, id: string, keyUid: string, address: string, signature: string) =
let rawTx = self.service.buildRawTransaction(chainId, $txData, signature)
self.signingResultReceived(topic, id, rawTx)
)
except Exception as e:
error "signTransaction failed: ", msg=e.msg
self.signingResultReceived(topic, id, res)
proc sendTransaction*(self: Controller, topic: string, id: string, address: string, chainId: int, txJson: string, password: string, pin: string) {.slot.} =
var res = ""
try:
let (txHash, txData) = self.service.buildTransaction(chainId, txJson)
if txHash.len == 0 or txData.isNil:
raise newException(CatchableError, "building transaction failed")
self.signMessageWithCallback(topic, id, address, txHash, password, pin,
proc (topic: string, id: string, keyUid: string, address: string, signature: string) =
let signedTxHash = self.service.sendTransactionWithSignature(chainId, $txData, signature)
self.signingResultReceived(topic, id, signedTxHash)
)
except Exception as e:
error "sendTransaction failed: ", msg=e.msg
self.signingResultReceived(topic, id, res)
proc getEstimatedTime(self: Controller, chainId: int, maxFeePerGasHex: string): int {.slot.} = proc getEstimatedTime(self: Controller, chainId: int, maxFeePerGasHex: string): int {.slot.} =
return self.service.getEstimatedTime(chainId, maxFeePerGasHex).int return self.service.getEstimatedTime(chainId, maxFeePerGasHex).int

View File

@ -1,4 +1,4 @@
import NimQml, chronicles, times, json import NimQml, chronicles, times, json, uuids
import strutils import strutils
import backend/wallet_connect as status_go import backend/wallet_connect as status_go
@ -8,6 +8,7 @@ import app_service/service/settings/service as settings_service
import app_service/common/wallet_constants import app_service/common/wallet_constants
from app_service/service/transaction/dto import PendingTransactionTypeDto from app_service/service/transaction/dto import PendingTransactionTypeDto
import app_service/service/transaction/service as tr import app_service/service/transaction/service as tr
import app_service/service/keycard/service as keycard_service
import app/global/global_singleton import app/global/global_singleton
@ -25,7 +26,8 @@ logScope:
const UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER* = "WalletSection-WCModule" const UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER* = "WalletSection-WCModule"
type type
AuthenticationResponseFn* = proc(password: string, pin: string, success: bool) AuthenticationResponseFn* = proc(keyUid: string, password: string, pin: string)
SignResponseFn* = proc(keyUid: string, signature: string)
QtObject: QtObject:
type Service* = ref object of QObject type Service* = ref object of QObject
@ -33,8 +35,11 @@ QtObject:
threadpool: ThreadPool threadpool: ThreadPool
settingsService: settings_service.Service settingsService: settings_service.Service
transactions: tr.Service transactions: tr.Service
keycardService: keycard_service.Service
connectionKeycardResponse: UUID
authenticationCallback: AuthenticationResponseFn authenticationCallback: AuthenticationResponseFn
signCallback: SignResponseFn
proc delete*(self: Service) = proc delete*(self: Service) =
self.QObject.delete self.QObject.delete
@ -44,6 +49,7 @@ QtObject:
threadpool: ThreadPool, threadpool: ThreadPool,
settingsService: settings_service.Service, settingsService: settings_service.Service,
transactions: tr.Service, transactions: tr.Service,
keycardService: keycard_service.Service,
): Service = ): Service =
new(result, delete) new(result, delete)
result.QObject.setup result.QObject.setup
@ -52,25 +58,19 @@ QtObject:
result.threadpool = threadpool result.threadpool = threadpool
result.settingsService = settings_service result.settingsService = settings_service
result.transactions = transactions result.transactions = transactions
result.keycardService = keycardService
proc init*(self: Service) = proc init*(self: Service) =
self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args): self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args):
let args = SharedKeycarModuleArgs(e) let args = SharedKeycarModuleArgs(e)
if args.uniqueIdentifier != UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER: if args.uniqueIdentifier != UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER:
return return
if self.authenticationCallback == nil: if self.authenticationCallback == nil:
error "unexpected user authenticated event; no callback set" error "unexpected user authenticated event; no callback set"
return return
defer: defer:
self.authenticationCallback = nil self.authenticationCallback = nil
self.authenticationCallback(args.keyUid, args.password, args.pin)
if args.password == "" and args.pin == "":
info "fail to authenticate user"
self.authenticationCallback("", "", false)
return
self.authenticationCallback(args.password, args.pin, true)
proc addSession*(self: Service, session_json: string): bool = proc addSession*(self: Service, session_json: string): bool =
# TODO #14588: call it async # TODO #14588: call it async
@ -114,105 +114,104 @@ QtObject:
let testChains = self.settingsService.areTestNetworksEnabled() let testChains = self.settingsService.areTestNetworksEnabled()
# TODO #14588: call it async # TODO #14588: call it async
return status_go.getDapps(validAtEpoch, testChains) return status_go.getDapps(validAtEpoch, testChains)
proc getActiveSessions*(self: Service, validAtTimestamp: int64): JsonNode = proc getActiveSessions*(self: Service, validAtTimestamp: int64): JsonNode =
# TODO #14588: call it async # TODO #14588: call it async
return status_go.getActiveSessions(validAtTimestamp) return status_go.getActiveSessions(validAtTimestamp)
# Will fail if another authentication is in progress # Will fail if another authentication is in progress
proc authenticateUser*(self: Service, keyUid: string, callback: AuthenticationResponseFn): bool = proc authenticateUser*(self: Service, keyUid: string, callback: AuthenticationResponseFn): bool =
if self.authenticationCallback != nil: if self.authenticationCallback != nil:
return false return false
self.authenticationCallback = callback self.authenticationCallback = callback
let data = SharedKeycarModuleAuthenticationArgs( let data = SharedKeycarModuleAuthenticationArgs(
uniqueIdentifier: UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER, uniqueIdentifier: UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER,
keyUid: keyUid) keyUid: keyUid)
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data) self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data)
return true return true
proc signMessageUnsafe*(self: Service, address: string, password: string, message: string): string = proc hashMessageEIP191*(self: Service, message: string): string =
return status_go.signMessageUnsafe(address, password, message) let hashRes = hashMessageEIP191("0x" & toHex(message))
if not hashRes.error.isNil:
error "hashMessageEIP191 failed: ", msg=hashRes.error.message
return ""
return hashRes.result.getStr()
proc signMessage*(self: Service, address: string, password: string, message: string): string = proc signMessage*(self: Service, address: string, hashedPassword: string, hashedMessage: string): string =
return status_go.signMessage(address, password, message) var signMsgRes: JsonNode
let err = wallet.signMessage(signMsgRes,
hashedMessage,
address,
hashedPassword)
if err.len > 0:
error "status-go - wallet_signMessage failed", err=err
return signMsgRes.getStr
proc safeSignTypedData*(self: Service, address: string, password: string, typedDataJson: string, chainId: int, legacy: bool): string = proc buildTransaction*(self: Service, chainId: int, txJson: string): tuple[txToSign: string, txData: JsonNode] =
return status_go.safeSignTypedData(address, password, typedDataJson, chainId, legacy)
proc signTransaction*(self: Service, address: string, chainId: int, password: string, txJson: string): string =
var buildTxResponse: JsonNode var buildTxResponse: JsonNode
var err = wallet.buildTransaction(buildTxResponse, chainId, txJson) var err = wallet.buildTransaction(buildTxResponse, chainId, txJson)
if err.len > 0: if err.len > 0:
error "status-go - wallet_buildTransaction failed", err=err error "status-go - wallet_buildTransaction failed", err=err
return "" return
if buildTxResponse.isNil or buildTxResponse.kind != JsonNodeKind.JObject or if buildTxResponse.isNil or buildTxResponse.kind != JsonNodeKind.JObject or
not buildTxResponse.hasKey("txArgs") or not buildTxResponse.hasKey("messageToSign"): not buildTxResponse.hasKey("txArgs") or not buildTxResponse.hasKey("messageToSign"):
error "unexpected wallet_buildTransaction response" error "unexpected wallet_buildTransaction response"
return "" return
var txToBeSigned = buildTxResponse["messageToSign"].getStr result.txToSign = buildTxResponse["messageToSign"].getStr
if txToBeSigned.len != wallet_constants.TX_HASH_LEN_WITH_PREFIX: if result.txToSign.len != wallet_constants.TX_HASH_LEN_WITH_PREFIX:
error "unexpected tx hash length" error "unexpected tx hash length"
return "" return
result.txData = buildTxResponse["txArgs"]
var signMsgRes: JsonNode
err = wallet.signMessage(signMsgRes,
txToBeSigned,
address,
hashPassword(password))
if err.len > 0:
error "status-go - wallet_signMessage failed", err=err
let signature = singletonInstance.utils.removeHexPrefix(signMsgRes.getStr)
proc buildRawTransaction*(self: Service, chainId: int, txData: string, signature: string): string =
var txResponse: JsonNode var txResponse: JsonNode
err = wallet.buildRawTransaction(txResponse, chainId, $buildTxResponse["txArgs"], signature) var err = wallet.buildRawTransaction(txResponse, chainId, txData, signature)
if err.len > 0: if err.len > 0:
error "status-go - wallet_buildRawTransaction failed", err=err error "status-go - wallet_buildRawTransaction failed", err=err
return "" return
if txResponse.isNil or txResponse.kind != JsonNodeKind.JObject or not txResponse.hasKey("rawTx"): if txResponse.isNil or txResponse.kind != JsonNodeKind.JObject or not txResponse.hasKey("rawTx"):
error "unexpected buildRawTransaction response" error "unexpected wallet_buildRawTransaction response"
return "" return
return txResponse["rawTx"].getStr return txResponse["rawTx"].getStr
proc sendTransaction*(self: Service, address: string, chainId: int, password: string, txJson: string): string = proc sendTransactionWithSignature*(self: Service, chainId: int, txData: string, signature: string): string =
var buildTxResponse: JsonNode
var err = wallet.buildTransaction(buildTxResponse, chainId, txJson)
if err.len > 0:
error "status-go - wallet_buildTransaction failed", err=err
return ""
if buildTxResponse.isNil or buildTxResponse.kind != JsonNodeKind.JObject or
not buildTxResponse.hasKey("txArgs") or not buildTxResponse.hasKey("messageToSign"):
error "unexpected wallet_buildTransaction response"
return ""
var txToBeSigned = buildTxResponse["messageToSign"].getStr
if txToBeSigned.len != wallet_constants.TX_HASH_LEN_WITH_PREFIX:
error "unexpected tx hash length"
return ""
var signMsgRes: JsonNode
err = wallet.signMessage(signMsgRes,
txToBeSigned,
address,
hashPassword(password))
if err.len > 0:
error "status-go - wallet_signMessage failed", err=err
let signature = singletonInstance.utils.removeHexPrefix(signMsgRes.getStr)
var txResponse: JsonNode var txResponse: JsonNode
err = wallet.sendTransactionWithSignature(txResponse, chainId, let err = wallet.sendTransactionWithSignature(txResponse,
$PendingTransactionTypeDto.WalletConnectTransfer, $buildTxResponse["txArgs"], signature) chainId,
$PendingTransactionTypeDto.WalletConnectTransfer,
txData,
singletonInstance.utils.removeHexPrefix(signature))
if err.len > 0: if err.len > 0:
error "status-go - sendTransactionWithSignature failed", err=err error "status-go - sendTransactionWithSignature failed", err=err
return "" return ""
if txResponse.isNil or txResponse.kind != JsonNodeKind.JString: if txResponse.isNil or txResponse.kind != JsonNodeKind.JString:
error "unexpected sendTransactionWithSignature response" error "unexpected sendTransactionWithSignature response"
return "" return ""
return txResponse.getStr return txResponse.getStr
proc hashTypedData*(self: Service, data: string): string =
var response: JsonNode
let err = wallet.hashTypedData(response, data)
if err.len > 0:
error "status-go - hashTypedData failed", err=err
return ""
if response.isNil or response.kind != JsonNodeKind.JString:
error "unexpected hashTypedData response"
return ""
return response.getStr
proc hashTypedDataV4*(self: Service, data: string): string =
var response: JsonNode
let err = wallet.hashTypedDataV4(response, data)
if err.len > 0:
error "status-go - hashTypedDataV4 failed", err=err
return ""
if response.isNil or response.kind != JsonNodeKind.JString:
error "unexpected hashTypedDataV4 response"
return ""
return response.getStr
# empty maxFeePerGasHex will fetch the current chain's maxFeePerGas # empty maxFeePerGasHex will fetch the current chain's maxFeePerGas
proc getEstimatedTime*(self: Service, chainId: int, maxFeePerGasHex: string): EstimatedTime = proc getEstimatedTime*(self: Service, chainId: int, maxFeePerGasHex: string): EstimatedTime =
var maxFeePerGas: float64 var maxFeePerGas: float64
@ -237,4 +236,41 @@ QtObject:
return self.transactions.getEstimatedTime(chainId, $(maxFeePerGas)) return self.transactions.getEstimatedTime(chainId, $(maxFeePerGas))
proc getSuggestedFees*(self: Service, chainId: int): SuggestedFeesDto = proc getSuggestedFees*(self: Service, chainId: int): SuggestedFeesDto =
return self.transactions.suggestedFees(chainId) return self.transactions.suggestedFees(chainId)
proc disconnectKeycardReponseSignal(self: Service) =
self.events.disconnect(self.connectionKeycardResponse)
proc connectKeycardReponseSignal(self: Service) =
self.connectionKeycardResponse = self.events.onWithUUID(SIGNAL_KEYCARD_RESPONSE) do(e: Args):
let args = KeycardLibArgs(e)
self.disconnectKeycardReponseSignal()
if self.signCallback == nil:
error "unexpected user authenticated event; no callback set"
return
defer:
self.signCallback = nil
let currentFlow = self.keycardService.getCurrentFlow()
if currentFlow != KCSFlowType.Sign:
error "unexpected keycard flow type: ", currentFlow
self.signCallback("", "")
return
let signature = "0x" &
singletonInstance.utils.removeHexPrefix(args.flowEvent.txSignature.r) &
singletonInstance.utils.removeHexPrefix(args.flowEvent.txSignature.s) &
singletonInstance.utils.removeHexPrefix(args.flowEvent.txSignature.v)
self.signCallback(args.flowEvent.keyUid, signature)
proc cancelCurrentFlow*(self: Service) =
self.keycardService.cancelCurrentFlow()
proc runSigningOnKeycard*(self: Service, keyUid: string, path: string, hashedMessageToSign: string, pin: string, callback: SignResponseFn): bool =
if pin.len == 0:
return false
if self.signCallback != nil:
return false
self.signCallback = callback
self.cancelCurrentFlow()
self.connectKeycardReponseSignal()
self.keycardService.startSignFlow(path, hashedMessageToSign, pin)
return true

View File

@ -1,7 +1,9 @@
import json, logging import json, json_serialization, logging
import core, response_type import core, response_type
from ./gen import rpc from ./gen import rpc
import status_go
rpc(signMessage, "wallet"): rpc(signMessage, "wallet"):
message: string message: string
address: string address: string
@ -92,3 +94,21 @@ proc sendTransactionWithSignature*(resultOut: var JsonNode, chainId: int, txType
except Exception as e: except Exception as e:
warn e.msg warn e.msg
return e.msg return e.msg
proc hashTypedData*(resultOut: var JsonNode, data: string): string =
try:
let rawResponse = status_go.hashTypedData(data)
var response = Json.decode(rawResponse, RpcResponse[JsonNode])
return prepareResponse(resultOut, response)
except Exception as e:
warn e.msg
return e.msg
proc hashTypedDataV4*(resultOut: var JsonNode, data: string): string =
try:
let rawResponse = status_go.hashTypedDataV4(data)
var response = Json.decode(rawResponse, RpcResponse[JsonNode])
return prepareResponse(resultOut, response)
except Exception as e:
warn e.msg
return e.msg

View File

@ -1,15 +1,8 @@
import options, logging import options, logging
import json, json_serialization import json, json_serialization
import core, response_type import core, response_type
import strutils
from gen import rpc from gen import rpc
import backend/wallet
import status_go
import app_service/service/community/dto/sign_params
import app_service/common/utils
rpc(addWalletConnectSession, "wallet"): rpc(addWalletConnectSession, "wallet"):
sessionJson: string sessionJson: string
@ -23,13 +16,6 @@ rpc(getWalletConnectActiveSessions, "wallet"):
rpc(hashMessageEIP191, "wallet"): rpc(hashMessageEIP191, "wallet"):
message: string message: string
rpc(safeSignTypedDataForDApps, "wallet"):
typedJson: string
address: string
password: string
chainId: int
legacy: bool
proc isSuccessResponse(rpcResponse: RpcResponse[JsonNode]): bool = proc isSuccessResponse(rpcResponse: RpcResponse[JsonNode]): bool =
return rpcResponse.error.isNil return rpcResponse.error.isNil
@ -53,7 +39,7 @@ proc disconnectSession*(topic: string): bool =
proc getActiveSessions*(validAtTimestamp: int64): JsonNode = proc getActiveSessions*(validAtTimestamp: int64): JsonNode =
try: try:
let rpcRes = getWalletConnectActiveSessions(validAtTimestamp) let rpcRes = getWalletConnectActiveSessions(validAtTimestamp)
if(not isSuccessResponse(rpcRes)): if(not isSuccessResponse(rpcRes)):
return nil return nil
@ -83,47 +69,4 @@ proc getDapps*(validAtEpoch: int64, testChains: bool): string =
return if jsonArray != "null": jsonArray else: "[]" return if jsonArray != "null": jsonArray else: "[]"
except Exception as e: except Exception as e:
error "GetWalletConnectDapps failed: ", "msg", e.msg error "GetWalletConnectDapps failed: ", "msg", e.msg
return "" return ""
proc signMessageUnsafe*(address: string, password: string, message: string): string =
try:
let signParams = SignParamsDto(address: address, password: hashPassword(password), data: "0x" & toHex(message))
let paramsStr = $toJson(signParams)
let rpcResRaw = status_go.signMessage(paramsStr)
let rpcRes = Json.decode(rpcResRaw, RpcResponse[JsonNode])
if(not rpcRes.error.isNil):
return ""
return rpcRes.result.getStr()
except Exception as e:
error "status_go.signMessage failed: ", "msg", e.msg
return ""
proc signMessage*(address: string, password: string, message: string): string =
try:
let hashRes = hashMessageEIP191("0x" & toHex(message))
if not isSuccessResponse(hashRes):
error "wallet_hashMessageEIP191 failed: ", "msg", hashRes.error.message
return ""
let safeHash = hashRes.result.getStr()
let signRes = wallet.signMessage(safeHash, address, hashPassword(password))
if not isSuccessResponse(signRes):
error "wallet_signMessage failed: ", "msg", signRes.error.message
return ""
return signRes.result.getStr()
except Exception as e:
error "signMessageForDApps failed: ", "msg", e.msg
return ""
proc safeSignTypedData*(address: string, password: string, typedDataJson: string, chainId: int, legacy: bool): string =
try:
let rpcRes = safeSignTypedDataForDApps(typedDataJson, address, hashPassword(password), chainId, legacy)
if not isSuccessResponse(rpcRes):
return ""
return rpcRes.result.getStr()
except Exception as e:
error (if legacy: "wallet_safeSignTypedDataForDApps" else: "wallet_signTypedDataV4") & " failed: ", "msg", e.msg
return ""

View File

@ -274,7 +274,7 @@ Item {
compare(handler.store.authenticateUserCalls.length, 1, "expected a call to store.authenticateUser") compare(handler.store.authenticateUserCalls.length, 1, "expected a call to store.authenticateUser")
let store = handler.store let store = handler.store
store.userAuthenticated(td.topic, td.request.id, "password", "") store.userAuthenticated(td.topic, td.request.id, "hello world", "")
compare(store.signMessageCalls.length, 1, "expected a call to store.signMessage") compare(store.signMessageCalls.length, 1, "expected a call to store.signMessage")
compare(store.signMessageCalls[0].message, td.request.data) compare(store.signMessageCalls[0].message, td.request.data)
} }

View File

@ -116,6 +116,22 @@ SQUtils.QObject {
root.displayToastMessage(qsTr("Failed to authenticate %1 from %2").arg(methodStr).arg(session.peer.metadata.url), true) root.displayToastMessage(qsTr("Failed to authenticate %1 from %2").arg(methodStr).arg(session.peer.metadata.url), true)
}) })
} }
function onSigningResult(topic, id, data) {
let isSuccessful = (data != "")
if (isSuccessful) {
// acceptSessionRequest will trigger an sdk.sessionRequestUserAnswerResult signal
sdk.acceptSessionRequest(topic, id, data)
} else {
console.error("signing error")
var request = requests.findRequest(topic, id)
if (request === null) {
console.error("Error finding event for topic", topic, "id", id)
return
}
root.sessionRequestResult(request, isSuccessful)
}
}
} }
SQUtils.QObject { SQUtils.QObject {
@ -334,64 +350,75 @@ SQUtils.QObject {
return return
} }
if (password !== "") { if (password === "") {
let actionResult = "" console.error("No password provided to sign message")
if (request.method === SessionRequest.methods.sign.name) { return
actionResult = store.signMessageUnsafe(request.topic, request.id, }
request.account.address, password,
SessionRequest.methods.personalSign.getMessageFromData(request.data)) if (request.method === SessionRequest.methods.sign.name) {
} else if (request.method === SessionRequest.methods.personalSign.name) { store.signMessageUnsafe(request.topic,
actionResult = store.signMessage(request.topic, request.id, request.id,
request.account.address, password, request.account.address,
SessionRequest.methods.personalSign.getMessageFromData(request.data)) SessionRequest.methods.personalSign.getMessageFromData(request.data),
} else if (request.method === SessionRequest.methods.signTypedData_v4.name || password,
request.method === SessionRequest.methods.signTypedData.name) pin)
{ } else if (request.method === SessionRequest.methods.personalSign.name) {
let legacy = request.method === SessionRequest.methods.signTypedData.name store.signMessage(request.topic,
actionResult = store.safeSignTypedData(request.topic, request.id, request.id,
request.account.address, password, request.account.address,
SessionRequest.methods.personalSign.getMessageFromData(request.data),
password,
pin)
} else if (request.method === SessionRequest.methods.signTypedData_v4.name ||
request.method === SessionRequest.methods.signTypedData.name)
{
let legacy = request.method === SessionRequest.methods.signTypedData.name
store.safeSignTypedData(request.topic,
request.id,
request.account.address,
SessionRequest.methods.signTypedData.getMessageFromData(request.data), SessionRequest.methods.signTypedData.getMessageFromData(request.data),
request.network.chainId, legacy) request.network.chainId,
} else if (d.isTransactionMethod(request.method)) { legacy,
let txObj = d.getTxObject(request.method, request.data) password,
if (!!payload) { pin)
let feesInfoJson = payload } else if (d.isTransactionMethod(request.method)) {
let hexFeesJson = root.store.convertFeesInfoToHex(feesInfoJson) let txObj = d.getTxObject(request.method, request.data)
if (!!hexFeesJson) { if (!!payload) {
let feesInfo = JSON.parse(hexFeesJson) let feesInfoJson = payload
if (feesInfo.maxFeePerGas) { let hexFeesJson = root.store.convertFeesInfoToHex(feesInfoJson)
txObj.maxFeePerGas = feesInfo.maxFeePerGas if (!!hexFeesJson) {
} let feesInfo = JSON.parse(hexFeesJson)
if (feesInfo.maxPriorityFeePerGas) { if (feesInfo.maxFeePerGas) {
txObj.maxPriorityFeePerGas = feesInfo.maxPriorityFeePerGas txObj.maxFeePerGas = feesInfo.maxFeePerGas
} }
if (feesInfo.maxPriorityFeePerGas) {
txObj.maxPriorityFeePerGas = feesInfo.maxPriorityFeePerGas
} }
delete txObj.gasLimit
delete txObj.gasPrice
}
// Remove nonce from txObj to be auto-filled by the wallet
delete txObj.nonce
if (request.method === SessionRequest.methods.signTransaction.name) {
actionResult = store.signTransaction(request.topic, request.id,
request.account.address, request.network.chainId, password, txObj)
} else if (request.method === SessionRequest.methods.sendTransaction.name) {
actionResult = store.sendTransaction(request.topic, request.id,
request.account.address, request.network.chainId, password, txObj)
} }
delete txObj.gasLimit
delete txObj.gasPrice
} }
// Remove nonce from txObj to be auto-filled by the wallet
delete txObj.nonce
let isSuccessful = (actionResult != "") if (request.method === SessionRequest.methods.signTransaction.name) {
if (isSuccessful) { store.signTransaction(request.topic,
// acceptSessionRequest will trigger an sdk.sessionRequestUserAnswerResult signal request.id,
sdk.acceptSessionRequest(request.topic, request.id, actionResult) request.account.address,
} else { request.network.chainId,
root.sessionRequestResult(request, isSuccessful) txObj,
password,
pin)
} else if (request.method === SessionRequest.methods.sendTransaction.name) {
store.sendTransaction(
request.topic,
request.id,
request.account.address,
request.network.chainId,
txObj,
password,
pin)
} }
} else if (pin !== "") {
console.debug("TODO #15097 sign message using keycard: ", request.data)
} else {
console.error("No password or pin provided to sign message")
} }
} }

View File

@ -214,48 +214,59 @@ WalletConnectSDKBase {
return return
} }
if (password !== "") { if (password === "") {
var actionResult = "" console.error("No password provided to sign message")
if (request.method === SessionRequest.methods.sign.name) { return
actionResult = store.signMessageUnsafe(request.topic, request.id, }
request.account.address, password,
SessionRequest.methods.personalSign.getMessageFromData(request.data)) if (request.method === SessionRequest.methods.sign.name) {
} else if (request.method === SessionRequest.methods.personalSign.name) { store.signMessageUnsafe(request.topic,
actionResult = store.signMessage(request.topic, request.id, request.id,
request.account.address, password, request.account.address,
SessionRequest.methods.personalSign.getMessageFromData(request.data)) SessionRequest.methods.personalSign.getMessageFromData(request.data),
} else if (request.method === SessionRequest.methods.signTypedData_v4.name || password,
request.method === SessionRequest.methods.signTypedData.name) pin)
{ } else if (request.method === SessionRequest.methods.personalSign.name) {
let legacy = request.method === SessionRequest.methods.signTypedData.name store.signMessage(request.topic,
actionResult = store.safeSignTypedData(request.topic, request.id, request.id,
request.account.address, password, request.account.address,
SessionRequest.methods.personalSign.getMessageFromData(request.data),
password,
pin)
} else if (request.method === SessionRequest.methods.signTypedData_v4.name ||
request.method === SessionRequest.methods.signTypedData.name)
{
let legacy = request.method === SessionRequest.methods.signTypedData.name
store.safeSignTypedData(request.topic,
request.id,
request.account.address,
SessionRequest.methods.signTypedData.getMessageFromData(request.data), SessionRequest.methods.signTypedData.getMessageFromData(request.data),
request.network.chainId, legacy) request.network.chainId,
} else if (request.method === SessionRequest.methods.signTransaction.name) { legacy,
let txObj = SessionRequest.methods.signTransaction.getTxObjFromData(request.data) password,
actionResult = store.signTransaction(request.topic, request.id, pin)
request.account.address, request.network.chainId, password, txObj) } else if (request.method === SessionRequest.methods.signTransaction.name) {
} else if (request.method === SessionRequest.methods.sendTransaction.name) { let txObj = SessionRequest.methods.signTransaction.getTxObjFromData(request.data)
let txObj = SessionRequest.methods.sendTransaction.getTxObjFromData(request.data) store.signTransaction(request.topic,
actionResult = store.sendTransaction(request.topic, request.id, request.id,
request.account.address, request.network.chainId, password, txObj) request.account.address,
} request.network.chainId,
let isSuccessful = (actionResult != "") txObj,
if (isSuccessful) { password,
// acceptSessionRequest will trigger an sdk.sessionRequestUserAnswerResult signal pin)
acceptSessionRequest(request.topic, request.method, request.id, actionResult) } else if (request.method === SessionRequest.methods.sendTransaction.name) {
} else { let txObj = SessionRequest.methods.sendTransaction.getTxObjFromData(request.data)
root.sessionRequestResult(request, isSuccessful) store.sendTransaction(request.topic,
} request.id,
} else if (pin !== "") { request.account.address,
console.debug("TODO #15097 sign message using keycard: ", request.data) request.network.chainId,
} else { txObj,
console.error("No password or pin provided to sign message") password,
pin)
} }
} }
function acceptSessionRequest(topic, method, id, signature) { function acceptSessionRequest(topic, id, signature) {
console.debug(`Connector DappsConnectorSDK.acceptSessionRequest; requestId: ${root.requestId}, signature: "${signature}"`) console.debug(`Connector DappsConnectorSDK.acceptSessionRequest; requestId: ${root.requestId}, signature: "${signature}"`)
sessionRequestLoader.active = false sessionRequestLoader.active = false
@ -322,6 +333,16 @@ WalletConnectSDKBase {
root.displayToastMessage(qsTr("Failed to authenticate %1").arg(session.peer.metadata.url), true) root.displayToastMessage(qsTr("Failed to authenticate %1").arg(session.peer.metadata.url), true)
}) })
} }
function onSigningResult(topic, id, data) {
let isSuccessful = (data != "")
if (isSuccessful) {
// acceptSessionRequest will trigger an sdk.sessionRequestUserAnswerResult signal
d.acceptSessionRequest(topic, id, data)
} else {
console.error("signing error")
}
}
} }
Loader { Loader {

View File

@ -245,10 +245,6 @@ QObject {
readonly property var supportedAccountsModel: SortFilterProxyModel { readonly property var supportedAccountsModel: SortFilterProxyModel {
sourceModel: root.walletRootStore.nonWatchAccounts sourceModel: root.walletRootStore.nonWatchAccounts
filters: ValueFilter {
roleName: "keycardAccount"
value: false
}
} }
property var currentSessionProposal: null property var currentSessionProposal: null

View File

@ -12,6 +12,8 @@ QObject {
signal userAuthenticated(string topic, string id, string password, string pin, string payload) signal userAuthenticated(string topic, string id, string password, string pin, string payload)
signal userAuthenticationFailed(string topic, string id) signal userAuthenticationFailed(string topic, string id)
signal signingResult(string topic, string id, string data)
function addWalletConnectSession(sessionJson) { function addWalletConnectSession(sessionJson) {
return controller.addWalletConnectSession(sessionJson) return controller.addWalletConnectSession(sessionJson)
} }
@ -31,19 +33,16 @@ QObject {
} }
} }
// Returns the hex encoded signature of the message or empty string if error function signMessageUnsafe(topic, id, address, message, password, pin = "") {
function signMessageUnsafe(topic, id, address, password, message) { controller.signMessageUnsafe(topic, id, address, message, password, pin)
return controller.signMessageUnsafe(address, password, message)
} }
// Returns the hex encoded signature of the message or empty string if error function signMessage(topic, id, address, message, password, pin = "") {
function signMessage(topic, id, address, password, message) { controller.signMessage(topic, id, address, message, password, pin)
return controller.signMessage(address, password, message)
} }
// Returns the hex encoded signature of the typedDataJson or empty string if error function safeSignTypedData(topic, id, address, typedDataJson, chainId, legacy, password, pin = "") {
function safeSignTypedData(topic, id, address, password, typedDataJson, chainId, legacy) { controller.safeSignTypedData(topic, id, address, typedDataJson, chainId, legacy, password, pin)
return controller.safeSignTypedData(address, password, typedDataJson, chainId, legacy)
} }
// Remove leading zeros from hex number as expected by status-go // Remove leading zeros from hex number as expected by status-go
@ -91,16 +90,14 @@ QObject {
return JSON.parse(controller.getSuggestedFeesJson(chainId)) return JSON.parse(controller.getSuggestedFeesJson(chainId))
} }
// Returns the hex encoded signature of the transaction or empty string if error
function signTransaction(topic, id, address, chainId, password, txObj) { function signTransaction(topic, id, address, chainId, password, txObj) {
let tx = prepareTxForStatusGo(txObj) let tx = prepareTxForStatusGo(txObj)
return controller.signTransaction(address, chainId, password, JSON.stringify(tx)) controller.signTransaction(topic, id, address, chainId, JSON.stringify(tx), password, pin)
} }
// Returns the hash of the transaction or empty string if error function sendTransaction(topic, id, address, chainId, txObj, password, pin = "") {
function sendTransaction(topic, id, address, chainId, password, txObj) {
let tx = prepareTxForStatusGo(txObj) let tx = prepareTxForStatusGo(txObj)
return controller.sendTransaction(address, chainId, password, JSON.stringify(tx)) controller.sendTransaction(topic, id, address, chainId, JSON.stringify(tx), password, pin)
} }
/// \c getDapps triggers an async response to \c dappsListReceived /// \c getDapps triggers an async response to \c dappsListReceived
@ -149,5 +146,9 @@ QObject {
root.userAuthenticationFailed(topic, id) root.userAuthenticationFailed(topic, id)
} }
} }
function onSigningResultReceived(topic, id, data) {
root.signingResult(topic, id, data)
}
} }
} }

@ -1 +1 @@
Subproject commit 5546ffaea2b2135e7a3da58845e230d8e26fe709 Subproject commit 8923d70ec0348c7b238ce6d5d86ebe6682577ba4