From 3bcf7ecf2998b1bfe05a8ac9657ddbad002b3d5d Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 31 Dec 2019 05:50:45 -0500 Subject: [PATCH] Add BIP39 validation for seed phrase recovery Signed-off-by: yenda --- externs.js | 3 ++- .../status/ethereum/module/StatusModule.java | 22 ++++++++++++++++++ .../ios/RCTStatus/RCTStatus.m | 9 ++++++++ src/status_im/ethereum/mnemonic.cljs | 2 +- src/status_im/multiaccounts/recover/core.cljs | 23 +++++++++++++------ src/status_im/native_module/core.cljs | 6 +++++ .../screens/multiaccounts/recover/views.cljs | 8 +------ .../test/multiaccounts/recover/core.cljs | 5 +--- translations/ar.json | 2 -- translations/en.json | 2 -- translations/fr.json | 2 -- translations/it.json | 2 -- translations/ko.json | 2 -- translations/zh.json | 2 -- 14 files changed, 58 insertions(+), 32 deletions(-) diff --git a/externs.js b/externs.js index f89775cb1a..6250e584a5 100644 --- a/externs.js +++ b/externs.js @@ -614,5 +614,6 @@ var TopLevel = { "multiAccountReset" : function () {}, "multiAccountLoadAccount" : function () {}, "multiAccountStoreAccount" : function () {}, - "multiAccountImportMnemonic" : function () {} + "multiAccountImportMnemonic" : function () {}, + "validateMnemonic" : function () {} } 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 c266c46361..daf61f624a 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 @@ -1253,4 +1253,26 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL public void isDeviceRooted(final Callback callback) { callback.invoke(rootedDevice); } + + @ReactMethod + public void validateMnemonic(final String seed, final Callback callback) { + Log.d(TAG, "validateMnemonic"); + if (!checkAvailability()) { + callback.invoke(false); + return; + } + + Runnable r = new Runnable() { + @Override + public void run() { + String resValidateMnemonic = Statusgo.validateMnemonic(seed); + + Log.d(TAG, resValidateMnemonic); + callback.invoke(resValidateMnemonic); + } + }; + + StatusThreadPoolExecutor.getInstance().execute(r); + } } + diff --git a/modules/react-native-status/ios/RCTStatus/RCTStatus.m b/modules/react-native-status/ios/RCTStatus/RCTStatus.m index e98064aa13..5ec9c927ec 100644 --- a/modules/react-native-status/ios/RCTStatus/RCTStatus.m +++ b/modules/react-native-status/ios/RCTStatus/RCTStatus.m @@ -601,6 +601,15 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(identicon:(NSString *)publicKey) { return StatusgoIdenticon(publicKey); } +RCT_EXPORT_METHOD(validateMnemonic:(NSString *)seed + callback:(RCTResponseSenderBlock)callback) { +#if DEBUG + NSLog(@"validateMnemonic() method called"); +#endif + NSString *result = StatusgoValidateMnemonic(seed); + callback(@[result]); +} + RCT_EXPORT_METHOD(identiconAsync:(NSString *)publicKey callback:(RCTResponseSenderBlock)callback) { #if DEBUG diff --git a/src/status_im/ethereum/mnemonic.cljs b/src/status_im/ethereum/mnemonic.cljs index 443579bf67..48d0af439e 100644 --- a/src/status_im/ethereum/mnemonic.cljs +++ b/src/status_im/ethereum/mnemonic.cljs @@ -6,7 +6,7 @@ (defn sanitize-passphrase [s] (-> (string/trim s) - (string/replace #"\s+" " "))) + (string/replace #"[\s\n]+" " "))) (defn passphrase->words [s] (when s diff --git a/src/status_im/multiaccounts/recover/core.cljs b/src/status_im/multiaccounts/recover/core.cljs index f21c7dcc09..8c3827d415 100644 --- a/src/status_im/multiaccounts/recover/core.cljs +++ b/src/status_im/multiaccounts/recover/core.cljs @@ -22,10 +22,13 @@ [root-key multiaccounts] (contains? multiaccounts (:key-uid root-key))) +(re-frame/reg-fx + ::validate-mnemonic + (fn [[passphrase callback]] + (status/validate-mnemonic passphrase callback))) + (defn check-phrase-warnings [recovery-phrase] - (cond (string/blank? recovery-phrase) :required-field - (not (mnemonic/valid-words? recovery-phrase)) :recovery-phrase-invalid - (not (mnemonic/status-generated-phrase? recovery-phrase)) :recovery-phrase-unknown-words)) + (cond (string/blank? recovery-phrase) :required-field)) (fx/defn set-phrase {:events [:multiaccounts.recover/passphrase-input-changed]} @@ -160,15 +163,21 @@ (navigation/navigate-to-cofx :recover-multiaccount-enter-phrase nil))) (fx/defn proceed-to-import-mnemonic - {:events [:multiaccounts.recover/enter-phrase-next-pressed]} - [{:keys [db] :as cofx}] + {:events [:multiaccounts.recover/phrase-validated]} + [{:keys [db] :as cofx} phrase-warnings] (let [{:keys [password passphrase]} (:intro-wizard db)] - (if (check-phrase-warnings passphrase) + (if-not (string/blank? (:error (types/json->clj phrase-warnings))) (popover/show-popover cofx {:view :custom-seed-phrase}) (when (mnemonic/valid-length? passphrase) - {::import-multiaccount {:passphrase passphrase + {::import-multiaccount {:passphrase (mnemonic/sanitize-passphrase passphrase) :password password}})))) +(fx/defn seed-phrase-next-pressed + {:events [:multiaccounts.recover/enter-phrase-next-pressed]} + [{:keys [db] :as cofx}] + (let [{:keys [passphrase]} (:intro-wizard db)] + {::validate-mnemonic [passphrase #(re-frame/dispatch [:multiaccounts.recover/phrase-validated %])]})) + (fx/defn continue-to-import-mnemonic {:events [::continue-pressed]} [{:keys [db] :as cofx}] diff --git a/src/status_im/native_module/core.cljs b/src/status_im/native_module/core.cljs index b090edee8a..de68ca9e2a 100644 --- a/src/status_im/native_module/core.cljs +++ b/src/status_im/native_module/core.cljs @@ -338,3 +338,9 @@ [seed callback] (log/debug "[native-module] gfycat-identicon-async") (.generateAliasAndIdenticonAsync (status) seed callback)) + +(defn validate-mnemonic + "Validate that a mnemonic conforms to BIP39 dictionary/checksum standards" + [mnemonic callback] + (log/debug "[native-module] validate-mnemonic") + (.validateMnemonic (status) mnemonic callback)) \ No newline at end of file diff --git a/src/status_im/ui/screens/multiaccounts/recover/views.cljs b/src/status_im/ui/screens/multiaccounts/recover/views.cljs index e5f2ba53d1..e49c263276 100644 --- a/src/status_im/ui/screens/multiaccounts/recover/views.cljs +++ b/src/status_im/ui/screens/multiaccounts/recover/views.cljs @@ -34,15 +34,9 @@ :line-height 22}} (i18n/label :t/custom-seed-phrase-text-1) [{:style {:color colors/black}} - (i18n/label :t/custom-seed-phrase-text-2)] - (i18n/label :t/custom-seed-phrase-text-3) - [{:style {:color colors/black}} - (i18n/label :t/custom-seed-phrase-text-4)]]] + (i18n/label :t/custom-seed-phrase-text-2)]]] [react/view {:margin-vertical 24 :align-items :center} - [button/button {:on-press #(re-frame/dispatch [::multiaccounts.recover/continue-pressed]) - :accessibility-label :continue-custom-seed-phrase - :label (i18n/label :t/continue)}] [button/button {:on-press #(re-frame/dispatch [:hide-popover]) :accessibility-label :cancel-custom-seed-phrase :type :secondary diff --git a/test/cljs/status_im/test/multiaccounts/recover/core.cljs b/test/cljs/status_im/test/multiaccounts/recover/core.cljs index 5c843d305a..d239287e6c 100644 --- a/test/cljs/status_im/test/multiaccounts/recover/core.cljs +++ b/test/cljs/status_im/test/multiaccounts/recover/core.cljs @@ -2,7 +2,6 @@ (:require [cljs.test :refer-macros [deftest is testing]] [status-im.multiaccounts.recover.core :as models] [status-im.multiaccounts.create.core :as multiaccounts.create] - [clojure.string :as string] [status-im.utils.security :as security] [status-im.i18n :as i18n])) @@ -10,9 +9,7 @@ (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")))) + (is (= :required-field (models/check-phrase-warnings "")))) ;;;; handlers diff --git a/translations/ar.json b/translations/ar.json index 618f22fc5b..a9bf20c004 100644 --- a/translations/ar.json +++ b/translations/ar.json @@ -289,8 +289,6 @@ "custom-seed-phrase": "عبارة البذور المخصصة", "custom-seed-phrase-text-1": "هذا يبدو وكأنه عبارة أولية مخصصة ولا يتطابق مع قاموس Status. هذا يمكن أن يعني أيضا", "custom-seed-phrase-text-2": "بعض الكلمات بها أخطاء إملائية.", - "custom-seed-phrase-text-3": "إذا كان الأمر كذلك ، فسوف ينتهي إنشاء", - "custom-seed-phrase-text-4": "حساب جديد", "dapp": "ÐApp", "dapp-would-like-to-connect-wallet": "ترغب في الاتصال", "dapps": "ÐApps", diff --git a/translations/en.json b/translations/en.json index 7170171206..7b2d86fbd1 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1116,8 +1116,6 @@ "custom-seed-phrase": "Custom seed phrase", "custom-seed-phrase-text-1": "This looks like a custom seed phrase and doesn't match the Status dictionary. This could also mean ", "custom-seed-phrase-text-2": "some words are misspelled.", - "custom-seed-phrase-text-3": " If so, you'll end up creating a", - "custom-seed-phrase-text-4": " new account", "to-enable-biometric": "To enable {{bio-type-label}}, your must save your password on the unlock screen", "ok-save-pass": "OK, save password", "lock-app-with": "Lock app with", diff --git a/translations/fr.json b/translations/fr.json index aee1097c87..e85cdd55db 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -281,8 +281,6 @@ "custom-seed-phrase": "Phrase de récupération personnalisée", "custom-seed-phrase-text-1": "Cela ressemble à une phrase de récupération personnalisée et ne correspond pas au dictionnaire de Status. Cela pourrait aussi signifier", "custom-seed-phrase-text-2": "certains mots sont mal orthographiés.", - "custom-seed-phrase-text-3": "Si c'est le cas, vous allez créer un", - "custom-seed-phrase-text-4": "nouveau compte", "dapp": "ÐApp", "dapp-would-like-to-connect-wallet": "souhaite se connecter à votre portefeuille", "dapps": "ÐApps", diff --git a/translations/it.json b/translations/it.json index 8650e78383..7724bc3d83 100644 --- a/translations/it.json +++ b/translations/it.json @@ -281,8 +281,6 @@ "custom-seed-phrase": "Frase di recupero personalizzata", "custom-seed-phrase-text-1": "Sembra una frase seed personalizzata e non corrisponde al dizionario di Status. Questo potrebbe anche significare", "custom-seed-phrase-text-2": "alcune parole sono scritte male.", - "custom-seed-phrase-text-3": "In tal caso, finirai per creare un", - "custom-seed-phrase-text-4": "Nuovo account", "dapp": "ÐApp", "dapp-would-like-to-connect-wallet": "vorrebbe connettersi a", "dapps": "ÐApps", diff --git a/translations/ko.json b/translations/ko.json index 093e7c1748..fd5da8cc26 100644 --- a/translations/ko.json +++ b/translations/ko.json @@ -279,8 +279,6 @@ "custom-seed-phrase": "커스텀 시드 구문", "custom-seed-phrase-text-1": "커스텀 시드 구문이 스테이터스 딕셔너리와 일치하지 않습니다. 또한", "custom-seed-phrase-text-2": "일부 단어의 철자가 잘못되었을 수 있습니다.", - "custom-seed-phrase-text-3": " 이 경우", - "custom-seed-phrase-text-4": "새 계정을 생성하게 됩니다.", "dapp": "디앱", "dapp-would-like-to-connect-wallet": "(이)가 사용자 다음에 연결합니다", "dapps": "디앱", diff --git a/translations/zh.json b/translations/zh.json index c88c3dc75e..dea47ed762 100644 --- a/translations/zh.json +++ b/translations/zh.json @@ -279,8 +279,6 @@ "custom-seed-phrase": "自定义助记词", "custom-seed-phrase-text-1": "这好像是一个自定义的助记词,与“Status”字库不匹配。这也可能意味着", "custom-seed-phrase-text-2": "有些单词拼写错误。", - "custom-seed-phrase-text-3": "如果是这样,您最终将创建一个", - "custom-seed-phrase-text-4": "新账户", "dapp": "ÐApp", "dapp-would-like-to-connect-wallet": "想要连接到", "dapps": "ÐApp",