Skip to content

Commit

Permalink
Add trezor wallet creation
Browse files Browse the repository at this point in the history
  • Loading branch information
matheusd committed Oct 23, 2018
1 parent c2667a8 commit 435ad8b
Show file tree
Hide file tree
Showing 27 changed files with 488 additions and 35 deletions.
2 changes: 0 additions & 2 deletions app/actions/ClientActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down Expand Up @@ -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"));
Expand Down
2 changes: 2 additions & 0 deletions app/actions/DaemonActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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) => {
Expand Down
70 changes: 68 additions & 2 deletions app/actions/TrezorActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 });
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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;
}
};
3 changes: 2 additions & 1 deletion app/components/modals/PassphraseModal/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const propTypes = {

const StandardPassphraseModal = (props) => {
const {
modalClassName,
show,
modalDescription,
children,
Expand All @@ -28,7 +29,7 @@ const StandardPassphraseModal = (props) => {
/>;

return (
<Modal className="passphrase-modal" {...{ show, onCancelModal }}>
<Modal className={"passphrase-modal " + (modalClassName || "")} {...{ show, onCancelModal }}>
<div className="passphrase-modal-header">
<div className="passphrase-modal-header-title">
<T id="passphraseModal.confirmationRequired" m="Confirmation Required" />
Expand Down
30 changes: 17 additions & 13 deletions app/components/modals/trezor/Modals.js
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -15,24 +16,27 @@ class TrezorModals extends React.Component {
}

render() {
let Component = null;

if (this.props.waitingForPin) {
return <PinModal
{...this.props}
onCancelModal={this.onCancelModal}
/>;
Component = PinModal;
} else if (this.props.waitingForPassPhrase) {
return <PassPhraseModal
{...this.props}
onCancelModal={this.onCancelModal}
/>;
if (this.props.walletCreationMasterPubkeyAttempt) {
Component = WalletCreationPassPhraseModal;
} else {
Component = PassPhraseModal;
}
} else if (this.props.waitingForWord) {
return <WordModal
Component = WordModal;
}

if (!Component) return null;
return (
<Component
{...this.props}
onCancelModal={this.onCancelModal}
/>;
} else {
return null;
}
/>
);
}
}

Expand Down
10 changes: 7 additions & 3 deletions app/components/modals/trezor/PassPhraseModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ class TrezorPassphraseModal extends React.Component {
}

onSubmit(passPhrase) {
console.log("gonna submit", passPhrase);
this.props.submitPassPhrase(passPhrase);
}

Expand All @@ -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 (
<PassphraseModal
show={true}
modalTitle={<T id="trezor.passphraseModal.title" m="Confirm Trezor Passphrase" />}
className="trezor-passphrase-modal"
modalTitle={<T id="trezor.passphraseModal.title" m="Enter Trezor Passphrase" />}
modalClassName={className}
modalDescription={
<p>
<T id="trezor.passphraseModal.description" m="Type the secret passphrase for the wallet stored in trezor {label}"
Expand Down
7 changes: 6 additions & 1 deletion app/components/modals/trezor/PinModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,14 @@ class PinModal extends React.Component {
<PinButton label={labels[index-1]} index={index} onClick={onPinButtonClick} />;

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

return (
<Modal className="passphrase-modal trezor-pin-modal" {...{ onCancelModal }}>
<Modal {...{ className, onCancelModal }}>
<h1><T id="trezor.pinModal.title" m="Enter Pin" /></h1>
<p><T id="trezor.pinModal.description" m="Click button sequence that corresponds to your pin on trezor {label}"
values={{ label: <span className="trezor-label">'{trezorLabel}'</span> }} /></p>
Expand Down
96 changes: 96 additions & 0 deletions app/components/modals/trezor/WalletCreationPassPhraseModal.js
Original file line number Diff line number Diff line change
@@ -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 (
<Modal className={className} onCancelModal={onCancelModal}>
<h1><T id="trezor.walletCreationPassPhraseModal.title" m="Type Wallet Creation PassPhrase" /></h1>
<p>
<T id="trezor.walletCreationpassphraseModal.description"
m={"Type the secret passphrase of the wallet to restore from the trezor device {label}"}
values={{ label: <span className="trezor-label">'{trezorLabel}'</span> }} />
</p>
<Documentation name="TrezorWalletCreationPassPhraseWarning" />

<PassphraseModalField
label={<T id="trezor.walltCreationPrivatePassphrase" m="Wallet PassPhrase" />}
>
<PasswordInput
autoFocus
placeholder=""
value={passphraseValue}
onChange={e => onChangePassphraseValue(e.target.value)}
onKeyDownSubmit={onSubmit}
showErrors={submitAttempted}
/>
</PassphraseModalField>

<PassphraseModalField
label={<T id="trezor.walltCreationPrivatePassphraseConfirm" m="Confirm Wallet PassPhrase" />}
>
<PasswordInput
placeholder=""
value={passphraseConfirmValue}
onChange={e => onChangePassphraseConfirmValue(e.target.value)}
onKeyDownSubmit={onSubmit}
showErrors={submitAttempted}
invalid={mismatchedValues}
invalidMessage={<T id="trezor.walletCreationPassphrasesMismatched" m="Passphrases are different" />}
/>
</PassphraseModalField>

<ButtonsToolbar {... { onCancelModal, onSubmit }} />
</Modal>
);
}
}

export default TrezorWalletCreationPassphraseModal;
8 changes: 7 additions & 1 deletion app/components/modals/trezor/WordModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Modal className="passphrase-modal trezor-word-modal" {...{ onCancelModal }}>
<Modal {...{ className, onCancelModal }}>
<h1><T id="trezor.wordModal.title" m="Type the requested word" /></h1>
<p><T id="trezor.wordModal.description" m="Type the word requested in the trezor device." /></p>

Expand Down
27 changes: 27 additions & 0 deletions app/components/views/GetStartedPage/TrezorConfig/Form.js
Original file line number Diff line number Diff line change
@@ -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,
}) => (
<Aux>
<ConfigButtons {...{ onTogglePinProtection, onTogglePassPhraseProtection,
onChangeHomeScreen, loading }} />

<ChangeLabel {...{ onChangeLabel, loading }} />

<RecoveryButtons {...{ onWipeDevice, onRecoverDevice, onInitDevice, loading }} />

<FirmwareUpdate {...{ onUpdateFirmware }} />
</Aux>
);
Loading

0 comments on commit 435ad8b

Please sign in to comment.