From 435ad8b105391fd29dc6140397844035157dce99 Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Tue, 24 Jul 2018 11:17:03 -0300 Subject: [PATCH] Add trezor wallet creation --- app/actions/ClientActions.js | 2 - app/actions/DaemonActions.js | 2 + app/actions/TrezorActions.js | 70 +++++++++++++- .../modals/PassphraseModal/Modal.js | 3 +- app/components/modals/trezor/Modals.js | 30 +++--- .../modals/trezor/PassPhraseModal.js | 10 +- app/components/modals/trezor/PinModal.js | 7 +- .../trezor/WalletCreationPassPhraseModal.js | 96 +++++++++++++++++++ app/components/modals/trezor/WordModal.js | 8 +- .../views/GetStartedPage/TrezorConfig/Form.js | 27 ++++++ .../views/GetStartedPage/TrezorConfig/Page.js | 33 +++++++ .../GetStartedPage/TrezorConfig/index.js | 94 ++++++++++++++++++ .../WalletSelection/CreateWalletForm.js | 12 +++ .../GetStartedPage/WalletSelection/index.js | 37 +++++-- app/components/views/GetStartedPage/index.js | 20 +++- app/connectors/createWallet.js | 7 ++ app/connectors/trezor.js | 5 + .../TrezorWalletCreationPassPhrase.md | 6 ++ app/i18n/docs/en/index.js | 1 + app/index.js | 6 +- app/main_dev/ipc.js | 3 +- app/reducers/snackbar.js | 12 +++ app/reducers/trezor.js | 18 ++++ app/selectors.js | 2 + app/style/GetStarted.less | 4 + app/style/Trezor.less | 6 ++ app/wallet/daemon.js | 2 +- 27 files changed, 488 insertions(+), 35 deletions(-) create mode 100644 app/components/modals/trezor/WalletCreationPassPhraseModal.js create mode 100644 app/components/views/GetStartedPage/TrezorConfig/Form.js create mode 100644 app/components/views/GetStartedPage/TrezorConfig/Page.js create mode 100644 app/components/views/GetStartedPage/TrezorConfig/index.js create mode 100644 app/i18n/docs/en/Warnings/TrezorWalletCreationPassPhrase.md diff --git a/app/actions/ClientActions.js b/app/actions/ClientActions.js index 7d8849cda4..2d0352faf2 100644 --- a/app/actions/ClientActions.js +++ b/app/actions/ClientActions.js @@ -14,7 +14,6 @@ import { getTransactions as walletGetTransactions } from "wallet/service"; import { TransactionDetails } from "middleware/walletrpc/api_pb"; import { clipboard } from "electron"; import { getStartupStats } from "./StatisticsActions"; -import * as trezorActions from "./TrezorActions"; export const goToTransactionHistory = () => (dispatch) => { dispatch(pushHistory("/transactions/history")); @@ -56,7 +55,6 @@ const startWalletServicesTrigger = () => (dispatch, getState) => new Promise((re await dispatch(getVotingServiceAttempt()); await dispatch(getAgendaServiceAttempt()); await dispatch(getStakepoolStats()); - await dispatch(trezorActions.loadDeviceList()); var goHomeCb = () => { dispatch(pushHistory("/home")); diff --git a/app/actions/DaemonActions.js b/app/actions/DaemonActions.js index f0a416512a..44a6ce82c1 100644 --- a/app/actions/DaemonActions.js +++ b/app/actions/DaemonActions.js @@ -11,6 +11,7 @@ import { setMustOpenForm, getWalletCfg, getAppdataPath, getRemoteCredentials, ge import { isTestNet } from "selectors"; import axios from "axios"; import { STANDARD_EXTERNAL_REQUESTS } from "main_dev/externalRequests"; +import { enableTrezor } from "./TrezorActions"; export const DECREDITON_VERSION = "DECREDITON_VERSION"; export const SELECT_LANGUAGE = "SELECT_LANGUAGE"; @@ -269,6 +270,7 @@ export const startWallet = (selectedWallet) => (dispatch, getState) => { dispatch({ type: WALLET_SETTINGS, currencyDisplay, gapLimit }); dispatch({ type: WALLET_STAKEPOOL_SETTINGS, activeStakePoolConfig, selectedStakePool, currentStakePoolConfig }); dispatch({ type: WALLET_LOADER_SETTINGS, discoverAccountsComplete }); + selectedWallet.value.isTrezor && dispatch(enableTrezor()); setTimeout(()=>dispatch(versionCheckAction()), 2000); }) .catch((err) => { diff --git a/app/actions/TrezorActions.js b/app/actions/TrezorActions.js index 0600a84780..b575f2fa1e 100644 --- a/app/actions/TrezorActions.js +++ b/app/actions/TrezorActions.js @@ -7,11 +7,13 @@ import { sprintf } from "sprintf-js"; import { rawHashToHex, rawToHex, hexToRaw, str2utf8hex, hex2b64 } from "helpers"; import { publishTransactionAttempt } from "./ControlActions"; import { model1_decred_homescreen } from "helpers/trezor"; +import { getWalletCfg } from "../config"; import { EXTERNALREQUEST_TREZOR_BRIDGE } from "main_dev/externalRequests"; import { SIGNTX_ATTEMPT, SIGNTX_FAILED, SIGNTX_SUCCESS, - SIGNMESSAGE_ATTEMPT, SIGNMESSAGE_FAILED, SIGNMESSAGE_SUCCESS + SIGNMESSAGE_ATTEMPT, SIGNMESSAGE_FAILED, SIGNMESSAGE_SUCCESS, + VALIDATEMASTERPUBKEY_SUCCESS, } from "./ControlActions"; const hardeningConstant = 0x80000000; @@ -31,15 +33,44 @@ function addressPath(index, branch, account, coinType) { ]; } +function accountPath(account, coinType) { + return [ + (44 | hardeningConstant) >>> 0, // purpose + ((coinType || 0)| hardeningConstant) >>> 0, // coin type + ((account || 0) | hardeningConstant) >>> 0 // account + ]; +} + +export const TRZ_TREZOR_ENABLED = "TRZ_TREZOR_ENABLED"; + +export const enableTrezor = () => (dispatch, getState) => { + const walletName = selectors.getWalletName(getState()); + + if (walletName) { + const config = getWalletCfg(selectors.isTestNet(getState()), walletName); + config.set("trezor", true); + } + + dispatch({ type: TRZ_TREZOR_ENABLED }); + + const { trezor: { deviceList, getDeviceListAttempt } } = getState(); + if (!deviceList && !getDeviceListAttempt) { + dispatch(loadDeviceList()); + } +}; + export const TRZ_LOADDEVICELIST_ATTEMPT = "TRZ_LOADDEVICELIST_ATTEMPT"; export const TRZ_LOADDEVICELIST_FAILED = "TRZ_LOADDEVICELIST_FAILED"; export const TRZ_LOADDEVICELIST_SUCCESS = "TRZ_LOADDEVICELIST_SUCCESS"; export const TRZ_DEVICELISTTRANSPORT_LOST = "TRZ_DEVICELISTTRANSPORT_LOST"; export const TRZ_SELECTEDDEVICE_CHANGED = "TRZ_SELECTEDDEVICE_CHANGED"; +export const TRZ_NOCONNECTEDDEVICE = "TRZ_NOCONNECTEDDEVICE"; export const loadDeviceList = () => (dispatch, getState) => { return new Promise((resolve, reject) => { - if (!getState().trezor.enabled) return; + const { trezor: { getDeviceListAttempt } } = getState(); + if (getDeviceListAttempt) return; + wallet.allowExternalRequest(EXTERNALREQUEST_TREZOR_BRIDGE); dispatch({ type: TRZ_LOADDEVICELIST_ATTEMPT }); @@ -120,6 +151,10 @@ export const selectDevice = (path) => async (dispatch, getState) => { setDeviceListeners(devList.devices[path], dispatch); }; +export const alertNoConnectedDevice = () => dispatch => { + dispatch({ type: TRZ_NOCONNECTEDDEVICE }); +}; + export const TRZ_PIN_REQUESTED = "TRZ_PIN_REQUESTED"; export const TRZ_PIN_ENTERED = "TRZ_PIN_ENTERED"; export const TRZ_PIN_CANCELED = "TRZ_PIN_CANCELED"; @@ -666,3 +701,34 @@ export const updateFirmware = (path) => async (dispatch, getState) => { dispatch({ error, type: TRZ_UPDATEFIRMWARE_FAILED }); } }; + +export const TRZ_GETWALLETCREATIONMASTERPUBKEY_ATTEMPT = "TRZ_GETWALLETCREATIONMASTERPUBKEY_ATTEMPT"; +export const TRZ_GETWALLETCREATIONMASTERPUBKEY_FAILED = "TRZ_GETWALLETCREATIONMASTERPUBKEY_FAILED"; +export const TRZ_GETWALLETCREATIONMASTERPUBKEY_SUCCESS = "TRZ_GETWALLETCREATIONMASTERPUBKEY_SUCCESS"; + +export const getWalletCreationMasterPubKey = () => async (dispatch, getState) => { + dispatch({ type: TRZ_GETWALLETCREATIONMASTERPUBKEY_ATTEMPT }); + + const device = selectors.trezorDevice(getState()); + if (!device) { + dispatch({ error: "Device not connected", type: TRZ_GETWALLETCREATIONMASTERPUBKEY_FAILED }); + return; + } + + const chainParams = selectors.chainParams(getState()); + + try { + const path = accountPath(WALLET_ACCOUNT, chainParams.HDCoinType); + + const masterPubKey = await deviceRun(dispatch, getState, device, async session => { + const res = await session.getPublicKey(path, chainParams.trezorCoinName, false); + return res.message.xpub; + }); + + dispatch({ type: VALIDATEMASTERPUBKEY_SUCCESS, isWatchOnly: true, masterPubKey }); + dispatch({ type: TRZ_GETWALLETCREATIONMASTERPUBKEY_SUCCESS }); + } catch (error) { + dispatch({ error, type: TRZ_GETWALLETCREATIONMASTERPUBKEY_FAILED }); + throw error; + } +}; diff --git a/app/components/modals/PassphraseModal/Modal.js b/app/components/modals/PassphraseModal/Modal.js index 397280ac4f..4f6b927d87 100644 --- a/app/components/modals/PassphraseModal/Modal.js +++ b/app/components/modals/PassphraseModal/Modal.js @@ -12,6 +12,7 @@ const propTypes = { const StandardPassphraseModal = (props) => { const { + modalClassName, show, modalDescription, children, @@ -28,7 +29,7 @@ const StandardPassphraseModal = (props) => { />; return ( - +
diff --git a/app/components/modals/trezor/Modals.js b/app/components/modals/trezor/Modals.js index c38896eaea..67bef119bf 100644 --- a/app/components/modals/trezor/Modals.js +++ b/app/components/modals/trezor/Modals.js @@ -1,6 +1,7 @@ import { trezor } from "connectors"; import PinModal from "./PinModal"; import PassPhraseModal from "./PassPhraseModal"; +import WalletCreationPassPhraseModal from "./WalletCreationPassPhraseModal"; import WordModal from "./WordModal"; import "style/Trezor.less"; @@ -15,24 +16,27 @@ class TrezorModals extends React.Component { } render() { + let Component = null; + if (this.props.waitingForPin) { - return ; + Component = PinModal; } else if (this.props.waitingForPassPhrase) { - return ; + if (this.props.walletCreationMasterPubkeyAttempt) { + Component = WalletCreationPassPhraseModal; + } else { + Component = PassPhraseModal; + } } else if (this.props.waitingForWord) { - return ; - } else { - return null; - } + /> + ); } } diff --git a/app/components/modals/trezor/PassPhraseModal.js b/app/components/modals/trezor/PassPhraseModal.js index 8825143411..37e8d4e808 100644 --- a/app/components/modals/trezor/PassPhraseModal.js +++ b/app/components/modals/trezor/PassPhraseModal.js @@ -8,7 +8,6 @@ class TrezorPassphraseModal extends React.Component { } onSubmit(passPhrase) { - console.log("gonna submit", passPhrase); this.props.submitPassPhrase(passPhrase); } @@ -18,11 +17,16 @@ class TrezorPassphraseModal extends React.Component { const trezorLabel = this.props.device ? this.props.device.features.label : ""; + const className = [ + "trezor-passphrase-modal", + this.props.isGetStarted ? "get-started" : "" + ].join(" "); + return ( } - className="trezor-passphrase-modal" + modalTitle={} + modalClassName={className} modalDescription={

; const trezorLabel = this.props.device ? this.props.device.features.label : ""; + const className = [ + "passphrase-modal", + "trezor-pin-modal", + this.props.isGetStarted ? "get-started" : "" + ].join(" "); return ( - +

'{trezorLabel}' }} />

diff --git a/app/components/modals/trezor/WalletCreationPassPhraseModal.js b/app/components/modals/trezor/WalletCreationPassPhraseModal.js new file mode 100644 index 0000000000..ae1ab9f205 --- /dev/null +++ b/app/components/modals/trezor/WalletCreationPassPhraseModal.js @@ -0,0 +1,96 @@ +import Modal from "../Modal"; +import { FormattedMessage as T } from "react-intl"; +import { Documentation } from "shared"; +import { PasswordInput, PassphraseModalField } from "inputs"; +import { ButtonsToolbar } from "../PassphraseModal"; + +@autobind +class TrezorWalletCreationPassphraseModal extends React.Component { + constructor(props) { + super(props); + this.state = { passphraseValue: "", passphraseConfirmValue: "", + submitAttempted: false, mismatchedValues: false }; + } + + componentWillUnmount() { + this.setState = { passphraseValue: "", passphraseConfirmValue: "" }; + } + + onSubmit() { + const { passphraseValue, passphraseConfirmValue } = this.state; + if (passphraseValue != passphraseConfirmValue) { + this.setState({ submitAttempted: true, mismatchedValues: true }); + return; + } + + this.props.submitPassPhrase(passphraseValue); + this.setState({ passphraseValue: "", passphraseConfirmValue: "" }); + } + + onChangePassphraseValue(passphraseValue) { + this.setState({ passphraseValue, submitAttempted: false, + mismatchedValues: false }); + } + + onChangePassphraseConfirmValue(passphraseConfirmValue) { + this.setState({ passphraseConfirmValue, submitAttempted: false, + mismatchedValues: false }); + } + + render() { + const { onCancelModal } = this.props; + const { onSubmit, onChangePassphraseValue, onChangePassphraseConfirmValue } = this; + const { submitAttempted, passphraseValue, passphraseConfirmValue, + mismatchedValues } = this.state; + + const trezorLabel = this.props.device ? this.props.device.features.label : ""; + + const className = [ + "trezor-passphrase-modal", + this.props.isGetStarted ? "get-started" : "" + ].join(" "); + + return ( + +

+

+ '{trezorLabel}' }} /> +

+ + + } + > + onChangePassphraseValue(e.target.value)} + onKeyDownSubmit={onSubmit} + showErrors={submitAttempted} + /> + + + } + > + onChangePassphraseConfirmValue(e.target.value)} + onKeyDownSubmit={onSubmit} + showErrors={submitAttempted} + invalid={mismatchedValues} + invalidMessage={} + /> + + + +
+ ); + } +} + +export default TrezorWalletCreationPassphraseModal; diff --git a/app/components/modals/trezor/WordModal.js b/app/components/modals/trezor/WordModal.js index 2fb0b34e18..b9ce098d09 100644 --- a/app/components/modals/trezor/WordModal.js +++ b/app/components/modals/trezor/WordModal.js @@ -46,8 +46,14 @@ class WordModal extends React.Component { render() { const { onCancelModal, onSubmit, onWordChanged, onSelectKeyDown, getSeedWords } = this; + const className = [ + "passphrase-modal", + "trezor-word-modal", + this.props.isGetStarted ? "get-started" : "" + ].join(" "); + return ( - +

diff --git a/app/components/views/GetStartedPage/TrezorConfig/Form.js b/app/components/views/GetStartedPage/TrezorConfig/Form.js new file mode 100644 index 0000000000..638c9a95c3 --- /dev/null +++ b/app/components/views/GetStartedPage/TrezorConfig/Form.js @@ -0,0 +1,27 @@ +import ChangeLabel from "views/TrezorPage/ChangeLabel"; +import ConfigButtons from "views/TrezorPage/ConfigButtons"; +import RecoveryButtons from "views/TrezorPage/RecoveryButtons"; +import FirmwareUpdate from "views/TrezorPage/FirmwareUpdate"; + +export default ({ + onTogglePinProtection, + onTogglePassPhraseProtection, + onChangeHomeScreen, + onChangeLabel, + onWipeDevice, + onRecoverDevice, + onInitDevice, + onUpdateFirmware, + loading, +}) => ( + + + + + + + + + +); diff --git a/app/components/views/GetStartedPage/TrezorConfig/Page.js b/app/components/views/GetStartedPage/TrezorConfig/Page.js new file mode 100644 index 0000000000..7e21eb9dc5 --- /dev/null +++ b/app/components/views/GetStartedPage/TrezorConfig/Page.js @@ -0,0 +1,33 @@ +import { Tooltip } from "shared"; +import { FormattedMessage as T } from "react-intl"; +import { LoaderBarBottom } from "indicators"; +import { AboutModalButtonInvisible } from "buttons"; + +export default ({ + onHideTrezorConfig, + getCurrentBlockCount, + getNeededBlocks, + getEstimatedTimeLeft, + appVersion, + updateAvailable, + children, +}) => ( +
+
+
+
+ }/> +
+
+ }>
+
+
+ +
+ {children} +
+ + +
+
+); diff --git a/app/components/views/GetStartedPage/TrezorConfig/index.js b/app/components/views/GetStartedPage/TrezorConfig/index.js new file mode 100644 index 0000000000..c9af646956 --- /dev/null +++ b/app/components/views/GetStartedPage/TrezorConfig/index.js @@ -0,0 +1,94 @@ +import { trezor } from "connectors"; +import { FormattedMessage as T } from "react-intl"; +import Form from "./Form"; +import Page from "./Page"; +import "style/Trezor.less"; + +@autobind +class TrezorConfig extends React.Component { + + constructor(props) { + super(props); + props.enableTrezor(); + } + + onTogglePinProtection() { + this.props.togglePinProtection(); + } + + onTogglePassPhraseProtection() { + this.props.togglePassPhraseProtection(); + } + + onChangeHomeScreen() { + this.props.changeToDecredHomeScreen(); + } + + onChangeLabel(newLabel) { + this.props.changeLabel(newLabel); + } + + onWipeDevice() { + this.props.wipeDevice(); + } + + onRecoverDevice() { + this.props.recoverDevice(); + } + + onInitDevice() { + this.props.initDevice(); + } + + onUpdateFirmware(path) { + this.props.updateFirmware(path); + } + + render() { + const { device } = this.props; + let children; + + if (!device) { + children = (
); + } else { + const loading = this.props.performingOperation; + + const { + onTogglePinProtection, + onTogglePassPhraseProtection, + onChangeHomeScreen, + onChangeLabel, + onWipeDevice, + onRecoverDevice, + onInitDevice, + onUpdateFirmware, + } = this; + + children = ( +
+ ); + } + + return ( + + {children} + + ); + } +} + +export default trezor(TrezorConfig); diff --git a/app/components/views/GetStartedPage/WalletSelection/CreateWalletForm.js b/app/components/views/GetStartedPage/WalletSelection/CreateWalletForm.js index 6b6749fafe..c062ba7209 100644 --- a/app/components/views/GetStartedPage/WalletSelection/CreateWalletForm.js +++ b/app/components/views/GetStartedPage/WalletSelection/CreateWalletForm.js @@ -38,6 +38,9 @@ const CreateWalletForm = ({ toggleWatchOnly, onChangeCreateWalletMasterPubKey, masterPubKeyError, + isTrezor, + toggleTrezor, + onShowTrezorConfig, }) => { return ( @@ -81,6 +84,15 @@ const CreateWalletForm = ({
+
+
+ +
+
+ + +
+
{isWatchingOnly &&
diff --git a/app/components/views/GetStartedPage/WalletSelection/index.js b/app/components/views/GetStartedPage/WalletSelection/index.js index 9e7db6a42a..781bc336aa 100644 --- a/app/components/views/GetStartedPage/WalletSelection/index.js +++ b/app/components/views/GetStartedPage/WalletSelection/index.js @@ -1,6 +1,5 @@ import { WalletSelectionFormBody } from "./Form"; import { createWallet } from "connectors"; - @autobind class WalletSelectionBody extends React.Component { constructor(props) { @@ -20,6 +19,7 @@ class WalletSelectionBody extends React.Component { walletMasterPubKey: "", masterPubKeyError: false, walletNameError: null, + isTrezor: false, }; } componentWillReceiveProps(nextProps) { @@ -55,6 +55,7 @@ class WalletSelectionBody extends React.Component { onCloseEditWallets, toggleWatchOnly, onChangeCreateWalletMasterPubKey, + toggleTrezor, } = this; const { selectedWallet, @@ -101,6 +102,7 @@ class WalletSelectionBody extends React.Component { walletNameError, maxWalletCount, isSPV, + toggleTrezor, ...this.props, ...this.state, }} @@ -139,7 +141,8 @@ class WalletSelectionBody extends React.Component { } createWallet() { const { newWalletName, createNewWallet, - isWatchingOnly, masterPubKeyError, walletMasterPubKey, walletNameError } = this.state; + isWatchingOnly, masterPubKeyError, walletMasterPubKey, walletNameError, + isTrezor } = this.state; if (newWalletName == "" || walletNameError) { this.setState({ hasFailedAttemptName: true }); return; @@ -150,13 +153,35 @@ class WalletSelectionBody extends React.Component { return; } } - this.props.onCreateWallet( - createNewWallet, - { label: newWalletName, value: { wallet: newWalletName, watchingOnly: isWatchingOnly } }); + if (isTrezor && !this.props.trezorDevice) { + this.props.trezorAlertNoConnectedDevice(); + return; + } + if (isTrezor) { + this.props.trezorGetWalletCreationMasterPubKey() + .then(() => { + this.props.onCreateWallet( + createNewWallet, + { label: newWalletName, value: { wallet: newWalletName, + watchingOnly: true, isTrezor } } ); + }); + } else { + this.props.onCreateWallet( + createNewWallet, + { label: newWalletName, value: { wallet: newWalletName, + watchingOnly: isWatchingOnly, isTrezor } } ); + } } toggleWatchOnly() { const { isWatchingOnly } = this.state; - this.setState({ isWatchingOnly : !isWatchingOnly }); + this.setState({ isWatchingOnly : !isWatchingOnly, isTrezor: false }); + } + toggleTrezor() { + const isTrezor = !this.state.isTrezor; + this.setState({ isTrezor, isWatchingOnly: false }); + if (isTrezor) { + this.props.trezorEnable(); + } } async onChangeCreateWalletMasterPubKey(walletMasterPubKey) { if (walletMasterPubKey === "") { diff --git a/app/components/views/GetStartedPage/index.js b/app/components/views/GetStartedPage/index.js index 53d8184309..df32ca2db2 100644 --- a/app/components/views/GetStartedPage/index.js +++ b/app/components/views/GetStartedPage/index.js @@ -7,6 +7,7 @@ import ReleaseNotes from "./ReleaseNotes"; import WalletSelectionBody from "./WalletSelection"; import StartRPCBody from "./StartRPC"; import SpvSync from "./SpvSync"; +import TrezorConfig from "./TrezorConfig"; import { AdvancedStartupBody, RemoteAppdataError } from "./AdvancedStartup"; import { RescanWalletBody } from "./RescanWallet/index"; import StakePoolsBody from "./StakePools"; @@ -19,7 +20,7 @@ class GetStartedPage extends React.Component { constructor(props) { super(props); this.state = { showSettings: false, showLogs: false, showReleaseNotes: false, - walletPrivatePassphrase: "" }; + walletPrivatePassphrase: "", showTrezorConfig: false }; } componentDidMount() { @@ -81,6 +82,14 @@ class GetStartedPage extends React.Component { this.setState({ showLogs: false }); } + onShowTrezorConfig() { + this.setState({ showTrezorConfig: true }); + } + + onHideTrezorConfig() { + this.setState({ showTrezorConfig: false }); + } + onSetWalletPrivatePassphrase(walletPrivatePassphrase) { this.setState({ walletPrivatePassphrase }); } @@ -110,6 +119,7 @@ class GetStartedPage extends React.Component { showSettings, showLogs, showReleaseNotes, + showTrezorConfig, ...state } = this.state; @@ -120,7 +130,9 @@ class GetStartedPage extends React.Component { onHideSettings, onShowLogs, onHideLogs, - onSetWalletPrivatePassphrase + onSetWalletPrivatePassphrase, + onShowTrezorConfig, + onHideTrezorConfig, } = this; const blockChainLoading = "blockchain-syncing"; @@ -138,6 +150,8 @@ class GetStartedPage extends React.Component { return ; } else if (showReleaseNotes) { return ; + } else if (showTrezorConfig) { + return ; } else if (isAdvancedDaemon && openForm && !remoteAppdataError && !isPrepared && !getWalletReady && !isSPV) { Form = AdvancedStartupBody; } else if (remoteAppdataError && !isPrepared && !getWalletReady && !isSPV) { @@ -238,6 +252,8 @@ class GetStartedPage extends React.Component { onShowLogs, onHideLogs, onSetWalletPrivatePassphrase, + onShowTrezorConfig, + onHideTrezorConfig, appVersion, updateAvailable, isSPV, diff --git a/app/connectors/createWallet.js b/app/connectors/createWallet.js index e341998e82..280a089a23 100644 --- a/app/connectors/createWallet.js +++ b/app/connectors/createWallet.js @@ -4,6 +4,7 @@ import { selectorMap } from "fp"; import * as sel from "selectors"; import * as wla from "actions/WalletLoaderActions"; import * as ca from "actions/ClientActions"; +import * as trza from "actions/TrezorActions"; const mapStateToProps = selectorMap({ createWalletExisting: sel.createWalletExisting, @@ -14,6 +15,8 @@ const mapStateToProps = selectorMap({ isCreatingWatchingOnly: sel.isWatchingOnly, masterPubKey: sel.masterPubKey, maxWalletCount: sel.maxWalletCount, + trezorDeviceList: sel.trezorDeviceList, + trezorDevice: sel.trezorDevice, }); const mapDispatchToProps = dispatch => bindActionCreators({ @@ -24,6 +27,10 @@ const mapDispatchToProps = dispatch => bindActionCreators({ createWatchOnlyWalletRequest: wla.createWatchOnlyWalletRequest, generateSeed: wla.generateSeed, decodeSeed: wla.decodeSeed, + trezorLoadDeviceList: trza.loadDeviceList, + trezorEnable: trza.enableTrezor, + trezorAlertNoConnectedDevice: trza.alertNoConnectedDevice, + trezorGetWalletCreationMasterPubKey: trza.getWalletCreationMasterPubKey }, dispatch); export default connect(mapStateToProps, mapDispatchToProps); diff --git a/app/connectors/trezor.js b/app/connectors/trezor.js index e4dca84497..d7b6e0adba 100644 --- a/app/connectors/trezor.js +++ b/app/connectors/trezor.js @@ -11,9 +11,13 @@ const mapStateToProps = selectorMap({ waitingForWord: sel.trezorWaitingForWord, device: sel.trezorDevice, performingOperation: sel.trezorPerformingOperation, + isGetStarted: sel.isGetStarted, + deviceList: sel.trezorDeviceList, + walletCreationMasterPubkeyAttempt: sel.trezorWalletCreationMasterPubkeyAttempt, }); const mapDispatchToProps = dispatch => bindActionCreators({ + loadDeviceList: trza.loadDeviceList, cancelCurrentOperation: trza.cancelCurrentOperation, submitPin: trza.submitPin, submitPassPhrase: trza.submitPassPhrase, @@ -26,6 +30,7 @@ const mapDispatchToProps = dispatch => bindActionCreators({ recoverDevice: trza.recoverDevice, initDevice: trza.initDevice, updateFirmware: trza.updateFirmware, + enableTrezor: trza.enableTrezor, }, dispatch); export default connect(mapStateToProps, mapDispatchToProps); diff --git a/app/i18n/docs/en/Warnings/TrezorWalletCreationPassPhrase.md b/app/i18n/docs/en/Warnings/TrezorWalletCreationPassPhrase.md new file mode 100644 index 0000000000..72aec96232 --- /dev/null +++ b/app/i18n/docs/en/Warnings/TrezorWalletCreationPassPhrase.md @@ -0,0 +1,6 @@ +**Warning!** Please note that the trezor wallet passphrase **cannot** be changed +as it is used to derive the addresses to control funds. If you lose or type the +wrong passphrase you may **lose your funds**. + +Please read the [Trezor Manual](https://doc.satoshilabs.com) for further +information about passphrase protected trezor devices. diff --git a/app/i18n/docs/en/index.js b/app/i18n/docs/en/index.js index adf045f422..ad251df2a8 100644 --- a/app/i18n/docs/en/index.js +++ b/app/i18n/docs/en/index.js @@ -12,6 +12,7 @@ export { default as SeedCopyWarning } from "./Warnings/SeedCopy.md"; export { default as WalletCreationWarning } from "./Warnings/WalletCreation.md"; export { default as TrezorWipeWarning } from "./Warnings/TrezorWipe.md"; export { default as TrezorFirmwareUpdateWarning } from "./Warnings/TrezorFirmwareUpdate.md"; +export { default as TrezorWalletCreationPassPhraseWarning } from "./Warnings/TrezorWalletCreationPassPhrase.md"; export { default as GetStartedTutorialPage01 } from "./GetStarted/TutorialPage01.md"; export { default as GetStartedTutorialPage02 } from "./GetStarted/TutorialPage02.md"; diff --git a/app/index.js b/app/index.js index 2310340cc1..f2f65e8e59 100644 --- a/app/index.js +++ b/app/index.js @@ -376,9 +376,10 @@ var initialState = { lastVettedFetchTime: new Date(0), // time when vetted proposals were requested }, trezor: { - enabled: true, - debug: true, + enabled: false, + debug: globalCfg.get("trezor_debug"), deviceList: null, + getDeviceListAttempt: false, transportError: false, device: null, performingOperation: false, @@ -390,6 +391,7 @@ var initialState = { pinMessage: null, passPhraseMessage: null, wordCallBack: null, + walletCreationMasterPubkeyAttempt: false, }, locales: locales }; diff --git a/app/main_dev/ipc.js b/app/main_dev/ipc.js index ddce85b0de..5c75640985 100644 --- a/app/main_dev/ipc.js +++ b/app/main_dev/ipc.js @@ -25,9 +25,10 @@ export const getAvailableWallets = (network) => { const cfg = getWalletCfg(isTestNet, wallet); const lastAccess = cfg.get("lastaccess"); const watchingOnly = cfg.get("iswatchonly"); + const isTrezor = cfg.get("trezor"); const walletDbFilePath = getWalletDBPathFromWallets(isTestNet, wallet); const finished = fs.pathExistsSync(walletDbFilePath); - availableWallets.push({ network, wallet, finished, lastAccess, watchingOnly }); + availableWallets.push({ network, wallet, finished, lastAccess, watchingOnly, isTrezor }); }); return availableWallets; diff --git a/app/reducers/snackbar.js b/app/reducers/snackbar.js index e3d9815aac..f958cb95af 100644 --- a/app/reducers/snackbar.js +++ b/app/reducers/snackbar.js @@ -53,6 +53,8 @@ import { TRZ_RECOVERDEVICE_SUCCESS, TRZ_RECOVERDEVICE_FAILED, TRZ_INITDEVICE_SUCCESS, TRZ_INITDEVICE_FAILED, TRZ_UPDATEFIRMWARE_SUCCESS, TRZ_UPDATEFIRMWARE_FAILED, + TRZ_NOCONNECTEDDEVICE, + TRZ_GETWALLETCREATIONMASTERPUBKEY_FAILED, } from "actions/TrezorActions"; import { @@ -252,6 +254,14 @@ const messages = defineMessages({ TRZ_UPDATEFIRMWARE_SUCCESS: { id: "trezor.updateFirmware.success", defaultMessage: "Firmware updated on trezor device" + }, + TRZ_NOCONNECTEDDEVICE: { + id: "trezor.noConnectedDevice", + defaultMessage: "No trezor device connected. Check the device connection and trezor bridge." + }, + TRZ_GETWALLETCREATIONMASTERPUBKEY_FAILED: { + id: "trezor.getWalletCreationMasterPubKey.failed", + defaultMessage: "Failed to obtain master extended pubkey from trezor device: {originalError}" } }); @@ -356,6 +366,8 @@ export default function snackbar(state = {}, action) { case TRZ_RECOVERDEVICE_FAILED: case TRZ_INITDEVICE_FAILED: case TRZ_UPDATEFIRMWARE_FAILED: + case TRZ_NOCONNECTEDDEVICE: + case TRZ_GETWALLETCREATIONMASTERPUBKEY_FAILED: type = "Error"; message = messages[action.type] || messages.defaultErrorMessage; values = { originalError: String(action.error) }; diff --git a/app/reducers/trezor.js b/app/reducers/trezor.js index f3ab5005fb..bed30cb0b0 100644 --- a/app/reducers/trezor.js +++ b/app/reducers/trezor.js @@ -1,4 +1,5 @@ import { + TRZ_TREZOR_ENABLED, TRZ_LOADDEVICELIST_ATTEMPT, TRZ_LOADDEVICELIST_FAILED, TRZ_LOADDEVICELIST_SUCCESS, TRZ_DEVICELISTTRANSPORT_LOST, TRZ_SELECTEDDEVICE_CHANGED, @@ -14,6 +15,7 @@ import { TRZ_RECOVERDEVICE_ATTEMPT, TRZ_RECOVERDEVICE_FAILED, TRZ_RECOVERDEVICE_SUCCESS, TRZ_INITDEVICE_ATTEMPT, TRZ_INITDEVICE_FAILED, TRZ_INITDEVICE_SUCCESS, TRZ_UPDATEFIRMWARE_ATTEMPT, TRZ_UPDATEFIRMWARE_FAILED, TRZ_UPDATEFIRMWARE_SUCCESS, + TRZ_GETWALLETCREATIONMASTERPUBKEY_ATTEMPT, TRZ_GETWALLETCREATIONMASTERPUBKEY_FAILED, TRZ_GETWALLETCREATIONMASTERPUBKEY_SUCCESS, } from "actions/TrezorActions"; import { SIGNTX_ATTEMPT, SIGNTX_FAILED, SIGNTX_SUCCESS @@ -21,20 +23,27 @@ import { export default function trezor(state = {}, action) { switch (action.type) { + case TRZ_TREZOR_ENABLED: + return { ...state, + enabled: true, + }; case TRZ_LOADDEVICELIST_ATTEMPT: return { ...state, deviceList: null, transportError: false, device: null, + getDeviceListAttempt: true, }; case TRZ_LOADDEVICELIST_SUCCESS: return { ...state, deviceList: action.deviceList, transportError: false, + getDeviceListAttempt: false, }; case TRZ_LOADDEVICELIST_FAILED: return { ...state, transportError: action.error, + getDeviceListAttempt: false, }; case TRZ_DEVICELISTTRANSPORT_LOST: return { ...state, @@ -99,6 +108,15 @@ export default function trezor(state = {}, action) { performingOperation: false, waitingForWord: false, }; + case TRZ_GETWALLETCREATIONMASTERPUBKEY_ATTEMPT: + return { ...state, + walletCreationMasterPubkeyAttempt: true + }; + case TRZ_GETWALLETCREATIONMASTERPUBKEY_SUCCESS: + case TRZ_GETWALLETCREATIONMASTERPUBKEY_FAILED: + return { ...state, + walletCreationMasterPubkeyAttempt: false + }; case SIGNTX_ATTEMPT: case TRZ_TOGGLEPINPROTECTION_ATTEMPT: case TRZ_TOGGLEPASSPHRASEPROTECTION_ATTEMPT: diff --git a/app/selectors.js b/app/selectors.js index 0dfd5cb4d2..6888040ca1 100644 --- a/app/selectors.js +++ b/app/selectors.js @@ -944,3 +944,5 @@ export const trezorWaitingForPassPhrase = get([ "trezor", "waitingForPassPhrase" export const trezorWaitingForWord = get([ "trezor", "waitingForWord" ]); export const trezorPerformingOperation = get([ "trezor", "performingOperation" ]); export const trezorDevice = get([ "trezor", "device" ]); +export const trezorDeviceList = get([ "trezor", "deviceList" ]); +export const trezorWalletCreationMasterPubkeyAttempt = get([ "trezor", "walletCreationMasterPubkeyAttempt" ]); diff --git a/app/style/GetStarted.less b/app/style/GetStarted.less index 6523c408f3..c7f166c30a 100644 --- a/app/style/GetStarted.less +++ b/app/style/GetStarted.less @@ -810,3 +810,7 @@ display: none; } } + +.getstarted-trezor-config-sections { + margin-top: 14em; +} diff --git a/app/style/Trezor.less b/app/style/Trezor.less index bf0890d25b..fc51cd6b25 100644 --- a/app/style/Trezor.less +++ b/app/style/Trezor.less @@ -28,6 +28,12 @@ } } +.trezor-pin-modal.get-started, +.trezor-word-modal.get-started, +.trezor-passphrase-modal.get-started { + margin: 10%; + padding: 2em; +} .trezor-label { font-family: @font-family-monospaced; diff --git a/app/wallet/daemon.js b/app/wallet/daemon.js index 38ccf1fe1d..471648e9f4 100644 --- a/app/wallet/daemon.js +++ b/app/wallet/daemon.js @@ -130,7 +130,7 @@ export const reloadAllowedExternalRequests = log(() => Promise export const allowExternalRequest = log(requestType => Promise .resolve(ipcRenderer.sendSync("allow-external-request", requestType)) - , "Allow External Request"); +, "Allow External Request"); export const allowStakePoolHost = log(host => Promise .resolve(ipcRenderer.sendSync("allow-stakepool-host", host))