From 2fe0e22c78b33966bfb48928ab5cdbc6358da354 Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Wed, 11 Jul 2018 10:46:05 -0300 Subject: [PATCH] Support signing messages on trezor --- app/actions/TrezorActions.js | 45 +++++++++++++++++++- app/components/buttons/SignMessageButton.js | 46 +++++++++++++++------ app/connectors/signMessagePage.js | 3 ++ app/helpers/byteActions.js | 9 ++++ app/selectors.js | 13 +++--- 5 files changed, 95 insertions(+), 21 deletions(-) diff --git a/app/actions/TrezorActions.js b/app/actions/TrezorActions.js index 387b34ed1b..ba0802c7fd 100644 --- a/app/actions/TrezorActions.js +++ b/app/actions/TrezorActions.js @@ -3,12 +3,15 @@ import * as wallet from "wallet"; import * as selectors from "selectors"; import fs from "fs"; import { sprintf } from "sprintf-js"; -import { rawHashToHex, rawToHex, hexToRaw } from "helpers"; +import { rawHashToHex, rawToHex, hexToRaw, str2utf8hex, hex2b64 } from "helpers"; import { publishTransactionAttempt } from "./ControlActions"; import { model1_decred_homescreen } from "helpers/trezor"; import { EXTERNALREQUEST_TREZOR_BRIDGE } from "main_dev/externalRequests"; -import { SIGNTX_ATTEMPT, SIGNTX_FAILED, SIGNTX_SUCCESS } from "./ControlActions"; +import { + SIGNTX_ATTEMPT, SIGNTX_FAILED, SIGNTX_SUCCESS, + SIGNMESSAGE_ATTEMPT, SIGNMESSAGE_FAILED, SIGNMESSAGE_SUCCESS +} from "./ControlActions"; const hardeningConstant = 0x80000000; @@ -290,6 +293,44 @@ export const signTransactionAttemptTrezor = (rawUnsigTx, constructTxResponse) => } }; +export const signMessageAttemptTrezor = (address, message) => async (dispatch, getState) => { + + dispatch({ type: SIGNMESSAGE_ATTEMPT }); + + const device = selectors.trezorDevice(getState()); + if (!device) { + dispatch({ error: "Device not connected", type: SIGNMESSAGE_FAILED }); + return; + } + + const chainParams = selectors.chainParams(getState()); + const { grpc: { walletService } } = getState(); + + try { + const addrValidResp = await wallet.validateAddress(walletService, address); + if (!addrValidResp.getIsValid()) throw "Input has an invalid address " + address; + if (!addrValidResp.getIsMine()) throw "Trezor only supports signing with wallet addresses"; + const addrIndex = addrValidResp.getIndex(); + const addrBranch = addrValidResp.getIsInternal() ? 1 : 0; + const address_n = addressPath(addrIndex, addrBranch, WALLET_ACCOUNT, + chainParams.HDCoinType); + + const signedMsg = await deviceRun(dispatch, getState, device, async session => { + await dispatch(checkTrezorIsDcrwallet(session)); + + return await session.signMessage(address_n, str2utf8hex(message), + chainParams.trezorCoinName, false); + }); + + const signature = hex2b64(signedMsg.message.signature); + dispatch({ getSignMessageSignature: signature, type: SIGNMESSAGE_SUCCESS }); + + } catch (error) { + dispatch({ error, type: SIGNMESSAGE_FAILED }); + } + +}; + // walletTxToBtcjsTx converts a tx decoded by the decred wallet (ie, // returned from the decodeRawTransaction call) into a bitcoinjs-compatible // transaction (to be used in trezor) diff --git a/app/components/buttons/SignMessageButton.js b/app/components/buttons/SignMessageButton.js index 62c9c96dda..7c672577f4 100644 --- a/app/components/buttons/SignMessageButton.js +++ b/app/components/buttons/SignMessageButton.js @@ -1,5 +1,5 @@ import { signMessagePage } from "connectors"; -import { PassphraseModalButton } from "./index"; +import { PassphraseModalButton, KeyBlueButton } from "./index"; import { FormattedMessage as T } from "react-intl"; @autobind @@ -16,19 +16,39 @@ class SignMessageButton extends React.Component { onSubmit && onSubmit(); } + async onAttemptSignMessageTrezor() { + const { address, message, disabled, signMessageAttemptTrezor, onSubmit } = this.props; + if (disabled || !signMessageAttemptTrezor) return; + await signMessageAttemptTrezor(address, message); + onSubmit && onSubmit(); + } + render() { - const { disabled, isSigningMessage, className } = this.props; - - return ( - } - className={className} - disabled={disabled} - onSubmit={this.onAttemptSignMessage} - loading={isSigningMessage} - buttonLabel={} - /> - ); + const { disabled, isSigningMessage, className, isTrezor } = this.props; + + if (isTrezor) { + return ( + + + + ); + } else { + return ( + } + className={className} + disabled={disabled} + onSubmit={this.onAttemptSignMessage} + loading={isSigningMessage} + buttonLabel={} + /> + ); + } } } diff --git a/app/connectors/signMessagePage.js b/app/connectors/signMessagePage.js index 9679f929eb..66cb133426 100644 --- a/app/connectors/signMessagePage.js +++ b/app/connectors/signMessagePage.js @@ -3,18 +3,21 @@ import { bindActionCreators } from "redux"; import { selectorMap } from "../fp"; import * as sel from "../selectors"; import * as ca from "../actions/ControlActions"; +import * as trza from "../actions/TrezorActions"; const mapStateToProps = selectorMap({ signMessageError: sel.signMessageError, signMessageSignature: sel.signMessageSignature, isSigningMessage: sel.isSigningMessage, walletService: sel.walletService, + isTrezor: sel.isTrezor, }); const mapDispatchToProps = dispatch => bindActionCreators({ signMessageAttempt: ca.signMessageAttempt, validateAddress: ca.validateAddress, signMessageCleanStore: ca.signMessageCleanStore, + signMessageAttemptTrezor: trza.signMessageAttemptTrezor, }, dispatch); export default connect(mapStateToProps, mapDispatchToProps); diff --git a/app/helpers/byteActions.js b/app/helpers/byteActions.js index 44643c10aa..18192ffff1 100644 --- a/app/helpers/byteActions.js +++ b/app/helpers/byteActions.js @@ -38,3 +38,12 @@ export function hexReversedHashToArray(hexStr) { res.reverse(); return res; } + +// str2utf8hex converts a (js, utf-16) string into (utf-8 encoded) hex. +export function str2utf8hex(str) { + return Buffer.from(str).toString("hex"); +} + +export function hex2b64(hex) { + return new Buffer(hex, "hex").toString("base64"); +} diff --git a/app/selectors.js b/app/selectors.js index a560a018cd..3aa94f6826 100644 --- a/app/selectors.js +++ b/app/selectors.js @@ -902,12 +902,6 @@ export const stakeRewardsStats = createSelector( export const modalVisible = get([ "control", "modalVisible" ]); -export const isSignVerifyMessageDisabled = or(isWatchingOnly, isWatchOnly); - -export const isCreateAccountDisabled = or(isWatchingOnly, isWatchOnly); - -export const isChangePassPhraseDisabled = or(isWatchingOnly, isWatchOnly); - export const politeiaBetaEnabled = get([ "governance", "politeiaBetaEnabled" ]); // TODO: remove once politeia hits production export const politeiaURL = createSelector( [ isTestNet ], @@ -945,3 +939,10 @@ export const trezorWaitingForPassPhrase = get([ "trezor", "waitingForPassPhrase" export const trezorWaitingForWord = get([ "trezor", "waitingForWord" ]); export const trezorPerformingOperation = get([ "trezor", "performingOperation" ]); export const trezorDevice = get([ "trezor", "device" ]); + +// Functionalities deactivated +export const isSignVerifyMessageDisabled = and(or(isWatchingOnly, isWatchOnly), not(isTrezor)); + +export const isCreateAccountDisabled = or(isWatchingOnly, isWatchOnly); + +export const isChangePassPhraseDisabled = or(isWatchingOnly, isWatchOnly);