feature #5310 - improved validation in account recovery screen; also bug #5353

Signed-off-by: Goran Jovic <goranjovic@gmail.com>
This commit is contained in:
Goran Jovic 2018-08-12 12:21:32 +02:00
parent 861892e776
commit 2b89d1e25c
No known key found for this signature in database
GPG Key ID: D429D1A9B2EB8A8E
16 changed files with 352 additions and 100 deletions

View File

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Fixed
### Changed
- Improved validation in account recovery. We now show a warning if any words are not from our mnemonic dictionary.
## [0.9.25 - Unreleased]
### Added

View File

@ -32,6 +32,8 @@
:mailserver-connection-error "Could not connect to mailserver"
:dont-allow "Don't Allow"
:custom "Custom"
:required-field "Required field"
:are-you-sure? "Are you sure?"
:camera-access-error "To grant the required camera permission, please go to your system settings and make sure that Status > Camera is selected."
:photos-access-error "To grant the required photos permission, please go to your system settings and make sure that Status > Photos is selected."
@ -172,7 +174,6 @@
:enter-word "Enter word"
:check-your-recovery-phrase "Check your recovery phrase"
:wrong-word "Wrong word"
:are-you-sure? "Are you sure?"
:are-you-sure-description "You will not be able to see the whole recovery phrase again"
:you-are-all-set "Youre all set!"
:you-are-all-set-description "Now if you lose your phone you can restore your account and wallet using the recovery phrase."
@ -393,6 +394,12 @@
:recover "Recover"
:twelve-words-in-correct-order "12 words in correct order"
:enter-12-words "Enter the 12 words of your recovery phrase, separated by single spaces"
:recover-password-too-short "Password is too short"
:recovery-phrase-invalid "Recovery phrase is invalid"
:recovery-phrase-unknown-words "Some words might be misspelled"
:recovery-typo-dialog-title "Is the phrase correct?"
:recovery-typo-dialog-description "If you enter the wrong words, you will create a new account instead of recovering an old one."
:recovery-confirm-phrase "Confirm phrase"
;;accounts
:recover-access "Recover access"

View File

@ -13,6 +13,26 @@
;;;; Handlers
(handlers/register-handler-fx
:recover/set-phrase
(fn [cofx [_ recovery-phrase]]
(models/set-phrase recovery-phrase cofx)))
(handlers/register-handler-fx
:recover/validate-phrase
(fn [cofx _]
(models/validate-phrase cofx)))
(handlers/register-handler-fx
:recover/set-password
(fn [cofx [_ masked-password]]
(models/set-password masked-password cofx)))
(handlers/register-handler-fx
:recover/validate-password
(fn [cofx _]
(models/validate-password cofx)))
(handlers/register-handler-fx
:account-recovered
(fn [cofx [_ result password]]
@ -25,5 +45,10 @@
(handlers/register-handler-fx
:recover-account
(fn [cofx [_ masked-passphrase password]]
(models/recover-account masked-passphrase password cofx)))
(fn [cofx _]
(models/recover-account cofx)))
(handlers/register-handler-fx
:recover-account-with-checks
(fn [cofx _]
(models/recover-account-with-checks cofx)))

View File

@ -10,13 +10,31 @@
[status-im.utils.security :as security]
[status-im.utils.signing-phrase.core :as signing-phrase]
[status-im.utils.hex :as utils.hex]
[status-im.constants :as constants]))
[status-im.constants :as constants]
[cljs.spec.alpha :as spec]
[status-im.ui.screens.accounts.db :as db]
[status-im.utils.ethereum.mnemonic :as mnemonic]
[status-im.i18n :as i18n]))
;;;; helpers
(defn check-password-errors [password]
(cond (string/blank? password) :required-field
(not (db/valid-length? password)) :recover-password-too-short))
(defn check-phrase-errors [recovery-phrase]
(cond (string/blank? recovery-phrase) :required-field
(not (mnemonic/valid-phrase? recovery-phrase)) :recovery-phrase-invalid))
(defn check-phrase-warnings [recovery-phrase]
(when (not (mnemonic/status-generated-phrase? recovery-phrase))
:recovery-phrase-unknown-words))
;;;; FX
(defn recover-account-fx! [masked-passphrase password]
(status/recover-account
(security/unmask masked-passphrase)
(mnemonic/sanitize-passphrase (security/unmask masked-passphrase))
password
(fn [result]
;; here we deserialize result, dissoc mnemonic and serialize the result again
@ -29,6 +47,27 @@
;;;; Handlers
(defn set-phrase [recovery-phrase {:keys [db]}]
{:db (update db :accounts/recover assoc
:passphrase (string/lower-case recovery-phrase)
:passphrase-valid? (not (check-phrase-errors recovery-phrase)))})
(defn validate-phrase [{:keys [db]}]
(let [recovery-phrase (get-in db [:accounts/recover :passphrase])]
{:db (update db :accounts/recover assoc
:passphrase-error (check-phrase-errors recovery-phrase)
:passphrase-warning (check-phrase-warnings recovery-phrase))}))
(defn set-password [masked-password {:keys [db]}]
(let [password (security/unmask masked-password)]
{:db (update db :accounts/recover assoc
:password password
:password-valid? (not (check-password-errors password)))}))
(defn validate-password [{:keys [db]}]
(let [password (get-in db [:accounts/recover :password])]
{:db (assoc-in db [:accounts/recover :password-error] (check-password-errors password))}))
(defn on-account-recovered [result password {:keys [db]}]
(let [data (types/json->clj result)
public-key (:pubkey data)
@ -51,10 +90,20 @@
(assoc :dispatch-later [{:ms 2000 :dispatch [:account-recovered-navigate]}])))))
(defn account-recovered-navigate [{:keys [db]}]
{:db (assoc-in db [:accounts/recover :processing] false)
{:db (assoc-in db [:accounts/recover :processing?] false)
:dispatch-n [[:navigate-to-clean :home]
[:request-notifications]]})
(defn recover-account [masked-passphrase password {:keys [db]}]
{:db (assoc-in db [:accounts/recover :processing] true)
:recover-account-fx [masked-passphrase password]})
(defn recover-account [{:keys [db]}]
(let [{:keys [password passphrase]} (:accounts/recover db)]
{:db (assoc-in db [:accounts/recover :processing?] true)
:recover-account-fx [(security/mask-data passphrase) password]}))
(defn recover-account-with-checks [{:keys [db] :as cofx}]
(let [{:keys [passphrase]} (:accounts/recover db)]
(if (mnemonic/status-generated-phrase? passphrase)
(recover-account cofx)
{:show-confirmation {:title (i18n/label :recovery-typo-dialog-title)
:content (i18n/label :recovery-typo-dialog-description)
:confirm-button-text (i18n/label :recovery-confirm-phrase)
:on-accept #(re-frame/dispatch [:recover-account])}})))

View File

@ -3,4 +3,4 @@
(defmethod nav/preload-data! :recover
[db]
(update db :accounts/recover dissoc :password :passphrase :processing))
(update db :accounts/recover dissoc :password :passphrase :processing?))

View File

@ -5,6 +5,12 @@
{:flex 1
:background-color colors/white})
(def inputs-container
{:margin 16})
(def password-input
{:margin-top 10})
(def bottom-button-container
{:flex-direction :row
:margin-horizontal 12

View File

@ -1,26 +1,22 @@
(ns status-im.ui.screens.accounts.recover.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.ui.components.text-input.view :as text-input]
[status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.styles :as components.styles]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.i18n :as i18n]
[status-im.ui.screens.accounts.recover.styles :as styles]
[status-im.ui.screens.accounts.db :as db]
[status-im.ui.components.styles :as components.styles]
[status-im.utils.config :as config]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.core :as utils.core]
[status-im.react-native.js-dependencies :as js-dependencies]
[cljs.spec.alpha :as spec]
[status-im.ui.components.common.common :as components.common]
[status-im.utils.security :as security]))
(defview passphrase-input [passphrase]
(letsubs [error [:get-in [:accounts/recover :passphrase-error]]
input-ref (reagent/atom nil)]
(defview passphrase-input [passphrase error warning]
(letsubs [input-ref (reagent/atom nil)]
{:component-did-mount (fn [_] (when config/testfairy-enabled?
(.hideView js-dependencies/testfairy @input-ref)))}
[text-input/text-input-with-label
@ -32,47 +28,47 @@
:multiline true
:default-value passphrase
:auto-correct false
:on-change-text #(re-frame/dispatch [:set-in [:accounts/recover :passphrase] (string/lower-case %)])
:error error}]))
:on-change-text #(re-frame/dispatch [:recover/set-phrase %])
:on-blur #(re-frame/dispatch [:recover/validate-phrase])
:error (cond error (i18n/label error)
warning (i18n/label warning))}]))
(defview password-input [password]
(letsubs [error [:get-in [:accounts/recover :password-error]]]
[react/view {:style {:margin-top 10}
:important-for-accessibility :no-hide-descendants}
[text-input/text-input-with-label
{:label (i18n/label :t/password)
:placeholder (i18n/label :t/enter-password)
:default-value password
:auto-focus false
:on-change-text #(re-frame/dispatch [:set-in [:accounts/recover :password] %])
:secure-text-entry true
:error error}]]))
(defview password-input [password error]
[react/view {:style styles/password-input
:important-for-accessibility :no-hide-descendants}
[text-input/text-input-with-label
{:label (i18n/label :t/password)
:placeholder (i18n/label :t/enter-password)
:default-value password
:auto-focus false
:on-change-text #(re-frame/dispatch [:recover/set-password (security/mask-data %)])
:on-blur #(re-frame/dispatch [:recover/validate-password])
:secure-text-entry true
:error (when error (i18n/label error))}]])
(defview recover []
(letsubs [{:keys [passphrase password processing]} [:get :accounts/recover]]
(let [words (ethereum/passphrase->words passphrase)
valid-form? (and
(ethereum/valid-words? words)
(spec/valid? ::db/password password))]
(letsubs [{:keys [passphrase password processing passphrase-valid? password-valid?
password-error passphrase-error passphrase-warning processing?]} [:get-recover-account]]
(let [valid-form? (and password-valid? passphrase-valid?)]
[react/keyboard-avoiding-view {:style styles/screen-container}
[status-bar/status-bar]
[toolbar/toolbar nil toolbar/default-nav-back
[toolbar/content-title (i18n/label :t/sign-in-to-another)]]
[components.common/separator]
[react/view {:margin 16}
[passphrase-input (or passphrase "")]
[password-input (or password "")]]
[react/view {:flex 1}]
[react/view styles/inputs-container
[passphrase-input (or passphrase "") passphrase-error passphrase-warning]
[password-input (or password "") password-error]]
[react/view components.styles/flex]
(if processing
[react/view styles/processing-view
[react/activity-indicator {:animating true}]
[react/i18n-text {:style styles/sign-you-in :key :sign-you-in}]]
[react/i18n-text {:style styles/sign-you-in
:key :sign-you-in}]]
[react/view {:style styles/bottom-button-container}
[react/view {:style {:flex 1}}]
[react/view {:style components.styles/flex}]
[components.common/bottom-button
{:forward? true
:label (i18n/label :t/sign-in)
:disabled? (not valid-form?)
:on-press (fn [_]
(let [masked-passphrase (security/mask-data (ethereum/words->passphrase words))]
(re-frame/dispatch [:recover-account masked-passphrase password])))}]])])))
:disabled? (or processing? (not valid-form?))
:on-press (utils.core/wrap-call-once!
#(re-frame/dispatch [:recover-account-with-checks]))}]])])))

View File

@ -1,31 +1,40 @@
(ns status-im.ui.screens.accounts.subs
(:require [re-frame.core :refer [reg-sub subscribe]]
(:require [re-frame.core :as re-frame]
[clojure.string :as string]
[status-im.ui.screens.accounts.db :as db]
[status-im.utils.ethereum.core :as ethereum]
[cljs.spec.alpha :as spec]))
(reg-sub :get-current-public-key
(fn [db]
(:current-public-key db)))
(re-frame/reg-sub
:get-current-public-key
(fn [db]
(:current-public-key db)))
(reg-sub :get-accounts
(fn [db]
(:accounts/accounts db)))
(re-frame/reg-sub
:get-accounts
(fn [db]
(:accounts/accounts db)))
(reg-sub :get-current-account
(fn [db]
(:account/account db)))
(re-frame/reg-sub
:get-current-account
(fn [db]
(:account/account db)))
(reg-sub :get-current-account-hex
:<- [:get-current-account]
(fn [{:keys [address]}]
(ethereum/normalized-address address)))
(re-frame/reg-sub
:get-current-account-hex
:<- [:get-current-account]
(fn [{:keys [address]}]
(ethereum/normalized-address address)))
(reg-sub
(re-frame/reg-sub
:get-account-creation-next-enabled?
(fn [{:accounts/keys [create]}]
(let [{:keys [step password password-confirm name]} create]
(or (and password (= :enter-password step) (spec/valid? ::db/password password))
(and password-confirm (= :confirm-password step) (spec/valid? ::db/password password-confirm))
(and name (= :enter-name step) (not (string/blank? name)))))))
(re-frame/reg-sub
:get-recover-account
(fn [db]
(:accounts/recover db)))

View File

@ -1,7 +1,10 @@
(ns status-im.ui.screens.browser.subs
(:require [re-frame.core :as re-frame]))
(re-frame/reg-sub :browsers :browser/browsers)
(re-frame/reg-sub
:browsers
(fn [db _]
(:browser/browsers db)))
(re-frame/reg-sub
:get-current-browser

View File

@ -25,28 +25,6 @@
(defn network-with-upstream-rpc? [network]
(get-in network [:config :UpstreamConfig :Enabled]))
(defn passphrase->words [s]
(when s
(-> (string/trim s)
(string/replace-all #"\s+" " ")
(string/split #" "))))
(defn words->passphrase [v]
(string/join " " v))
(def valid-word-counts #{12 15 18 21 24})
(defn valid-word-counts? [v]
(boolean (valid-word-counts (count v))))
(defn- valid-word? [s]
(re-matches #"^[A-z]+$" s))
(defn valid-words? [v]
(and
(valid-word-counts? v)
(every? valid-word? v)))
(def hex-prefix "0x")
(defn normalized-address [address]

File diff suppressed because one or more lines are too long

View File

@ -36,6 +36,7 @@
[status-im.test.utils.ethereum.eip681]
[status-im.test.utils.ethereum.core]
[status-im.test.utils.ethereum.ens]
[status-im.test.utils.ethereum.mnemonic]
[status-im.test.utils.random]
[status-im.test.utils.gfycat.core]
[status-im.test.utils.signing-phrase.core]
@ -47,7 +48,8 @@
[status-im.test.utils.universal-links.core]
[status-im.test.utils.http]
[status-im.test.ui.screens.events]
[status-im.test.ui.screens.accounts.login.events]
[status-im.test.ui.screens.accounts.login.models]
[status-im.test.ui.screens.accounts.recover.models]
[status-im.test.ui.screens.wallet.db]))
(enable-console-print!)
@ -94,6 +96,7 @@
'status-im.test.utils.clocks
'status-im.test.utils.ethereum.eip681
'status-im.test.utils.ethereum.core
'status-im.test.utils.ethereum.mnemonic
'status-im.test.utils.ethereum.ens
'status-im.test.utils.random
'status-im.test.utils.gfycat.core
@ -105,6 +108,7 @@
'status-im.test.utils.universal-links.core
'status-im.test.utils.http
'status-im.test.ui.screens.events
'status-im.test.ui.screens.accounts.login.events
'status-im.test.ui.screens.accounts.login.models
'status-im.test.ui.screens.accounts.recover.models
'status-im.test.ui.screens.wallet.db
'status-im.test.browser.events)

View File

@ -1,4 +1,4 @@
(ns status-im.test.ui.screens.accounts.login.events
(ns status-im.test.ui.screens.accounts.login.models
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.config :as config]
[status-im.ui.screens.accounts.login.models :as models]))

View File

@ -0,0 +1,125 @@
(ns status-im.test.ui.screens.accounts.recover.models
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.ui.screens.accounts.recover.models :as models]
[clojure.string :as string]
[status-im.utils.security :as security]
[status-im.i18n :as i18n]))
;;;; helpers
(deftest check-password-errors
(is (= :required-field (models/check-password-errors nil)))
(is (= :required-field (models/check-password-errors " ")))
(is (= :required-field (models/check-password-errors " \t\n ")))
(is (= :recover-password-too-short) (models/check-password-errors "a"))
(is (= :recover-password-too-short) (models/check-password-errors "abc"))
(is (= :recover-password-too-short) (models/check-password-errors "12345"))
(is (nil? (models/check-password-errors "123456")))
(is (nil? (models/check-password-errors "thisisapasswoord"))))
(deftest check-phrase-errors
(is (= :required-field (models/check-phrase-errors nil)))
(is (= :required-field (models/check-phrase-errors " ")))
(is (= :required-field (models/check-phrase-errors " \t\n ")))
(is (= :recovery-phrase-invalid (models/check-phrase-errors "phrase with four words")))
(is (= :recovery-phrase-invalid (models/check-phrase-errors "phrase with five cool words")))
(is (nil? (models/check-phrase-errors "monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey")))
(is (nil? (models/check-phrase-errors (string/join " " (repeat 15 "monkey")))))
(is (nil? (models/check-phrase-errors (string/join " " (repeat 18 "monkey")))))
(is (nil? (models/check-phrase-errors (string/join " " (repeat 24 "monkey")))))
(is (= :recovery-phrase-invalid (models/check-phrase-errors (string/join " " (repeat 14 "monkey")))))
(is (= :recovery-phrase-invalid (models/check-phrase-errors (string/join " " (repeat 11 "monkey")))))
(is (= :recovery-phrase-invalid (models/check-phrase-errors (string/join " " (repeat 19 "monkey")))))
(is (= :recovery-phrase-invalid (models/check-phrase-errors "monkey monkey monkey 12345 monkey adf+123 monkey monkey monkey monkey monkey monkey")))
;;NOTE(goranjovic): the following check should be ok because we sanitize extra whitespace
(is (nil? (models/check-phrase-errors " monkey monkey monkey\t monkey monkey monkey monkey monkey monkey monkey monkey monkey \t "))))
(deftest check-phrase-warnings
(is (nil? (models/check-phrase-warnings "monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey")))
(is (nil? (models/check-phrase-warnings "game buzz method pretty olympic fat quit display velvet unveil marine crater")))
(is (= :recovery-phrase-unknown-words (models/check-phrase-warnings "game buzz method pretty zeus fat quit display velvet unveil marine crater"))))
;;;; handlers
(deftest set-phrase
(is (= {:db {:accounts/recover {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"
:passphrase-valid? true}}}
(models/set-phrase "game buzz method pretty olympic fat quit display velvet unveil marine crater" {:db {}})))
(is (= {:db {:accounts/recover {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"
:passphrase-valid? true}}}
(models/set-phrase "Game buzz method pretty Olympic fat quit DISPLAY velvet unveil marine crater" {:db {}})))
(is (= {:db {:accounts/recover {:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"
:passphrase-valid? true}}}
(models/set-phrase "game buzz method pretty zeus fat quit display velvet unveil marine crater" {:db {}})))
(is (= {:db {:accounts/recover {:passphrase " game\t buzz method pretty olympic fat quit\t display velvet unveil marine crater "
:passphrase-valid? true}}}
(models/set-phrase " game\t buzz method pretty olympic fat quit\t display velvet unveil marine crater " {:db {}})))
(is (= {:db {:accounts/recover {:passphrase "game buzz method pretty 1234 fat quit display velvet unveil marine crater"
:passphrase-valid? false}}}
(models/set-phrase "game buzz method pretty 1234 fat quit display velvet unveil marine crater" {:db {}}))))
(deftest validate-phrase
(is (= {:db {:accounts/recover {:passphrase-error nil
:passphrase-warning nil
:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"}}}
(models/validate-phrase {:db {:accounts/recover {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"}}})))
(is (= {:db {:accounts/recover {:passphrase-error nil
:passphrase-warning :recovery-phrase-unknown-words
:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"}}}
(models/validate-phrase {:db {:accounts/recover {:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"}}})))
(is (= {:db {:accounts/recover {:passphrase-error :recovery-phrase-invalid
:passphrase-warning :recovery-phrase-unknown-words
:passphrase "game buzz method pretty 1234 fat quit display velvet unveil marine crater"}}}
(models/validate-phrase {:db {:accounts/recover {:passphrase "game buzz method pretty 1234 fat quit display velvet unveil marine crater"}}}))))
(deftest set-password
(is (= {:db {:accounts/recover {:password " "
:password-valid? false}}}
(models/set-password (security/mask-data " ") {:db {}})))
(is (= {:db {:accounts/recover {:password "abc"
:password-valid? false}}}
(models/set-password (security/mask-data "abc") {:db {}})))
(is (= {:db {:accounts/recover {:password "thisisapaswoord"
:password-valid? true}}}
(models/set-password (security/mask-data "thisisapaswoord") {:db {}}))))
(deftest validate-password
(is (= {:db {:accounts/recover {:password " "
:password-error :required-field}}}
(models/validate-password {:db {:accounts/recover {:password " "}}})))
(is (= {:db {:accounts/recover {:password "abc"
:password-error :recover-password-too-short}}}
(models/validate-password {:db {:accounts/recover {:password "abc"}}})))
(is (= {:db {:accounts/recover {:password "thisisapaswoord"
:password-error nil}}}
(models/validate-password {:db {:accounts/recover {:password "thisisapaswoord"}}}))))
(deftest recover-account
(let [new-cofx (models/recover-account {:db {:accounts/recover
{:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"
:password "thisisapaswoord"}}})]
(is (= {:accounts/recover {:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"
:password "thisisapaswoord"
:processing? true}}
(:db new-cofx)))
(is (= security/MaskedData
(-> new-cofx :recover-account-fx first type)))
(is (= "thisisapaswoord" (-> new-cofx :recover-account-fx second)))))
(deftest recover-account-with-checks
(let [new-cofx (models/recover-account-with-checks {:db {:accounts/recover
{:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"
:password "thisisapaswoord"}}})]
(is (= {:accounts/recover {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"
:password "thisisapaswoord"
:processing? true}}
(:db new-cofx)))
(is (= security/MaskedData
(-> new-cofx :recover-account-fx first type)))
(is (= "thisisapaswoord" (-> new-cofx :recover-account-fx second))))
(let [new-cofx (models/recover-account-with-checks {:db {:accounts/recover
{:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"
:password "thisisapaswoord"}}})]
(is (= (i18n/label :recovery-typo-dialog-title) (-> new-cofx :show-confirmation :title)))
(is (= (i18n/label :recovery-typo-dialog-description) (-> new-cofx :show-confirmation :content)))
(is (= (i18n/label :recovery-confirm-phrase) (-> new-cofx :show-confirmation :confirm-button-text)))))

View File

@ -10,17 +10,6 @@
{:to "0x29b5f6efad2ad701952dfde9f29c960b5d6199c5"
:data "0x70a08231000000000000000000000000a7cfd581060ec66414790691681732db249502bd"})))))
(deftest valid-words?
(is (not (true? (ethereum/valid-words? ["rate" "rate"]))))
(is (not (true? (ethereum/valid-words? ["rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate?"]))))
(is (true? (ethereum/valid-words? ["rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate"]))))
(deftest passphrase->words?
(is (= ["one" "two" "three" "for" "five" "six" "seven" "height" "nine" "ten" "eleven" "twelve"]
(ethereum/passphrase->words "one two three for five six seven height nine ten eleven twelve"))
(= ["one" "two" "three" "for" "five" "six" "seven" "height" "nine" "ten" "eleven" "twelve"]
(ethereum/passphrase->words " one two three for five six seven height nine ten eleven twelve "))))
(deftest chain-id->chain-keyword
(is (= (ethereum/chain-id->chain-keyword 1) :mainnet))
(is (= (ethereum/chain-id->chain-keyword 3) :testnet))

View File

@ -0,0 +1,23 @@
(ns status-im.test.utils.ethereum.mnemonic
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.ethereum.mnemonic :as mnemonic]))
(deftest valid-words?
(is (not (mnemonic/valid-words? ["rate" "rate"])))
(is (not (mnemonic/valid-words? ["rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate?"])))
(is (mnemonic/valid-words? ["rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate"])))
(deftest valid-phrase
(is (not (mnemonic/valid-phrase? "rate rate")))
(is (not (mnemonic/valid-phrase? "rate rate rate rate rate rate rate rate rate rate rate rate?")))
(is (mnemonic/valid-phrase? "rate rate rate rate rate rate rate rate rate rate rate rate")))
(deftest passphrase->words?
(is (= ["one" "two" "three" "for" "five" "six" "seven" "height" "nine" "ten" "eleven" "twelve"]
(mnemonic/passphrase->words "one two three for five six seven height nine ten eleven twelve"))
(= ["one" "two" "three" "for" "five" "six" "seven" "height" "nine" "ten" "eleven" "twelve"]
(mnemonic/passphrase->words " one two three for five six seven height nine ten eleven twelve "))))
(deftest status-generate-phrase?
(is (mnemonic/status-generated-phrase? "game buzz method pretty olympic fat quit display velvet unveil marine crater"))
(is (not (mnemonic/status-generated-phrase? "game buzz method pretty zeus fat quit display velvet unveil marine crater"))))