diff --git a/src/app/global/utils.nim b/src/app/global/utils.nim index fd9f4179f8..b89616108f 100644 --- a/src/app/global/utils.nim +++ b/src/app/global/utils.nim @@ -167,3 +167,8 @@ QtObject: # Changes publicKey compression between 33-bytes and multiformat zQ.. proc changeCommunityKeyCompression*(self: Utils, publicKey: string): string {.slot.} = changeCommunityKeyCompression(publicKey) + + proc removeHexPrefix*(self: Utils, value: string): string = + if value.startsWith("0x"): + return value[2..^1] + return value \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/wallet_connect/controller.nim b/src/app/modules/main/wallet_section/wallet_connect/controller.nim index 40880d9412..3b3c049dd9 100644 --- a/src/app/modules/main/wallet_section/wallet_connect/controller.nim +++ b/src/app/modules/main/wallet_section/wallet_connect/controller.nim @@ -2,67 +2,126 @@ import NimQml, logging, json import backend/wallet_connect as backend +import app/global/global_singleton import app/core/eventemitter import app/core/signals/types import app_service/common/utils as common_utils +import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module + import constants +import session_response_dto, helper + +const UNIQUE_WALLET_CONNECT_MODULE_SIGNING_IDENTIFIER* = "WalletConnect-Signing" QtObject: type Controller* = ref object of QObject events: EventEmitter + sessionRequestJson: JsonNode + + ## Forward declarations + proc onDataSigned(self: Controller, keyUid: string, path: string, r: string, s: string, v: string, pin: string) + proc finishSessionRequest(self: Controller, signature: string) proc setup(self: Controller) = self.QObject.setup + # Register for wallet events + self.events.on(SignalType.Wallet.event, proc(e: Args) = + # TODO #12434: async processing + discard + ) + + self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_DATA_SIGNED) do(e: Args): + let args = SharedKeycarModuleArgs(e) + if args.uniqueIdentifier != UNIQUE_WALLET_CONNECT_MODULE_SIGNING_IDENTIFIER: + return + self.onDataSigned(args.keyUid, args.path, args.r, args.s, args.v, args.pin) + proc delete*(self: Controller) = self.QObject.delete proc newController*(events: EventEmitter): Controller = new(result, delete) - result.events = events - result.setup() - # Register for wallet events - result.events.on(SignalType.Wallet.event, proc(e: Args) = - # TODO #12434: async processing - discard - ) + proc onDataSigned(self: Controller, keyUid: string, path: string, r: string, s: string, v: string, pin: string) = + if keyUid.len == 0 or path.len == 0 or r.len == 0 or s.len == 0 or v.len == 0 or pin.len == 0: + error "invalid data signed" + return + let signature = "0x" & r & s & v + self.finishSessionRequest(signature) # supportedNamespaces is a Namespace as defined in status-go: services/wallet/walletconnect/walletconnect.go proc proposeUserPair*(self: Controller, sessionProposalJson: string, supportedNamespacesJson: string) {.signal.} proc pairSessionProposal(self: Controller, sessionProposalJson: string) {.slot.} = - let ok = backend.pair(sessionProposalJson, proc (res: JsonNode) = - let sessionProposalJson = if res.hasKey("sessionProposal"): $res["sessionProposal"] else: "" - let supportedNamespacesJson = if res.hasKey("supportedNamespaces"): $res["supportedNamespaces"] else: "" - - self.proposeUserPair(sessionProposalJson, supportedNamespacesJson) - ) - - if not ok: + var res: JsonNode + let err = backend.pair(res, sessionProposalJson) + if err.len > 0: error "Failed to pair session" + return + let sessionProposalJson = if res.hasKey("sessionProposal"): $res["sessionProposal"] else: "" + let supportedNamespacesJson = if res.hasKey("supportedNamespaces"): $res["supportedNamespaces"] else: "" + self.proposeUserPair(sessionProposalJson, supportedNamespacesJson) proc respondSessionRequest*(self: Controller, sessionRequestJson: string, signedJson: string, error: bool) {.signal.} + proc sendTransaction(self: Controller, signature: string) = + let finalSignature = singletonInstance.utils.removeHexPrefix(signature) + var res: JsonNode + let err = backend.sendTransaction(res, finalSignature) + if err.len > 0: + error "Failed to send tx" + return + let sessionResponseDto = res.toSessionResponseDto() + self.respondSessionRequest($self.sessionRequestJson, sessionResponseDto.signedMessage, false) + + proc finishSessionRequest(self: Controller, signature: string) = + let requestMethod = getRequestMethod(self.sessionRequestJson) + if requestMethod == RequestMethod.SendTransaction: + self.sendTransaction(signature) + elif requestMethod == RequestMethod.PersonalSign: + self.respondSessionRequest($self.sessionRequestJson, signature, false) + else: + error "Unknown request method" + proc sessionRequest*(self: Controller, sessionRequestJson: string, password: string) {.slot.} = - let hashedPasssword = common_utils.hashPassword(password) - let ok = backend.sessionRequest(sessionRequestJson, hashedPasssword, proc (res: JsonNode) = - let sessionRequestJson = if res.hasKey("sessionRequest"): $res["sessionRequest"] else: "" - let signedJson = if res.hasKey("signed"): $res["signed"] else: "" + try: + self.sessionRequestJson = parseJson(sessionRequestJson) + var sessionRes: JsonNode + let err = backend.sessionRequest(sessionRes, sessionRequestJson) + if err.len > 0: + error "Failed to request a session" + self.respondSessionRequest($sessionRequestJson, "", true) + return - self.respondSessionRequest(sessionRequestJson, signedJson, false) - ) - - if not ok: - self.respondSessionRequest(sessionRequestJson, "", true) + let sessionResponseDto = sessionRes.toSessionResponseDto() + if sessionResponseDto.signOnKeycard: + let data = SharedKeycarModuleSigningArgs(uniqueIdentifier: UNIQUE_WALLET_CONNECT_MODULE_SIGNING_IDENTIFIER, + keyUid: sessionResponseDto.keyUid, + path: sessionResponseDto.addressPath, + dataToSign: singletonInstance.utils.removeHexPrefix(sessionResponseDto.messageToSign)) + self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_SIGN_DATA, data) + else: + let hashedPasssword = common_utils.hashPassword(password) + var signMsgRes: JsonNode + let err = backend.signMessage(signMsgRes, + sessionResponseDto.messageToSign, + sessionResponseDto.address, + hashedPasssword) + if err.len > 0: + error "Failed to sign message on statusgo side" + return + let signature = signMsgRes.getStr + self.finishSessionRequest(signature) + except: + error "session request action failed" proc getProjectId*(self: Controller): string {.slot.} = return constants.WALLET_CONNECT_PROJECT_ID - QtProperty[string] projectId: read = getProjectId \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/wallet_connect/helper.nim b/src/app/modules/main/wallet_section/wallet_connect/helper.nim new file mode 100644 index 0000000000..6337b283d6 --- /dev/null +++ b/src/app/modules/main/wallet_section/wallet_connect/helper.nim @@ -0,0 +1,24 @@ +import json, strutils + +include app_service/common/json_utils + +type + RequestMethod* {.pure.} = enum + Unknown = "unknown" + SendTransaction = "eth_sendTransaction" + PersonalSign = "personal_sign" + +## provided json represents a `SessionRequest` +proc getRequestMethod*(jsonObj: JsonNode): RequestMethod = + var paramsJsonObj: JsonNode + if jsonObj.getProp("params", paramsJsonObj): + var requestJsonObj: JsonNode + if paramsJsonObj.getProp("request", requestJsonObj): + var requestMethod: string + discard requestJsonObj.getProp("method", requestMethod) + try: + return parseEnum[RequestMethod](requestMethod) + except: + discard + return RequestMethod.Unknown + diff --git a/src/app/modules/main/wallet_section/wallet_connect/session_response_dto.nim b/src/app/modules/main/wallet_section/wallet_connect/session_response_dto.nim new file mode 100644 index 0000000000..c3b2eb2758 --- /dev/null +++ b/src/app/modules/main/wallet_section/wallet_connect/session_response_dto.nim @@ -0,0 +1,21 @@ +import json + +include app_service/common/json_utils + +type + SessionResponseDto* = ref object + keyUid*: string + address*: string + addressPath*: string + signOnKeycard*: bool + messageToSign*: string + signedMessage*: string + +proc toSessionResponseDto*(jsonObj: JsonNode): SessionResponseDto = + result = SessionResponseDto() + discard jsonObj.getProp("keyUid", result.keyUid) + discard jsonObj.getProp("address", result.address) + discard jsonObj.getProp("addressPath", result.addressPath) + discard jsonObj.getProp("signOnKeycard", result.signOnKeycard) + discard jsonObj.getProp("messageToSign", result.messageToSign) + discard jsonObj.getProp("signedMessage", result.signedMessage) \ No newline at end of file diff --git a/src/backend/wallet_connect.nim b/src/backend/wallet_connect.nim index b245b4e0c8..a476d998d4 100644 --- a/src/backend/wallet_connect.nim +++ b/src/backend/wallet_connect.nim @@ -11,30 +11,56 @@ import backend # Declared in services/wallet/walletconnect/walletconnect.go const ErrorChainsNotSupported*: string = "chains not supported" +rpc(wCSignMessage, "wallet"): + message: string + address: string + password: string + +rpc(wCSendTransaction, "wallet"): + signature: string + rpc(wCPairSessionProposal, "wallet"): sessionProposalJson: string rpc(wCSessionRequest, "wallet"): sessionRequestJson: string - hashedPassword: string + +proc prepareResponse(res: var JsonNode, rpcResponse: RpcResponse[JsonNode]): string = + if not rpcResponse.error.isNil: + return rpcResponse.error.message + if rpcResponse.result.isNil: + return "no result" + res = rpcResponse.result + +proc signMessage*(res: var JsonNode, message: string, address: string, password: string): string = + try: + let response = wCSignMessage(message, address, password) + return prepareResponse(res, response) + except Exception as e: + warn e.msg + return e.msg + +proc sendTransaction*(res: var JsonNode, signature: string): string = + try: + let response = wCSendTransaction(signature) + return prepareResponse(res, response) + except Exception as e: + warn e.msg + return e.msg # TODO #12434: async answer -proc pair*(sessionProposalJson: string, callback: proc(response: JsonNode): void): bool = +proc pair*(res: var JsonNode, sessionProposalJson: string): string = try: let response = wCPairSessionProposal(sessionProposalJson) - if response.error == nil and response.result != nil: - callback(response.result) - return response.error == nil + return prepareResponse(res, response) except Exception as e: warn e.msg - return false + return e.msg -proc sessionRequest*(sessionRequestJson: string, hashedPassword: string, callback: proc(response: JsonNode): void): bool = +proc sessionRequest*(res: var JsonNode, sessionRequestJson: string): string = try: - let response = wCSessionRequest(sessionRequestJson, hashedPassword) - if response.error == nil and response.result != nil: - callback(response.result) - return response.error == nil + let response = wCSessionRequest(sessionRequestJson) + return prepareResponse(res, response) except Exception as e: warn e.msg - return false \ No newline at end of file + return e.msg \ No newline at end of file diff --git a/ui/app/AppLayouts/Wallet/views/walletconnect/WalletConnect.qml b/ui/app/AppLayouts/Wallet/views/walletconnect/WalletConnect.qml index dc68e722cf..9b1c196ad6 100644 --- a/ui/app/AppLayouts/Wallet/views/walletconnect/WalletConnect.qml +++ b/ui/app/AppLayouts/Wallet/views/walletconnect/WalletConnect.qml @@ -275,8 +275,8 @@ Item { root.state = d.waitingPairState } - function onRespondSessionRequest(sessionRequestJson, signedJson, error) { - console.log("@dd respondSessionRequest", sessionRequestJson, signedJson, error) + function onRespondSessionRequest(sessionRequestJson, signedData, error) { + console.log("@dd respondSessionRequest", sessionRequestJson, " signedData", signedData, " error: ", error) if (error) { d.setStatusText("Session Request error", "red") sdkView.rejectSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, true) @@ -284,7 +284,7 @@ Item { } d.sessionRequest = JSON.parse(sessionRequestJson) - d.signedData = JSON.parse(signedJson) + d.signedData = signedData sdkView.acceptSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, d.signedData) diff --git a/vendor/status-go b/vendor/status-go index bddc48f265..5e2af9e4fa 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit bddc48f2658eb13f260697132f68373d8379cb7b +Subproject commit 5e2af9e4fa329762ef55c9e4904a4e1465367090