From 5e39b2f0fe3768a28ad9c8207fae02a23b219604 Mon Sep 17 00:00:00 2001 From: Simon McLoughlin Date: Thu, 14 Dec 2023 14:46:14 +0000 Subject: [PATCH 1/3] - reject sign requests that don't start with 05 - make sure all WC2 errors are main threaded --- .../Extensions/String+extensions.swift | 8 +++++ .../Localization/en.lproj/Localizable.strings | 1 + .../Services/WalletConnectService.swift | 36 +++++++++++++------ 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/Kukai Mobile/Extensions/String+extensions.swift b/Kukai Mobile/Extensions/String+extensions.swift index c76fd4b8..6429c2a7 100644 --- a/Kukai Mobile/Extensions/String+extensions.swift +++ b/Kukai Mobile/Extensions/String+extensions.swift @@ -28,6 +28,14 @@ extension String { return readable ?? "" } + public func isMichelsonEncodedString() -> Bool { + if String(self.prefix(2)) == "05" || String(self.prefix(4)) == "0x05" { + return true + } + + return false + } + private func processString(fromIndex: Int) -> String { let index = self.index(self.startIndex, offsetBy: fromIndex) let subString = String(self.suffix(from: index)) diff --git a/Kukai Mobile/Localization/en.lproj/Localizable.strings b/Kukai Mobile/Localization/en.lproj/Localizable.strings index 1136fac7..22dbe401 100644 --- a/Kukai Mobile/Localization/en.lproj/Localizable.strings +++ b/Kukai Mobile/Localization/en.lproj/Localizable.strings @@ -47,3 +47,4 @@ "error-cant-fav"="Unable to favourite token"; "error-cant-unfav"="Unable to unfavourite token"; "error-image-not-in-cahce"="Unable to locate image in cache, please make sure the image is displayed correctly"; +"error-unsupported-sign"="Unsupported signature request"; diff --git a/Kukai Mobile/Services/WalletConnectService.swift b/Kukai Mobile/Services/WalletConnectService.swift index 12cb61ae..946e8cb7 100644 --- a/Kukai Mobile/Services/WalletConnectService.swift +++ b/Kukai Mobile/Services/WalletConnectService.swift @@ -163,13 +163,29 @@ public class WalletConnectService { processWalletConnectRequest() } else if request.method == "tezos_sign" { - delegate?.signRequested() + + // Check for valid type + if let params = try? request.params.get([String: String].self), let expression = params["payload"], expression.isMichelsonEncodedString(), expression.humanReadableStringFromMichelson() != "" { + delegate?.signRequested() + } else { + Task { + try? await WalletConnectService.reject(topic: request.topic, requestId: request.id) + TransactionService.shared.resetWalletConnectState() + } + delegateErrorOnMain(message: "error-unsupported-sign".localized(), error: nil) + } } else if request.method == "tezos_getAccounts" { delegate?.provideAccountList() } else { - delegate?.error(message: "Unsupported WC method: \(request.method)", error: nil) + delegateErrorOnMain(message: "Unsupported WC method: \(request.method)", error: nil) + } + } + + private func delegateErrorOnMain(message: String, error: Error?) { + DispatchQueue.main.async { [weak self] in + self?.delegate?.error(message: message, error: error) } } @@ -234,7 +250,7 @@ public class WalletConnectService { } catch { Logger.app.error("WC Pairing connect error: \(error)") - self.delegate?.error(message: "Unable to connect to: \(uri.absoluteString), due to: \(error)", error: error) + delegateErrorOnMain(message: "Unable to connect to: \(uri.absoluteString), due to: \(error)", error: error) } } } @@ -243,7 +259,7 @@ public class WalletConnectService { public func respondWithAccounts() { guard let request = TransactionService.shared.walletConnectOperationData.request else { Logger.app.error("WC Approve Session error: Unable to find request") - self.delegate?.error(message: "Wallet connect: Unable to respond to request for list of wallets", error: nil) + delegateErrorOnMain(message: "Wallet connect: Unable to respond to request for list of wallets", error: nil) return } @@ -288,7 +304,7 @@ public class WalletConnectService { } catch { Logger.app.error("WC Approve Session error: \(error)") - self.delegate?.error(message: "Wallet connect: error returning list of accounts: \(error)", error: error) + delegateErrorOnMain(message: "Wallet connect: error returning list of accounts: \(error)", error: error) } } } @@ -360,13 +376,13 @@ public class WalletConnectService { private func processWalletConnectRequest() { guard let wcRequest = TransactionService.shared.walletConnectOperationData.request else { - self.delegate?.error(message: "Unable to process wallet connect request", error: nil) + self.delegateErrorOnMain(message: "Unable to process wallet connect request", error: nil) return } DependencyManager.shared.tezosNodeClient.getNetworkInformation { _, error in if let err = error { - self.delegate?.error(message: "Unable to fetch info from the Tezos node, please try again", error: err) + self.delegateErrorOnMain(message: "Unable to fetch info from the Tezos node, please try again", error: err) return } @@ -375,12 +391,12 @@ public class WalletConnectService { (wcRequest.chainId.absoluteString == "tezos:\(tezosChainName)" || (wcRequest.chainId.absoluteString == "tezos:ghostnet" && tezosChainName == "ithacanet")) else { let onDevice = DependencyManager.shared.currentNetworkType == .mainnet ? "Mainnet" : "Ghostnet" - self.delegate?.error(message: "Request is for a different network than the one currently selected on device (\"\(onDevice)\"). Please check the dApp and apps settings to match sure they match", error: nil) + self.delegateErrorOnMain(message: "Request is for a different network than the one currently selected on device (\"\(onDevice)\"). Please check the dApp and apps settings to match sure they match", error: nil) return } guard let params = try? wcRequest.params.get(WalletConnectRequestParams.self), let wallet = WalletCacheService().fetchWallet(forAddress: params.account) else { - self.delegate?.error(message: "Unable to parse response or locate wallet", error: nil) + self.delegateErrorOnMain(message: "Unable to parse response or locate wallet", error: nil) return } @@ -392,7 +408,7 @@ public class WalletConnectService { DependencyManager.shared.tezosNodeClient.estimate(operations: convertedOps, walletAddress: wallet.address, base58EncodedPublicKey: wallet.publicKeyBase58encoded()) { [weak self] result in guard let estimationResult = try? result.get() else { - self?.delegate?.error(message: "Unable to estimate fees", error: result.getFailure()) + self?.delegateErrorOnMain(message: "Unable to estimate fees", error: result.getFailure()) return } From bba4a66c7b6c1e0ed9d61c3e117bdead80427c59 Mon Sep 17 00:00:00 2001 From: Simon McLoughlin Date: Thu, 14 Dec 2023 15:13:32 +0000 Subject: [PATCH 2/3] add extra error handling to WC2 pair flow to ensure no unresponsive screens --- Kukai Mobile/Localization/en.lproj/Localizable.strings | 1 + .../WalletConnectPairViewController.swift | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/Kukai Mobile/Localization/en.lproj/Localizable.strings b/Kukai Mobile/Localization/en.lproj/Localizable.strings index 22dbe401..b3e0036b 100644 --- a/Kukai Mobile/Localization/en.lproj/Localizable.strings +++ b/Kukai Mobile/Localization/en.lproj/Localizable.strings @@ -48,3 +48,4 @@ "error-cant-unfav"="Unable to unfavourite token"; "error-image-not-in-cahce"="Unable to locate image in cache, please make sure the image is displayed correctly"; "error-unsupported-sign"="Unsupported signature request"; +"error-wc2-unrecoverable"="An unknown error occured with the connection. This operation can't continue. Please check the other application and try agian"; diff --git a/Kukai Mobile/Modules/ConnectedApps/WalletConnectPairViewController.swift b/Kukai Mobile/Modules/ConnectedApps/WalletConnectPairViewController.swift index 0d7995e0..d196eb2c 100644 --- a/Kukai Mobile/Modules/ConnectedApps/WalletConnectPairViewController.swift +++ b/Kukai Mobile/Modules/ConnectedApps/WalletConnectPairViewController.swift @@ -68,12 +68,21 @@ class WalletConnectPairViewController: UIViewController, BottomSheetCustomFixedP } } + private func unrecoverableError() { + self.hideLoadingModal(completion: { [weak self] in + TransactionService.shared.resetWalletConnectState() + self?.windowError(withTitle: "error".localized(), description: "error-wc2-unrecoverable".localized()) + self?.presentingViewController?.dismiss(animated: true) + }) + } + @IBAction func closeButtonTapped(_ sender: Any) { rejectTapped("") } @IBAction func connectTapped(_ sender: Any) { guard let proposal = TransactionService.shared.walletConnectOperationData.proposal, let account = DependencyManager.shared.selectedWalletAddress else { + unrecoverableError() return } @@ -131,6 +140,7 @@ class WalletConnectPairViewController: UIViewController, BottomSheetCustomFixedP @IBAction func rejectTapped(_ sender: Any) { guard let proposal = TransactionService.shared.walletConnectOperationData.proposal else { + unrecoverableError() return } From d34f950bba91227e28594a2dea3566c70b18d0ad Mon Sep 17 00:00:00 2001 From: Simon McLoughlin Date: Thu, 14 Dec 2023 15:39:52 +0000 Subject: [PATCH 3/3] tweak toast styles and add shadow --- Kukai Mobile/Controls/Toast.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Kukai Mobile/Controls/Toast.swift b/Kukai Mobile/Controls/Toast.swift index 6d881105..67e64d4e 100644 --- a/Kukai Mobile/Controls/Toast.swift +++ b/Kukai Mobile/Controls/Toast.swift @@ -17,9 +17,7 @@ class Toast { private init() { toastView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 34)) //toastView.translatesAutoresizingMaskIntoConstraints = false - toastView.backgroundColor = .colorNamed("BG2") - toastView.borderColor = .colorNamed("BG4") - toastView.borderWidth = 1 + toastView.backgroundColor = .colorNamed("BG12") toastView.customCornerRadius = 8 toastLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 34)) @@ -27,7 +25,7 @@ class Toast { toastLabel.textAlignment = .center toastLabel.numberOfLines = 1 toastLabel.font = .custom(ofType: .bold, andSize: 12) - toastLabel.textColor = .colorNamed("Txt6") + toastLabel.textColor = .colorNamed("Txt14") toastView.addSubview(toastLabel) NSLayoutConstraint.activate([ @@ -63,6 +61,11 @@ class Toast { toastView.setNeedsLayout() toastView.layoutIfNeeded() + if let first = toastView.layer.sublayers?.first, first.shadowPath != nil { + first.removeFromSuperlayer() + } + toastView.addShadow(color: UIColor(red: 0, green: 0, blue: 0, alpha: 0.23), opacity: 1, offset: CGSize(width: 1, height: 2), radius: 5) + attachedTo.isUserInteractionEnabled = false // Animate view appeareance in UIView.animate(withDuration: 0.3) { [weak self] in