feat(@desktop/walletconnect): signing tx or personal sign via keycard
Closes: #12730
This commit is contained in:
parent
c9fb3c5746
commit
132e05a8c6
|
@ -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
|
|
@ -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) =
|
||||
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)
|
||||
)
|
||||
|
||||
if not ok:
|
||||
error "Failed to pair session"
|
||||
|
||||
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.} =
|
||||
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
|
||||
|
||||
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)
|
||||
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: ""
|
||||
|
||||
self.respondSessionRequest(sessionRequestJson, signedJson, false)
|
||||
)
|
||||
|
||||
if not ok:
|
||||
self.respondSessionRequest(sessionRequestJson, "", true)
|
||||
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
|
|
@ -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
|
||||
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit bddc48f2658eb13f260697132f68373d8379cb7b
|
||||
Subproject commit 5e2af9e4fa329762ef55c9e4904a4e1465367090
|
Loading…
Reference in New Issue