From feffcaf33d419b3e95ce304cd83332b98bf1109f Mon Sep 17 00:00:00 2001 From: Dmitry Novotochinov Date: Tue, 12 Mar 2019 19:30:06 +0300 Subject: [PATCH] [#7133] sign tx with keycard Signed-off-by: Dmitry Novotochinov --- STATUS_GO_VERSION | 2 +- mobile_files/package.json.orig | 2 +- mobile_files/yarn.lock | 6 +- .../status/ethereum/module/StatusModule.java | 39 +++++ src/status_im/events.cljs | 22 ++- src/status_im/hardwallet/card.cljs | 8 + src/status_im/hardwallet/core.cljs | 156 ++++++++++++++---- src/status_im/hardwallet/fx.cljs | 9 + src/status_im/models/wallet.cljs | 4 +- src/status_im/native_module/core.cljs | 6 + src/status_im/native_module/impl/module.cljs | 8 + .../ui/screens/hardwallet/connect/views.cljs | 18 +- .../ui/screens/hardwallet/pin/views.cljs | 14 +- .../ui/screens/hardwallet/settings/subs.cljs | 6 + src/status_im/ui/screens/routing/modals.cljs | 8 +- .../ui/screens/routing/wallet_stack.cljs | 2 + .../ui/screens/wallet/send/events.cljs | 137 +++++++++++---- .../ui/screens/wallet/send/subs.cljs | 6 + .../ui/screens/wallet/send/views.cljs | 81 +++++---- .../ui/screens/wallet/sign_message/views.cljs | 25 ++- translations/en.json | 1 + 21 files changed, 438 insertions(+), 122 deletions(-) diff --git a/STATUS_GO_VERSION b/STATUS_GO_VERSION index 12045771a7..78c6544d1b 100644 --- a/STATUS_GO_VERSION +++ b/STATUS_GO_VERSION @@ -1 +1 @@ -0.23.0-beta.8-chaos +0.23.0-beta.10 diff --git a/mobile_files/package.json.orig b/mobile_files/package.json.orig index 5aeefad42c..7cea7efb8f 100644 --- a/mobile_files/package.json.orig +++ b/mobile_files/package.json.orig @@ -54,7 +54,7 @@ "react-native-safe-area-view": "0.9.0", "react-native-securerandom": "git+https://github.com/status-im/react-native-securerandom.git#0.1.1-2", "react-native-splash-screen": "3.1.1", - "react-native-status-keycard": "git+https://github.com/status-im/react-native-status-keycard.git#v2.3.10", + "react-native-status-keycard": "git+https://github.com/status-im/react-native-status-keycard.git#v2.4.0", "react-native-svg": "^9.2.4", "react-native-svg-transformer": "^0.12.1", "react-native-tcp": "git+https://github.com/status-im/react-native-tcp.git#v3.3.0-1-status", diff --git a/mobile_files/yarn.lock b/mobile_files/yarn.lock index 446e34efce..45401b1cf1 100644 --- a/mobile_files/yarn.lock +++ b/mobile_files/yarn.lock @@ -5355,9 +5355,9 @@ react-native-splash-screen@3.1.1: resolved "https://registry.yarnpkg.com/react-native-splash-screen/-/react-native-splash-screen-3.1.1.tgz#1a4e46c9fdce53ff52af2a2cb4181788c4e30b30" integrity sha512-PU2YocOSGbLjL9Vgcq/cwMNuHHKNjjuPpa1IPMuWo+6EB/fSZ5VOmxSa7+eucQe3631s3NhGuk3eHKahU03a4Q== -"react-native-status-keycard@git+https://github.com/status-im/react-native-status-keycard.git#v2.3.10": - version "2.3.10" - resolved "git+https://github.com/status-im/react-native-status-keycard.git#a6da0a7889fdbc8cdf61771cf1b989c5cfb14b08" +"react-native-status-keycard@git+https://github.com/status-im/react-native-status-keycard.git#v2.4.0": + version "2.4.0" + resolved "git+https://github.com/status-im/react-native-status-keycard.git#b9a66144b3942e40ffb30cb27fa01202b0d11061" react-native-svg-transformer@^0.12.1: version "0.12.1" diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java index f9cf48c101..f8587338cb 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java @@ -700,6 +700,45 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL StatusThreadPoolExecutor.getInstance().execute(r); } + @ReactMethod + public void hashMessage(final String message, final Callback callback) { + Log.d(TAG, "hashMessage"); + if (!checkAvailability()) { + callback.invoke(false); + return; + } + + Runnable r = new Runnable() { + @Override + public void run() { + String res = Statusgo.hashMessage(message); + callback.invoke(res); + } + }; + + StatusThreadPoolExecutor.getInstance().execute(r); + } + + @ReactMethod + public void hashTypedData(final String data, final Callback callback) { + Log.d(TAG, "hashTypedData"); + if (!checkAvailability()) { + callback.invoke(false); + return; + } + + Runnable r = new Runnable() { + @Override + public void run() { + String res = Statusgo.hashTypedData(data); + callback.invoke(res); + } + }; + + StatusThreadPoolExecutor.getInstance().execute(r); + } + + @ReactMethod public void sendTransactionWithSignature(final String txArgsJSON, final String signature, final Callback callback) { Log.d(TAG, "sendTransactionWithSignature"); diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index c6ce4e649e..c9c53678b9 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -949,7 +949,7 @@ (handlers/register-handler-fx :hardwallet/get-application-info (fn [cofx _] - (hardwallet/get-application-info cofx nil))) + (hardwallet/get-application-info cofx nil nil))) (handlers/register-handler-fx :hardwallet.callback/on-get-application-info-success @@ -1089,6 +1089,11 @@ (fn [cofx [_ data]] (hardwallet/on-get-keys-success cofx data))) +(handlers/register-handler-fx + :hardwallet.callback/on-sign-success + (fn [cofx [_ data]] + (hardwallet/on-sign-success cofx data))) + (handlers/register-handler-fx :hardwallet/auto-login (fn [cofx _] @@ -1109,6 +1114,11 @@ (fn [cofx [_ error]] (hardwallet/on-get-keys-error cofx error))) +(handlers/register-handler-fx + :hardwallet.callback/on-sign-error + (fn [cofx [_ error]] + (hardwallet/on-sign-error cofx error))) + (handlers/register-handler-fx :hardwallet.ui/status-hardwallet-option-pressed (fn [cofx _] @@ -1243,6 +1253,11 @@ (fn [cofx [_ number step]] (hardwallet/update-pin cofx number step))) +(handlers/register-handler-fx + :hardwallet.ui/navigate-back-button-clicked + (fn [cofx _] + (hardwallet/navigate-back-button-clicked cofx))) + (handlers/register-handler-fx :hardwallet/process-pin-input (fn [cofx _] @@ -1336,6 +1351,11 @@ (fn [cofx _] (hardwallet/navigate-to-reset-card-screen cofx))) +(handlers/register-handler-fx + :hardwallet/sign + (fn [cofx _] + (hardwallet/sign cofx))) + ;; browser module (handlers/register-handler-fx diff --git a/src/status_im/hardwallet/card.cljs b/src/status_im/hardwallet/card.cljs index 449d9bc6a4..43dbd02b84 100644 --- a/src/status_im/hardwallet/card.cljs +++ b/src/status_im/hardwallet/card.cljs @@ -154,3 +154,11 @@ (getKeys pairing pin) (then #(re-frame/dispatch [:hardwallet.callback/on-get-keys-success %])) (catch #(re-frame/dispatch [:hardwallet.callback/on-get-keys-error (error-object->map %)])))) + +(defn sign + [{:keys [pairing pin hash]}] + (when (and pairing pin hash) + (.. keycard + (sign pairing pin hash) + (then #(re-frame/dispatch [:hardwallet.callback/on-sign-success %])) + (catch #(re-frame/dispatch [:hardwallet.callback/on-sign-error (error-object->map %)]))))) diff --git a/src/status_im/hardwallet/core.cljs b/src/status_im/hardwallet/core.cljs index 710a3af8bd..ab04e07fc2 100644 --- a/src/status_im/hardwallet/core.cljs +++ b/src/status_im/hardwallet/core.cljs @@ -7,13 +7,16 @@ [status-im.utils.platform :as platform] [taoensso.timbre :as log] [status-im.i18n :as i18n] + [status-im.utils.types :as types] [status-im.accounts.create.core :as accounts.create] [status-im.node.core :as node] [status-im.utils.datetime :as utils.datetime] [status-im.data-store.accounts :as accounts-store] + [status-im.utils.ethereum.core :as ethereum] [clojure.string :as string] [status-im.accounts.login.core :as accounts.login] - [status-im.accounts.recover.core :as accounts.recover])) + [status-im.accounts.recover.core :as accounts.recover] + [status-im.models.wallet :as models.wallet])) (def default-pin "000000") @@ -27,7 +30,7 @@ (defn get-pairing ([db] - (get-pairing db nil)) + (get-pairing db (get-in db [:hardwallet :application-info :instance-uid]))) ([db instance-uid] (or (get-in db [:account/account :keycard-pairing]) @@ -36,6 +39,21 @@ (:keycard-pairing (find-account-by-keycard-instance-uid db instance-uid)))))) +(fx/defn navigate-back-button-clicked + [{:keys [db] :as cofx}] + (let [screen-before (second (get db :navigation-stack)) + navigate-to-browser? (contains? #{:wallet-sign-message-modal + :wallet-send-transaction-modal + :wallet-send-modal-stack} screen-before)] + (if navigate-to-browser? + (fx/merge cofx + {:db (assoc-in db [:hardwallet :on-card-connected] nil)} + (models.wallet/discard-transaction) + (navigation/navigate-to-cofx :browser nil)) + (fx/merge cofx + {:db (assoc-in db [:hardwallet :on-card-connected] nil)} + (navigation/navigate-back))))) + (fx/defn remove-pairing-from-account [{:keys [db]} {:keys [remove-instance-uid?]}] (let [account (cond-> (:account/account db) @@ -188,6 +206,7 @@ (defn enter-pin-screen-did-load [{:keys [db]}] {:db (-> db + (assoc-in [:hardwallet :pin :sign] []) (assoc-in [:hardwallet :pin :login] []) (assoc-in [:hardwallet :pin :current] []))}) @@ -274,7 +293,8 @@ (let [info' (js->clj info :keywordize-keys true) {:keys [pin-retry-counter puk-retry-counter instance-uid]} info' connect-screen? (= (:view-id db) :hardwallet-connect) - card-state (get-in db [:hardwallet :card-state]) + {:keys [card-state on-card-read]} (:hardwallet db) + on-success' (or on-success on-card-read) accounts-screen? (= :accounts (:view-id db)) auto-login? (and accounts-screen? (not= on-success :hardwallet/auto-login)) @@ -295,9 +315,10 @@ instance-uid) (check-card-state)) (if (zero? puk-retry-counter) - (navigation/navigate-to-cofx :keycard-settings nil) - (when on-success - (dispatch-event on-success)))))) + {:utils/show-popup {:title (i18n/label :t/error) + :content (i18n/label :t/keycard-blocked)}} + (when on-success' + (dispatch-event on-success')))))) (fx/defn on-get-application-info-error [{:keys [db] :as cofx} error] @@ -570,12 +591,13 @@ :enter-step :puk :puk []})})) (fx/defn get-application-info - [{:keys [db]} pairing] + [{:keys [db]} pairing on-card-read] (let [instance-uid (get-in db [:hardwallet :application-info :instance-uid]) pairing' (or pairing (when instance-uid (get-pairing db instance-uid)))] - {:hardwallet/get-application-info {:pairing pairing'}})) + {:hardwallet/get-application-info {:pairing pairing' + :on-success on-card-read}})) (fx/defn on-verify-pin-success [{:keys [db] :as cofx}] @@ -586,7 +608,7 @@ (update-in [:hardwallet :pin] merge {:status nil :error-label nil}))} (when-not (contains? #{:hardwallet/unpair :hardwallet/unpair-and-delete} on-verified) - (get-application-info pairing)) + (get-application-info pairing nil)) (when on-verified (dispatch-on-verified-event on-verified))))) @@ -613,7 +635,7 @@ :error-label nil})) :utils/show-popup {:title "" :content (i18n/label :t/pin-changed {:pin pin})}} - (navigation/navigate-to-cofx :keycard-settings nil)))) + (navigation/navigate-back)))) (fx/defn on-change-pin-error [{:keys [db]} error] @@ -664,7 +686,8 @@ (defn- unblock-pin [{:keys [db] :as fx}] (let [puk (vector->string (get-in fx [:db :hardwallet :pin :puk])) - pairing (get-pairing db)] + instance-uid (get-in db [:hardwallet :application-info :instance-uid]) + pairing (get-pairing db instance-uid)] {:db (assoc-in db [:hardwallet :pin :status] :verifying) :hardwallet/unblock-pin {:puk puk :new-pin default-pin @@ -709,8 +732,26 @@ (assoc-in [:hardwallet :on-card-read] :hardwallet/login-with-keycard))} (navigation/navigate-to-cofx :hardwallet-connect nil))))) +(fx/defn sign + [{:keys [db] :as cofx}] + (let [card-connected? (get-in db [:hardwallet :card-connected?]) + pairing (get-pairing db) + hash (get-in db [:hardwallet :hash]) + pin (vector->string (get-in db [:hardwallet :pin :sign]))] + (if card-connected? + {:db (-> db + (assoc-in [:hardwallet :card-read-in-progress?] true) + (assoc-in [:hardwallet :pin :status] :verifying)) + :hardwallet/sign {:hash (ethereum/naked-address hash) + :pairing pairing + :pin pin}} + (fx/merge cofx + {:db (assoc-in db [:hardwallet :on-card-connected] :hardwallet/sign)} + (navigation/navigate-to-cofx :hardwallet-connect nil))))) + ; PIN enter steps: ; login - PIN is used to login +; sign - PIN for transaction sign ; current - current PIN to perform actions which require PIN auth ; original - new PIN when user changes it or creates new one ; confirmation - confirmation for new PIN @@ -738,6 +779,10 @@ (= pin-code-length numbers-entered)) (verify-pin) + (and (= enter-step :sign) + (= pin-code-length numbers-entered)) + (sign) + (and (= enter-step :puk) (= puk-code-length numbers-entered)) (unblock-pin) @@ -798,11 +843,11 @@ :else (get-in db [:hardwallet :on-card-read])) pairing (get-pairing db instance-uid)] (fx/merge cofx - {:db (-> db - (assoc-in [:hardwallet :card-connected?] true) - (assoc-in [:hardwallet :card-read-in-progress?] (boolean on-card-read))) - :hardwallet/get-application-info {:pairing pairing - :on-success on-card-read}} + {:db (-> db + (assoc-in [:hardwallet :card-connected?] true) + (assoc-in [:hardwallet :card-read-in-progress?] (boolean on-card-read)))} + (when (not= on-card-connected :hardwallet/sign) + (get-application-info pairing on-card-read)) (when (and on-card-connected (not login?)) (dispatch-event on-card-connected)) @@ -1057,30 +1102,81 @@ encryption-public-key]} (js->clj data :keywordize-keys true) whisper-public-key' (str "0x" whisper-public-key) {:keys [photo-path name]} (get-in db [:accounts/accounts wallet-address]) - password encryption-public-key] + password encryption-public-key + instance-uid (get-in db [:hardwallet :application-info :instance-uid])] (fx/merge cofx - {:db (-> db - (assoc-in [:hardwallet :pin :status] nil) - (assoc-in [:hardwallet :whisper-public-key] whisper-public-key') - (assoc-in [:hardwallet :whisper-private-key] whisper-private-key) - (assoc-in [:hardwallet :wallet-address] wallet-address) - (assoc-in [:hardwallet :encryption-public-key] encryption-public-key) - (update :accounts/login assoc - :password password - :address wallet-address - :photo-path photo-path - :name name))} + {:db (-> db + (assoc-in [:hardwallet :pin :status] nil) + (assoc-in [:hardwallet :pin :login] []) + (assoc-in [:hardwallet :whisper-public-key] whisper-public-key') + (assoc-in [:hardwallet :whisper-private-key] whisper-private-key) + (assoc-in [:hardwallet :wallet-address] wallet-address) + (assoc-in [:hardwallet :encryption-public-key] encryption-public-key) + (update :accounts/login assoc + :password password + :address wallet-address + :photo-path photo-path + :name name)) + :hardwallet/get-application-info {:pairing (get-pairing db instance-uid)}} (accounts.login/user-login true)))) (fx/defn on-get-keys-error [{:keys [db] :as cofx} error] (log/debug "[hardwallet] get keys error: " error) - (let [tag-was-lost? (= "Tag was lost." (:error error))] + (let [tag-was-lost? (= "Tag was lost." (:error error)) + instance-uid (get-in db [:hardwallet :application-info :instance-uid])] (if tag-was-lost? {:utils/show-popup {:title (i18n/label :t/error) :content (i18n/label :t/tag-was-lost)}} (fx/merge cofx - {:hardwallet/get-application-info {:pairing (get-pairing db)} + {:hardwallet/get-application-info {:pairing (get-pairing db instance-uid)} :db (update-in db [:hardwallet :pin] merge {:status :error + :login [] :error-label :t/pin-mismatch})} (navigation/navigate-to-cofx :enter-pin nil))))) + +(fx/defn send-transaction-with-signature + [_ data] + {:send-transaction-with-signature data}) + +(fx/defn sign-message-completed + [{:keys [db]} signature] + (let [screen-params (get-in db [:navigation/screen-params :wallet-sign-message-modal]) + signature' (-> signature + ; add 27 to last byte + ; https://github.com/ethereum/go-ethereum/blob/master/internal/ethapi/api.go#L431 + (clojure.string/replace-first #"00$", "1b") + (clojure.string/replace-first #"01$", "1c") + (ethereum/normalized-address))] + {:dispatch + [:status-im.ui.screens.wallet.send.events/sign-message-completed + screen-params + {:result signature'}]})) + +(fx/defn on-sign-success + [{:keys [db] :as cofx} signature] + (log/debug "[hardwallet] sign success: " signature) + (let [transaction (get-in db [:hardwallet :transaction])] + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :pin :sign] []) + (assoc-in [:hardwallet :pin :status] nil) + (assoc-in [:hardwallet :on-card-connected] nil) + (assoc-in [:hardwallet :hash] nil) + (assoc-in [:hardwallet :transaction] nil))} + (get-application-info (get-pairing db) nil) + (if transaction + (send-transaction-with-signature {:transaction (types/clj->json transaction) + :signature signature + :on-completed #(re-frame/dispatch [:status-im.ui.screens.wallet.send.events/transaction-completed (types/json->clj %)])}) + (sign-message-completed signature))))) + +(fx/defn on-sign-error + [{:keys [db] :as cofx} error] + (log/debug "[hardwallet] sign error: " error) + (fx/merge cofx + {:db (update-in db [:hardwallet :pin] merge {:status :error + :sign [] + :error-label :t/pin-mismatch})} + (navigation/navigate-to-cofx :enter-pin nil) + (get-application-info (get-pairing db) nil))) diff --git a/src/status_im/hardwallet/fx.cljs b/src/status_im/hardwallet/fx.cljs index fcd4618c2c..c8ef4e9c46 100644 --- a/src/status_im/hardwallet/fx.cljs +++ b/src/status_im/hardwallet/fx.cljs @@ -77,6 +77,15 @@ :hardwallet/get-keys card/get-keys) +(re-frame/reg-fx + :hardwallet/sign + card/sign) + (re-frame/reg-fx :hardwallet/login-with-keycard statusgo/login-with-keycard) + +(re-frame/reg-fx + :send-transaction-with-signature + (fn [{:keys [transaction signature on-completed]}] + (statusgo/send-transaction-with-signature transaction signature on-completed))) diff --git a/src/status_im/models/wallet.cljs b/src/status_im/models/wallet.cljs index 842f758539..e1dd5951ee 100644 --- a/src/status_im/models/wallet.cljs +++ b/src/status_im/models/wallet.cljs @@ -120,7 +120,7 @@ :error message} :webview webview-bridge})) -(defn dapp-complete-transaction [id result method message-id webview] +(defn dapp-complete-transaction [id result method message-id webview keycard?] (cond-> {:browser/send-to-bridge {:message {:type constants/web3-send-async-callback :messageId message-id :result {:jsonrpc "2.0" @@ -130,7 +130,7 @@ :dispatch [:navigate-back]} (constants/web3-sign-message? method) - (assoc :dispatch [:navigate-back]) + (assoc :dispatch (if keycard? [:navigate-to :browser] [:navigate-back])) (= method constants/web3-send-transaction) (assoc :dispatch [:navigate-to-clean :wallet-transaction-sent-modal]))) diff --git a/src/status_im/native_module/core.cljs b/src/status_im/native_module/core.cljs index 343ec81ab4..6c27ef50a1 100644 --- a/src/status_im/native_module/core.cljs +++ b/src/status_im/native_module/core.cljs @@ -58,6 +58,12 @@ (defn hash-transaction [rpcParams callback] (native-module/hash-transaction rpcParams callback)) +(defn hash-message [message callback] + (native-module/hash-message message callback)) + +(defn hash-typed-data [data callback] + (native-module/hash-typed-data data callback)) + (defn send-transaction-with-signature [rpcParams sig callback] (native-module/send-transaction-with-signature rpcParams sig callback)) diff --git a/src/status_im/native_module/impl/module.cljs b/src/status_im/native_module/impl/module.cljs index 9926ac4091..1836a584d6 100644 --- a/src/status_im/native_module/impl/module.cljs +++ b/src/status_im/native_module/impl/module.cljs @@ -98,6 +98,14 @@ (when (and @node-started status) (.hashTransaction status rpcParams callback))) +(defn hash-message [message callback] + (when (and @node-started status) + (.hashMessage status message callback))) + +(defn hash-typed-data [data callback] + (when (and @node-started status) + (.hashTypedData status data callback))) + (defn sign-typed-data [data password callback] (when (and @node-started status) (.signTypedData status data password callback))) diff --git a/src/status_im/ui/screens/hardwallet/connect/views.cljs b/src/status_im/ui/screens/hardwallet/connect/views.cljs index 2bc7d22c97..3aef984857 100644 --- a/src/status_im/ui/screens/hardwallet/connect/views.cljs +++ b/src/status_im/ui/screens/hardwallet/connect/views.cljs @@ -40,7 +40,8 @@ (i18n/label :t/go-to-settings)]]]) (defview hardwallet-connect [] - (letsubs [nfc-enabled? [:hardwallet/nfc-enabled?]] + (letsubs [nfc-enabled? [:hardwallet/nfc-enabled?] + setup-step [:hardwallet-setup-step]] [react/view styles/container [status-bar/status-bar] [react/view {:flex 1 @@ -53,10 +54,11 @@ (if nfc-enabled? [nfc-enabled] [nfc-disabled])] - [react/view styles/bottom-container - [react/touchable-highlight {:on-press #(.openURL react/linking "https://hardwallet.status.im")} - [react/view styles/product-info-container - [react/text {:style styles/product-info-text} - (i18n/label :t/product-information)] - [vector-icons/icon :main-icons/link {:color colors/blue - :container-style styles/external-link-icon}]]]]]])) + (if (= setup-step :begin) + [react/view styles/bottom-container + [react/touchable-highlight {:on-press #(.openURL react/linking "https://hardwallet.status.im")} + [react/view styles/product-info-container + [react/text {:style styles/product-info-text} + (i18n/label :t/product-information)] + [vector-icons/icon :main-icons/link {:color colors/blue + :container-style styles/external-link-icon}]]]])]])) diff --git a/src/status_im/ui/screens/hardwallet/pin/views.cljs b/src/status_im/ui/screens/hardwallet/pin/views.cljs index ae300ac747..ae1020cf7a 100644 --- a/src/status_im/ui/screens/hardwallet/pin/views.cljs +++ b/src/status_im/ui/screens/hardwallet/pin/views.cljs @@ -9,7 +9,8 @@ [status-im.ui.screens.hardwallet.components :as components] [status-im.ui.components.toolbar.view :as toolbar] [status-im.ui.components.status-bar.view :as status-bar] - [status-im.ui.components.toolbar.actions :as actions])) + [status-im.ui.components.toolbar.actions :as actions] + [status-im.ui.components.toolbar.actions :as toolbar.actions])) (defn numpad-button [n step enabled?] [react/touchable-highlight @@ -31,8 +32,7 @@ [numpad-row [4 5 6] step enabled?] [numpad-row [7 8 9] step enabled?] [react/view styles/numpad-row-container - [react/view styles/numpad-empty-button - [react/text {:style styles/numpad-empty-button-text}]] + [react/view styles/numpad-empty-button] [numpad-button 0 step enabled?] [react/touchable-highlight {:on-press #(when enabled? @@ -125,7 +125,12 @@ [react/view {:flex 1 :background-color colors/white} [status-bar/status-bar] - [toolbar/toolbar nil toolbar/default-nav-back nil] + [toolbar/toolbar + nil + [toolbar/nav-button (assoc toolbar.actions/default-back + :handler + #(re-frame/dispatch [:hardwallet.ui/navigate-back-button-clicked]))] + nil] (if (zero? pin-retry-counter) [pin-view {:pin pin :retry-counter (when (< puk-retry-counter puk-retries) puk-retry-counter) @@ -144,6 +149,7 @@ :t/current-pin) :description-label (case step :current :t/current-pin-description + :sign :t/current-pin-description :login :t/login-pin-description :t/new-pin-description) :step step diff --git a/src/status_im/ui/screens/hardwallet/settings/subs.cljs b/src/status_im/ui/screens/hardwallet/settings/subs.cljs index 6b8ff69387..8b2096b29a 100644 --- a/src/status_im/ui/screens/hardwallet/settings/subs.cljs +++ b/src/status_im/ui/screens/hardwallet/settings/subs.cljs @@ -33,3 +33,9 @@ :keycard-reset-card-disabled? (fn [db] (get-in db [:hardwallet :reset-card :disabled?] false))) + +(re-frame/reg-sub + :keycard-account? + (fn [db] + (boolean + (get-in db [:account/account :keycard-instance-uid])))) diff --git a/src/status_im/ui/screens/routing/modals.cljs b/src/status_im/ui/screens/routing/modals.cljs index c013dda365..79025ace4a 100644 --- a/src/status_im/ui/screens/routing/modals.cljs +++ b/src/status_im/ui/screens/routing/modals.cljs @@ -4,13 +4,17 @@ [{:name :wallet-send-modal-stack :screens [:wallet-send-transaction-modal :wallet-transaction-sent-modal - :wallet-transaction-fee] + :wallet-transaction-fee + :hardwallet-connect + :enter-pin] :config {:initialRouteName :wallet-send-transaction-modal}} {:name :wallet-send-modal-stack-with-onboarding :screens [:wallet-onboarding-setup-modal :wallet-send-transaction-modal :wallet-transaction-sent-modal - :wallet-transaction-fee] + :wallet-transaction-fee + :hardwallet-connect + :enter-pin] :config {:initialRouteName :wallet-onboarding-setup-modal}} :chat-modal :show-extension-modal diff --git a/src/status_im/ui/screens/routing/wallet_stack.cljs b/src/status_im/ui/screens/routing/wallet_stack.cljs index 43b74bb430..faabfa1c08 100644 --- a/src/status_im/ui/screens/routing/wallet_stack.cljs +++ b/src/status_im/ui/screens/routing/wallet_stack.cljs @@ -11,6 +11,8 @@ :screens [:wallet-send-transaction :recent-recipients :wallet-transaction-sent + :enter-pin + :hardwallet-connect :recipient-qr-code :wallet-send-assets]} {:name :request-transaction-stack diff --git a/src/status_im/ui/screens/wallet/send/events.cljs b/src/status_im/ui/screens/wallet/send/events.cljs index bcec42f850..c958c25e84 100644 --- a/src/status_im/ui/screens/wallet/send/events.cljs +++ b/src/status_im/ui/screens/wallet/send/events.cljs @@ -20,7 +20,8 @@ [status-im.utils.types :as types] [status-im.utils.utils :as utils] [status-im.utils.config :as config] - [status-im.transport.utils :as transport.utils])) + [status-im.transport.utils :as transport.utils] + [status-im.hardwallet.core :as hardwallet])) ;;;; FX @@ -77,27 +78,6 @@ #(re-frame/dispatch [::transaction-completed (types/json->clj %)]) password]})))) -;; SIGN MESSAGE -(handlers/register-handler-fx - :wallet/sign-message - (fn [_ [_ typed? screen-params password-error-cb]] - (let [{:keys [data from password]} screen-params] - (if typed? - {::sign-typed-data {:data data - :password password - :account from - :on-completed #(re-frame/dispatch [::sign-message-completed - screen-params - (types/json->clj %) - password-error-cb])}} - {::sign-message {:params {:data data - :password (security/safe-unmask-data password) - :account from} - :on-completed #(re-frame/dispatch [::sign-message-completed - screen-params - (types/json->clj %) - password-error-cb])}})))) - ;; SEND TRANSACTION CALLBACK (handlers/register-handler-fx ::transaction-completed @@ -139,6 +119,20 @@ (if on-result {:dispatch (conj on-result id result method)}))))) +(handlers/register-handler-fx + ::hash-message-completed + (fn [{:keys [db] :as cofx} [_ {:keys [result error]}]] + (let [db' (assoc-in db [:wallet :send-transaction :in-progress?] false)] + (if error + ;; ERROR + (models.wallet/handle-transaction-error (assoc cofx :db db') error) + ;; RESULT + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :pin :enter-step] :sign) + (assoc-in [:hardwallet :hash] result))} + (navigation/navigate-to-cofx :enter-pin nil)))))) + ;; DISCARD TRANSACTION (handlers/register-handler-fx :wallet/discard-transaction @@ -148,8 +142,9 @@ (handlers/register-handler-fx :wallet.dapp/transaction-on-result (fn [{db :db} [_ message-id id result method]] - (let [webview (:webview-bridge db)] - (models.wallet/dapp-complete-transaction (int id) result method message-id webview)))) + (let [webview (:webview-bridge db) + keycard? (boolean (get-in db [:account/account :keycard-instance-uid]))] + (models.wallet/dapp-complete-transaction (int id) result method message-id webview keycard?)))) (handlers/register-handler-fx :wallet.dapp/transaction-on-error @@ -165,6 +160,7 @@ (let [{:keys [send-transaction transactions-queue]} (:wallet db) {:keys [payload message-id] :as queued-transaction} (last transactions-queue) {:keys [method params id]} payload + keycard? (boolean (get-in db [:account/account :keycard-instance-uid])) db' (update-in db [:wallet :transactions-queue] drop-last)] (when (and (not (contains? #{:wallet-transaction-sent :wallet-transaction-sent-modal} @@ -182,14 +178,18 @@ (let [typed? (not= constants/web3-personal-sign method) [address data] (models.wallet/normalize-sign-message-params params)] (if (and address data) - (let [screen-params {:id (str (or id message-id)) - :from address - :data data - :typed? typed? - :decoded-data (if typed? (types/json->clj data) (transport.utils/to-utf8 data)) - :on-result [:wallet.dapp/transaction-on-result message-id] - :on-error [:wallet.dapp/transaction-on-error message-id] - :method method}] + (let [signing-phrase (-> (get-in db [:account/account :signing-phrase]) + (clojure.string/replace-all #" " " ")) + screen-params {:id (str (or id message-id)) + :from address + :data data + :typed? typed? + :decoded-data (if typed? (types/json->clj data) (transport.utils/to-utf8 data)) + :on-result [:wallet.dapp/transaction-on-result message-id] + :on-error [:wallet.dapp/transaction-on-error message-id] + :method method + :signing-phrase signing-phrase + :keycard? keycard?}] (navigation/navigate-to-cofx {:db db'} :wallet-sign-message-modal screen-params)) {:db db'}))))))) @@ -205,7 +205,7 @@ #(when send-command? (commands-sending/send % chat-id send-command? params)) (navigation/navigate-to-clean - (if (= (:view-id db) :wallet-send-transaction) + (if (contains? #{:wallet-send-transaction :enter-pin :hardwallet-connect} (:view-id db)) :wallet-transaction-sent :wallet-transaction-sent-modal) {}))))) @@ -314,3 +314,74 @@ {:dispatch-later [{:ms 400 :dispatch [:check-dapps-transactions-queue]}]} (navigation/navigate-back)))) +(re-frame/reg-fx + ::hash-transaction + (fn [{:keys [transaction all-tokens symbol chain on-completed]}] + (status/hash-transaction (types/clj->json transaction) on-completed))) + +(re-frame/reg-fx + ::hash-message + (fn [{:keys [message on-completed]}] + (status/hash-message message on-completed))) + +(re-frame/reg-fx + ::hash-typed-data + (fn [{:keys [data on-completed]}] + (status/hash-typed-data data on-completed))) + +(defn send-keycard-transaction + [{{:keys [chain] :as db} :db}] + (let [{:keys [symbol] :as transaction} (get-in db [:wallet :send-transaction]) + all-tokens (:wallet/all-tokens db) + from (get-in db [:account/account :address])] + {::hash-transaction {:transaction (models.wallet/prepare-send-transaction from transaction) + :all-tokens all-tokens + :symbol symbol + :chain chain + :on-completed #(re-frame/dispatch [:wallet.callback/hash-transaction-completed %])}})) + +(handlers/register-handler-fx + :wallet.callback/hash-transaction-completed + (fn [{:keys [db] :as cofx} [_ result]] + (let [{:keys [transaction hash]} (:result (types/json->clj result))] + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :pin :enter-step] :sign) + (assoc-in [:hardwallet :transaction] transaction) + (assoc-in [:hardwallet :hash] hash))} + (navigation/navigate-to-clean :enter-pin nil))))) + +(handlers/register-handler-fx + :wallet.ui/sign-transaction-button-clicked + (fn [{:keys [db] :as cofx} _] + (let [keycard-account? (boolean (get-in db [:account/account :keycard-instance-uid]))] + (if keycard-account? + (send-keycard-transaction cofx) + {:db (assoc-in db [:wallet :send-transaction :show-password-input?] true)})))) + +(handlers/register-handler-fx + :wallet.ui/sign-message-button-clicked + (fn [{:keys [db]} [_ typed? screen-params password-error-cb]] + (let [{:keys [data from password]} screen-params + keycard-account? (boolean (get-in db [:account/account :keycard-instance-uid]))] + (if keycard-account? + (if typed? + {::hash-typed-data {:data data + :on-completed #(re-frame/dispatch [::hash-message-completed (types/json->clj %)])}} + {::hash-message {:message (ethereum/naked-address data) + :on-completed #(re-frame/dispatch [::hash-message-completed (types/json->clj %)])}}) + (if typed? + {::sign-typed-data {:data data + :password password + :account from + :on-completed #(re-frame/dispatch [::sign-message-completed + screen-params + (types/json->clj %) + password-error-cb])}} + {::sign-message {:params {:data data + :password (security/safe-unmask-data password) + :account from} + :on-completed #(re-frame/dispatch [::sign-message-completed + screen-params + (types/json->clj %) + password-error-cb])}}))))) diff --git a/src/status_im/ui/screens/wallet/send/subs.cljs b/src/status_im/ui/screens/wallet/send/subs.cljs index c3d04af789..ff80ca220e 100644 --- a/src/status_im/ui/screens/wallet/send/subs.cljs +++ b/src/status_im/ui/screens/wallet/send/subs.cljs @@ -89,3 +89,9 @@ (models.wallet/add-max-fee) (check-sufficient-funds balance symbol amount) (check-sufficient-gas balance symbol amount)))) + +(re-frame/reg-sub + :wallet.send/signing-phrase-with-padding + :<- [:account/account] + (fn [{:keys [signing-phrase]}] + (clojure.string/replace-all signing-phrase #" " " "))) diff --git a/src/status_im/ui/screens/wallet/send/views.cljs b/src/status_im/ui/screens/wallet/send/views.cljs index 0f187cb034..99034b09e4 100644 --- a/src/status_im/ui/screens/wallet/send/views.cljs +++ b/src/status_im/ui/screens/wallet/send/views.cljs @@ -41,8 +41,8 @@ (defn- advanced-cartouche [native-currency {:keys [max-fee gas gas-price]}] [react/view - [wallet.components/cartouche {:on-press #(do (re-frame/dispatch [:wallet.send/clear-gas]) - (re-frame/dispatch [:navigate-to :wallet-transaction-fee]))} + [wallet.components/cartouche {:on-press #(do (re-frame/dispatch [:wallet.send/clear-gas]) + (re-frame/dispatch [:navigate-to :wallet-transaction-fee]))} (i18n/label :t/wallet-transaction-fee) [react/view {:style styles/advanced-options-text-wrapper :accessibility-label :transaction-fee-button} @@ -67,11 +67,11 @@ [advanced-cartouche native-currency transaction])]) (defview password-input-panel [message-label spinning?] - (letsubs [account [:account/account] + (letsubs [account [:account/account] wrong-password? [:wallet.send/wrong-password?] - signing-phrase (:signing-phrase @account) - bottom-value (animation/create-value -250) - opacity-value (animation/create-value 0)] + signing-phrase (:signing-phrase @account) + bottom-value (animation/create-value -250) + opacity-value (animation/create-value 0)] {:component-did-mount #(send.animations/animate-sign-panel opacity-value bottom-value)} [react/animated-view {:style (styles/animated-sign-panel bottom-value)} (when wrong-password? @@ -119,7 +119,7 @@ ;; "Sign Transaction >" button (defn- sign-transaction-button [amount-error to amount sufficient-funds? sufficient-gas? modal? online?] (let [sign-enabled? (and (nil? amount-error) - (or modal? (not (empty? to))) ;;NOTE(goranjovic) - contract creation will have empty `to` + (or modal? (not (empty? to))) ;;NOTE(goranjovic) - contract creation will have empty `to` (not (nil? amount)) sufficient-funds? sufficient-gas? @@ -129,19 +129,32 @@ [react/view] [button/button {:style components.styles/flex :disabled? (not sign-enabled?) - :on-press #(re-frame/dispatch [:set-in - [:wallet :send-transaction :show-password-input?] - true]) + :on-press #(re-frame/dispatch [:wallet.ui/sign-transaction-button-clicked]) :text-style {:color :white} :accessibility-label :sign-transaction-button} (i18n/label :t/transactions-sign-transaction) [vector-icons/icon :main-icons/next {:color (if sign-enabled? colors/white colors/white-light-transparent)}]]])) -(defn- render-send-transaction-view [{:keys [modal? transaction scroll advanced? network all-tokens amount-input network-status]}] +(defn signing-phrase-view [signing-phrase] + [react/view {:flex-direction :column + :align-items :center + :margin-top 10} + [react/view (assoc styles/signing-phrase-container :width "90%" :height 40) + [react/text {:accessibility-label :signing-phrase-text + :style {:padding-vertical 16 + :text-align :center}} + signing-phrase]] + [react/text {:style {:color :white + :text-align :center + :font-size 12 + :padding-vertical 14}} + (i18n/label :t/signing-phrase-warning)]]) + +(defn- render-send-transaction-view [{:keys [modal? transaction scroll advanced? keycard? signing-phrase network all-tokens amount-input network-status]}] (let [{:keys [amount amount-text amount-error asset-error show-password-input? to to-name sufficient-funds? sufficient-gas? in-progress? from-chat? symbol]} transaction - chain (ethereum/network->chain-keyword network) - native-currency (tokens/native-currency chain) + chain (ethereum/network->chain-keyword network) + native-currency (tokens/native-currency chain) {:keys [decimals] :as token} (tokens/asset-for all-tokens chain symbol) online? (= :online network-status)] [wallet.components/simple-screen {:avoid-keyboard? (not modal?) @@ -172,7 +185,9 @@ :amount-text amount-text :input-options {:on-change-text #(re-frame/dispatch [:wallet.send/set-and-validate-amount % symbol decimals]) :ref (partial reset! amount-input)}} token] - [advanced-options advanced? native-currency transaction scroll]]] + [advanced-options advanced? native-currency transaction scroll] + (when keycard? + [signing-phrase-view signing-phrase])]] (if show-password-input? [enter-password-buttons in-progress? #(re-frame/dispatch [:wallet/cancel-entering-password]) @@ -186,11 +201,11 @@ ;; MAIN SEND TRANSACTION VIEW (defn- send-transaction-view [{:keys [scroll] :as opts}] (let [amount-input (atom nil) - handler (fn [_] - (when (and scroll @scroll @amount-input - (.isFocused @amount-input)) - (log/debug "Amount field focused, scrolling down") - (.scrollToEnd @scroll)))] + handler (fn [_] + (when (and scroll @scroll @amount-input + (.isFocused @amount-input)) + (log/debug "Amount field focused, scrolling down") + (.scrollToEnd @scroll)))] (reagent/create-class {:component-will-mount (fn [_] ;;NOTE(goranjovic): keyboardDidShow is for android and keyboardWillShow for ios @@ -201,33 +216,41 @@ ;; SEND TRANSACTION FROM WALLET (CHAT) (defview send-transaction [] - (letsubs [transaction [:wallet.send/transaction] - advanced? [:wallet.send/advanced?] - network [:account/network] - scroll (atom nil) + (letsubs [transaction [:wallet.send/transaction] + advanced? [:wallet.send/advanced?] + network [:account/network] + scroll (atom nil) network-status [:network-status] - all-tokens [:wallet/all-tokens]] + all-tokens [:wallet/all-tokens] + signing-phrase [:wallet.send/signing-phrase-with-padding] + keycard? [:keycard-account?]] [send-transaction-view {:modal? false :transaction transaction :scroll scroll :advanced? advanced? + :keycard? keycard? + :signing-phrase signing-phrase :network network :all-tokens all-tokens :network-status network-status}])) ;; SEND TRANSACTION FROM DAPP (defview send-transaction-modal [] - (letsubs [transaction [:wallet.send/transaction] - advanced? [:wallet.send/advanced?] - network [:account/network] - scroll (atom nil) + (letsubs [transaction [:wallet.send/transaction] + advanced? [:wallet.send/advanced?] + network [:account/network] + scroll (atom nil) network-status [:network-status] - all-tokens [:wallet/all-tokens]] + all-tokens [:wallet/all-tokens] + signing-phrase [:wallet.send/signing-phrase-with-padding] + keycard? [:keycard-account?]] (if transaction [send-transaction-view {:modal? true :transaction transaction :scroll scroll :advanced? advanced? + :keycard? keycard? + :signing-phrase signing-phrase :network network :all-tokens all-tokens :network-status network-status}] diff --git a/src/status_im/ui/screens/wallet/sign_message/views.cljs b/src/status_im/ui/screens/wallet/sign_message/views.cljs index eba4a0b0a8..09d0436e5e 100644 --- a/src/status_im/ui/screens/wallet/sign_message/views.cljs +++ b/src/status_im/ui/screens/wallet/sign_message/views.cljs @@ -7,6 +7,7 @@ [status-im.ui.screens.wallet.components.views :as components] [status-im.ui.screens.wallet.components.views :as wallet.components] [status-im.ui.screens.wallet.send.styles :as styles] + [status-im.ui.screens.wallet.send.views :as wallet.send.views] [status-im.ui.screens.wallet.main.views :as wallet.main.views] [status-im.ui.components.toolbar.actions :as actions] [status-im.ui.components.toolbar.view :as toolbar] @@ -30,10 +31,11 @@ #(actions/default-handler)))] [toolbar/content-title {:color :white} title]])) -(defview enter-password-buttons [value-atom spinning? cancel-handler sign-handler sign-label] +(defview enter-password-buttons [value-atom {:keys [spinning? keycard?]} cancel-handler sign-handler sign-label] (letsubs [network-status [:network-status]] (let [password (:password @value-atom) - sign-enabled? (and (not (nil? password)) (not= password ""))] + sign-enabled? (or keycard? + (and (not (nil? password)) (not= password "")))] [bottom-buttons/bottom-buttons styles/sign-buttons [button/button {:style components.styles/flex @@ -82,7 +84,7 @@ ;; SIGN MESSAGE FROM DAPP (defview sign-message-modal [] (letsubs [value-atom (reagent/atom nil) - {:keys [decoded-data in-progress? typed?] :as screen-params} [:get-screen-params :wallet-sign-message-modal] + {:keys [decoded-data in-progress? typed? keycard? signing-phrase] :as screen-params} [:get-screen-params :wallet-sign-message-modal] network-status [:network-status]] [wallet.components/simple-screen {:status-bar-type :modal-wallet} [toolbar true (i18n/label :t/sign-message)] @@ -96,17 +98,24 @@ [components/amount-input {:disabled? true :input-options {:multiline true - :height 100} + :height (if typed? 300 100)} :amount-text (if typed? (str "Domain\n" (:domain decoded-data) "\nMessage\n" (:message decoded-data)) decoded-data)} - nil]]]] - [enter-password-buttons value-atom false + nil]]] + (when keycard? + [wallet.send.views/signing-phrase-view signing-phrase])] + [enter-password-buttons + value-atom + {:spinning? false :keycard? keycard?} #(re-frame/dispatch [:wallet/discard-transaction-navigate-back]) - #(re-frame/dispatch [:wallet/sign-message typed? (merge screen-params @value-atom) + #(re-frame/dispatch [:wallet.ui/sign-message-button-clicked + typed? + (merge screen-params @value-atom) (fn [] (swap! value-atom assoc :wrong-password? true))]) :t/transactions-sign] - [password-input-panel value-atom :t/signing-message-phrase-description false] + (when-not keycard? + [password-input-panel value-atom :t/signing-message-phrase-description false]) (when in-progress? [react/view styles/processing-view])]])) diff --git a/translations/en.json b/translations/en.json index 6f922789d0..eb63ecf982 100644 --- a/translations/en.json +++ b/translations/en.json @@ -630,6 +630,7 @@ "wallet-choose-from-contacts": "Choose from Contacts", "send-sending-to": "to {{recipient-name}}", "signing-phrase-description": "If you recognize these words, enter your login password to sign the transaction", + "signing-phrase-warning": "Only confirm the transaction if you recognize\n your three words", "currency-display-name-mad": "Moroccan Dirham", "here-is-your-signing-phrase": "Here is your signing phrase. You will use it to verify your transactions. *Write it down and keep it safe!*", "wants-to-access-profile": "wants to access to your profile",