new wallet sign with keycard flow

Signed-off-by: Dmitry Novotochinov <dmitry.novot@gmail.com>
This commit is contained in:
Dmitry Novotochinov 2019-07-03 21:38:10 +03:00
parent 068d726164
commit ac25f6766d
No known key found for this signature in database
GPG Key ID: 43D1DAF5AD39C927
12 changed files with 210 additions and 66 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -28,6 +28,7 @@
[status-im.fleet.core :as fleet] [status-im.fleet.core :as fleet]
[status-im.group-chats.core :as group-chats] [status-im.group-chats.core :as group-chats]
[status-im.hardwallet.core :as hardwallet] [status-im.hardwallet.core :as hardwallet]
[status-im.signing.keycard :as signing.keycard]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.init.core :as init] [status-im.init.core :as init]
[status-im.log-level.core :as log-level] [status-im.log-level.core :as log-level]
@ -1056,11 +1057,6 @@
(fn [cofx [_ data]] (fn [cofx [_ data]]
(hardwallet/on-get-keys-success 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 (handlers/register-handler-fx
:hardwallet/auto-login :hardwallet/auto-login
(fn [cofx _] (fn [cofx _]
@ -1343,16 +1339,6 @@
(fn [cofx _] (fn [cofx _]
(hardwallet/navigate-to-reset-card-screen cofx))) (hardwallet/navigate-to-reset-card-screen cofx)))
(handlers/register-handler-fx
:hardwallet/sign
(fn [cofx _]
(hardwallet/sign cofx)))
(handlers/register-handler-fx
:hardwallet/prepare-to-sign
(fn [cofx _]
(hardwallet/prepare-to-sign cofx)))
(handlers/register-handler-fx (handlers/register-handler-fx
:hardwallet/unblock-pin :hardwallet/unblock-pin
(fn [cofx _] (fn [cofx _]

View File

@ -1123,6 +1123,7 @@
nil))) nil)))
(fx/defn sign (fx/defn sign
{:events [:hardwallet/sign]}
[{:keys [db] :as cofx}] [{:keys [db] :as cofx}]
(let [card-connected? (get-in db [:hardwallet :card-connected?]) (let [card-connected? (get-in db [:hardwallet :card-connected?])
pairing (get-pairing db) pairing (get-pairing db)
@ -1140,24 +1141,25 @@
:pairing pairing :pairing pairing
:pin pin}} :pin pin}}
(fx/merge cofx (fx/merge cofx
{:db (assoc-in db [:hardwallet :on-card-connected] :hardwallet/sign)} {:db (-> db
(assoc-in [:signing/sign :keycard-step] :signing)
(assoc-in [:hardwallet :on-card-connected] :hardwallet/sign))}
(when-not keycard-match? (when-not keycard-match?
(show-wrong-keycard-alert card-connected?)) (show-wrong-keycard-alert card-connected?))))))
(navigate-to-connect-screen :hardwallet-connect-sign)))))
(fx/defn prepare-to-sign (fx/defn prepare-to-sign
{:events [:hardwallet/prepare-to-sign]}
[{:keys [db] :as cofx}] [{:keys [db] :as cofx}]
(let [card-connected? (get-in db [:hardwallet :card-connected?]) (let [card-connected? (get-in db [:hardwallet :card-connected?])
pairing (get-pairing db)] pairing (get-pairing db)]
(if card-connected? (if card-connected?
(get-application-info cofx pairing :hardwallet/sign)
(fx/merge cofx (fx/merge cofx
{:db (assoc-in db [:hardwallet :on-card-connected] :hardwallet/prepare-to-sign)} {:db (assoc-in db [:signing/sign :keycard-step] :signing)}
(navigation/navigate-to-cofx (get-application-info pairing :hardwallet/sign))
(if (= (:view-id db) :enter-pin-modal) (fx/merge cofx
:hardwallet-connect-modal {:db (-> db
:hardwallet-connect-sign) (assoc-in [:signing/sign :keycard-step] :connect)
nil))))) (assoc-in [:hardwallet :on-card-connected] :hardwallet/prepare-to-sign))}))))
(fx/defn import-account (fx/defn import-account
[{:keys [db] :as cofx}] [{:keys [db] :as cofx}]
@ -1590,23 +1592,22 @@
{:send-transaction-with-signature data}) {:send-transaction-with-signature data})
(fx/defn sign-message-completed (fx/defn sign-message-completed
[{:keys [db]} signature] [_ signature]
(let [screen-params (get-in db [:navigation/screen-params :wallet-sign-message-modal]) (let [signature' (-> signature
signature' (-> signature
; add 27 to last byte ; add 27 to last byte
; https://github.com/ethereum/go-ethereum/blob/master/internal/ethapi/api.go#L431 ; https://github.com/ethereum/go-ethereum/blob/master/internal/ethapi/api.go#L431
(clojure.string/replace-first #"00$", "1b") (clojure.string/replace-first #"00$", "1b")
(clojure.string/replace-first #"01$", "1c") (clojure.string/replace-first #"01$", "1c")
(ethereum/normalized-address))] (ethereum/normalized-address))]
{:dispatch {:dispatch
[:status-im.ui.screens.wallet.send.events/sign-message-completed [:signing/sign-message-completed (types/clj->json {:result signature'})]}))
screen-params
{:result signature'}]}))
(fx/defn on-sign-success (fx/defn on-sign-success
{:events [:hardwallet.callback/on-sign-success]}
[{:keys [db] :as cofx} signature] [{:keys [db] :as cofx} signature]
(log/debug "[hardwallet] sign success: " signature) (log/debug "[hardwallet] sign success: " signature)
(let [transaction (get-in db [:hardwallet :transaction])] (let [transaction (get-in db [:hardwallet :transaction])
tx-obj (select-keys transaction [:from :to :value :gas :gasPrice])]
(fx/merge cofx (fx/merge cofx
{:db (-> db {:db (-> db
(assoc-in [:hardwallet :pin :sign] []) (assoc-in [:hardwallet :pin :sign] [])
@ -1618,30 +1619,28 @@
(if transaction (if transaction
(send-transaction-with-signature {:transaction (types/clj->json transaction) (send-transaction-with-signature {:transaction (types/clj->json transaction)
:signature signature :signature signature
:on-completed #(re-frame/dispatch [:wallet.callback/transaction-completed (types/json->clj %)])}) :on-completed #(re-frame/dispatch [:signing/transaction-completed % tx-obj])})
(sign-message-completed signature))))) (sign-message-completed signature)))))
(fx/defn on-sign-error (fx/defn on-sign-error
[{:keys [db] :as cofx} error] [{:keys [db] :as cofx} error]
(log/debug "[hardwallet] sign error: " error) (log/debug "[hardwallet] sign error: " error)
(let [tag-was-lost? (= "Tag was lost." (:error error)) (let [tag-was-lost? (= "Tag was lost." (:error error))]
modal? (get-in db [:navigation/screen-params :wallet-send-modal-stack :modal?])]
(fx/merge cofx (fx/merge cofx
(if tag-was-lost? (if tag-was-lost?
(fx/merge cofx (fx/merge cofx
{:db (-> db {:db (-> db
(assoc-in [:hardwallet :on-card-connected] :hardwallet/prepare-to-sign) (assoc-in [:hardwallet :on-card-connected] :hardwallet/prepare-to-sign)
(assoc-in [:hardwallet :pin :status] nil)) (assoc-in [:hardwallet :pin :status] nil)
(assoc-in [:signing/sign :keycard-step] :connect))
:utils/show-popup {:title (i18n/label :t/error) :utils/show-popup {:title (i18n/label :t/error)
:content (i18n/label :t/cannot-read-card)}} :content (i18n/label :t/cannot-read-card)}}))
(navigate-to-connect-screen :hardwallet-connect-sign)))
(if (re-matches pin-mismatch-error (:error error)) (if (re-matches pin-mismatch-error (:error error))
(fx/merge cofx (fx/merge cofx
{:db (update-in db [:hardwallet :pin] merge {:status :error {:db (-> db
:sign [] (update-in [:hardwallet :pin] merge {:status :error
:error-label :t/pin-mismatch})} :sign []
(navigation/navigate-to-cofx (if modal? :error-label :t/pin-mismatch})
:enter-pin-modal (assoc-in [:signing/sign :keycard-step] :pin))}
:enter-pin-sign) nil)
(get-application-info (get-pairing db) nil)) (get-application-info (get-pairing db) nil))
(show-wrong-keycard-alert cofx true))))) (show-wrong-keycard-alert cofx true)))))

View File

@ -13,6 +13,7 @@
:secret-keys (js-require/js-require "./resources/images/ui/secret-keys.png") :secret-keys (js-require/js-require "./resources/images/ui/secret-keys.png")
:keycard-lock (js-require/js-require "./resources/images/ui/keycard-lock.png") :keycard-lock (js-require/js-require "./resources/images/ui/keycard-lock.png")
:keycard (js-require/js-require "./resources/images/ui/keycard.png") :keycard (js-require/js-require "./resources/images/ui/keycard.png")
:keycard-logo (js-require/js-require "./resources/images/ui/keycard-logo.png")
:keycard-phone (js-require/js-require "./resources/images/ui/keycard-phone.png") :keycard-phone (js-require/js-require "./resources/images/ui/keycard-phone.png")
:keycard-connection (js-require/js-require "./resources/images/ui/keycard-connection.png") :keycard-connection (js-require/js-require "./resources/images/ui/keycard-connection.png")
:keycard-nfc-on (js-require/js-require "./resources/images/ui/keycard-nfc-on.png") :keycard-nfc-on (js-require/js-require "./resources/images/ui/keycard-nfc-on.png")

View File

@ -166,13 +166,14 @@
(fx/defn show-sign [{:keys [db] :as cofx}] (fx/defn show-sign [{:keys [db] :as cofx}]
(let [{:signing/keys [queue]} db (let [{:signing/keys [queue]} db
{{:keys [gas gasPrice] :as tx-obj} :tx-obj {:keys [data typed?] :as message} :message :as tx} (last queue)] {{:keys [gas gasPrice] :as tx-obj} :tx-obj {:keys [data typed?] :as message} :message :as tx} (last queue)
keycard-account? (boolean (get-in db [:account/account :keycard-instance-uid]))]
(if message (if message
{:db (assoc db {:db (assoc db
:signing/in-progress? true :signing/in-progress? true
:signing/queue (drop-last queue) :signing/queue (drop-last queue)
:signing/tx tx :signing/tx tx
:signing/sign {:type :password :signing/sign {:type (if keycard-account? :keycard :password)
:formatted-data (if typed? (types/json->clj data) (ethereum/hex-to-utf8 data))})} :formatted-data (if typed? (types/json->clj data) (ethereum/hex-to-utf8 data))})}
(fx/merge cofx (fx/merge cofx
{:db {:db

View File

@ -0,0 +1,76 @@
(ns status-im.signing.keycard
(:require [re-frame.core :as re-frame]
[status-im.utils.fx :as fx]
[status-im.native-module.core :as status]
[status-im.utils.types :as types]
[status-im.ethereum.abi-spec :as abi-spec]
[status-im.ethereum.core :as ethereum]))
(re-frame/reg-fx
::hash-transaction
(fn [{:keys [transaction 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 prepare-transaction
[{:keys [gas gasPrice data nonce tx-obj]}]
(let [{:keys [from to value]} tx-obj]
(cond-> {:from from
:to to
:value value
:gas (str "0x" (abi-spec/number-to-hex gas))
:gasPrice (str "0x" (abi-spec/number-to-hex gasPrice))}
data
(assoc :data data)
nonce
(assoc :nonce nonce))))
(fx/defn hash-message
[_ {:keys [data typed?]}]
(if typed?
{::hash-typed-data {:data data
:on-completed #(re-frame/dispatch [:signing.keycard.callback/hash-message-completed %])}}
{::hash-message {:message (ethereum/naked-address data)
:on-completed #(re-frame/dispatch [:signing.keycard.callback/hash-message-completed %])}}))
(fx/defn hash-message-completed
{:events [:signing.keycard.callback/hash-message-completed]}
[{:keys [db]} result]
(let [{:keys [result error]} (types/json->clj result)]
{:db (-> db
(assoc-in [:hardwallet :hash] result))}))
(fx/defn hash-transaction
[{:keys [db]}]
{::hash-transaction {:transaction (prepare-transaction (:signing/tx db))
:on-completed #(re-frame/dispatch [:signing.keycard.callback/hash-transaction-completed %])}})
(fx/defn hash-transaction-completed
{:events [:signing.keycard.callback/hash-transaction-completed]}
[{:keys [db]} result]
(let [{:keys [transaction hash]} (:result (types/json->clj result))]
{:db (-> db
(assoc-in [:hardwallet :transaction] transaction)
(assoc-in [:hardwallet :hash] hash))}))
(fx/defn sign-with-keycard
{:events [:signing.ui/sign-with-keycard-pressed]}
[{:keys [db] :as cofx}]
(let [message (get-in db [:signing/tx :message])]
(fx/merge cofx
{:db (-> db
(assoc-in [:hardwallet :pin :enter-step] :sign)
(assoc-in [:signing/sign :keycard-step] :pin)
(assoc-in [:signing/sign :type] :keycard))}
(if message
(hash-message message)
(hash-transaction)))))

View File

@ -226,6 +226,11 @@
:<- [:dimensions/window] :<- [:dimensions/window]
:width) :width)
(re-frame/reg-sub
:dimensions/window-height
:<- [:dimensions/window]
:height)
(re-frame/reg-sub (re-frame/reg-sub
:get-screen-params :get-screen-params
:<- [:screen-params] :<- [:screen-params]

View File

@ -24,6 +24,7 @@
(def black-transparent (alpha black 0.1)) ;; Used as background color for rounded button on dark background and as background color for containers like "Backup seed phrase" (def black-transparent (alpha black 0.1)) ;; Used as background color for rounded button on dark background and as background color for containers like "Backup seed phrase"
(def black-transparent-40 (alpha black 0.4)) (def black-transparent-40 (alpha black 0.4))
(def gray-light black-transparent) ;; Used as divider color (def gray-light black-transparent) ;; Used as divider color
(def black-light "#2d2d2d")
;; DARK GREY ;; DARK GREY
(def gray "#939ba1") ;; Dark grey, used as a background for a light foreground and as section header and secondary text color (def gray "#939ba1") ;; Dark grey, used as a background for a light foreground and as section header and secondary text color

View File

@ -21,10 +21,10 @@
{:color colors/red {:color colors/red
:text-align :center}) :text-align :center})
(def center-container (defn center-container [title]
{:flex-direction :column {:flex-direction :column
:align-items :center :align-items :center
:margin-top 28}) :margin-top (if title 28 5)})
(def center-title-text (def center-title-text
{:typography :header}) {:typography :header})

View File

@ -75,12 +75,14 @@
(let [enabled? (not= status :verifying)] (let [enabled? (not= status :verifying)]
[react/scroll-view [react/scroll-view
[react/view styles/pin-container [react/view styles/pin-container
[react/view styles/center-container [react/view (styles/center-container title-label)
[react/text {:style styles/center-title-text} (when title-label
(i18n/label title-label)] [react/text {:style styles/center-title-text}
[react/text {:style styles/create-pin-text (i18n/label title-label)])
:number-of-lines 2} (when description-label
(i18n/label description-label)] [react/text {:style styles/create-pin-text
:number-of-lines 2}
(i18n/label description-label)])
(when retry-counter (when retry-counter
[react/text {:style {:font-weight "700" [react/text {:style {:font-weight "700"
:padding-top 10 :padding-top 10

View File

@ -18,7 +18,9 @@
[status-im.ui.screens.signing.sheets :as sheets] [status-im.ui.screens.signing.sheets :as sheets]
[status-im.ethereum.tokens :as tokens] [status-im.ethereum.tokens :as tokens]
[clojure.string :as string] [clojure.string :as string]
[status-im.ui.screens.signing.styles :as styles])) [status-im.ui.screens.signing.styles :as styles]
[status-im.react-native.resources :as resources]
[status-im.ui.screens.hardwallet.pin.views :as pin.views]))
(defn hide-panel-anim (defn hide-panel-anim
[bottom-anim-value alpha-value window-height] [bottom-anim-value alpha-value window-height]
@ -95,14 +97,79 @@
[react/view {:padding 6} [react/view {:padding 6}
[react/text {:style {:color colors/blue}} (i18n/label :t/cancel)]]]]) [react/text {:style {:color colors/blue}} (i18n/label :t/cancel)]]]])
(views/defview password-view [{:keys [type error in-progress? enabled?]}] (views/defview keycard-pin-view []
(views/letsubs [pin [:hardwallet/pin]]
[react/view
[pin.views/pin-view
{:pin pin
:retry-counter nil
:step :sign
:status nil
:error-label nil}]]))
(defn- keycard-connect-view []
[react/view {:padding-vertical 20
:flex 1
:align-items :center
:justify-content :center}
[react/image {:source (resources/get-image :keycard-phone)
:resize-mode :center
:style {:width 160
:height 170}}]
[react/view {:margin-top 10}
[react/text {:style {:text-align :center
:color colors/gray}}
(i18n/label :t/hold-card)]]])
(defn- keycard-processing-view []
[react/view {:flex-direction :column
:flex 1
:justify-content :center
:align-items :center}
[react/activity-indicator {:size :large
:animating true}]
[react/text {:style {:margin-top 16
:color colors/gray}}
(i18n/label :t/processing)]])
(defn- sign-with-keycard-button
[amount-error gas-error]
[button/button {:on-press #(re-frame/dispatch [:signing.ui/sign-with-keycard-pressed])
:text-style {:padding-right 2
:padding-left 16}
:style {:background-color colors/black-light
:padding-top 2
:border-radius 8}
:disabled? (or amount-error gas-error)}
(i18n/label :t/sign-with)
[react/view {:padding-right 16}
[react/image {:source (resources/get-image :keycard-logo)
:style {:width 64
:margin-bottom 7
:height 26}}]]])
(defn- signing-phrase-view [phrase]
[react/view {:align-items :center}
[react/text {:style {:color colors/gray :padding-bottom 8}} (i18n/label :t/signing-phrase)]
[react/text phrase]])
(defn- keycard-view
[{:keys [keycard-step]} phrase]
[react/view {:height 450}
[signing-phrase-view phrase]
(case keycard-step
:pin [keycard-pin-view]
:connect [keycard-connect-view]
:signing [keycard-processing-view]
[react/view {:align-items :center :margin-top 16 :margin-bottom 40}
[sign-with-keycard-button nil nil]])])
(views/defview password-view [{:keys [type error in-progress? enabled?] :as sign}]
(views/letsubs [phrase [:signing/phrase]] (views/letsubs [phrase [:signing/phrase]]
(case type (case type
:password :password
[react/view {:padding-top 16 :padding-bottom 16} [react/view {:padding-top 16 :padding-bottom 16}
[react/view {:align-items :center} [signing-phrase-view phrase]
[react/text {:style {:color colors/gray :padding-bottom 8}} (i18n/label :t/signing-phrase)]
[react/text phrase]]
[text-input/text-input-with-label [text-input/text-input-with-label
{:secure-text-entry true {:secure-text-entry true
:placeholder (i18n/label :t/enter-password) :placeholder (i18n/label :t/enter-password)
@ -119,10 +186,12 @@
[button/primary-button {:on-press #(re-frame/dispatch [:signing.ui/sign-is-pressed]) [button/primary-button {:on-press #(re-frame/dispatch [:signing.ui/sign-is-pressed])
:disabled? (not enabled?)} :disabled? (not enabled?)}
(i18n/label :t/transactions-sign)])]] (i18n/label :t/transactions-sign)])]]
:keycard
[keycard-view sign phrase]
[react/view]))) [react/view])))
(views/defview message-sheet [] (views/defview message-sheet []
(views/letsubs [{:keys [formatted-data] :as sign} [:signing/sign]] (views/letsubs [{:keys [formatted-data type] :as sign} [:signing/sign]]
[react/view styles/message [react/view styles/message
[react/view styles/message-header [react/view styles/message-header
[react/text {:style {:typography :title-bold}} (i18n/label :t/signing-a-message)] [react/text {:style {:typography :title-bold}} (i18n/label :t/signing-a-message)]
@ -140,7 +209,8 @@
(views/letsubs [fee [:signing/fee] (views/letsubs [fee [:signing/fee]
sign [:signing/sign] sign [:signing/sign]
chain [:ethereum/chain-keyword] chain [:ethereum/chain-keyword]
{:keys [amount-error gas-error]} [:signing/amount-errors]] {:keys [amount-error gas-error]} [:signing/amount-errors]
keycard-account? [:keycard-account?]]
(let [display-symbol (wallet.utils/display-symbol token) (let [display-symbol (wallet.utils/display-symbol token)
fee-display-symbol (wallet.utils/display-symbol (tokens/native-currency chain))] fee-display-symbol (wallet.utils/display-symbol (tokens/native-currency chain))]
[react/view styles/sheet [react/view styles/sheet
@ -167,9 +237,11 @@
{:content (fn [] [sheets/fee-bottom-sheet fee-display-symbol]) {:content (fn [] [sheets/fee-bottom-sheet fee-display-symbol])
:content-height 270}])}] :content-height 270}])}]
[react/view {:align-items :center :margin-top 16 :margin-bottom 40} [react/view {:align-items :center :margin-top 16 :margin-bottom 40}
[button/primary-button {:on-press #(re-frame/dispatch [:set :signing/sign {:type :password}]) (if keycard-account?
:disabled? (or amount-error gas-error)} [sign-with-keycard-button amount-error gas-error]
(i18n/label :t/sign-with-password)]]])]))) [button/primary-button {:on-press #(re-frame/dispatch [:set :signing/sign {:type :password}])
:disabled? (or amount-error gas-error)}
(i18n/label :t/sign-with-password)])]])])))
(defn signing-view [tx window-height] (defn signing-view [tx window-height]
(let [bottom-anim-value (anim/create-value window-height) (let [bottom-anim-value (anim/create-value window-height)

View File

@ -1076,6 +1076,7 @@
"signing-phrase" : "Signing phrase", "signing-phrase" : "Signing phrase",
"network-fee" : "Network fee", "network-fee" : "Network fee",
"sign-with-password" : "Sign with password", "sign-with-password" : "Sign with password",
"sign-with" : "Sign with",
"signing-a-message" : "Signing a message", "signing-a-message" : "Signing a message",
"select-chat" : "Select chat to start messaging", "select-chat" : "Select chat to start messaging",
"etherscan-lookup": "Look up on Etherscan", "etherscan-lookup": "Look up on Etherscan",