feat(@desktop/walletconnect): signing tx or personal sign via keycard

Closes: #12730
This commit is contained in:
Sale Djenic 2023-11-20 16:18:37 +01:00 committed by saledjenic
parent c9fb3c5746
commit 132e05a8c6
7 changed files with 176 additions and 41 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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
return e.msg

View File

@ -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)

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit bddc48f2658eb13f260697132f68373d8379cb7b
Subproject commit 5e2af9e4fa329762ef55c9e4904a4e1465367090