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..
|
# Changes publicKey compression between 33-bytes and multiformat zQ..
|
||||||
proc changeCommunityKeyCompression*(self: Utils, publicKey: string): string {.slot.} =
|
proc changeCommunityKeyCompression*(self: Utils, publicKey: string): string {.slot.} =
|
||||||
changeCommunityKeyCompression(publicKey)
|
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 backend/wallet_connect as backend
|
||||||
|
|
||||||
|
import app/global/global_singleton
|
||||||
import app/core/eventemitter
|
import app/core/eventemitter
|
||||||
import app/core/signals/types
|
import app/core/signals/types
|
||||||
|
|
||||||
import app_service/common/utils as common_utils
|
import app_service/common/utils as common_utils
|
||||||
|
|
||||||
|
import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module
|
||||||
|
|
||||||
import constants
|
import constants
|
||||||
|
import session_response_dto, helper
|
||||||
|
|
||||||
|
const UNIQUE_WALLET_CONNECT_MODULE_SIGNING_IDENTIFIER* = "WalletConnect-Signing"
|
||||||
|
|
||||||
QtObject:
|
QtObject:
|
||||||
type
|
type
|
||||||
Controller* = ref object of QObject
|
Controller* = ref object of QObject
|
||||||
events: EventEmitter
|
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) =
|
proc setup(self: Controller) =
|
||||||
self.QObject.setup
|
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) =
|
proc delete*(self: Controller) =
|
||||||
self.QObject.delete
|
self.QObject.delete
|
||||||
|
|
||||||
proc newController*(events: EventEmitter): Controller =
|
proc newController*(events: EventEmitter): Controller =
|
||||||
new(result, delete)
|
new(result, delete)
|
||||||
|
|
||||||
result.events = events
|
result.events = events
|
||||||
|
|
||||||
result.setup()
|
result.setup()
|
||||||
|
|
||||||
# Register for wallet events
|
proc onDataSigned(self: Controller, keyUid: string, path: string, r: string, s: string, v: string, pin: string) =
|
||||||
result.events.on(SignalType.Wallet.event, proc(e: Args) =
|
if keyUid.len == 0 or path.len == 0 or r.len == 0 or s.len == 0 or v.len == 0 or pin.len == 0:
|
||||||
# TODO #12434: async processing
|
error "invalid data signed"
|
||||||
discard
|
return
|
||||||
)
|
let signature = "0x" & r & s & v
|
||||||
|
self.finishSessionRequest(signature)
|
||||||
|
|
||||||
# supportedNamespaces is a Namespace as defined in status-go: services/wallet/walletconnect/walletconnect.go
|
# supportedNamespaces is a Namespace as defined in status-go: services/wallet/walletconnect/walletconnect.go
|
||||||
proc proposeUserPair*(self: Controller, sessionProposalJson: string, supportedNamespacesJson: string) {.signal.}
|
proc proposeUserPair*(self: Controller, sessionProposalJson: string, supportedNamespacesJson: string) {.signal.}
|
||||||
|
|
||||||
proc pairSessionProposal(self: Controller, sessionProposalJson: string) {.slot.} =
|
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 sessionProposalJson = if res.hasKey("sessionProposal"): $res["sessionProposal"] else: ""
|
||||||
let supportedNamespacesJson = if res.hasKey("supportedNamespaces"): $res["supportedNamespaces"] else: ""
|
let supportedNamespacesJson = if res.hasKey("supportedNamespaces"): $res["supportedNamespaces"] else: ""
|
||||||
|
|
||||||
self.proposeUserPair(sessionProposalJson, supportedNamespacesJson)
|
self.proposeUserPair(sessionProposalJson, supportedNamespacesJson)
|
||||||
)
|
|
||||||
|
|
||||||
if not ok:
|
|
||||||
error "Failed to pair session"
|
|
||||||
|
|
||||||
proc respondSessionRequest*(self: Controller, sessionRequestJson: string, signedJson: string, error: bool) {.signal.}
|
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.} =
|
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 hashedPasssword = common_utils.hashPassword(password)
|
||||||
let ok = backend.sessionRequest(sessionRequestJson, hashedPasssword, proc (res: JsonNode) =
|
var signMsgRes: JsonNode
|
||||||
let sessionRequestJson = if res.hasKey("sessionRequest"): $res["sessionRequest"] else: ""
|
let err = backend.signMessage(signMsgRes,
|
||||||
let signedJson = if res.hasKey("signed"): $res["signed"] else: ""
|
sessionResponseDto.messageToSign,
|
||||||
|
sessionResponseDto.address,
|
||||||
self.respondSessionRequest(sessionRequestJson, signedJson, false)
|
hashedPasssword)
|
||||||
)
|
if err.len > 0:
|
||||||
|
error "Failed to sign message on statusgo side"
|
||||||
if not ok:
|
return
|
||||||
self.respondSessionRequest(sessionRequestJson, "", true)
|
let signature = signMsgRes.getStr
|
||||||
|
self.finishSessionRequest(signature)
|
||||||
|
except:
|
||||||
|
error "session request action failed"
|
||||||
|
|
||||||
proc getProjectId*(self: Controller): string {.slot.} =
|
proc getProjectId*(self: Controller): string {.slot.} =
|
||||||
return constants.WALLET_CONNECT_PROJECT_ID
|
return constants.WALLET_CONNECT_PROJECT_ID
|
||||||
|
|
||||||
QtProperty[string] projectId:
|
QtProperty[string] projectId:
|
||||||
read = getProjectId
|
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
|
# Declared in services/wallet/walletconnect/walletconnect.go
|
||||||
const ErrorChainsNotSupported*: string = "chains not supported"
|
const ErrorChainsNotSupported*: string = "chains not supported"
|
||||||
|
|
||||||
|
rpc(wCSignMessage, "wallet"):
|
||||||
|
message: string
|
||||||
|
address: string
|
||||||
|
password: string
|
||||||
|
|
||||||
|
rpc(wCSendTransaction, "wallet"):
|
||||||
|
signature: string
|
||||||
|
|
||||||
rpc(wCPairSessionProposal, "wallet"):
|
rpc(wCPairSessionProposal, "wallet"):
|
||||||
sessionProposalJson: string
|
sessionProposalJson: string
|
||||||
|
|
||||||
rpc(wCSessionRequest, "wallet"):
|
rpc(wCSessionRequest, "wallet"):
|
||||||
sessionRequestJson: string
|
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
|
# TODO #12434: async answer
|
||||||
proc pair*(sessionProposalJson: string, callback: proc(response: JsonNode): void): bool =
|
proc pair*(res: var JsonNode, sessionProposalJson: string): string =
|
||||||
try:
|
try:
|
||||||
let response = wCPairSessionProposal(sessionProposalJson)
|
let response = wCPairSessionProposal(sessionProposalJson)
|
||||||
if response.error == nil and response.result != nil:
|
return prepareResponse(res, response)
|
||||||
callback(response.result)
|
|
||||||
return response.error == nil
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
warn e.msg
|
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:
|
try:
|
||||||
let response = wCSessionRequest(sessionRequestJson, hashedPassword)
|
let response = wCSessionRequest(sessionRequestJson)
|
||||||
if response.error == nil and response.result != nil:
|
return prepareResponse(res, response)
|
||||||
callback(response.result)
|
|
||||||
return response.error == nil
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
warn e.msg
|
warn e.msg
|
||||||
return false
|
return e.msg
|
|
@ -275,8 +275,8 @@ Item {
|
||||||
root.state = d.waitingPairState
|
root.state = d.waitingPairState
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRespondSessionRequest(sessionRequestJson, signedJson, error) {
|
function onRespondSessionRequest(sessionRequestJson, signedData, error) {
|
||||||
console.log("@dd respondSessionRequest", sessionRequestJson, signedJson, error)
|
console.log("@dd respondSessionRequest", sessionRequestJson, " signedData", signedData, " error: ", error)
|
||||||
if (error) {
|
if (error) {
|
||||||
d.setStatusText("Session Request error", "red")
|
d.setStatusText("Session Request error", "red")
|
||||||
sdkView.rejectSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, true)
|
sdkView.rejectSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, true)
|
||||||
|
@ -284,7 +284,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
d.sessionRequest = JSON.parse(sessionRequestJson)
|
d.sessionRequest = JSON.parse(sessionRequestJson)
|
||||||
d.signedData = JSON.parse(signedJson)
|
d.signedData = signedData
|
||||||
|
|
||||||
sdkView.acceptSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, d.signedData)
|
sdkView.acceptSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, d.signedData)
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit bddc48f2658eb13f260697132f68373d8379cb7b
|
Subproject commit 5e2af9e4fa329762ef55c9e4904a4e1465367090
|
Loading…
Reference in New Issue