[#21577] Keycard [Wallet] - Derives next account for profile key pair (#21753)

This commit is contained in:
flexsurfer 2024-12-10 18:07:45 +01:00 committed by GitHub
parent a821ec5a14
commit 949e78a17e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 292 additions and 164 deletions

View File

@ -10,19 +10,44 @@
[utils.re-frame :as rf]
[utils.security.core :as security]))
(rf/reg-fx :effects.keycard/call-on-auth-success
(fn [on-auth-success]
(when on-auth-success (on-auth-success ""))))
(defn- handle-password-success
[has-partially-operable-accounts? on-auth-success password]
(let [sha3-pwd (security/hash-masked-password password)
on-auth-success-callback #(on-auth-success sha3-pwd)]
(rf/dispatch [:standard-auth/set-success true])
(rf/dispatch [:standard-auth/reset-login-password])
(if has-partially-operable-accounts?
(rf/dispatch [:wallet/make-partially-operable-accounts-fully-operable
{:password sha3-pwd
:on-success on-auth-success-callback
:on-error on-auth-success-callback}])
(on-auth-success-callback))))
(defn authorize
[{:keys [db]} [{:keys [on-auth-success keycard-supported?] :as args}]]
[{:keys [db]} [{:keys [on-auth-success] :as args}]]
(let [key-uid (get-in db [:profile/profile :key-uid])
keycard? (get-in db [:profile/profile :keycard-pairing])]
keycard-profile? (get-in db [:profile/profile :keycard-pairing])]
{:fx
[(if keycard?
(if keycard-supported?
[:effects.keycard/call-on-auth-success on-auth-success]
[:dispatch [:keycard/feature-unavailable-show]])
[(if keycard-profile?
[:dispatch
[:standard-auth/authorize-with-keycard
{:on-complete
(fn [pin]
(rf/dispatch
[:keycard/connect
{:key-uid key-uid
:on-success
(fn []
(rf/dispatch
[:keycard/get-keys
{:pin pin
:on-success (fn [{:keys [encryption-public-key]}]
(rf/dispatch [:keycard/disconnect])
(handle-password-success false
on-auth-success
encryption-public-key))
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error
%])}]))}]))}]]
[:effects.biometric/check-if-available
{:key-uid key-uid
:on-success #(rf/dispatch [:standard-auth/authorize-with-biometric args])
@ -84,21 +109,12 @@
(defn- bottom-sheet-password-view
[{:keys [on-press-biometric on-auth-success auth-button-icon-left auth-button-label]}]
(fn []
(let [has-partially-operable-accounts? (rf/sub [:wallet/has-partially-operable-accounts?])
handle-password-success
(fn [password]
(let [sha3-pwd (security/hash-masked-password password)
on-auth-success-callback #(on-auth-success sha3-pwd)]
(rf/dispatch [:standard-auth/set-success true])
(rf/dispatch [:standard-auth/reset-login-password])
(if has-partially-operable-accounts?
(rf/dispatch [:wallet/make-partially-operable-accounts-fully-operable
{:password sha3-pwd
:on-success on-auth-success-callback
:on-error on-auth-success-callback}])
(on-auth-success-callback))))]
(let [has-partially-operable-accounts? (rf/sub [:wallet/has-partially-operable-accounts?])]
[enter-password/view
{:on-enter-password handle-password-success
{:on-enter-password #(handle-password-success
has-partially-operable-accounts?
on-auth-success
%)
:on-press-biometrics on-press-biometric
:button-icon-left auth-button-icon-left
:button-label auth-button-label}])))

View File

@ -9,25 +9,24 @@
(defn view
[{:keys [track-text customization-color auth-button-label on-auth-success on-auth-fail
auth-button-icon-left size blur? container-style disabled? dependencies keycard-supported?]
auth-button-icon-left size blur? container-style disabled? dependencies on-complete]
:or {container-style {:flex 1}}}]
(let [theme (quo.theme/use-theme)
auth-method (rf/sub [:auth-method])
biometric-auth? (= auth-method constants/auth-method-biometric)
on-complete (rn/use-callback
on-complete-callback (rn/use-callback
(fn [reset-slider-fn]
(js/setTimeout #(reset-slider-fn false) 500)
(rf/dispatch
[:standard-auth/authorize
(rf/dispatch [:standard-auth/authorize
{:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:keycard-supported? keycard-supported?
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label}]))
(vec (conj dependencies on-auth-success on-auth-fail)))
on-complete (or on-complete on-complete-callback)
biometric-type (rf/sub [:biometrics/supported-type])]
[quo/slide-button
{:container-style container-style

View File

@ -27,14 +27,19 @@
contact-request-received? (= contact-request-state
constants/contact-request-state-received)
contact-request-pending? (= contact-request-state
constants/contact-request-state-sent)]
constants/contact-request-state-sent)
keycard? (rf/sub [:keycard/keycard-profile?])
keycard-feature-unavailable (rn/use-callback
#(rf/dispatch [:keycard/feature-unavailable-show]))]
[rn/view {:style style/container}
[quo/permission-context
{:blur? true
:on-press (cond
(and community-chat? (not pending?) (not joined))
(if keycard?
keycard-feature-unavailable
#(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id community-id}])
{:community-id community-id}]))
(not community-chat?)
#(rf/dispatch [:chat.ui/show-profile contact-id]))

View File

@ -27,7 +27,10 @@
(fn []
(let [theme (quo.theme/use-theme)
{:keys [id]} (rf/sub [:get-screen-params])
{:keys [color name images]} (rf/sub [:communities/community id])]
{:keys [color name images]} (rf/sub [:communities/community id])
keycard? (rf/sub [:keycard/keycard-profile?])
keycard-feature-unavailable (rn/use-callback
#(rf/dispatch [:keycard/feature-unavailable-show]))]
[rn/safe-area-view {:flex 1}
[gesture/scroll-view {:style style/container}
[rn/view style/page-container
@ -59,7 +62,9 @@
(i18n/label :t/cancel)]
[quo/button
{:accessibility-label :join-community-button
:on-press #(join-community-and-navigate-back id)
:on-press (if keycard?
keycard-feature-unavailable
#(join-community-and-navigate-back id))
:container-style {:flex 1}
:inner-style {:background-color (colors/resolve-color color theme)}}
(i18n/label :t/request-to-join)]]

View File

@ -106,12 +106,14 @@
[quo/text (i18n/label :t/network-not-supported)])
(defn- request-access-button
[id color]
[id color keycard? keycard-feature-unavailable]
[quo/button
{:on-press (if config/community-accounts-selection-enabled?
{:on-press (if keycard?
keycard-feature-unavailable
(if config/community-accounts-selection-enabled?
#(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id id}])
#(rf/dispatch [:open-modal :community-requests-to-join {:id id}]))
#(rf/dispatch [:open-modal :community-requests-to-join {:id id}])))
:accessibility-label :show-request-to-join-screen-button
:customization-color color
:container-style {:margin-bottom 12}
@ -126,11 +128,13 @@
(defn- token-requirements
[{:keys [id color role-permissions?]}]
(let [{:keys [can-request-access? no-member-permission? networks-not-supported?
(let
[{:keys [can-request-access? no-member-permission? networks-not-supported?
highest-permission-role
tokens]} (rf/sub [:community/token-gated-overview id])
highest-role-text (i18n/label
(communities.utils/role->translation-key highest-permission-role :t/member))
(communities.utils/role->translation-key highest-permission-role
:t/member))
on-press (rn/use-callback
(fn []
(if config/community-accounts-selection-enabled?
@ -140,13 +144,17 @@
{:id id}])))
[id])
on-press-info #(rf/dispatch
[:show-bottom-sheet {:content token-gated-communities-info}])]
[:show-bottom-sheet {:content token-gated-communities-info}])
keycard? (rf/sub [:keycard/keycard-profile?])
keycard-feature-unavailable (rn/use-callback
#(rf/dispatch [:keycard/feature-unavailable-show]))]
(cond
networks-not-supported?
[network-not-supported]
(or (not role-permissions?) no-member-permission?)
[request-access-button id color]
[request-access-button id color keycard? keycard-feature-unavailable]
:else
[quo/community-token-gating
@ -154,7 +162,7 @@
:tokens tokens
:community-color color
:satisfied? can-request-access?
:on-press on-press
:on-press (if keycard? keycard-feature-unavailable on-press)
:on-press-info on-press-info}])))
(defn- join-community

View File

@ -9,7 +9,9 @@
status-im.contexts.keycard.pin.events
status-im.contexts.keycard.sign.events
[status-im.contexts.keycard.utils :as keycard.utils]
utils.datetime))
[utils.address :as address]
utils.datetime
[utils.ethereum.eip.eip55 :as eip55]))
(rf/reg-event-fx :keycard/on-card-connected
(fn [{:keys [db]} _]
@ -65,6 +67,30 @@
(fn [_ [data]]
{:effects.keycard/export-key data}))
(rf/reg-event-fx :keycard/connect-derive-address-and-add-account
(fn [_ [{:keys [pin derivation-path key-uid account-preferences]}]]
{:fx [[:dispatch
[:keycard/connect
{:key-uid key-uid
:on-success
(fn []
(rf/dispatch
[:keycard/export-key
{:pin pin
:path derivation-path
:on-success (fn [public-key]
(let [derived-account {:public-key (str "0x" public-key)
:address (eip55/address->checksum
(str "0x"
(address/public-key->address
(subs public-key 2))))
:path derivation-path}]
(rf/dispatch [:keycard/disconnect])
(rf/dispatch [:wallet/add-account
(assoc account-preferences :key-uid key-uid)
derived-account])))
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]))}]]]}))
(rf/reg-event-fx :keycard/cancel-connection
(fn [{:keys [db]}]
{:db (update db

View File

@ -5,5 +5,8 @@
(rf/reg-event-fx
:keycard/feature-unavailable-show
(fn [_]
{:fx [[:dispatch [:show-bottom-sheet {:content feature-unavailable/view}]]]}))
(fn [_ [options]]
{:fx [[:dispatch
[:show-bottom-sheet
{:theme (:theme options)
:content feature-unavailable/view}]]]}))

View File

@ -7,8 +7,10 @@
[utils.re-frame :as rf]))
(defn- on-press-biometric-enable
[button-label theme]
[button-label theme keycard-profile?]
(fn []
(if keycard-profile?
(rf/dispatch [:keycard/feature-unavailable-show {:theme :dark}])
(rf/dispatch
[:standard-auth/authorize-with-password
{:blur? true
@ -19,12 +21,14 @@
(rf/dispatch
[:biometric/authenticate
{:on-success #(rf/dispatch [:biometric/enable password])
:on-fail #(rf/dispatch [:biometric/show-message (ex-cause %)])}]))}])))
:on-fail #(rf/dispatch [:biometric/show-message
(ex-cause %)])}]))}]))))
(defn- get-biometric-item
[theme]
(let [auth-method (rf/sub [:auth-method])
biometric-type (rf/sub [:biometrics/supported-type])
keycard-profile? (rf/sub [:keycard/keycard-profile?])
label (biometric/get-label-by-type biometric-type)
icon (biometric/get-icon-by-type biometric-type)
supported? (boolean biometric-type)
@ -33,7 +37,7 @@
press-handler (when supported?
(if biometric-on?
(fn [] (rf/dispatch [:biometric/disable]))
(on-press-biometric-enable label theme)))]
(on-press-biometric-enable label theme keycard-profile?)))]
{:title label
:image-props icon
:image :icon
@ -58,7 +62,8 @@
(defn view
[]
(let [theme (quo.theme/use-theme)]
(let [theme (quo.theme/use-theme)
keycard-profile? (rf/sub [:keycard/keycard-profile?])]
[quo/overlay {:type :shell :top-inset? true}
[quo/page-nav
{:background :blur
@ -68,6 +73,7 @@
[quo/category
{:key :category
:data [(get-biometric-item theme)
(get-change-password-item)]
(when-not keycard-profile?
(get-change-password-item))]
:blur? true
:list-type :settings}]]))

View File

@ -41,7 +41,7 @@
(defn- keypair
[{keypair-type :type
:keys [accounts name]
:keys [accounts name keycard?]
:as item}
_ _
{:keys [profile-picture compressed-key customization-color]}]
@ -59,7 +59,7 @@
:type (if default-keypair?
:default-keypair
:keypair)
:stored :on-device
:stored (if keycard? :on-keycard :on-device)
:shortened-key shortened-key
:customization-color customization-color
:profile-picture profile-picture})}))
@ -68,7 +68,7 @@
[quo/keypair
{:blur? true
:status-indicator false
:stored :on-device
:stored (if keycard? :on-keycard :on-device)
:action (if default-keypair? :none :options)
:accounts accounts
:customization-color customization-color

View File

@ -38,7 +38,11 @@
#(if (:enabled? %)
:paired-devices
:unpaired-devices)
other-devices)]
other-devices)
keycard? (rf/sub [:keycard/keycard-profile?])
keycard-feature-unavailable (rn/use-callback
#(rf/dispatch [:keycard/feature-unavailable-show
{:theme :dark}]))]
[quo/overlay {:type :shell :top-inset? true}
[quo/page-nav
{:type :no-title
@ -60,7 +64,9 @@
:type :primary
:customization-color profile-color
:icon-only? true
:on-press open-setup-syncing-with-customization-color}
:on-press (if keycard?
keycard-feature-unavailable
open-setup-syncing-with-customization-color)}
:i/add]]
[device/view (merge user-device {:this-device? true})]
(when (seq paired-devices)

View File

@ -69,7 +69,9 @@
(let [{:keys [customization-color] :as profile} (rf/sub [:profile/profile-with-image])
{:keys [address path watch-only?]} (rf/sub [:wallet/current-viewing-account])
{keypair-name :name
keypair-type :type} (rf/sub [:wallet/current-viewing-account-keypair])
keypair-type :type
keycards :keycards} (rf/sub [:wallet/current-viewing-account-keypair])
keypair-keycard? (boolean (seq keycards))
networks (rf/sub [:wallet/network-preference-details])
origin-type (case keypair-type
:seed
@ -98,7 +100,7 @@
(when (not watch-only?)
[quo/account-origin
{:type origin-type
:stored :on-device
:stored (if keypair-keycard? :on-keycard :on-device)
:profile-picture (profile.utils/photo profile)
:customization-color customization-color
:derivation-path path

View File

@ -58,13 +58,14 @@
[item _ _
{:keys [profile-picture compressed-key selected-key-uid set-selected-key-uid customization-color]}]
(let [profile-keypair? (= (:type item) :profile)
keycard? (boolean (seq (:keycards item)))
accounts (parse-accounts (:accounts item))]
[quo/keypair
{:customization-color customization-color
:profile-picture (when profile-keypair? profile-picture)
:status-indicator false
:type (if profile-keypair? :default-keypair :other)
:stored :on-device
:stored (if keycard? :on-keycard :on-device)
:on-options-press #(js/alert "Options pressed")
:action :selector
:blur? false

View File

@ -22,7 +22,7 @@
[utils.string]))
(defn- get-keypair-data
[{:keys [title primary-keypair? new-keypair? derivation-path customization-color]}]
[{:keys [title primary-keypair? new-keypair? derivation-path customization-color keycard?]}]
[{:title title
:image (if primary-keypair? :avatar :icon)
:image-props (if primary-keypair?
@ -35,7 +35,7 @@
:button-text (i18n/label :t/edit)
:alignment :flex-start}
:description :text
:description-props {:text (i18n/label :t/on-device)}}
:description-props {:text (i18n/label (if keycard? :on-keycard :on-device))}}
(when-not (string/blank? derivation-path)
(let [formatted-path (string/replace derivation-path #"/" " / ")
on-auth-success (fn [password]
@ -123,7 +123,7 @@
:container-style color-picker-style}]])])))
(defn- new-account-origin
[{:keys [keypair-title derivation-path customization-color]}]
[{:keys [keypair-title derivation-path customization-color keycard?]}]
(let [{keypair-name :name} (rf/sub [:wallet/selected-keypair])
primary? (rf/sub [:wallet/selected-primary-keypair?])
keypair-name (or keypair-title
@ -135,7 +135,8 @@
[quo/category
{:list-type :settings
:label (i18n/label :t/origin)
:data (get-keypair-data {:primary-keypair? primary?
:data (get-keypair-data {:keycard? keycard?
:primary-keypair? primary?
:title keypair-name
:derivation-path derivation-path
:customization-color customization-color})}]]))
@ -229,7 +230,8 @@
set-derivation-path #(reset! derivation-path %)]
(fn [{:keys [on-change-text set-account-color set-emoji customization-color error]}]
(let [{:keys [derived-from
key-uid]} (rf/sub [:wallet/selected-keypair])
key-uid keycards]} (rf/sub [:wallet/selected-keypair])
keycard? (boolean (seq keycards))
on-auth-success (rn/use-callback
(fn [password]
(let [preferences {:account-name @account-name
@ -242,6 +244,22 @@
:key-uid key-uid
:derivation-path @derivation-path
:account-preferences preferences}])))
[derived-from])
on-complete (rn/use-callback
(fn []
(let [preferences {:account-name @account-name
:color @account-color
:emoji @emoji}]
(rf/dispatch
[:standard-auth/authorize-with-keycard
{:on-complete
#(rf/dispatch
[:keycard/connect-derive-address-and-add-account
{:pin %
:derived-from-address derived-from
:key-uid key-uid
:derivation-path @derivation-path
:account-preferences preferences}])}])))
[derived-from])]
(rn/use-effect
#(rf/dispatch
@ -252,8 +270,11 @@
[floating-button
{:account-color @account-color
:slide-button-props {:on-auth-success on-auth-success
:disabled? (or (empty? @account-name)
:slide-button-props {:on-auth-success (when-not keycard? on-auth-success)
:on-complete
(when keycard? on-complete)
:disabled?
(or (empty? @account-name)
(string/blank? @derivation-path)
(some? error))}}
[avatar
@ -269,7 +290,8 @@
{:account-color @account-color
:set-account-color set-account-color}]
[new-account-origin
{:derivation-path @derivation-path
{:keycard? keycard?
:derivation-path @derivation-path
:customization-color customization-color}]]))))
(defn view

View File

@ -307,9 +307,8 @@
(rf/reg-event-fx :wallet/add-account
(fn [_
[{:keys [key-uid password account-name emoji color type]
:or {type :generated}}
{:keys [public-key address path] :as _derived-account}]]
[{:keys [key-uid password account-name emoji color type] :or {type :generated}}
{:keys [public-key address path]}]]
(let [lowercase-address (some-> address
string/lower-case)
account-config {:key-uid (when (= type :generated) key-uid)

View File

@ -248,7 +248,9 @@
account))}
user-props {:full-name to-address
:address (utils/get-shortened-address
to-address)}]
to-address)}
sign-on-keycard? (get-in transaction-for-signing
[:signingDetails :signOnKeycard])]
;; In token send flow we already have transaction built when
;; we reach confirmation screen. But in send collectible flow
;; routes request happens at the same time with navigation to
@ -279,20 +281,24 @@
:transaction-type transaction-type}]
(when (and (not loading-suggested-routes?) route (seq route))
[standard-auth/slide-button
{:keycard-supported? true
:size :size-48
{:size :size-48
:track-text (if (= transaction-type :tx/bridge)
(i18n/label :t/slide-to-bridge)
(i18n/label :t/slide-to-send))
:container-style {:z-index 2}
:disabled? (not transaction-for-signing)
:customization-color account-color
:on-auth-success
(fn [data]
(rf/dispatch
:auth-button-label (i18n/label :t/confirm)
:on-complete (when sign-on-keycard?
#(rf/dispatch
[:wallet/prepare-signatures-for-transactions
:send data]))
:auth-button-label (i18n/label :t/confirm)}])]
:send
""]))
:on-auth-success
(fn [psw]
(rf/dispatch
[:wallet/prepare-signatures-for-transactions :send
psw]))}])]
:gradient-cover? true
:customization-color (:color account)}
[rn/view

View File

@ -220,15 +220,20 @@
(let [loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
swap-proposal (rf/sub [:wallet/swap-proposal-without-fees])
account (rf/sub [:wallet/current-viewing-account])
transaction-for-signing (rf/sub [:wallet/swap-transaction-for-signing])
sign-on-keycard? (get-in transaction-for-signing
[:signingDetails :signOnKeycard])
on-auth-success (rn/use-callback
#(rf/dispatch [:wallet/prepare-signatures-for-transactions :swap %]))]
#(rf/dispatch [:wallet/prepare-signatures-for-transactions :swap %]))
on-complete (rn/use-callback
#(rf/dispatch [:wallet/prepare-signatures-for-transactions :swap ""]))]
[standard-auth/slide-button
{:size :size-48
:track-text (i18n/label :t/slide-to-sign)
:container-style {:z-index 2}
:customization-color (:color account)
:disabled? (or loading-swap-proposal? (not swap-proposal))
:keycard-supported? true
:on-complete (when sign-on-keycard? on-complete)
:on-auth-success on-auth-success
:auth-button-label (i18n/label :t/confirm)}]))

View File

@ -168,7 +168,9 @@
transaction-for-signing (rf/sub [:wallet/swap-transaction-for-signing])
swap-proposal (rf/sub [:wallet/swap-proposal-without-fees])
account (rf/sub [:wallet/current-viewing-account])
account-color (:color account)]
account-color (:color account)
sign-on-keycard? (get-in transaction-for-signing
[:signingDetails :signOnKeycard])]
[standard-auth/slide-button
{:size :size-48
:track-text (i18n/label :t/slide-to-swap)
@ -178,7 +180,11 @@
(not swap-proposal)
(not transaction-for-signing))
:auth-button-label (i18n/label :t/confirm)
:keycard-supported? true
:on-complete (when sign-on-keycard?
#(rf/dispatch
[:wallet/prepare-signatures-for-transactions
:swap
""]))
:on-auth-success (fn [data]
(rf/dispatch [:wallet/stop-get-swap-proposal])
(rf/dispatch [:wallet/prepare-signatures-for-transactions :swap data]))}]))

View File

@ -349,12 +349,13 @@
:<- [:wallet/keypairs-list]
(fn [keypairs [_ format-options]]
(reduce
(fn [acc {:keys [accounts name type key-uid lowest-operability]}]
(fn [acc {:keys [accounts name type key-uid lowest-operability keycards]}]
(if (= lowest-operability :no)
(update acc
:missing
conj
{:type type
:keycard? (boolean (seq keycards))
:name name
:key-uid key-uid
:accounts (format-settings-missing-keypair-accounts accounts)})
@ -362,6 +363,7 @@
:operable
conj
{:type type
:keycard? (boolean (seq keycards))
:name name
:key-uid key-uid
:accounts (format-settings-keypair-accounts accounts format-options)})))

View File

@ -141,3 +141,14 @@
(defn zero-address?
[address]
(= address zero-address))
(defn public-key->address
[public-key]
(let [length (count public-key)
normalized-key (case length
132 (str "0x" (subs public-key 4))
130 public-key
128 (str "0x" public-key)
nil)]
(when normalized-key
(subs (native-module/sha3 normalized-key) 26))))