diff --git a/app/actions/DaemonActions.js b/app/actions/DaemonActions.js index aa703f6d1d..367afcbcfe 100644 --- a/app/actions/DaemonActions.js +++ b/app/actions/DaemonActions.js @@ -12,6 +12,7 @@ import { isTestNet } from "selectors"; import axios from "axios"; import { STANDARD_EXTERNAL_REQUESTS } from "main_dev/externalRequests"; import { DIFF_CONNECTION_ERROR } from "main_dev/constants"; +import { enableTrezor } from "./TrezorActions"; export const DECREDITON_VERSION = "DECREDITON_VERSION"; export const SELECT_LANGUAGE = "SELECT_LANGUAGE"; @@ -294,6 +295,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 new file mode 100644 index 0000000000..b656eda5ba --- /dev/null +++ b/app/actions/TrezorActions.js @@ -0,0 +1,761 @@ +import * as trezorjs from "trezor.js"; +import trezorTransports from "trezor-link"; +import * as wallet from "wallet"; +import * as selectors from "selectors"; +import fs from "fs"; +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, + VALIDATEMASTERPUBKEY_SUCCESS, +} from "./ControlActions"; + +const hardeningConstant = 0x80000000; + +// Right now (2018-07-06) dcrwallet only supports a single account on watch only +// wallets. Therefore we are limited to using this single account when signing +// transactions via trezor. +const WALLET_ACCOUNT = 0; + +function addressPath(index, branch, account, coinType) { + return [ + (44 | hardeningConstant) >>> 0, // purpose + ((coinType || 0)| hardeningConstant) >>> 0, // coin type + ((account || 0) | hardeningConstant) >>> 0, // account + (branch || 0) >>> 0, // branch + index >>> 0 // index + ]; +} + +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_CLEAR_DEVICELIST = "TRZ_CLEAR_DEVICELIST"; + +export const reloadTrezorDeviceList = () => (dispatch) => { + dispatch({ type: TRZ_CLEAR_DEVICELIST }); + 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) => { + const { trezor: { getDeviceListAttempt } } = getState(); + if (getDeviceListAttempt) return; + + wallet.allowExternalRequest(EXTERNALREQUEST_TREZOR_BRIDGE); + + dispatch({ type: TRZ_LOADDEVICELIST_ATTEMPT }); + const debug = getState().trezor.debug; + + // TODO: decide whether we want to provide our own config blob. + const configUrl = "https://wallet.trezor.io/data/config_signed.bin?" + + Date.now(); + + const opts = { debug, debugInfo: debug, configUrl, + transport: new trezorTransports.BridgeV2() }; + const devList = new trezorjs.DeviceList(opts); + let resolvedTransport = false; + + devList.on("transport", t => { + debug && console.log("transport", t); + if (resolvedTransport) return; + resolvedTransport = true; // resolved with success + dispatch({ deviceList: devList, type: TRZ_LOADDEVICELIST_SUCCESS }); + resolve(t); + }); + + devList.on("error", err => { + debug && console.log("error", err); + if (!resolvedTransport && err.message.includes("ECONNREFUSED")) { + resolvedTransport = true; // resolved with failure + dispatch({ error: err.message, type: TRZ_LOADDEVICELIST_FAILED }); + reject(err); + } else if (err.message.includes("socket hang up")) { + // this might happen any time throughout the app lifetime if the bridge + // service is shutdown for any reason + dispatch({ error: err.message, type: TRZ_DEVICELISTTRANSPORT_LOST }); + } + }); + + devList.on("connect", device => { + debug && console.log("connect", Object.keys(devList.devices), device); + const currentDevice = getState().trezor.device; + if (!currentDevice) { + // first device connected. Use it. + dispatch({ device, type: TRZ_SELECTEDDEVICE_CHANGED }); + setDeviceListeners(device, dispatch); + } + }); + + devList.on("disconnect", device => { + debug && console.log("disconnect", Object.keys(devList.devices), device); + const currentDevice = getState().trezor.device; + if (currentDevice && device.originalDescriptor.path === currentDevice.originalDescriptor.path ) { + const devicePaths = Object.keys(devList.devices); + + // we were using the device that was just disconnected. Pick a new + // device to use. + if (devicePaths.length === 0) { + // no more devices left to use + dispatch({ device: null, type: TRZ_SELECTEDDEVICE_CHANGED }); + } else { + dispatch({ device: devList.devices[devicePaths[0]], type: TRZ_SELECTEDDEVICE_CHANGED }); + } + } + }); + + devList.on("connectUnacquired", device => { + debug && console.log("connect unacquired", device); + }); + + devList.on("disconnectUnacquired", device => { + debug && console.log("d.catch(error => dispatch({ error, type: SIGNTX_FAILED }));isconnect unacquired", device); + }); + + }); +}; + +export const selectDevice = (path) => async (dispatch, getState) => { + const devList = getState().trezor.deviceList; + if (!devList.devices[path]) return; + dispatch({ device: devList.devices[path], type: TRZ_SELECTEDDEVICE_CHANGED }); + 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"; +export const TRZ_PASSPHRASE_REQUESTED = "TRZ_PASSPHRASE_REQUESTED"; +export const TRZ_PASSPHRASE_ENTERED = "TRZ_PASSPHRASE_ENTERED"; +export const TRZ_PASSPHRASE_CANCELED = "TRZ_PASSPHRASE_CANCELED"; +export const TRZ_WORD_REQUESTED = "TRZ_WORD_REQUESTED"; +export const TRZ_WORD_ENTERED = "TRZ_WORD_ENTERED"; +export const TRZ_WORD_CANCELED = "TRZ_WORD_CANCELED"; + +function setDeviceListeners(device, dispatch) { + device.on("pin", (pinMessage, pinCallBack) => { + dispatch({ pinMessage, pinCallBack, type: TRZ_PIN_REQUESTED }); + }); + + device.on("passphrase", (passPhraseCallBack) => { + dispatch({ passPhraseCallBack, type: TRZ_PASSPHRASE_REQUESTED }); + }); + + device.on("word", (wordCallBack) => { + dispatch({ wordCallBack, type: TRZ_WORD_REQUESTED }); + }); +} + +// deviceRun is the main function for executing trezor operations. This handles +// cleanup for cancellations and device disconnections during mid-operation (eg: +// someone disconnected trezor while it was waiting for a pin input). +// In general, fn itself shouldn't handle errors, letting this function handle +// the common cases, which are then propagated up the call stack into fn's +// parent. +async function deviceRun(dispatch, getState, device, fn) { + + const handleError = error => { + const { trezor: { waitingForPin, waitingForPassphrase, debug } } = getState(); + debug && console.log("Handle error no deviceRun", error); + if (waitingForPin) dispatch({ error, type: TRZ_PIN_CANCELED }); + if (waitingForPassphrase) dispatch({ error, type: TRZ_PASSPHRASE_CANCELED }); + if (error instanceof Error) { + if (error.message.includes("Inconsistent state")) { + return "Device returned inconsistent state. Disconnect and reconnect the device."; + } + } + return error; + }; + + try { + return await device.run(async session => { + try { + return await fn(session); + } catch (err) { + // doesn't seem to be reachable by trezor interruptions, but might be + // caused by fn() failing in some other way (even though it's + // recommended not to do (non-trezor) lengthy operations inside fn()) + throw handleError(err); + } + }); + } catch (outerErr) { + throw handleError(outerErr); + } +} + +export const TRZ_CANCELOPERATION_SUCCESS = "TRZ_CANCELOPERATION_SUCCESS"; +export const TRZ_CANCELOPERATION_FAILED = "TRZ_CANCELOPERATION_FAILED"; + +// Note that calling this function while no pin/passphrase operation is running +// will attempt to steal the device, cancelling operations from apps *other +// than decrediton*. +export const cancelCurrentOperation = () => async (dispatch, getState) => { + const device = selectors.trezorDevice(getState()); + const { trezor: { pinCallBack, passPhraseCallBack, wordCallBack } } = getState(); + + if (!device) return; + try { + if (pinCallBack) await pinCallBack("cancelled", null); + else if (passPhraseCallBack) await passPhraseCallBack("cancelled", null); + else if (wordCallBack) await wordCallBack("cancelled", null); + else await device.steal(); + + dispatch({ type: TRZ_CANCELOPERATION_SUCCESS }); + } catch (error) { + dispatch({ error, type: TRZ_CANCELOPERATION_FAILED }); + throw error; + } +}; + +export const TRZ_CLEARDEVICESESSION_SUCCESS = "TRZ_CLEARDEVICESESSION_SUCCESS"; +export const TRZ_CLEARDEVICESESSION_FAILED = "TRZ_CLEARDEVICESESSION_FAILED"; + +// Closes a device session, locking the device (if it requires a pin). +export const clearDeviceSession = () => async (dispatch, getState) => { + const device = selectors.trezorDevice(getState()); + if (!device) return; + + await dispatch(cancelCurrentOperation()); + try { + await deviceRun(dispatch, getState, device, async session => { + await session.clearSession(); + }); + dispatch({ type: TRZ_CLEARDEVICESESSION_SUCCESS }); + } catch (error) { + dispatch({ error, type: TRZ_CLEARDEVICESESSION_FAILED }); + } +}; + +export const submitPin = (pin) => (dispatch, getState) => { + const { trezor: { pinCallBack } } = getState(); + dispatch({ type: TRZ_PIN_ENTERED }); + if (!pinCallBack) return; + pinCallBack(null, pin); +}; + +export const submitPassPhrase = (passPhrase) => (dispatch, getState) => { + const { trezor: { passPhraseCallBack } } = getState(); + dispatch({ type: TRZ_PASSPHRASE_ENTERED }); + if (!passPhraseCallBack) return; + passPhraseCallBack(null, passPhrase); +}; + +export const submitWord = (word) => (dispatch, getState) => { + const { trezor: { wordCallBack } } = getState(); + dispatch({ type: TRZ_WORD_ENTERED }); + if (!wordCallBack) return; + wordCallBack(null, word); +}; + +// checkTrezorIsDcrwallet verifies whether the wallet currently running on +// dcrwallet (presumably a watch only wallet created from a trezor provided +// xpub) is the same wallet as the one of the currently connected trezor. This +// function throws an error if they are not the same. +// This is useful for making sure, prior to performing some wallet related +// function such as transaction signing, that trezor will correctly perform the +// operation. +// Note that this might trigger pin/passphrase modals, depending on the current +// trezor configuration. +// The way the check is performed is by generating the first address from the +// trezor wallet and then validating this address agains dcrwallet, ensuring +// this is an owned address at the appropriate branch/index. +// This check is only valid for a single session (ie, a single execution of +// `deviceRun`) as the physical device might change between sessions. +const checkTrezorIsDcrwallet = (session) => async (dispatch, getState) => { + const { grpc: { walletService } } = getState(); + const chainParams = selectors.chainParams(getState()); + + const address_n = addressPath(0, 0, WALLET_ACCOUNT, chainParams.HDCoinType); + const resp = await session.getAddress(address_n, chainParams.trezorCoinName, false); + const addr = resp.message.address; + + const addrValidResp = await wallet.validateAddress(walletService, addr); + if (!addrValidResp.getIsValid()) throw "Trezor provided an invalid address " + addr; + + if (!addrValidResp.getIsMine()) throw "Trezor and dcrwallet not running from the same extended public key"; + + if (addrValidResp.getIndex() !== 0) throw "Wallet replied with wrong index."; +}; + +export const signTransactionAttemptTrezor = (rawUnsigTx, constructTxResponse) => async (dispatch, getState) => { + dispatch({ type: SIGNTX_ATTEMPT }); + + const { grpc: { decodeMessageService, walletService }, trezor: { debug } } = getState(); + const chainParams = selectors.chainParams(getState()); + + const device = selectors.trezorDevice(getState()); + if (!device) { + dispatch({ error: "Device not connected", type: SIGNTX_FAILED }); + return; + } + + debug && console.log("construct tx response", constructTxResponse); + + try { + const decodedUnsigTxResp = await wallet.decodeTransaction(decodeMessageService, rawUnsigTx); + const decodedUnsigTx = decodedUnsigTxResp.getTransaction(); + const inputTxs = await wallet.getInputTransactions(walletService, + decodeMessageService, decodedUnsigTx); + const refTxs = inputTxs.map(walletTxToRefTx); + + const changeIndex = constructTxResponse.getChangeIndex(); + const txInfo = await dispatch(walletTxToBtcjsTx(decodedUnsigTx, + changeIndex, inputTxs)); + + const signedRaw = await deviceRun(dispatch, getState, device, async session => { + await dispatch(checkTrezorIsDcrwallet(session)); + + const signedResp = await session.signTx(txInfo.inputs, txInfo.outputs, + refTxs, chainParams.trezorCoinName, 0); + return signedResp.message.serialized.serialized_tx; + }); + + dispatch({ type: SIGNTX_SUCCESS }); + dispatch(publishTransactionAttempt(hexToRaw(signedRaw))); + + } catch (error) { + dispatch({ error, type: SIGNTX_FAILED }); + } +}; + +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) +export const walletTxToBtcjsTx = (tx, changeIndex, inputTxs) => async (dispatch, getState) => { + const { grpc: { walletService } } = getState(); + const chainParams = selectors.chainParams(getState()); + + const inputTxsMap = inputTxs.reduce((m, tx) => { + m[rawHashToHex(tx.getTransactionHash())] = tx; + return m; + }, {}); + + const inputs = []; + for (const inp of tx.getInputsList()) { + const inputTx = inputTxsMap[rawHashToHex(inp.getPreviousTransactionHash())]; + if (!inputTx) throw "Cannot sign transaction without knowing source tx " + + rawHashToHex(inp.getPreviousTransactionHash()); + + const inputTxOut = inputTx.getOutputsList()[inp.getPreviousTransactionIndex()]; + if (!inputTxOut) throw sprintf("Trying to use unknown outpoint %s:%d as input", + rawHashToHex(inp.getPreviousTransactionHash()), inp.getPreviousTransactionIndex()); + + const addr = inputTxOut.getAddressesList()[0]; + if (!addr) throw sprintf("Outpoint %s:%d does not have addresses.", + rawHashToHex(inp.getPreviousTransactionHash()), inp.getPreviousTransactionIndex()); + + const addrValidResp = await wallet.validateAddress(walletService, addr); + if (!addrValidResp.getIsValid()) throw "Input has an invalid address " + addr; + + // Trezor firmware (mcu) currently (2018-06-25) only support signing + // when all inputs of the transaction are from the wallet. This happens + // due to the fact that trezor firmware re-calculates the source + // pkscript given the address_n of the input, instead of using it (the + // pkscript) directly when hashing the tx prior to signing. This needs + // to be changed so that we can perform more advanced types of + // transactions. + if (!addrValidResp.getIsMine()) throw "Trezor only supports signing when all inputs are from the wallet."; + + const addrIndex = addrValidResp.getIndex(); + const addrBranch = addrValidResp.getIsInternal() ? 1 : 0; + inputs.push({ + prev_hash: rawHashToHex(inp.getPreviousTransactionHash()), + prev_index: inp.getPreviousTransactionIndex(), + amount: inp.getAmountIn(), + sequence: inp.getSequence(), + address_n: addressPath(addrIndex, addrBranch, WALLET_ACCOUNT, + chainParams.HDCoinType), + decred_tree: inp.getTree() + + // FIXME: this needs to be supported on trezor.js. + // decredTree: inp.getTree(), + // decredScriptVersion: 0, + }); + } + + const outputs = []; + for (const outp of tx.getOutputsList()) { + if (outp.getAddressesList().length != 1) { + // TODO: this will be true on OP_RETURNs. Support those. + throw "Output has different number of addresses than expected"; + } + + let addr = outp.getAddressesList()[0]; + const addrValidResp = await wallet.validateAddress(walletService, addr); + if (!addrValidResp.getIsValid()) throw "Not a valid address: " + addr; + let address_n = null; + + if (outp.getIndex() === changeIndex) { + const addrIndex = addrValidResp.getIndex(); + const addrBranch = addrValidResp.getIsInternal() ? 1 : 0; + address_n = addressPath(addrIndex, addrBranch, WALLET_ACCOUNT, + chainParams.HDCoinType); + addr = null; + } + + outputs.push({ + amount: outp.getValue(), + script_type: "PAYTOADDRESS", // needs to change on OP_RETURNs + address: addr, + address_n: address_n, + decred_script_version: outp.getVersion(), + }); + } + + const txInfo = { + lock_time: tx.getLockTime(), + version: tx.getVersion(), + expiry: tx.getExpiry(), + inputs, + outputs + }; + + return txInfo; +}; + +// walletTxToRefTx converts a tx decoded by the decred wallet into a trezor +// RefTransaction object to be used with SignTx. +export function walletTxToRefTx(tx) { + const inputs = tx.getInputsList().map(inp => ({ + amount: inp.getAmountIn(), + prev_hash: rawHashToHex(inp.getPreviousTransactionHash()), + prev_index: inp.getPreviousTransactionIndex(), + decred_tree: inp.getTree() + + // TODO: this needs to be supported on trezor.js + // decredTree: inp.getTree(), + // decredScriptVersion: 0, + })); + + const bin_outputs = tx.getOutputsList().map(outp => ({ + amount: outp.getValue(), + script_pubkey: rawToHex(outp.getScript()), + decred_script_version: outp.getVersion(), + })); + + const txInfo = { + hash: rawHashToHex(tx.getTransactionHash()), + lock_time: tx.getLockTime(), + version: tx.getVersion(), + expiry: tx.getExpiry(), + inputs, + bin_outputs, + }; + + return txInfo; +} + +export const TRZ_TOGGLEPINPROTECTION_ATTEMPT = "TRZ_TOGGLEPINPROTECTION_ATTEMPT"; +export const TRZ_TOGGLEPINPROTECTION_FAILED = "TRZ_TOGGLEPINPROTECTION_FAILED"; +export const TRZ_TOGGLEPINPROTECTION_SUCCESS = "TRZ_TOGGLEPINPROTECTION_SUCCESS"; + +export const togglePinProtection = () => async (dispatch, getState) => { + + const device = selectors.trezorDevice(getState()); + if (!device) { + dispatch({ error: "Device not connected", type: TRZ_TOGGLEPINPROTECTION_FAILED }); + return; + } + + const clearProtection = !!device.features.pin_protection; + dispatch({ clearProtection, type: TRZ_TOGGLEPINPROTECTION_ATTEMPT }); + + try { + await deviceRun(dispatch, getState, device, async session => { + await session.changePin(clearProtection); + }); + dispatch({ clearProtection, deviceLabel: device.features.label, type: TRZ_TOGGLEPINPROTECTION_SUCCESS }); + } catch (error) { + dispatch({ error, type: TRZ_TOGGLEPINPROTECTION_FAILED }); + } +}; + +export const TRZ_TOGGLEPASSPHRASEPROTECTION_ATTEMPT = "TRZ_TOGGLEPASSPHRASEPROTECTION_ATTEMPT"; +export const TRZ_TOGGLEPASSPHRASEPROTECTION_FAILED = "TRZ_TOGGLEPASSPHRASEPROTECTION_FAILED"; +export const TRZ_TOGGLEPASSPHRASEPROTECTION_SUCCESS = "TRZ_TOGGLEPASSPHRASEPROTECTION_SUCCESS"; + +export const togglePassPhraseProtection = () => async (dispatch, getState) => { + + const device = selectors.trezorDevice(getState()); + if (!device) { + dispatch({ error: "Device not connected", type: TRZ_TOGGLEPASSPHRASEPROTECTION_FAILED }); + return; + } + + const enableProtection = !device.features.passphrase_protection; + dispatch({ enableProtection, type: TRZ_TOGGLEPASSPHRASEPROTECTION_ATTEMPT }); + + try { + await deviceRun(dispatch, getState, device, async session => { + await session.togglePassphrase(enableProtection); + }); + dispatch({ enableProtection, deviceLabel: device.features.label, type: TRZ_TOGGLEPASSPHRASEPROTECTION_SUCCESS }); + } catch (error) { + dispatch({ error, type: TRZ_TOGGLEPASSPHRASEPROTECTION_FAILED }); + } +}; + +export const TRZ_CHANGEHOMESCREEN_ATTEMPT = "TRZ_CHANGEHOMESCREEN_ATTEMPT"; +export const TRZ_CHANGEHOMESCREEN_FAILED = "TRZ_CHANGEHOMESCREEN_FAILED"; +export const TRZ_CHANGEHOMESCREEN_SUCCESS = "TRZ_CHANGEHOMESCREEN_SUCCESS"; + +export const changeToDecredHomeScreen = () => async (dispatch, getState) => { + const device = selectors.trezorDevice(getState()); + if (!device) { + dispatch({ error: "Device not connected", type: TRZ_TOGGLEPASSPHRASEPROTECTION_FAILED }); + return; + } + + dispatch({ type: TRZ_CHANGEHOMESCREEN_ATTEMPT }); + + try { + await deviceRun(dispatch, getState, device, async session => { + await session.changeHomescreen(model1_decred_homescreen); + }); + dispatch({ type: TRZ_CHANGEHOMESCREEN_SUCCESS }); + } catch (error) { + dispatch({ error, type: TRZ_CHANGEHOMESCREEN_FAILED }); + } +}; + +export const TRZ_CHANGELABEL_ATTEMPT = "TRZ_CHANGELABEL_ATTEMPT"; +export const TRZ_CHANGELABEL_FAILED = "TRZ_CHANGELABEL_FAILED"; +export const TRZ_CHANGELABEL_SUCCESS = "TRZ_CHANGELABEL_SUCCESS"; + +export const changeLabel = (label) => async (dispatch, getState) => { + const device = selectors.trezorDevice(getState()); + if (!device) { + dispatch({ error: "Device not connected", type: TRZ_CHANGELABEL_FAILED }); + return; + } + + dispatch({ type: TRZ_CHANGELABEL_ATTEMPT }); + + try { + await deviceRun(dispatch, getState, device, async session => { + await session.changeLabel(label); + }); + dispatch({ deviceLabel: label, type: TRZ_CHANGELABEL_SUCCESS }); + } catch (error) { + dispatch({ error, type: TRZ_CHANGELABEL_FAILED }); + } +}; + +export const TRZ_WIPEDEVICE_ATTEMPT = "TRZ_WIPEDEVICE_ATTEMPT"; +export const TRZ_WIPEDEVICE_FAILED = "TRZ_WIPEDEVICE_FAILED"; +export const TRZ_WIPEDEVICE_SUCCESS = "TRZ_WIPEDEVICE_SUCCESS"; + +export const wipeDevice = () => async (dispatch, getState) => { + const device = selectors.trezorDevice(getState()); + if (!device) { + dispatch({ error: "Device not connected", type: TRZ_WIPEDEVICE_FAILED }); + return; + } + + dispatch({ type: TRZ_WIPEDEVICE_ATTEMPT }); + + try { + await deviceRun(dispatch, getState, device, async session => { + await session.wipeDevice(); + }); + dispatch({ type: TRZ_WIPEDEVICE_SUCCESS }); + } catch (error) { + dispatch({ error, type: TRZ_WIPEDEVICE_FAILED }); + } +}; + +export const TRZ_RECOVERDEVICE_ATTEMPT = "TRZ_RECOVERDEVICE_ATTEMPT"; +export const TRZ_RECOVERDEVICE_FAILED = "TRZ_RECOVERDEVICE_FAILED"; +export const TRZ_RECOVERDEVICE_SUCCESS = "TRZ_RECOVERDEVICE_SUCCESS"; + +export const recoverDevice = () => async (dispatch, getState) => { + const device = selectors.trezorDevice(getState()); + if (!device) { + dispatch({ error: "Device not connected", type: TRZ_RECOVERDEVICE_FAILED }); + return; + } + + dispatch({ type: TRZ_RECOVERDEVICE_ATTEMPT }); + + try { + await deviceRun(dispatch, getState, device, async session => { + const settings = { + word_count: 24, // FIXED at 24 (256 bits) + passphrase_protection: false, + pin_protection: false, + label: "New DCR Trezor", + dry_run: false, + }; + + await session.recoverDevice(settings); + }); + dispatch({ type: TRZ_RECOVERDEVICE_SUCCESS }); + } catch (error) { + dispatch({ error, type: TRZ_RECOVERDEVICE_FAILED }); + } +}; + +export const TRZ_INITDEVICE_ATTEMPT = "TRZ_INITDEVICE_ATTEMPT"; +export const TRZ_INITDEVICE_FAILED = "TRZ_INITDEVICE_FAILED"; +export const TRZ_INITDEVICE_SUCCESS = "TRZ_INITDEVICE_SUCCESS"; + +export const initDevice = () => async (dispatch, getState) => { + const device = selectors.trezorDevice(getState()); + if (!device) { + dispatch({ error: "Device not connected", type: TRZ_RECOVERDEVICE_FAILED }); + return; + } + + dispatch({ type: TRZ_INITDEVICE_ATTEMPT }); + + try { + await deviceRun(dispatch, getState, device, async session => { + const settings = { + strength: 256, // 24 words + passphrase_protection: false, + pin_protection: false, + label: "New DCR Trezor", + }; + + await session.resetDevice(settings); + }); + dispatch({ type: TRZ_INITDEVICE_SUCCESS }); + } catch (error) { + dispatch({ error, type: TRZ_INITDEVICE_FAILED }); + } +}; + +export const TRZ_UPDATEFIRMWARE_ATTEMPT = "TRZ_UPDATEFIRMWARE_ATTEMPT"; +export const TRZ_UPDATEFIRMWARE_FAILED = "TRZ_UPDATEFIRMWARE_FAILED"; +export const TRZ_UPDATEFIRMWARE_SUCCESS = "TRZ_UPDATEFIRMWARE_SUCCESS"; + +export const updateFirmware = (path) => async (dispatch, getState) => { + const device = selectors.trezorDevice(getState()); + if (!device) { + dispatch({ error: "Device not connected", type: TRZ_UPDATEFIRMWARE_FAILED }); + return; + } + + dispatch({ type: TRZ_UPDATEFIRMWARE_ATTEMPT }); + + try { + const rawFirmware = fs.readFileSync(path); + const hexFirmware = rawToHex(rawFirmware); + + await deviceRun(dispatch, getState, device, async session => { + await session.updateFirmware(hexFirmware); + }); + dispatch({ type: TRZ_UPDATEFIRMWARE_SUCCESS }); + } catch (error) { + 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/actions/WalletLoaderActions.js b/app/actions/WalletLoaderActions.js index 16be0abe7c..e47c0b7331 100644 --- a/app/actions/WalletLoaderActions.js +++ b/app/actions/WalletLoaderActions.js @@ -14,6 +14,7 @@ import { isTestNet } from "selectors"; import { SpvSyncRequest, SyncNotificationType, RpcSyncRequest } from "../middleware/walletrpc/api_pb"; import { push as pushHistory } from "react-router-redux"; import { stopNotifcations } from "./NotificationActions"; +import { clearDeviceSession as trezorClearDeviceSession } from "./TrezorActions"; const MAX_RPC_RETRIES = 5; const RPC_RETRY_DELAY = 5000; @@ -182,6 +183,7 @@ export const closeWalletRequest = () => async(dispatch, getState) => { await dispatch(stopNotifcations()); await dispatch(syncCancel()); await dispatch(rescanCancel()); + await dispatch(trezorClearDeviceSession()); await closeWallet(getState().walletLoader.loader); await wallet.stopWallet(); dispatch({ type: CLOSEWALLET_SUCCESS }); diff --git a/app/components/SideBar/MenuLinks/index.js b/app/components/SideBar/MenuLinks/index.js index 8d369103f3..bee0f83a9d 100644 --- a/app/components/SideBar/MenuLinks/index.js +++ b/app/components/SideBar/MenuLinks/index.js @@ -23,6 +23,11 @@ class MenuLinks extends React.Component { constructor (props) { super(props); + + this.links = [ ...linkList ]; + if (props.isTrezor) { + this.links.push({ path: "/trezor", link: , icon:"trezor" }); + } } componentDidMount() { @@ -87,7 +92,7 @@ class MenuLinks extends React.Component { return ( - {linkList.map(link => this.getMenuLink(link))} + {this.links.map(link => this.getMenuLink(link))} {caret} ); diff --git a/app/components/buttons/SendTransactionButton.js b/app/components/buttons/SendTransactionButton.js index 9102838256..510e93e210 100644 --- a/app/components/buttons/SendTransactionButton.js +++ b/app/components/buttons/SendTransactionButton.js @@ -1,5 +1,6 @@ import { send } from "connectors"; import { PassphraseModalButton } from "./index"; +import KeyBlueButton from "./KeyBlueButton"; import { FormattedMessage as T } from "react-intl"; @autobind @@ -16,22 +17,41 @@ class SendTransactionButton extends React.Component { onSubmit && onSubmit(); } + async onAttemptSignTransactionTrezor() { + const { unsignedTransaction, onAttemptSignTransactionTrezor, + constructTxResponse, disabled, onSubmit } = this.props; + if (disabled || !onAttemptSignTransactionTrezor) return; + await onAttemptSignTransactionTrezor(unsignedTransaction, constructTxResponse); + onSubmit && onSubmit(); + } + render() { - const { disabled, isSendingTransaction, onShow, showModal, children } = this.props; + const { disabled, isSendingTransaction, children, isTrezor } = this.props; - return ( - } - modalDescription={children} - showModal={showModal} - onShow={onShow} - disabled={disabled || isSendingTransaction} - className="content-send" - onSubmit={this.onAttemptSignTransaction} - loading={isSendingTransaction} - buttonLabel={} - /> - ); + if (isTrezor) { + return ( + + + + ); + } else { + return ( + } + modalDescription={children} + disabled={disabled || isSendingTransaction} + className="content-send" + onSubmit={this.onAttemptSignTransaction} + loading={isSendingTransaction} + buttonLabel={} + /> + ); + } } } 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/components/modals/PassphraseModal/Modal.js b/app/components/modals/PassphraseModal/Modal.js index 6e3cc0c8f5..7cb0416132 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, modalTitle, @@ -29,7 +30,7 @@ const StandardPassphraseModal = (props) => { />; return ( - +
{modalTitle ? modalTitle : diff --git a/app/components/modals/trezor/Modals.js b/app/components/modals/trezor/Modals.js new file mode 100644 index 0000000000..67bef119bf --- /dev/null +++ b/app/components/modals/trezor/Modals.js @@ -0,0 +1,46 @@ +import { trezor } from "connectors"; +import PinModal from "./PinModal"; +import PassPhraseModal from "./PassPhraseModal"; +import WalletCreationPassPhraseModal from "./WalletCreationPassPhraseModal"; +import WordModal from "./WordModal"; +import "style/Trezor.less"; + +@autobind +class TrezorModals extends React.Component { + constructor(props) { + super(props); + } + + onCancelModal() { + this.props.cancelCurrentOperation(); + } + + render() { + let Component = null; + + if (this.props.waitingForPin) { + Component = PinModal; + } else if (this.props.waitingForPassPhrase) { + if (this.props.walletCreationMasterPubkeyAttempt) { + Component = WalletCreationPassPhraseModal; + } else { + Component = PassPhraseModal; + } + } else if (this.props.waitingForWord) { + Component = WordModal; + } + + if (!Component) return null; + return ( + + ); + } +} + +const TrezorModalsOrNone = ({ isTrezor, ...props }) => + isTrezor ? : null; + +export default trezor(TrezorModalsOrNone); diff --git a/app/components/modals/trezor/PassPhraseModal.js b/app/components/modals/trezor/PassPhraseModal.js new file mode 100644 index 0000000000..37e8d4e808 --- /dev/null +++ b/app/components/modals/trezor/PassPhraseModal.js @@ -0,0 +1,42 @@ +import { PassphraseModal } from "../PassphraseModal"; +import { FormattedMessage as T } from "react-intl"; + +@autobind +class TrezorPassphraseModal extends React.Component { + constructor(props) { + super(props); + } + + onSubmit(passPhrase) { + this.props.submitPassPhrase(passPhrase); + } + + render() { + const { onCancelModal } = this.props; + const { onSubmit } = this; + + const trezorLabel = this.props.device ? this.props.device.features.label : ""; + + const className = [ + "trezor-passphrase-modal", + this.props.isGetStarted ? "get-started" : "" + ].join(" "); + + return ( + } + modalClassName={className} + modalDescription={ +

+ '{trezorLabel}' }} /> +

+ } + {...{ onCancelModal, onSubmit }} + /> + ); + } +} + +export default TrezorPassphraseModal; diff --git a/app/components/modals/trezor/PinModal.js b/app/components/modals/trezor/PinModal.js new file mode 100644 index 0000000000..cc239ebea3 --- /dev/null +++ b/app/components/modals/trezor/PinModal.js @@ -0,0 +1,92 @@ +import Modal from "../Modal"; +import { FormattedMessage as T } from "react-intl"; +import { PasswordInput } from "inputs"; +import { ButtonsToolbar } from "../PassphraseModal"; +import { InvisibleButton } from "buttons"; + +const PIN_LABELS = "ABCDEFGHI"; + +const PinButton = ({ index, label, onClick }) => +
onClick(index)}>{label}
; + +@autobind +class PinModal extends React.Component { + constructor(props) { + super(props); + this.state = { currentPin: "" }; + } + + onPinButtonClick(index) { + this.setState({ currentPin: this.state.currentPin + index }); + } + + onCancelModal() { + this.setState({ currentPin: "" }); + this.props.onCancelModal(); + } + + onSubmit() { + this.props.submitPin(this.state.currentPin); + } + + onClearPin() { + this.setState({ currentPin: "" }); + } + + onChangeCurrentPin(e) { + const txt = (e.target.value || "").toUpperCase().trim(); + let pin = ""; + for (let i = 0; i < txt.length; i++) { + const idx = PIN_LABELS.indexOf(txt[i]); + if (idx > -1) pin = pin + "" + (idx+1); + } + this.setState({ currentPin: pin }); + } + + render() { + const { onCancelModal, onSubmit, onPinButtonClick, onClearPin, + onChangeCurrentPin } = this; + + const currentPin = this.state.currentPin.split("").map(v => PIN_LABELS[parseInt(v)-1]).join(""); + + const Button = ({ index }) => + ; + + 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}' }} />

+
+
+
+ + + +
+
+ +
+ +
+ ); + } +} + +export default PinModal; 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 new file mode 100644 index 0000000000..b9ce098d09 --- /dev/null +++ b/app/components/modals/trezor/WordModal.js @@ -0,0 +1,85 @@ +import Modal from "../Modal"; +import { FormattedMessage as T } from "react-intl"; +import { ButtonsToolbar } from "../PassphraseModal"; +import Select from "react-select"; +import { word_list } from "helpers/trezor"; + +const input_options = word_list.map(w => ({ word: w })); + +@autobind +class WordModal extends React.Component { + constructor(props) { + super(props); + this.state = { word: "", value: null }; + } + + onCancelModal() { + this.setState({ word: "", value: null }); + this.props.onCancelModal(); + } + + onSubmit() { + if (!this.state.word) return; + this.props.submitWord(this.state.word); + this.setState({ word: "", value: null }); + } + + onWordChanged(value) { + this.setState({ word: value, value: { word: value } }); + } + + onSelectKeyDown(e) { + if (e.keyCode === 13 && this.state.word) { + this.onSubmit(); + } + } + + getSeedWords (input, callback) { + input = input.toLowerCase(); + const options = input_options + .filter(w => w.word.toLowerCase().substr(0, input.length) === input); + callback(null, { + options: options.slice(0, 5) + }); + } + + render() { + const { onCancelModal, onSubmit, onWordChanged, onSelectKeyDown, getSeedWords } = this; + + const className = [ + "passphrase-modal", + "trezor-word-modal", + this.props.isGetStarted ? "get-started" : "" + ].join(" "); + + return ( + +

+

+ +
+ n && n.focus()} + autoFocus + simpleValue + multi={false} + clearable={false} + multi={false} + filterOptions={false} + valueKey="word" + labelKey="word" + loadOptions={getSeedWords} + onChange={onWordChanged} + value={this.state.value} + placeholder={} + onInputKeyDown={onSelectKeyDown} + /> +
+ + +
+ ); + } +} + +export default WordModal; diff --git a/app/components/modals/trezor/index.js b/app/components/modals/trezor/index.js new file mode 100644 index 0000000000..a4848bf877 --- /dev/null +++ b/app/components/modals/trezor/index.js @@ -0,0 +1 @@ +export { default as TrezorModals } from "./Modals"; diff --git a/app/components/shared/VerticalAccordion.js b/app/components/shared/VerticalAccordion.js index b57d6cc64c..1e3e639cb0 100644 --- a/app/components/shared/VerticalAccordion.js +++ b/app/components/shared/VerticalAccordion.js @@ -11,32 +11,15 @@ class VerticalAccordion extends React.Component { } componentDidUpdate(prevProps) { - let oldChildren, newChildren, newKeys; - let needUpdate = false; - if(prevProps.children) { - oldChildren = prevProps.children; - } - if (this.props.children) { - newChildren = this.props.children; - newKeys = Object.keys(newChildren.props); - } - if (oldChildren && newChildren) { - newKeys.forEach( key => { - if (typeof(newChildren.props[key]) !== "object" && - typeof(newChildren.props[key]) !== "function") { - if (oldChildren.props[key] !== newChildren.props[key]) { - needUpdate = true; - return; - } - } - }); - } + const needUpdate = + (prevProps.show !== this.props.show) || + (prevProps.children !== this.props.children) || + (prevProps.height !== this.props.height); - if (prevProps.show !== this.props.show || needUpdate) { + if (needUpdate) { this.setState({ shownStyles: this.chosenStyles(this.props, this.props.show), }); - this.chosenStyles(this.props, this.props.show); } } diff --git a/app/components/views/GetStartedPage/TrezorConfig/Page.js b/app/components/views/GetStartedPage/TrezorConfig/Page.js new file mode 100644 index 0000000000..3ae488613f --- /dev/null +++ b/app/components/views/GetStartedPage/TrezorConfig/Page.js @@ -0,0 +1,32 @@ +import { Tooltip } from "shared"; +import { LoaderBarBottom } from "indicators"; +import { AboutModalButton, GoBackMsg } from "../messages"; + +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..19f5801826 --- /dev/null +++ b/app/components/views/GetStartedPage/TrezorConfig/index.js @@ -0,0 +1,46 @@ +import { trezor } from "connectors"; +import { FormattedMessage as T } from "react-intl"; +import ConfigSections from "views/TrezorPage/ConfigSections"; +import Page from "./Page"; +import { InvisibleButton } from "buttons"; +import "style/Trezor.less"; + +@autobind +class TrezorConfig extends React.Component { + + constructor(props) { + super(props); + props.enableTrezor(); + } + + renderNoDevice() { + return <> +
+
+ + + +
+ ; + } + + render() { + const { device } = this.props; + let children; + + if (!device) { + children = this.renderNoDevice(); + } else { + const loading = this.props.performingOperation; + 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 c752179619..9973301ef5 100644 --- a/app/components/views/GetStartedPage/WalletSelection/CreateWalletForm.js +++ b/app/components/views/GetStartedPage/WalletSelection/CreateWalletForm.js @@ -37,6 +37,9 @@ const CreateWalletForm = ({ toggleWatchOnly, onChangeCreateWalletMasterPubKey, masterPubKeyError, + isTrezor, + toggleTrezor, + onShowTrezorConfig, }) => { return ( @@ -80,6 +83,15 @@ const CreateWalletForm = ({
+
+
+ +
+
+ + +
+
{isWatchingOnly &&
diff --git a/app/components/views/GetStartedPage/WalletSelection/index.js b/app/components/views/GetStartedPage/WalletSelection/index.js index 44a3de10f1..b4578b673f 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, }; } componentDidUpdate(prevProps) { @@ -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 937b327f15..ac19ef74c4 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() { @@ -78,6 +79,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 }); } @@ -107,6 +116,7 @@ class GetStartedPage extends React.Component { showSettings, showLogs, showReleaseNotes, + showTrezorConfig, ...state } = this.state; @@ -117,7 +127,9 @@ class GetStartedPage extends React.Component { onHideSettings, onShowLogs, onHideLogs, - onSetWalletPrivatePassphrase + onSetWalletPrivatePassphrase, + onShowTrezorConfig, + onHideTrezorConfig, } = this; const blockChainLoading = "blockchain-syncing"; @@ -135,6 +147,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) { @@ -235,6 +249,8 @@ class GetStartedPage extends React.Component { onShowLogs, onHideLogs, onSetWalletPrivatePassphrase, + onShowTrezorConfig, + onHideTrezorConfig, appVersion, updateAvailable, isSPV, diff --git a/app/components/views/TrezorPage/ChangeLabel.js b/app/components/views/TrezorPage/ChangeLabel.js new file mode 100644 index 0000000000..711458e4aa --- /dev/null +++ b/app/components/views/TrezorPage/ChangeLabel.js @@ -0,0 +1,61 @@ +import { VerticalAccordion } from "shared"; +import { FormattedMessage as T } from "react-intl"; +import { TextInput } from "inputs"; +import { KeyBlueButton } from "buttons"; + +@autobind +class ChangeLabel extends React.Component { + constructor(props) { + super(props); + this.state = { newLabel: "", show: false }; + } + + onChangeLabelClicked() { + this.props.onChangeLabel(this.state.newLabel); + } + + onNewLabelChanged(e) { + this.setState({ newLabel: e.target.value }); + } + + onToggleAccordion() { + this.setState({ show: !this.state.show }); + } + + render() { + + const changeLabelHeader = ( + + + + ); + + const { loading } = this.props; + + return ( + +
+
+ +
+
+ + + +
+
+ + ); + } +} + +export default ChangeLabel; diff --git a/app/components/views/TrezorPage/ConfigButtons.js b/app/components/views/TrezorPage/ConfigButtons.js new file mode 100644 index 0000000000..ad630f380c --- /dev/null +++ b/app/components/views/TrezorPage/ConfigButtons.js @@ -0,0 +1,56 @@ +import { VerticalAccordion } from "shared"; +import { FormattedMessage as T } from "react-intl"; +import { KeyBlueButton } from "buttons"; + +@autobind +class ConfigButtons extends React.Component { + constructor(props) { + super(props); + this.state = { show: false }; + } + + onToggleAccordion() { + this.setState({ show: !this.state.show }); + } + + render() { + + const ConfigButtonsHeader = ( + + + + ); + + const { loading, onTogglePinProtection, onTogglePassPhraseProtection, + onChangeHomeScreen, onClearDeviceSession } = this.props; + + return ( + + + + + + + + + + + + + + + + + + + ); + } +} + +export default ConfigButtons; diff --git a/app/components/views/TrezorPage/ConfigSections.js b/app/components/views/TrezorPage/ConfigSections.js new file mode 100644 index 0000000000..f456e9362f --- /dev/null +++ b/app/components/views/TrezorPage/ConfigSections.js @@ -0,0 +1,83 @@ +import ChangeLabel from "./ChangeLabel"; +import ConfigButtons from "./ConfigButtons"; +import RecoveryButtons from "./RecoveryButtons"; +import FirmwareUpdate from "./FirmwareUpdate"; + +@autobind +class TrezorConfigSections extends React.Component { + + constructor(props) { + super(props); + } + + 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(); + } + + onUpdateFirmware(path) { + this.props.updateFirmware(path); + } + + onInitDevice() { + this.props.initDevice(); + } + + onReloadDeviceList() { + this.props.reloadDeviceList(); + } + + onClearDeviceSession() { + this.props.clearDeviceSession(); + } + + render() { + const { + onTogglePinProtection, + onTogglePassPhraseProtection, + onChangeHomeScreen, + onChangeLabel, + onWipeDevice, + onRecoverDevice, + onInitDevice, + onUpdateFirmware, + onClearDeviceSession, + loading, + } = this; + + return ( + <> + + + + + + + + + ); + } + +} + +export default TrezorConfigSections; diff --git a/app/components/views/TrezorPage/FirmwareUpdate.js b/app/components/views/TrezorPage/FirmwareUpdate.js new file mode 100644 index 0000000000..7828c0d4a2 --- /dev/null +++ b/app/components/views/TrezorPage/FirmwareUpdate.js @@ -0,0 +1,60 @@ +import { VerticalAccordion } from "shared"; +import { FormattedMessage as T } from "react-intl"; +import { DangerButton } from "buttons"; +import { Documentation } from "shared"; +import { PathBrowseInput } from "inputs"; + +@autobind +class FirmwareUpdate extends React.Component { + constructor(props) { + super(props); + this.state = { path: "", show: false }; + } + + onChangePath(path) { + this.setState({ path }); + } + + onUpdateFirmware() { + this.props.onUpdateFirmware(this.state.path); + } + + onToggleAccordion() { + this.setState({ show: !this.state.show }); + } + + render() { + + const header = ( + + + + ); + + const { loading } = this.props; + + return ( + +
+ +
+ +

+
+ + + + +
+ + ); + } +} + +export default FirmwareUpdate; diff --git a/app/components/views/TrezorPage/Header.js b/app/components/views/TrezorPage/Header.js new file mode 100644 index 0000000000..2f271b7b0b --- /dev/null +++ b/app/components/views/TrezorPage/Header.js @@ -0,0 +1,9 @@ +import { FormattedMessage as T } from "react-intl"; +import { StandaloneHeader } from "layout"; + +export default () => + } + description={} + />; diff --git a/app/components/views/TrezorPage/NoDevicePage.js b/app/components/views/TrezorPage/NoDevicePage.js new file mode 100644 index 0000000000..ca4e20bf28 --- /dev/null +++ b/app/components/views/TrezorPage/NoDevicePage.js @@ -0,0 +1,17 @@ +import { FormattedMessage as T } from "react-intl"; +import Header from "./Header"; +import { StandalonePage } from "layout"; +import { InvisibleButton } from "buttons"; + +export default ({ onReloadDeviceList }) => ( + }> +
+ +
+
+ + + +
+
+); diff --git a/app/components/views/TrezorPage/Page.js b/app/components/views/TrezorPage/Page.js new file mode 100644 index 0000000000..85362fdf0b --- /dev/null +++ b/app/components/views/TrezorPage/Page.js @@ -0,0 +1,9 @@ +import { StandalonePage } from "layout"; +import ConfigSections from "./ConfigSections"; +import Header from "./Header"; + +export default props => ( + }> + + +); diff --git a/app/components/views/TrezorPage/RecoveryButtons.js b/app/components/views/TrezorPage/RecoveryButtons.js new file mode 100644 index 0000000000..c95a93bf6d --- /dev/null +++ b/app/components/views/TrezorPage/RecoveryButtons.js @@ -0,0 +1,55 @@ +import { VerticalAccordion } from "shared"; +import { FormattedMessage as T } from "react-intl"; +import { DangerButton } from "buttons"; +import { Documentation } from "shared"; + +@autobind +class RecoveryButtons extends React.Component { + constructor(props) { + super(props); + this.state = { show: false }; + } + + onToggleAccordion() { + this.setState({ show: !this.state.show }); + } + + render() { + + const header = ( + + + + ); + + const { loading, onWipeDevice, onRecoverDevice, onInitDevice } = this.props; + + return ( + +
+ +
+ + + + + + + + + + + +
+ + ); + } +} + +export default RecoveryButtons; diff --git a/app/components/views/TrezorPage/index.js b/app/components/views/TrezorPage/index.js new file mode 100644 index 0000000000..02fca7729a --- /dev/null +++ b/app/components/views/TrezorPage/index.js @@ -0,0 +1,12 @@ +import { trezor } from "connectors"; +import Page from "./Page"; +import NoDevicePage from "./NoDevicePage"; +import "style/Trezor.less"; + +const TrezorPage = ({ device, reloadDeviceList, ...props }) => ( + !device + ? + : +); + +export default trezor(TrezorPage); diff --git a/app/config.js b/app/config.js index 54f171a33b..e4a19a699e 100644 --- a/app/config.js +++ b/app/config.js @@ -44,6 +44,9 @@ export function initWalletCfg(testnet, walletPath) { if (!config.has("politeia_last_access_block")) { config.set("politeia_last_access_block", 0); } + if (!config.has("trezor")) { + config.set("trezor", false); + } stakePoolInfo(function(foundStakePoolConfigs) { if (foundStakePoolConfigs !== null) { updateStakePoolConfig(config, foundStakePoolConfigs); 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/index.js b/app/connectors/index.js index 82574fc9e4..f50380eb3e 100644 --- a/app/connectors/index.js +++ b/app/connectors/index.js @@ -54,3 +54,4 @@ export { default as proposals } from "./proposals"; export { default as newProposalCounts } from "./newProposalCounts"; export { default as network } from "./network"; export { default as treasuryInfo } from "./treasuryInfo"; +export { default as trezor } from "./trezor"; diff --git a/app/connectors/routing.js b/app/connectors/routing.js index 3fde906261..f9165df3d7 100644 --- a/app/connectors/routing.js +++ b/app/connectors/routing.js @@ -6,6 +6,7 @@ import * as ca from "../actions/ClientActions"; const mapStateToProps = selectorMap({ location: sel.location, + isTrezor: sel.isTrezor, }); const mapDispatchToProps = dispatch => bindActionCreators({ diff --git a/app/connectors/send.js b/app/connectors/send.js index 610c38fb9b..1511100a51 100644 --- a/app/connectors/send.js +++ b/app/connectors/send.js @@ -3,6 +3,7 @@ import { bindActionCreators } from "redux"; import { selectorMap } from "../fp"; import * as sel from "../selectors"; import * as ca from "../actions/ControlActions"; +import * as tza from "../actions/TrezorActions"; const mapStateToProps = selectorMap({ defaultSpendingAccount: sel.defaultSpendingAccount, @@ -17,6 +18,8 @@ const mapStateToProps = selectorMap({ unitDivisor: sel.unitDivisor, constructTxLowBalance: sel.constructTxLowBalance, isTransactionsSendTabDisabled: sel.isTransactionsSendTabDisabled, + constructTxResponse: sel.constructTxResponse, + isTrezor: sel.isTrezor, }); const mapDispatchToProps = dispatch => bindActionCreators({ @@ -25,6 +28,7 @@ const mapDispatchToProps = dispatch => bindActionCreators({ onClearTransaction: ca.clearTransaction, getNextAddressAttempt: ca.getNextAddressAttempt, validateAddress: ca.validateAddress, + onAttemptSignTransactionTrezor: tza.signTransactionAttemptTrezor, }, dispatch); export default connect(mapStateToProps, mapDispatchToProps); diff --git a/app/connectors/signMessagePage.js b/app/connectors/signMessagePage.js index 83d8a8eda0..fc07f59c46 100644 --- a/app/connectors/signMessagePage.js +++ b/app/connectors/signMessagePage.js @@ -3,6 +3,7 @@ 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, @@ -10,12 +11,14 @@ const mapStateToProps = selectorMap({ isSigningMessage: sel.isSigningMessage, walletService: sel.walletService, isSignMessageDisabled: sel.isSignMessageDisabled, + 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/connectors/trezor.js b/app/connectors/trezor.js new file mode 100644 index 0000000000..98ab0b86f1 --- /dev/null +++ b/app/connectors/trezor.js @@ -0,0 +1,38 @@ +import { connect } from "react-redux"; +import { selectorMap } from "../fp"; +import { bindActionCreators } from "redux"; +import * as sel from "../selectors"; +import * as trza from "../actions/TrezorActions"; + +const mapStateToProps = selectorMap({ + isTrezor: sel.isTrezor, + waitingForPin: sel.trezorWaitingForPin, + waitingForPassPhrase: sel.trezorWaitingForPassPhrase, + 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, + submitWord: trza.submitWord, + togglePinProtection: trza.togglePinProtection, + togglePassPhraseProtection: trza.togglePassPhraseProtection, + changeToDecredHomeScreen: trza.changeToDecredHomeScreen, + changeLabel: trza.changeLabel, + wipeDevice: trza.wipeDevice, + recoverDevice: trza.recoverDevice, + initDevice: trza.initDevice, + updateFirmware: trza.updateFirmware, + enableTrezor: trza.enableTrezor, + reloadDeviceList: trza.reloadTrezorDeviceList, + clearDeviceSession: trza.clearDeviceSession, +}, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps); diff --git a/app/containers/App.js b/app/containers/App.js index 1f4af06c6c..8102096240 100644 --- a/app/containers/App.js +++ b/app/containers/App.js @@ -11,12 +11,12 @@ import FatalErrorPage from "components/views/FatalErrorPage"; import Snackbar from "components/Snackbar"; import AboutModal from "../components/modals/AboutModal/AboutModal"; import { log } from "wallet"; +import { TrezorModals } from "components/modals/trezor"; import "style/Themes.less"; import "style/Layout.less"; import { ipcRenderer } from "electron"; const topLevelAnimation = { atEnter: { opacity: 0 }, atLeave: { opacity: 0 }, atActive: { opacity: 1 } }; - @autobind class App extends React.Component { static propTypes = { @@ -119,7 +119,7 @@ class App extends React.Component { - +
); diff --git a/app/containers/Wallet.js b/app/containers/Wallet.js index fc3640517b..43257d6199 100644 --- a/app/containers/Wallet.js +++ b/app/containers/Wallet.js @@ -14,6 +14,7 @@ import TransactionPage from "components/views/TransactionPage"; import TicketsPage from "components/views/TicketsPage"; import TutorialsPage from "components/views/TutorialsPage"; import GovernancePage from "components/views/GovernancePage"; +import TrezorPage from "components/views/TrezorPage"; import SideBar from "components/SideBar"; import { BlurableContainer } from "layout"; import { walletContainer, theming } from "connectors"; @@ -44,6 +45,7 @@ class Wallet extends React.Component { +
diff --git a/app/helpers/byteActions.js b/app/helpers/byteActions.js index 2e10938bde..cb7a369894 100644 --- a/app/helpers/byteActions.js +++ b/app/helpers/byteActions.js @@ -77,3 +77,12 @@ export function readFileBackward(path, maxSize, end) { } ); } + +// 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/helpers/trezor.js b/app/helpers/trezor.js new file mode 100644 index 0000000000..4d97efa979 --- /dev/null +++ b/app/helpers/trezor.js @@ -0,0 +1,209 @@ +export const model1_decred_homescreen = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007ffe003ff00000000000000000000007fffc007fe0000000000000000000001ffff800ffc0000000000000000000003ffff001ff8000000000000000000000ffffe003ff0000000000000000000001ffffc007fe0000000000000000000003ffff800ffc0000000000000000000003ffff001ff80000000000000000000007fffe003ff00000000000000000000007ff00007fe0000000000000000000000ffc0000ffc0000000000000000000000ff80001fff0000000000000000000001ff00003fffc000000000000000000001ff00007ffff000000000000000000001fe0000fffff800000000000000000001fe0001fffff800000000000000000001fe0003fffffc00000000000000000001fe0000003ffe00000000000000000001fe00000007fe00000000000000000001fe00000003ff00000000000000000001fe00000001ff00000000000000000001ff00000001ff00000000000000000001ff80000000ff00000000000000000000ffc0000000ff00000000000000000000ffe0000000ff800000000000000000007ffc000000ff800000000000000000007fffff8000ff800000000000000000003fffff0000ff000000000000000000001ffffe0000ff000000000000000000000ffffc0001ff0000000000000000000003fff80001ff0000000000000000000001fff00003ff00000000000000000000007fe00007fe0000000000000000000000ffc0001ffe0000000000000000000001ff800ffffc0000000000000000000003ff001ffff80000000000000000000007fe003ffff8000000000000000000000ffc007ffff0000000000000000000001ff800ffffe0000000000000000000003ff001ffff80000000000000000000007fe003ffff0000000000000000000000ffc007fff80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + +export const word_list = [ + "abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", + "access", "accident", "account", "accuse", "achieve", "acid", "acoustic", "acquire", "across", "act", + "action", "actor", "actress", "actual", "adapt", "add", "addict", "address", "adjust", "admit", + "adult", "advance", "advice", "aerobic", "affair", "afford", "afraid", "again", "age", "agent", + "agree", "ahead", "aim", "air", "airport", "aisle", "alarm", "album", "alcohol", "alert", + "alien", "all", "alley", "allow", "almost", "alone", "alpha", "already", "also", "alter", + "always", "amateur", "amazing", "among", "amount", "amused", "analyst", "anchor", "ancient", "anger", + "angle", "angry", "animal", "ankle", "announce", "annual", "another", "answer", "antenna", "antique", + "anxiety", "any", "apart", "apology", "appear", "apple", "approve", "april", "arch", "arctic", + "area", "arena", "argue", "arm", "armed", "armor", "army", "around", "arrange", "arrest", + "arrive", "arrow", "art", "artefact", "artist", "artwork", "ask", "aspect", "assault", "asset", + "assist", "assume", "asthma", "athlete", "atom", "attack", "attend", "attitude", "attract", "auction", + "audit", "august", "aunt", "author", "auto", "autumn", "average", "avocado", "avoid", "awake", + "aware", "away", "awesome", "awful", "awkward", "axis", "baby", "bachelor", "bacon", "badge", + "bag", "balance", "balcony", "ball", "bamboo", "banana", "banner", "bar", "barely", "bargain", + "barrel", "base", "basic", "basket", "battle", "beach", "bean", "beauty", "because", "become", + "beef", "before", "begin", "behave", "behind", "believe", "below", "belt", "bench", "benefit", + "best", "betray", "better", "between", "beyond", "bicycle", "bid", "bike", "bind", "biology", + "bird", "birth", "bitter", "black", "blade", "blame", "blanket", "blast", "bleak", "bless", + "blind", "blood", "blossom", "blouse", "blue", "blur", "blush", "board", "boat", "body", + "boil", "bomb", "bone", "bonus", "book", "boost", "border", "boring", "borrow", "boss", + "bottom", "bounce", "box", "boy", "bracket", "brain", "brand", "brass", "brave", "bread", + "breeze", "brick", "bridge", "brief", "bright", "bring", "brisk", "broccoli", "broken", "bronze", + "broom", "brother", "brown", "brush", "bubble", "buddy", "budget", "buffalo", "build", "bulb", + "bulk", "bullet", "bundle", "bunker", "burden", "burger", "burst", "bus", "business", "busy", + "butter", "buyer", "buzz", "cabbage", "cabin", "cable", "cactus", "cage", "cake", "call", + "calm", "camera", "camp", "can", "canal", "cancel", "candy", "cannon", "canoe", "canvas", + "canyon", "capable", "capital", "captain", "car", "carbon", "card", "cargo", "carpet", "carry", + "cart", "case", "cash", "casino", "castle", "casual", "cat", "catalog", "catch", "category", + "cattle", "caught", "cause", "caution", "cave", "ceiling", "celery", "cement", "census", "century", + "cereal", "certain", "chair", "chalk", "champion", "change", "chaos", "chapter", "charge", "chase", + "chat", "cheap", "check", "cheese", "chef", "cherry", "chest", "chicken", "chief", "child", + "chimney", "choice", "choose", "chronic", "chuckle", "chunk", "churn", "cigar", "cinnamon", "circle", + "citizen", "city", "civil", "claim", "clap", "clarify", "claw", "clay", "clean", "clerk", + "clever", "click", "client", "cliff", "climb", "clinic", "clip", "clock", "clog", "close", + "cloth", "cloud", "clown", "club", "clump", "cluster", "clutch", "coach", "coast", "coconut", + "code", "coffee", "coil", "coin", "collect", "color", "column", "combine", "come", "comfort", + "comic", "common", "company", "concert", "conduct", "confirm", "congress", "connect", "consider", "control", + "convince", "cook", "cool", "copper", "copy", "coral", "core", "corn", "correct", "cost", + "cotton", "couch", "country", "couple", "course", "cousin", "cover", "coyote", "crack", "cradle", + "craft", "cram", "crane", "crash", "crater", "crawl", "crazy", "cream", "credit", "creek", + "crew", "cricket", "crime", "crisp", "critic", "crop", "cross", "crouch", "crowd", "crucial", + "cruel", "cruise", "crumble", "crunch", "crush", "cry", "crystal", "cube", "culture", "cup", + "cupboard", "curious", "current", "curtain", "curve", "cushion", "custom", "cute", "cycle", "dad", + "damage", "damp", "dance", "danger", "daring", "dash", "daughter", "dawn", "day", "deal", + "debate", "debris", "decade", "december", "decide", "decline", "decorate", "decrease", "deer", "defense", + "define", "defy", "degree", "delay", "deliver", "demand", "demise", "denial", "dentist", "deny", + "depart", "depend", "deposit", "depth", "deputy", "derive", "describe", "desert", "design", "desk", + "despair", "destroy", "detail", "detect", "develop", "device", "devote", "diagram", "dial", "diamond", + "diary", "dice", "diesel", "diet", "differ", "digital", "dignity", "dilemma", "dinner", "dinosaur", + "direct", "dirt", "disagree", "discover", "disease", "dish", "dismiss", "disorder", "display", "distance", + "divert", "divide", "divorce", "dizzy", "doctor", "document", "dog", "doll", "dolphin", "domain", + "donate", "donkey", "donor", "door", "dose", "double", "dove", "draft", "dragon", "drama", + "drastic", "draw", "dream", "dress", "drift", "drill", "drink", "drip", "drive", "drop", + "drum", "dry", "duck", "dumb", "dune", "during", "dust", "dutch", "duty", "dwarf", + "dynamic", "eager", "eagle", "early", "earn", "earth", "easily", "east", "easy", "echo", + "ecology", "economy", "edge", "edit", "educate", "effort", "egg", "eight", "either", "elbow", + "elder", "electric", "elegant", "element", "elephant", "elevator", "elite", "else", "embark", "embody", + "embrace", "emerge", "emotion", "employ", "empower", "empty", "enable", "enact", "end", "endless", + "endorse", "enemy", "energy", "enforce", "engage", "engine", "enhance", "enjoy", "enlist", "enough", + "enrich", "enroll", "ensure", "enter", "entire", "entry", "envelope", "episode", "equal", "equip", + "era", "erase", "erode", "erosion", "error", "erupt", "escape", "essay", "essence", "estate", + "eternal", "ethics", "evidence", "evil", "evoke", "evolve", "exact", "example", "excess", "exchange", + "excite", "exclude", "excuse", "execute", "exercise", "exhaust", "exhibit", "exile", "exist", "exit", + "exotic", "expand", "expect", "expire", "explain", "expose", "express", "extend", "extra", "eye", + "eyebrow", "fabric", "face", "faculty", "fade", "faint", "faith", "fall", "false", "fame", + "family", "famous", "fan", "fancy", "fantasy", "farm", "fashion", "fat", "fatal", "father", + "fatigue", "fault", "favorite", "feature", "february", "federal", "fee", "feed", "feel", "female", + "fence", "festival", "fetch", "fever", "few", "fiber", "fiction", "field", "figure", "file", + "film", "filter", "final", "find", "fine", "finger", "finish", "fire", "firm", "first", + "fiscal", "fish", "fit", "fitness", "fix", "flag", "flame", "flash", "flat", "flavor", + "flee", "flight", "flip", "float", "flock", "floor", "flower", "fluid", "flush", "fly", + "foam", "focus", "fog", "foil", "fold", "follow", "food", "foot", "force", "forest", + "forget", "fork", "fortune", "forum", "forward", "fossil", "foster", "found", "fox", "fragile", + "frame", "frequent", "fresh", "friend", "fringe", "frog", "front", "frost", "frown", "frozen", + "fruit", "fuel", "fun", "funny", "furnace", "fury", "future", "gadget", "gain", "galaxy", + "gallery", "game", "gap", "garage", "garbage", "garden", "garlic", "garment", "gas", "gasp", + "gate", "gather", "gauge", "gaze", "general", "genius", "genre", "gentle", "genuine", "gesture", + "ghost", "giant", "gift", "giggle", "ginger", "giraffe", "girl", "give", "glad", "glance", + "glare", "glass", "glide", "glimpse", "globe", "gloom", "glory", "glove", "glow", "glue", + "goat", "goddess", "gold", "good", "goose", "gorilla", "gospel", "gossip", "govern", "gown", + "grab", "grace", "grain", "grant", "grape", "grass", "gravity", "great", "green", "grid", + "grief", "grit", "grocery", "group", "grow", "grunt", "guard", "guess", "guide", "guilt", + "guitar", "gun", "gym", "habit", "hair", "half", "hammer", "hamster", "hand", "happy", + "harbor", "hard", "harsh", "harvest", "hat", "have", "hawk", "hazard", "head", "health", + "heart", "heavy", "hedgehog", "height", "hello", "helmet", "help", "hen", "hero", "hidden", + "high", "hill", "hint", "hip", "hire", "history", "hobby", "hockey", "hold", "hole", + "holiday", "hollow", "home", "honey", "hood", "hope", "horn", "horror", "horse", "hospital", + "host", "hotel", "hour", "hover", "hub", "huge", "human", "humble", "humor", "hundred", + "hungry", "hunt", "hurdle", "hurry", "hurt", "husband", "hybrid", "ice", "icon", "idea", + "identify", "idle", "ignore", "ill", "illegal", "illness", "image", "imitate", "immense", "immune", + "impact", "impose", "improve", "impulse", "inch", "include", "income", "increase", "index", "indicate", + "indoor", "industry", "infant", "inflict", "inform", "inhale", "inherit", "initial", "inject", "injury", + "inmate", "inner", "innocent", "input", "inquiry", "insane", "insect", "inside", "inspire", "install", + "intact", "interest", "into", "invest", "invite", "involve", "iron", "island", "isolate", "issue", + "item", "ivory", "jacket", "jaguar", "jar", "jazz", "jealous", "jeans", "jelly", "jewel", + "job", "join", "joke", "journey", "joy", "judge", "juice", "jump", "jungle", "junior", + "junk", "just", "kangaroo", "keen", "keep", "ketchup", "key", "kick", "kid", "kidney", + "kind", "kingdom", "kiss", "kit", "kitchen", "kite", "kitten", "kiwi", "knee", "knife", + "knock", "know", "lab", "label", "labor", "ladder", "lady", "lake", "lamp", "language", + "laptop", "large", "later", "latin", "laugh", "laundry", "lava", "law", "lawn", "lawsuit", + "layer", "lazy", "leader", "leaf", "learn", "leave", "lecture", "left", "leg", "legal", + "legend", "leisure", "lemon", "lend", "length", "lens", "leopard", "lesson", "letter", "level", + "liar", "liberty", "library", "license", "life", "lift", "light", "like", "limb", "limit", + "link", "lion", "liquid", "list", "little", "live", "lizard", "load", "loan", "lobster", + "local", "lock", "logic", "lonely", "long", "loop", "lottery", "loud", "lounge", "love", + "loyal", "lucky", "luggage", "lumber", "lunar", "lunch", "luxury", "lyrics", "machine", "mad", + "magic", "magnet", "maid", "mail", "main", "major", "make", "mammal", "man", "manage", + "mandate", "mango", "mansion", "manual", "maple", "marble", "march", "margin", "marine", "market", + "marriage", "mask", "mass", "master", "match", "material", "math", "matrix", "matter", "maximum", + "maze", "meadow", "mean", "measure", "meat", "mechanic", "medal", "media", "melody", "melt", + "member", "memory", "mention", "menu", "mercy", "merge", "merit", "merry", "mesh", "message", + "metal", "method", "middle", "midnight", "milk", "million", "mimic", "mind", "minimum", "minor", + "minute", "miracle", "mirror", "misery", "miss", "mistake", "mix", "mixed", "mixture", "mobile", + "model", "modify", "mom", "moment", "monitor", "monkey", "monster", "month", "moon", "moral", + "more", "morning", "mosquito", "mother", "motion", "motor", "mountain", "mouse", "move", "movie", + "much", "muffin", "mule", "multiply", "muscle", "museum", "mushroom", "music", "must", "mutual", + "myself", "mystery", "myth", "naive", "name", "napkin", "narrow", "nasty", "nation", "nature", + "near", "neck", "need", "negative", "neglect", "neither", "nephew", "nerve", "nest", "net", + "network", "neutral", "never", "news", "next", "nice", "night", "noble", "noise", "nominee", + "noodle", "normal", "north", "nose", "notable", "note", "nothing", "notice", "novel", "now", + "nuclear", "number", "nurse", "nut", "oak", "obey", "object", "oblige", "obscure", "observe", + "obtain", "obvious", "occur", "ocean", "october", "odor", "off", "offer", "office", "often", + "oil", "okay", "old", "olive", "olympic", "omit", "once", "one", "onion", "online", + "only", "open", "opera", "opinion", "oppose", "option", "orange", "orbit", "orchard", "order", + "ordinary", "organ", "orient", "original", "orphan", "ostrich", "other", "outdoor", "outer", "output", + "outside", "oval", "oven", "over", "own", "owner", "oxygen", "oyster", "ozone", "pact", + "paddle", "page", "pair", "palace", "palm", "panda", "panel", "panic", "panther", "paper", + "parade", "parent", "park", "parrot", "party", "pass", "patch", "path", "patient", "patrol", + "pattern", "pause", "pave", "payment", "peace", "peanut", "pear", "peasant", "pelican", "pen", + "penalty", "pencil", "people", "pepper", "perfect", "permit", "person", "pet", "phone", "photo", + "phrase", "physical", "piano", "picnic", "picture", "piece", "pig", "pigeon", "pill", "pilot", + "pink", "pioneer", "pipe", "pistol", "pitch", "pizza", "place", "planet", "plastic", "plate", + "play", "please", "pledge", "pluck", "plug", "plunge", "poem", "poet", "point", "polar", + "pole", "police", "pond", "pony", "pool", "popular", "portion", "position", "possible", "post", + "potato", "pottery", "poverty", "powder", "power", "practice", "praise", "predict", "prefer", "prepare", + "present", "pretty", "prevent", "price", "pride", "primary", "print", "priority", "prison", "private", + "prize", "problem", "process", "produce", "profit", "program", "project", "promote", "proof", "property", + "prosper", "protect", "proud", "provide", "public", "pudding", "pull", "pulp", "pulse", "pumpkin", + "punch", "pupil", "puppy", "purchase", "purity", "purpose", "purse", "push", "put", "puzzle", + "pyramid", "quality", "quantum", "quarter", "question", "quick", "quit", "quiz", "quote", "rabbit", + "raccoon", "race", "rack", "radar", "radio", "rail", "rain", "raise", "rally", "ramp", + "ranch", "random", "range", "rapid", "rare", "rate", "rather", "raven", "raw", "razor", + "ready", "real", "reason", "rebel", "rebuild", "recall", "receive", "recipe", "record", "recycle", + "reduce", "reflect", "reform", "refuse", "region", "regret", "regular", "reject", "relax", "release", + "relief", "rely", "remain", "remember", "remind", "remove", "render", "renew", "rent", "reopen", + "repair", "repeat", "replace", "report", "require", "rescue", "resemble", "resist", "resource", "response", + "result", "retire", "retreat", "return", "reunion", "reveal", "review", "reward", "rhythm", "rib", + "ribbon", "rice", "rich", "ride", "ridge", "rifle", "right", "rigid", "ring", "riot", + "ripple", "risk", "ritual", "rival", "river", "road", "roast", "robot", "robust", "rocket", + "romance", "roof", "rookie", "room", "rose", "rotate", "rough", "round", "route", "royal", + "rubber", "rude", "rug", "rule", "run", "runway", "rural", "sad", "saddle", "sadness", + "safe", "sail", "salad", "salmon", "salon", "salt", "salute", "same", "sample", "sand", + "satisfy", "satoshi", "sauce", "sausage", "save", "say", "scale", "scan", "scare", "scatter", + "scene", "scheme", "school", "science", "scissors", "scorpion", "scout", "scrap", "screen", "script", + "scrub", "sea", "search", "season", "seat", "second", "secret", "section", "security", "seed", + "seek", "segment", "select", "sell", "seminar", "senior", "sense", "sentence", "series", "service", + "session", "settle", "setup", "seven", "shadow", "shaft", "shallow", "share", "shed", "shell", + "sheriff", "shield", "shift", "shine", "ship", "shiver", "shock", "shoe", "shoot", "shop", + "short", "shoulder", "shove", "shrimp", "shrug", "shuffle", "shy", "sibling", "sick", "side", + "siege", "sight", "sign", "silent", "silk", "silly", "silver", "similar", "simple", "since", + "sing", "siren", "sister", "situate", "six", "size", "skate", "sketch", "ski", "skill", + "skin", "skirt", "skull", "slab", "slam", "sleep", "slender", "slice", "slide", "slight", + "slim", "slogan", "slot", "slow", "slush", "small", "smart", "smile", "smoke", "smooth", + "snack", "snake", "snap", "sniff", "snow", "soap", "soccer", "social", "sock", "soda", + "soft", "solar", "soldier", "solid", "solution", "solve", "someone", "song", "soon", "sorry", + "sort", "soul", "sound", "soup", "source", "south", "space", "spare", "spatial", "spawn", + "speak", "special", "speed", "spell", "spend", "sphere", "spice", "spider", "spike", "spin", + "spirit", "split", "spoil", "sponsor", "spoon", "sport", "spot", "spray", "spread", "spring", + "spy", "square", "squeeze", "squirrel", "stable", "stadium", "staff", "stage", "stairs", "stamp", + "stand", "start", "state", "stay", "steak", "steel", "stem", "step", "stereo", "stick", + "still", "sting", "stock", "stomach", "stone", "stool", "story", "stove", "strategy", "street", + "strike", "strong", "struggle", "student", "stuff", "stumble", "style", "subject", "submit", "subway", + "success", "such", "sudden", "suffer", "sugar", "suggest", "suit", "summer", "sun", "sunny", + "sunset", "super", "supply", "supreme", "sure", "surface", "surge", "surprise", "surround", "survey", + "suspect", "sustain", "swallow", "swamp", "swap", "swarm", "swear", "sweet", "swift", "swim", + "swing", "switch", "sword", "symbol", "symptom", "syrup", "system", "table", "tackle", "tag", + "tail", "talent", "talk", "tank", "tape", "target", "task", "taste", "tattoo", "taxi", + "teach", "team", "tell", "ten", "tenant", "tennis", "tent", "term", "test", "text", + "thank", "that", "theme", "then", "theory", "there", "they", "thing", "this", "thought", + "three", "thrive", "throw", "thumb", "thunder", "ticket", "tide", "tiger", "tilt", "timber", + "time", "tiny", "tip", "tired", "tissue", "title", "toast", "tobacco", "today", "toddler", + "toe", "together", "toilet", "token", "tomato", "tomorrow", "tone", "tongue", "tonight", "tool", + "tooth", "top", "topic", "topple", "torch", "tornado", "tortoise", "toss", "total", "tourist", + "toward", "tower", "town", "toy", "track", "trade", "traffic", "tragic", "train", "transfer", + "trap", "trash", "travel", "tray", "treat", "tree", "trend", "trial", "tribe", "trick", + "trigger", "trim", "trip", "trophy", "trouble", "truck", "true", "truly", "trumpet", "trust", + "truth", "try", "tube", "tuition", "tumble", "tuna", "tunnel", "turkey", "turn", "turtle", + "twelve", "twenty", "twice", "twin", "twist", "two", "type", "typical", "ugly", "umbrella", + "unable", "unaware", "uncle", "uncover", "under", "undo", "unfair", "unfold", "unhappy", "uniform", + "unique", "unit", "universe", "unknown", "unlock", "until", "unusual", "unveil", "update", "upgrade", + "uphold", "upon", "upper", "upset", "urban", "urge", "usage", "use", "used", "useful", + "useless", "usual", "utility", "vacant", "vacuum", "vague", "valid", "valley", "valve", "van", + "vanish", "vapor", "various", "vast", "vault", "vehicle", "velvet", "vendor", "venture", "venue", + "verb", "verify", "version", "very", "vessel", "veteran", "viable", "vibrant", "vicious", "victory", + "video", "view", "village", "vintage", "violin", "virtual", "virus", "visa", "visit", "visual", + "vital", "vivid", "vocal", "voice", "void", "volcano", "volume", "vote", "voyage", "wage", + "wagon", "wait", "walk", "wall", "walnut", "want", "warfare", "warm", "warrior", "wash", + "wasp", "waste", "water", "wave", "way", "wealth", "weapon", "wear", "weasel", "weather", + "web", "wedding", "weekend", "weird", "welcome", "west", "wet", "whale", "what", "wheat", + "wheel", "when", "where", "whip", "whisper", "wide", "width", "wife", "wild", "will", + "win", "window", "wine", "wing", "wink", "winner", "winter", "wire", "wisdom", "wise", + "wish", "witness", "wolf", "woman", "wonder", "wood", "wool", "word", "work", "world", + "worry", "worth", "wrap", "wreck", "wrestle", "wrist", "write", "wrong", "yard", "year", + "yellow", "you", "young", "youth", "zebra", "zero", "zone", "zoo", +]; diff --git a/app/i18n/docs/en/Warnings/TrezorFirmwareUpdate.md b/app/i18n/docs/en/Warnings/TrezorFirmwareUpdate.md new file mode 100644 index 0000000000..f78fe840a8 --- /dev/null +++ b/app/i18n/docs/en/Warnings/TrezorFirmwareUpdate.md @@ -0,0 +1,8 @@ +**Warning!** Only use official firmware distributed by SatoshiLabs (Trezor +manufacturer) or other very trusted sources. Using a non standard firmware +or one obtained from an unreputable individual or company might result in **loss +or stealing of funds**. + +Also, ensure you have access to your seed, that it is valid and that it +corresponds to the funds in this wallet. Firmware updates might cause all data +in the device to be wiped, requiring a recover operation afterwards. 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/Warnings/TrezorWipe.md b/app/i18n/docs/en/Warnings/TrezorWipe.md new file mode 100644 index 0000000000..8b2766d4f8 --- /dev/null +++ b/app/i18n/docs/en/Warnings/TrezorWipe.md @@ -0,0 +1,8 @@ +**Warning!** Please ensure you have the seed for your trezor device stored in a +secure and accessible location, that it is valid, and that it is the correct key +for this wallet before performing a device wipe and recovery. + +Failure to do so will result in **loss of funds**. + +Please also note that Decrediton only supports recovering 24 word (256 bit) +seeds and that the words for trezor and dcrwallet imports are *different*. diff --git a/app/i18n/docs/en/index.js b/app/i18n/docs/en/index.js index bce414595d..a37f5255a4 100644 --- a/app/i18n/docs/en/index.js +++ b/app/i18n/docs/en/index.js @@ -10,6 +10,9 @@ export { default as ScriptNotRedeemableInfo } from "./InfoModals/ScriptNotRedeem 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 930277d257..5241259bf8 100644 --- a/app/index.js +++ b/app/index.js @@ -385,6 +385,24 @@ var initialState = { proposals: {}, // map from proposal token (id) to proposal details lastVettedFetchTime: new Date(0), // time when vetted proposals were requested }, + trezor: { + enabled: false, + debug: globalCfg.get("trezor_debug"), + deviceList: null, + getDeviceListAttempt: false, + transportError: false, + device: null, + performingOperation: false, + waitingForPin: false, + waitingForPassPhrase: false, + waitingForWord: false, + pinCallBack: null, + passPhraseCallBack: null, + pinMessage: null, + passPhraseMessage: null, + wordCallBack: null, + walletCreationMasterPubkeyAttempt: false, + }, locales: locales }; diff --git a/app/main.development.js b/app/main.development.js index 3ed2ffe727..d485465393 100644 --- a/app/main.development.js +++ b/app/main.development.js @@ -7,7 +7,7 @@ import { createLogger, lastLogLine, GetDcrdLogs, GetDcrwalletLogs } from "./main import { OPTIONS, USAGE_MESSAGE, VERSION_MESSAGE, BOTH_CONNECTION_ERR_MESSAGE, MAX_LOG_LENGTH } from "./main_dev/constants"; import { getWalletsDirectoryPath, getWalletsDirectoryPathNetwork, appDataDirectory } from "./main_dev/paths"; import { getGlobalCfgPath, checkAndInitWalletCfg } from "./main_dev/paths"; -import { installSessionHandlers, reloadAllowedExternalRequests, allowStakepoolRequests } from "./main_dev/externalRequests"; +import { installSessionHandlers, reloadAllowedExternalRequests, allowStakepoolRequests, allowExternalRequest } from "./main_dev/externalRequests"; import { setupProxy } from "./main_dev/proxy"; import { cleanShutdown, GetDcrdPID, GetDcrwPID } from "./main_dev/launch"; import { getAvailableWallets, startDaemon, createWallet, removeWallet, stopDaemon, stopWallet, startWallet, checkDaemon, deleteDaemon, setWatchingOnlyWallet, getWatchingOnlyWallet, getDaemonInfo } from "./main_dev/ipc"; @@ -114,11 +114,17 @@ ipcMain.on("reload-allowed-external-request", (event) => { reloadAllowedExternalRequests(); event.returnValue = true; }); + ipcMain.on("allow-stakepool-host", (event, host) => { allowStakepoolRequests(host); event.returnValue = true; }); +ipcMain.on("allow-external-request", (event, requestType) => { + allowExternalRequest(requestType); + event.returnValue = true; +}); + ipcMain.on("setup-proxy", () => { setupProxy(logger); }); diff --git a/app/main_dev/externalRequests.js b/app/main_dev/externalRequests.js index 6299efdf5f..3132970abf 100644 --- a/app/main_dev/externalRequests.js +++ b/app/main_dev/externalRequests.js @@ -16,6 +16,7 @@ export const EXTERNALREQUEST_STAKEPOOL_LISTING = "EXTERNALREQUEST_STAKEPOOL_LIST export const EXTERNALREQUEST_UPDATE_CHECK = "EXTERNALREQUEST_UPDATE_CHECK"; export const EXTERNALREQUEST_POLITEIA = "EXTERNALREQUEST_POLITEIA"; export const EXTERNALREQUEST_DCRDATA = "EXTERNALREQUEST_DCRDATA"; +export const EXTERNALREQUEST_TREZOR_BRIDGE = "EXTERNALREQUEST_TREZOR_BRIDGE"; // These are the requests allowed when the standard privacy mode is selected. export const STANDARD_EXTERNAL_REQUESTS = [ @@ -67,6 +68,11 @@ export const installSessionHandlers = (mainLogger) => { callback({ cancel: true, requestHeaders: details.requestHeaders }); } else { logger.log("verbose", details.method + " " + details.url); + if (allowedExternalRequests[EXTERNALREQUEST_TREZOR_BRIDGE] && /^http:\/\/127.0.0.1:21325\//.test(details.url)) { + // trezor bridge requires this as an origin to prevent unwanted access. + details.requestHeaders["Origin"] = "https://dummy-origin-to-fool-trezor-bridge.trezor.io"; + } + callback({ cancel: false, requestHeaders: details.requestHeaders }); } }); @@ -98,6 +104,17 @@ export const allowExternalRequest = (externalReqType) => { case EXTERNALREQUEST_DCRDATA: addAllowedURL(DCRDATA_URL_TESTNET); addAllowedURL(DCRDATA_URL_MAINNET); + break; + case EXTERNALREQUEST_TREZOR_BRIDGE: + addAllowedURL(/^http:\/\/127.0.0.1:21324\//); + addAllowedURL(/^http:\/\/127.0.0.1:21325\//); + + // TODO: decide whether we want to provide our own signed config + addAllowedURL(/^https:\/\/wallet.trezor.io\/data\/config_signed.bin\?[\d]+$/); + + // TODO: decide if we wanna block this + addAllowedURL(/^https:\/\/wallet.trezor.io\/data\/bridge\/latest.txt\?[\d]+$/); + break; default: logger.log("error", "Unknown external request type: " + externalReqType); diff --git a/app/main_dev/ipc.js b/app/main_dev/ipc.js index 167dbe261e..54d6876aa4 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/index.js b/app/reducers/index.js index 3c58ca0ebe..b124d85f9b 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -14,6 +14,7 @@ import sidebar from "./sidebar"; import snackbar from "./snackbar"; import statistics from "./statistics"; import governance from "./governance"; +import trezor from "./trezor"; const rootReducer = combineReducers({ grpc, @@ -30,6 +31,7 @@ const rootReducer = combineReducers({ snackbar, statistics, governance, + trezor, }); export default rootReducer; diff --git a/app/reducers/snackbar.js b/app/reducers/snackbar.js index 67c0776e0d..51ae356c14 100644 --- a/app/reducers/snackbar.js +++ b/app/reducers/snackbar.js @@ -45,6 +45,18 @@ import { GETWALLETSEEDSVC_FAILED, SPVSYNC_FAILED, } from "actions/WalletLoaderActions"; +import { + TRZ_TOGGLEPINPROTECTION_SUCCESS, TRZ_TOGGLEPINPROTECTION_FAILED, + TRZ_TOGGLEPASSPHRASEPROTECTION_SUCCESS, TRZ_TOGGLEPASSPHRASEPROTECTION_FAILED, + TRZ_CHANGEHOMESCREEN_SUCCESS, TRZ_CHANGEHOMESCREEN_FAILED, + TRZ_CHANGELABEL_SUCCESS, TRZ_CHANGELABEL_FAILED, + TRZ_WIPEDEVICE_SUCCESS, TRZ_WIPEDEVICE_FAILED, + 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 { GETACTIVEVOTE_FAILED, GETVETTED_FAILED, GETPROPOSAL_FAILED, @@ -225,6 +237,54 @@ const messages = defineMessages({ WRONG_PASSPHRASE_MSG: { id: "errors.wrongPassphrase", defaultMessage: "Wrong private passphrase entered. Please verify you have typed the correct private passphrase for the wallet." + }, + TRZ_TOGGLEPINPROTECTION_SUCCESS_ENABLED: { + id: "trezor.pinProtectionSuccess.enabled", + defaultMessage: "Pin protection has been enabled in trezor '{label}'" + }, + TRZ_TOGGLEPINPROTECTION_SUCCESS_DISABLED: { + id: "trezor.pinProtectionSuccess.disabled", + defaultMessage: "Pin protection has been disabled in trezor '{label}'" + }, + TRZ_TOGGLEPASSPHRASEPROTECTION_SUCCESS_ENABLED: { + id: "trezor.passphraseProtectionSuccess.enabled", + defaultMessage: "Passphrase protection has been enabled in trezor '{label}'" + }, + TRZ_TOGGLEPASSPHRASEPROTECTION_SUCCESS_DISABLED: { + id: "trezor.passphraseProtectionSuccess.disabled", + defaultMessage: "Passphrase protection has been disabled in trezor '{label}'" + }, + TRZ_CHANGEHOMESCREEN_SUCCESS: { + id: "trezor.changeHomeScreen.success", + defaultMessage: "Trezor home screen successfully changed" + }, + TRZ_CHANGELABEL_SUCCESS: { + id: "trezor.changeLabel.success", + defaultMessage: "Changed label on selected trezor to '{label}'" + }, + TRZ_WIPEDEVICE_SUCCESS: { + id: "trezor.wipeDevice.success", + defaultMessage: "Trezor device wiped" + }, + TRZ_RECOVERDEVICE_SUCCESS: { + id: "trezor.recoverDevice.success", + defaultMessage: "Trezor device recovered" + }, + TRZ_INITDEVICE_SUCCESS: { + id: "trezor.initDevice.success", + defaultMessage: "Trezor device initialized with new seed" + }, + 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}" } }); @@ -280,6 +340,12 @@ export default function snackbar(state = {}, action) { case PURCHASETICKETS_SUCCESS: case ADDCUSTOMSTAKEPOOL_SUCCESS: case PUBLISHTX_SUCCESS: + case TRZ_CHANGEHOMESCREEN_SUCCESS: + case TRZ_WIPEDEVICE_SUCCESS: + case TRZ_RECOVERDEVICE_SUCCESS: + case TRZ_INITDEVICE_SUCCESS: + case TRZ_UPDATEFIRMWARE_SUCCESS: + case TRZ_TOGGLEPASSPHRASEPROTECTION_SUCCESS: type = "Success"; message = messages[action.type] || messages.defaultSuccessMessage; @@ -294,6 +360,11 @@ export default function snackbar(state = {}, action) { case PUBLISHTX_SUCCESS: values = { hash: action.hash }; break; + case TRZ_TOGGLEPINPROTECTION_SUCCESS: + case TRZ_TOGGLEPASSPHRASEPROTECTION_SUCCESS: + case TRZ_CHANGELABEL_SUCCESS: + values = { label: action.deviceLabel }; + break; } break; @@ -330,6 +401,16 @@ export default function snackbar(state = {}, action) { case UPDATEVOTECHOICE_FAILED: case GETACCOUNTEXTENDEDKEY_FAILED: case STARTTICKETBUYERV2_FAILED: + case TRZ_TOGGLEPINPROTECTION_FAILED: + case TRZ_TOGGLEPASSPHRASEPROTECTION_FAILED: + case TRZ_CHANGEHOMESCREEN_FAILED: + case TRZ_CHANGELABEL_FAILED: + case TRZ_WIPEDEVICE_FAILED: + case TRZ_RECOVERDEVICE_FAILED: + case TRZ_INITDEVICE_FAILED: + case TRZ_UPDATEFIRMWARE_FAILED: + case TRZ_NOCONNECTEDDEVICE: + case TRZ_GETWALLETCREATIONMASTERPUBKEY_FAILED: if (action.error && String(action.error).indexOf("wallet.Unlock: invalid passphrase:: secretkey.DeriveKey") > -1) { // intercepting all wrong passphrase errors, independently of which error // state was triggered. Not terribly pretty. @@ -355,6 +436,12 @@ export default function snackbar(state = {}, action) { } break; + + case TRZ_TOGGLEPINPROTECTION_SUCCESS: + type = "Success"; + message = messages["TRZ_TOGGLEPINPROTECTION_SUCCESS_" + (action.clearProtection ? "DISABLED" : "ENABLED")]; + values = { label: action.deviceLabel }; + break; } if (!message || !type) { diff --git a/app/reducers/trezor.js b/app/reducers/trezor.js new file mode 100644 index 0000000000..07dd3b566f --- /dev/null +++ b/app/reducers/trezor.js @@ -0,0 +1,164 @@ +import { + TRZ_TREZOR_ENABLED, + TRZ_LOADDEVICELIST_ATTEMPT, TRZ_LOADDEVICELIST_FAILED, TRZ_LOADDEVICELIST_SUCCESS, + TRZ_DEVICELISTTRANSPORT_LOST, + TRZ_SELECTEDDEVICE_CHANGED, + TRZ_PIN_REQUESTED, TRZ_PIN_ENTERED, TRZ_PIN_CANCELED, + TRZ_PASSPHRASE_REQUESTED, TRZ_PASSPHRASE_ENTERED, TRZ_PASSPHRASE_CANCELED, + TRZ_WORD_REQUESTED, TRZ_WORD_ENTERED, TRZ_WORD_CANCELED, + TRZ_CANCELOPERATION_SUCCESS, + TRZ_TOGGLEPINPROTECTION_ATTEMPT, TRZ_TOGGLEPINPROTECTION_FAILED, TRZ_TOGGLEPINPROTECTION_SUCCESS, + TRZ_TOGGLEPASSPHRASEPROTECTION_ATTEMPT, TRZ_TOGGLEPASSPHRASEPROTECTION_FAILED, TRZ_TOGGLEPASSPHRASEPROTECTION_SUCCESS, + TRZ_CHANGEHOMESCREEN_ATTEMPT, TRZ_CHANGEHOMESCREEN_FAILED, TRZ_CHANGEHOMESCREEN_SUCCESS, + TRZ_CHANGELABEL_ATTEMPT, TRZ_CHANGELABEL_FAILED, TRZ_CHANGELABEL_SUCCESS, + TRZ_WIPEDEVICE_ATTEMPT, TRZ_WIPEDEVICE_FAILED, TRZ_WIPEDEVICE_SUCCESS, + 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, + TRZ_CLEAR_DEVICELIST +} from "actions/TrezorActions"; +import { + SIGNTX_ATTEMPT, SIGNTX_FAILED, SIGNTX_SUCCESS +} from "actions/ControlActions"; + +export default function trezor(state = {}, action) { + switch (action.type) { + case TRZ_TREZOR_ENABLED: + return { ...state, + enabled: true, + }; + case TRZ_CLEAR_DEVICELIST: + return { ...state, + deviceList: null, + transportError: false, + device: null, + getDeviceListAttempt: 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, + deviceList: null, + transportError: action.error, + device: null, + performingOperation: false, + }; + case TRZ_SELECTEDDEVICE_CHANGED: + return { ...state, + device: action.device, + }; + case TRZ_PIN_REQUESTED: + return { ...state, + waitingForPin: true, + pinCallBack: action.pinCallBack, + pinMessage: action.pinMessage, + performingOperation: true, + }; + case TRZ_PIN_CANCELED: + case TRZ_PIN_ENTERED: + return { ...state, + waitingForPin: false, + pinCallBack: null, + pinMessage: null, + performingOperation: false, + }; + case TRZ_PASSPHRASE_REQUESTED: + return { ...state, + waitingForPassPhrase: true, + passPhraseCallBack: action.passPhraseCallBack, + performingOperation: true, + }; + case TRZ_PASSPHRASE_CANCELED: + case TRZ_PASSPHRASE_ENTERED: + return { ...state, + waitingForPassPhrase: false, + passPhraseCallBack: null, + performingOperation: false, + }; + case TRZ_WORD_REQUESTED: + return { ...state, + waitingForWord: true, + wordCallBack: action.wordCallBack, + performingOperation: true, + }; + case TRZ_WORD_CANCELED: + case TRZ_WORD_ENTERED: + return { ...state, + waitingForWord: false, + wordCallBack: null, + performingOperation: false, + }; + case TRZ_CANCELOPERATION_SUCCESS: + return { ...state, + waitingForPin: false, + pinCallBack: null, + pinMessage: null, + wordCallBack: null, + waitingForPassPhrase: false, + passPhraseCallBack: null, + 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: + case TRZ_CHANGEHOMESCREEN_ATTEMPT: + case TRZ_CHANGELABEL_ATTEMPT: + case TRZ_WIPEDEVICE_ATTEMPT: + case TRZ_RECOVERDEVICE_ATTEMPT: + case TRZ_INITDEVICE_ATTEMPT: + case TRZ_UPDATEFIRMWARE_ATTEMPT: + return { ...state, + performingOperation: true, + }; + case SIGNTX_FAILED: + case SIGNTX_SUCCESS: + case TRZ_TOGGLEPINPROTECTION_FAILED: + case TRZ_TOGGLEPINPROTECTION_SUCCESS: + case TRZ_TOGGLEPASSPHRASEPROTECTION_FAILED: + case TRZ_TOGGLEPASSPHRASEPROTECTION_SUCCESS: + case TRZ_CHANGEHOMESCREEN_FAILED: + case TRZ_CHANGEHOMESCREEN_SUCCESS: + case TRZ_CHANGELABEL_FAILED: + case TRZ_CHANGELABEL_SUCCESS: + case TRZ_WIPEDEVICE_FAILED: + case TRZ_WIPEDEVICE_SUCCESS: + case TRZ_RECOVERDEVICE_FAILED: + case TRZ_RECOVERDEVICE_SUCCESS: + case TRZ_INITDEVICE_FAILED: + case TRZ_INITDEVICE_SUCCESS: + case TRZ_UPDATEFIRMWARE_FAILED: + case TRZ_UPDATEFIRMWARE_SUCCESS: + return { ...state, + performingOperation: false, + }; + default: + return state; + } +} diff --git a/app/selectors.js b/app/selectors.js index c1ebe6fb1a..943c8dc318 100644 --- a/app/selectors.js +++ b/app/selectors.js @@ -613,7 +613,7 @@ export const defaultSpendingAccount = createSelector( export const changePassphraseRequestAttempt = get([ "control", "changePassphraseRequestAttempt" ]); export const constructTxLowBalance = get([ "control", "constructTxLowBalance" ]); -const constructTxResponse = get([ "control", "constructTxResponse" ]); +export const constructTxResponse = get([ "control", "constructTxResponse" ]); const constructTxRequestAttempt = get([ "control", "constructTxRequestAttempt" ]); const signTransactionRequestAttempt = get([ "control", "signTransactionRequestAttempt" ]); export const signTransactionError = get([ "control", "signTransactionError" ]); @@ -918,10 +918,12 @@ export const stakeRewardsStats = createSelector( export const modalVisible = get([ "control", "modalVisible" ]); export const aboutModalMacOSVisible = get([ "control", "aboutModalMacOSVisible" ]); -export const isSignMessageDisabled = isWatchingOnly; +export const isTrezor = get([ "trezor", "enabled" ]); + +export const isSignMessageDisabled = and(isWatchingOnly, not(isTrezor)); export const isCreateAccountDisabled = isWatchingOnly; export const isChangePassPhraseDisabled = isWatchingOnly; -export const isTransactionsSendTabDisabled = isWatchingOnly; +export const isTransactionsSendTabDisabled = and(isWatchingOnly, not(isTrezor)); export const isTicketPurchaseTabDisabled = isWatchingOnly; export const politeiaURL = createSelector( @@ -974,3 +976,11 @@ export const initialProposalLoading = createSelector( [ proposalsDetails, getVettedProposalsAttempt ], ( proposals, getVettedAttempt ) => (Object.keys(proposals).length === 0) && getVettedAttempt ); + +export const trezorWaitingForPin = get([ "trezor", "waitingForPin" ]); +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 b7f346cdb2..9b72095c0e 100644 --- a/app/style/GetStarted.less +++ b/app/style/GetStarted.less @@ -802,3 +802,7 @@ display: none; } } + +.getstarted-trezor-config-sections { + margin-top: 14em; +} diff --git a/app/style/Header.less b/app/style/Header.less index 32d1d7fecb..e3cb132ff4 100644 --- a/app/style/Header.less +++ b/app/style/Header.less @@ -70,6 +70,7 @@ &.tickets { background-image: @header-tickets; } &.transactions { background-image: @header-transactions; } &.governance { background-image: @header-governance; } + &.trezor { background-image: @menu-trezor-active; } &.tx-detail-icon-ticket { background-image: @ticket-live-icon; } &.tx-detail-icon-vote { background-image: @ticket-voted-icon; } diff --git a/app/style/Icons.less b/app/style/Icons.less index e96bb5cdfd..8b82f7ec67 100644 --- a/app/style/Icons.less +++ b/app/style/Icons.less @@ -132,6 +132,9 @@ @menu-settings-default: url('@{icon-root}/settings-default.png'); @menu-transactions-active: url('@{icon-root}/transactions-active.png'); @menu-transactions-default: url('@{icon-root}/transactions-default.png'); +@menu-trezor-active: url('@{icon-root}/trezor-active.png'); +@menu-trezor-default: url('@{icon-root}/trezor-default.png'); +@menu-trezor-hover: url('@{icon-root}/trezor-hover.png'); @menu-tickets-active: url('@{icon-root}/tickets-active.png'); @menu-tickets-default: url('@{icon-root}/tickets-default.png'); @menu-logo: url('@{icon-root}/menu-logo.svg'); diff --git a/app/style/MiscComponents.less b/app/style/MiscComponents.less index 0c445f7c48..ba77dcf246 100644 --- a/app/style/MiscComponents.less +++ b/app/style/MiscComponents.less @@ -368,6 +368,18 @@ background-image: @menu-animation-governance; } +.menu-link-active.menu-link.trezorIcon { + background-image: @menu-trezor-active; +} + +.menu-link.trezorIcon:not(.menu-link-active):hover { + background-image: @menu-trezor-hover; +} + +.menu-link.trezorIcon { + background-image: @menu-trezor-default; +} + .menu-caret { position: absolute; height: 52px; diff --git a/app/style/Trezor.less b/app/style/Trezor.less new file mode 100644 index 0000000000..d2c2161516 --- /dev/null +++ b/app/style/Trezor.less @@ -0,0 +1,66 @@ +@import (reference) "./main.less"; + +.trezor-pin-modal { + h1 { + margin: 0; + } + + .pin-pad { + display: grid; + grid-template-columns: 2em 2em 2em; + text-align: center; + font-size: 150%; + + .pin-pad-button { + padding: 0.5em 0em; + border: 1px solid transparent; + cursor: pointer; + + &:hover { + border: 1px solid #6397ff; + } + } + } +} + +.trezor-word-modal { + h1 { + margin: 0; + } +} + +.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; +} + +.trezor-config-regular-buttons .button { + margin-right: 1em; + margin-top: 1em; +} + +.trezor-config-accordion { + border-bottom: 1px solid #dadfe2; + border-radius: 4px; + padding: 1.5em 1em 1em; + background-color: #fff; + + .vertical-accordion-header { + font-size: 22px; + margin-bottom: 0.5em; + } + + .input-and-unit { + margin-bottom: 0.5em; + } +} + +.trezor-word-select { + min-height: 10em; +} diff --git a/app/style/icons/trezor-active.png b/app/style/icons/trezor-active.png new file mode 100644 index 0000000000..0cf0512a80 Binary files /dev/null and b/app/style/icons/trezor-active.png differ diff --git a/app/style/icons/trezor-default.png b/app/style/icons/trezor-default.png new file mode 100644 index 0000000000..6018422142 Binary files /dev/null and b/app/style/icons/trezor-default.png differ diff --git a/app/style/icons/trezor-hover.png b/app/style/icons/trezor-hover.png new file mode 100644 index 0000000000..40f98a9d66 Binary files /dev/null and b/app/style/icons/trezor-hover.png differ diff --git a/app/wallet/constants.js b/app/wallet/constants.js index 8019d34aea..c06167c8ae 100644 --- a/app/wallet/constants.js +++ b/app/wallet/constants.js @@ -7,15 +7,15 @@ export const TestNetParams = { SStxChangeMaturity: 1, GenesisTimestamp: 1489550400, TargetTimePerBlock: 2 * 60, // in seconds + WorkDiffWindowSize: 144, // no way to know which one the wallet is using right now, so we record both // types for the moment. LegacyHDCoinType: 11, HDCoinType: 1, - WorkDiffWindowSize: 144, - TreasuryAddress: "TcrypGAcGCRVXrES7hWqVZb5oLJKCZEtoL1", + trezorCoinName: "Decred Testnet", }; export const MainNetParams = { @@ -25,13 +25,13 @@ export const MainNetParams = { SStxChangeMaturity: 1, GenesisTimestamp: 1454954400, TargetTimePerBlock: 5 * 60, // in seconds + WorkDiffWindowSize: 144, // no way to know which one the wallet is using right now, so we record both // types for the moment. LegacyHDCoinType: 20, HDCoinType: 42, - WorkDiffWindowSize: 144, - TreasuryAddress: "Dcur2mcGjmENx4DhNqDctW5wJCVyT3Qeqkx", + trezorCoinName: "Decred", }; diff --git a/app/wallet/daemon.js b/app/wallet/daemon.js index 57092ff787..616c300b5f 100644 --- a/app/wallet/daemon.js +++ b/app/wallet/daemon.js @@ -137,6 +137,10 @@ export const reloadAllowedExternalRequests = log(() => Promise .resolve(ipcRenderer.sendSync("reload-allowed-external-request")) , "Reload allowed external request"); +export const allowExternalRequest = log(requestType => Promise + .resolve(ipcRenderer.sendSync("allow-external-request", requestType)) +, "Allow External Request"); + export const allowStakePoolHost = log(host => Promise .resolve(ipcRenderer.sendSync("allow-stakepool-host", host)) , "Allow StakePool Host"); diff --git a/package.json b/package.json index e2ddbb2ce0..e71a38e191 100644 --- a/package.json +++ b/package.json @@ -234,6 +234,7 @@ "string-argv": "0.1.1", "stylelint": "^9.7.0", "timezone-mock": "^1.0.2", + "trezor.js": "^6.17.6", "winston": "^2.3.1" }, "devEngines": { diff --git a/yarn.lock b/yarn.lock index 5838d41b5c..1a0349d93b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1038,11 +1038,21 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.11.0.tgz#ddd0d67a3b6c3810dd1a59e36675fa82de5e19ae" integrity sha512-R4Dvw6KjSYn/SpvjRchBOwXr14vVVcFXCtnM3f0aLvlJS8a599rrcEoihcP2/+Z/f75E5GNPd4aWM7j1yei9og== +"@types/node@^10.11.7": + version "10.12.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.10.tgz#4fa76e6598b7de3f0cb6ec3abacc4f59e5b3a2ce" + integrity sha512-8xZEYckCbUVgK8Eg7lf5Iy4COKJ5uXlnIOnePN0WUwSQggy9tolM+tDJf7wMOnT/JT/W9xDYIaYggt3mRV2O5w== + "@types/node@^8.0.24": version "8.10.30" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.30.tgz#2c82cbed5f79d72280c131d2acffa88fbd8dd353" integrity sha512-Le8HGMI5gjFSBqcCuKP/wfHC19oURzkU2D+ERIescUoJd+CmNEMYBib9LQ4zj1HHEZOJQWhw2ZTnbD8weASh/Q== +"@types/semver@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" + integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ== + "@webassemblyjs/ast@1.7.10": version "1.7.10" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.10.tgz#0cfc61d61286240b72fc522cb755613699eea40a" @@ -1600,6 +1610,14 @@ asap@~2.0.3: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= +ascli@~0.3: + version "0.3.0" + resolved "https://registry.yarnpkg.com/ascli/-/ascli-0.3.0.tgz#5e66230e5219fe3e8952a4efb4f20fae596a813a" + integrity sha1-XmYjDlIZ/j6JUqTvtPIPrllqgTo= + dependencies: + colour latest + optjs latest + asn1.js@^4.0.0: version "4.10.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" @@ -1971,6 +1989,13 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base-x@^3.0.2: + version "3.0.5" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.5.tgz#d3ada59afed05b921ab581ec3112e6444ba0795a" + integrity sha512-C3picSgzPSLE+jW3tcBzJoGwitOtazb5B+5YmAxZm2ybmTi9LNgAtDO/jjVEBZwHoXmDBZ9m/IELj3elJVRBcA== + dependencies: + safe-buffer "^5.0.1" + base64-js@^1.0.2, base64-js@^1.2.3: version "1.3.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" @@ -1989,6 +2014,14 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +bchaddrjs@^0.2.1: + version "0.2.3" + resolved "https://registry.yarnpkg.com/bchaddrjs/-/bchaddrjs-0.2.3.tgz#c3008ac85d7c7e583e83a30604c427b5bed26c68" + integrity sha512-0DVW8q3UFQFhrvt8Fowpkk+WvkYTZTSD1vGCQHrtMHZjRL6G/SoW0mgrREmgO1F/8TJ+Julri4UBWA8Gr7C5Yw== + dependencies: + bs58check "^2.1.2" + cashaddrjs "^0.2.9" + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -1996,16 +2029,106 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +bech32@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.3.tgz#bd47a8986bbb3eec34a56a097a84b8d3e9a2dfcd" + integrity sha512-yuVFUvrNcoJi0sv5phmqc6P+Fl1HjRDRNOOkHY2X/3LBy2bIGNSFx4fZ95HMaXHupuS7cZR15AsvtmCIF4UEyg== + +big-integer@^1.3.19, big-integer@^1.6.34: + version "1.6.36" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.36.tgz#78631076265d4ae3555c04f85e7d9d2f3a071a36" + integrity sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg== + big.js@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== +bigi@0.2.0, bigi@0.2.x: + version "0.2.0" + resolved "https://registry.yarnpkg.com/bigi/-/bigi-0.2.0.tgz#8bee26348b99c4ae2ed20481fb12384c32792f74" + integrity sha1-i+4mNIuZxK4u0gSB+xI4TDJ5L3Q= + +bigi@^1.1.0, bigi@^1.2.1, bigi@^1.4.0, bigi@^1.4.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/bigi/-/bigi-1.4.2.tgz#9c665a95f88b8b08fc05cfd731f561859d725825" + integrity sha1-nGZalfiLiwj8Bc/XMfVhhZ1yWCU= + binary-extensions@^1.0.0: version "1.12.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg== +binstring@0.2.x, binstring@~0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/binstring/-/binstring-0.2.1.tgz#8a174d301f6d54efda550dd98bb4cb524eacd75d" + integrity sha1-ihdNMB9tVO/aVQ3Zi7TLUk6s110= + +bip66@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22" + integrity sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI= + dependencies: + safe-buffer "^5.0.1" + +bitcoin-ops@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz#e45de620398e22fd4ca6023de43974ff42240278" + integrity sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow== + +bitcoin-script@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/bitcoin-script/-/bitcoin-script-0.1.1.tgz#52c504dddc1e3b1317a7b6567a88981b3ef3929c" + integrity sha1-UsUE3dweOxMXp7ZWeoiYGz7zkpw= + dependencies: + big-integer "^1.3.19" + bigi "^1.2.1" + coinkey "^0.1.0" + ecdsa "^0.6.0" + js-beautify "^1.5.4" + ripemd160 "^0.2.0" + secure-random "^1.1.1" + sha1 "^1.1.0" + sha256 "^0.1.1" + +bitcoinjs-lib-zcash@^3.5.2: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bitcoinjs-lib-zcash/-/bitcoinjs-lib-zcash-3.5.3.tgz#19c74f36a2f9b0bd6680a02087567db8454639e5" + integrity sha512-F2QfhMV/M+8ffWlm01LTQFn6r9NxhqsuQDWC/BNrsbOZMZMrVQvBI+AjH5u+qkSOBl3DubVSXluIZVSKcELA6A== + dependencies: + bech32 "^1.1.2" + bigi "^1.4.0" + bip66 "^1.1.0" + bitcoin-ops "^1.3.0" + bitcoin-script "^0.1.1" + blake2b "^2.1.2" + bs58check "^2.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.3" + ecurve "^1.0.0" + merkle-lib "^2.0.10" + pushdata-bitcoin "^1.0.1" + randombytes "^2.0.1" + safe-buffer "^5.0.1" + typeforce "^1.11.3" + varuint-bitcoin "^1.0.4" + wif "^2.0.1" + +blake2b-wasm@^1.1.0: + version "1.1.7" + resolved "https://registry.yarnpkg.com/blake2b-wasm/-/blake2b-wasm-1.1.7.tgz#e4d075da10068e5d4c3ec1fb9accc4d186c55d81" + integrity sha512-oFIHvXhlz/DUgF0kq5B1CqxIDjIJwh9iDeUUGQUcvgiGz7Wdw03McEO7CfLBy7QKGdsydcMCgO9jFNBAFCtFcA== + dependencies: + nanoassert "^1.0.0" + +blake2b@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/blake2b/-/blake2b-2.1.3.tgz#f5388be424768e7c6327025dad0c3c6d83351bca" + integrity sha512-pkDss4xFVbMb4270aCyGD3qLv92314Et+FsKzilCLxDz5DuZ2/1g3w4nmBbu6nKApPspnjG7JcwTjGZnduB1yg== + dependencies: + blake2b-wasm "^1.1.0" + nanoassert "^1.0.0" + block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" @@ -2189,6 +2312,30 @@ browserslist@^4.1.0: electron-to-chromium "^1.3.62" node-releases "^1.0.0-alpha.11" +bs58@0.3.x: + version "0.3.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-0.3.0.tgz#cb48107bf446727d3e17b21102da73ca89109588" + integrity sha1-y0gQe/RGcn0+F7IRAtpzyokQlYg= + dependencies: + bigi "0.2.0" + binstring "~0.2.0" + +bs58@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= + dependencies: + base-x "^3.0.2" + +bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" + integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== + dependencies: + bs58 "^4.0.0" + create-hash "^1.1.0" + safe-buffer "^5.1.2" + bser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" @@ -2233,6 +2380,11 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +bufferview@~1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bufferview/-/bufferview-1.0.1.tgz#7afd74a45f937fa422a1d338c08bbfdc76cd725d" + integrity sha1-ev10pF+Tf6QiodM4wIu/3HbNcl0= + builder-util-runtime@6.1.0, builder-util-runtime@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-6.1.0.tgz#85f0c7bddbe4950ad708a1455b6ba79e16f2b731" @@ -2273,6 +2425,14 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= +bytebuffer-old-fixed-webpack@3.5.6: + version "3.5.6" + resolved "https://registry.yarnpkg.com/bytebuffer-old-fixed-webpack/-/bytebuffer-old-fixed-webpack-3.5.6.tgz#5adc419c6a9b4692f217206703ec7431c759aa3f" + integrity sha1-WtxBnGqbRpLyFyBnA+x0McdZqj8= + dependencies: + bufferview "~1" + long "~2 >=2.2.3" + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -2391,6 +2551,13 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +cashaddrjs@^0.2.9: + version "0.2.9" + resolved "https://registry.yarnpkg.com/cashaddrjs/-/cashaddrjs-0.2.9.tgz#e38e323e11e0ab761767006b3938a36278dadec9" + integrity sha512-DhJF4iAH0/RM3UjHDHKRxzs09YGL9px+oTyizzydanhC7jTyM2aJ+aLKA96vZGTTWayvvr2iDF2l13lpqXiRFg== + dependencies: + big-integer "^1.6.34" + ccount@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.3.tgz#f1cec43f332e2ea5a569fd46f9f5bde4e6102aff" @@ -2450,6 +2617,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +"charenc@>= 0.0.1": + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + cheerio@^1.0.0-rc.2: version "1.0.0-rc.2" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" @@ -2593,6 +2765,23 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +coinkey@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/coinkey/-/coinkey-0.1.0.tgz#bdf2a953dcfe4fd70fdba3000c787ff369d8294c" + integrity sha1-vfKpU9z+T9cP26MADHh/82nYKUw= + dependencies: + coinstring "~0.2.0" + eckey "~0.4.0" + secure-random "~0.2.0" + +coinstring@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/coinstring/-/coinstring-0.2.0.tgz#fa2820497bb9e35b7cfa116f048219ca6f3f348f" + integrity sha1-+iggSXu541t8+hFvBIIZym8/NI8= + dependencies: + bs58 "0.3.x" + crypto-hashing "~0.3.0" + collapse-white-space@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.4.tgz#ce05cf49e54c3277ae573036a26851ba430a0091" @@ -2638,6 +2827,11 @@ colors@1.0.x: resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= +colour@latest: + version "0.7.1" + resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778" + integrity sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g= + combined-stream@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" @@ -2662,6 +2856,11 @@ commander@^2.15.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" integrity sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ== +commander@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" + integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -2712,6 +2911,14 @@ concurrently@^4.0.1: tree-kill "^1.1.0" yargs "^12.0.1" +config-chain@~1.1.5: + version "1.1.12" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" + integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + configstore@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" @@ -2751,6 +2958,11 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +convert-hex@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/convert-hex/-/convert-hex-0.1.0.tgz#08c04568922c27776b8a2e81a95d393362ea0b65" + integrity sha1-CMBFaJIsJ3drii6BqV05M2LqC2U= + convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1: version "1.6.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" @@ -2758,6 +2970,11 @@ convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1: dependencies: safe-buffer "~5.1.1" +convert-string@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/convert-string/-/convert-string-0.1.0.tgz#79ce41a9bb0d03bcf72cdc6a8f3c56fbbc64410a" + integrity sha1-ec5BqbsNA7z3LNxqjzxW+7xkQQo= + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -2840,7 +3057,7 @@ create-hash@^1.1.0, create-hash@^1.1.2: ripemd160 "^2.0.1" sha.js "^2.4.0" -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.3, create-hmac@^1.1.4: version "1.1.7" resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== @@ -2894,6 +3111,11 @@ cross-unzip@0.0.2: resolved "https://registry.yarnpkg.com/cross-unzip/-/cross-unzip-0.0.2.tgz#5183bc47a09559befcf98cc4657964999359372f" integrity sha1-UYO8R6CVWb78+YzEZXlkmZNZNy8= +"crypt@>= 0.0.1": + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -2918,6 +3140,14 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-hashing@~0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/crypto-hashing/-/crypto-hashing-0.3.1.tgz#0195548db8bdef50aa9d526514cc546e1e62fbce" + integrity sha1-AZVUjbi971CqnVJlFMxUbh5i+84= + dependencies: + binstring "0.2.x" + ripemd160 "~0.2.0" + crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" @@ -3436,6 +3666,58 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/ecdsa/-/ecdsa-0.6.0.tgz#35e9887b6f418ec7b98380170334dc2763a6b317" + integrity sha1-NemIe29Bjse5g4AXAzTcJ2Omsxc= + dependencies: + bigi "^1.2.1" + ecurve "^1.0.0" + +eckey@~0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/eckey/-/eckey-0.4.2.tgz#cea53b7d529e42168f2c8597a7e8d32bc9e39436" + integrity sha1-zqU7fVKeQhaPLIWXp+jTK8njlDY= + dependencies: + bigi "0.2.x" + ecurve "~0.3.0" + ecurve-names "~0.3.0" + +ecurve-names@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/ecurve-names/-/ecurve-names-0.3.0.tgz#f9525e403f44a35f7bc17557ff7e41091931d59c" + integrity sha1-+VJeQD9Eo197wXVX/35BCRkx1Zw= + dependencies: + bigi "0.2.x" + ecurve "~0.3.0" + +ecurve@^1.0.0, ecurve@^1.0.2, ecurve@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/ecurve/-/ecurve-1.0.6.tgz#dfdabbb7149f8d8b78816be5a7d5b83fcf6de797" + integrity sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w== + dependencies: + bigi "^1.1.0" + safe-buffer "^5.0.1" + +ecurve@~0.3.0: + version "0.3.2" + resolved "https://registry.yarnpkg.com/ecurve/-/ecurve-0.3.2.tgz#badeff9ef95399eea2e17d1b533f010484240b50" + integrity sha1-ut7/nvlTme6i4X0bUz8BBIQkC1A= + dependencies: + bigi "0.2.x" + +editorconfig@^0.15.0: + version "0.15.2" + resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.2.tgz#047be983abb9ab3c2eefe5199cb2b7c5689f0702" + integrity sha512-GWjSI19PVJAM9IZRGOS+YKI8LN+/sjkSjNyvxL5ucqP9/IqtYNXBaQ/6c/hkPNYQHyOHra2KoXZI/JVpuqwmcQ== + dependencies: + "@types/node" "^10.11.7" + "@types/semver" "^5.5.0" + commander "^2.19.0" + lru-cache "^4.1.3" + semver "^5.6.0" + sigmund "^1.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -5982,6 +6264,16 @@ jest@^23.6.0: import-local "^1.0.0" jest-cli "^23.6.0" +js-beautify@^1.5.4: + version "1.8.8" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.8.tgz#1eb175b73a3571a5f1ed8d98e7cf2b05bfa98471" + integrity sha512-qVNq7ZZ7ZbLdzorvSlRDadS0Rh5oyItaE95v6I4wbbuSiijxn7SnnsV6dvKlcXuO2jX7lK8tn9fBulx34K/Ejg== + dependencies: + config-chain "~1.1.5" + editorconfig "^0.15.0" + mkdirp "~0.5.0" + nopt "~4.0.1" + js-levenshtein@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.3.tgz#3ef627df48ec8cf24bacf05c0f184ff30ef413c5" @@ -6366,6 +6658,11 @@ lolex@^2.3.2, lolex@^2.7.4: resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.5.tgz#113001d56bfc7e02d56e36291cc5c413d1aa0733" integrity sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q== +"long@~2 >=2.2.3": + version "2.4.0" + resolved "https://registry.yarnpkg.com/long/-/long-2.4.0.tgz#9fa180bb1d9500cdc29c4156766a1995e1f4524f" + integrity sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8= + longest-streak@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e" @@ -6404,6 +6701,14 @@ lru-cache@^4.0.1, lru-cache@^4.1.1: pseudomap "^1.0.2" yallist "^2.1.2" +lru-cache@^4.1.3: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + make-dir@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -6576,6 +6881,11 @@ merge@^1.2.0: resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" integrity sha1-dTHjnUlJwoGma4xabgJl6LBYlNo= +merkle-lib@^2.0.10: + version "2.0.10" + resolved "https://registry.yarnpkg.com/merkle-lib/-/merkle-lib-2.0.10.tgz#82b8dbae75e27a7785388b73f9d7725d0f6f3326" + integrity sha1-grjbrnXieneFOItz+ddyXQ9vMyY= + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -6805,6 +7115,11 @@ nan@^2.9.2: resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099" integrity sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw== +nanoassert@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/nanoassert/-/nanoassert-1.1.0.tgz#4f3152e09540fde28c76f44b19bbcd1d5a42478d" + integrity sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40= + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -6891,7 +7206,7 @@ node-addon-loader@decred/node-addon-loader#master: dependencies: loader-utils "^1.1.0" -node-fetch@^1.0.1: +node-fetch@^1.0.1, node-fetch@^1.6.0: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== @@ -7034,7 +7349,7 @@ nomnom@~1.6.2: dependencies: abbrev "1" -nopt@^4.0.1: +nopt@^4.0.1, nopt@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= @@ -7233,7 +7548,7 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.0.4: +object.values@^1.0.3, object.values@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" integrity sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo= @@ -7284,6 +7599,11 @@ optionator@^0.8.1, optionator@^0.8.2: type-check "~0.3.2" wordwrap "~1.0.0" +optjs@latest: + version "3.2.2" + resolved "https://registry.yarnpkg.com/optjs/-/optjs-3.2.2.tgz#69a6ce89c442a44403141ad2f9b370bd5bb6f4ee" + integrity sha1-aabOicRCpEQDFBrS+bNwvVu29O4= + os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" @@ -7935,6 +8255,19 @@ prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, loose-envify "^1.3.1" object-assign "^4.1.1" +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= + +protobufjs-old-fixed-webpack@3.8.5: + version "3.8.5" + resolved "https://registry.yarnpkg.com/protobufjs-old-fixed-webpack/-/protobufjs-old-fixed-webpack-3.8.5.tgz#5813c1af9f1d136bbf39f4f9f2e6f3e43c389d06" + integrity sha1-WBPBr58dE2u/OfT58ubz5Dw4nQY= + dependencies: + ascli "~0.3" + bytebuffer-old-fixed-webpack "3.5.6" + proxy-addr@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" @@ -8001,6 +8334,13 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +pushdata-bitcoin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz#15931d3cd967ade52206f523aa7331aef7d43af7" + integrity sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc= + dependencies: + bitcoin-ops "^1.3.0" + qr-image@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/qr-image/-/qr-image-3.2.0.tgz#9fa8295beae50c4a149cf9f909a1db464a8672e8" @@ -8917,6 +9257,11 @@ rimraf@~2.4.0: dependencies: glob "^6.0.1" +ripemd160@^0.2.0, ripemd160@~0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-0.2.1.tgz#dee19248a3e1c815ff9aea39e753a337f56a243d" + integrity sha1-3uGSSKPhyBX/muo551OjN/VqJD0= + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -9043,6 +9388,21 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" +secure-random@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/secure-random/-/secure-random-1.1.1.tgz#0880f2d8c5185f4bcb4684058c836b4ddb07145a" + integrity sha1-CIDy2MUYX0vLRoQFjINrTdsHFFo= + +secure-random@~0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/secure-random/-/secure-random-0.2.1.tgz#1c2f08cb94d8c06deff52721a6045bba96f85a9a" + integrity sha1-HC8Iy5TYwG3v9SchpgRbupb4Wpo= + +semver-compare@1.0.0, semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" @@ -9142,6 +9502,22 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" +sha1@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/sha1/-/sha1-1.1.1.tgz#addaa7a93168f393f19eb2b15091618e2700f848" + integrity sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg= + dependencies: + charenc ">= 0.0.1" + crypt ">= 0.0.1" + +sha256@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/sha256/-/sha256-0.1.1.tgz#34296f90498da3e8c6b06fffe8e860dba299f902" + integrity sha1-NClvkEmNo+jGsG//6Ohg26KZ+QI= + dependencies: + convert-hex "~0.1.0" + convert-string "~0.1.0" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -9159,6 +9535,11 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== +sigmund@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -9925,6 +10306,35 @@ tree-kill@^1.1.0: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" integrity sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg== +trezor-link@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/trezor-link/-/trezor-link-1.6.0.tgz#d79fec3e6ecf57c74c11ffbe37ebbd9118a91826" + integrity sha512-wJnd6pUn2WYPyoqqGpPTyixUBvwKPwtq+CZ+uQFL03ttFxHzYzCCgy2cDe8gievY1S2L2MDQNX3+GovhFOzxjg== + dependencies: + bigi "^1.4.1" + ecurve "^1.0.3" + json-stable-stringify "^1.0.1" + node-fetch "^1.6.0" + object.values "^1.0.3" + protobufjs-old-fixed-webpack "3.8.5" + semver-compare "^1.0.0" + whatwg-fetch "0.11.0" + +trezor.js@^6.17.6: + version "6.19.0" + resolved "https://registry.yarnpkg.com/trezor.js/-/trezor.js-6.19.0.tgz#f71b0ca7c64acc631dcf3c6d34d59a4b4dc19369" + integrity sha512-LmsqUWya1psbON2s4ym8CXGVpASIsCXTIS8v1LTUKMXlNQ1uJnFsqt8FadwQ2Q1pX3TYyZS1kHYntT8+Zfe+wg== + dependencies: + bchaddrjs "^0.2.1" + bitcoinjs-lib-zcash "^3.5.2" + ecurve "^1.0.2" + node-fetch "^1.6.0" + randombytes "^2.0.1" + semver-compare "1.0.0" + trezor-link "1.6.0" + unorm "^1.3.3" + whatwg-fetch "0.11.0" + trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -10009,6 +10419,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typeforce@^1.11.3: + version "1.16.0" + resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.16.0.tgz#060f871420f4ed90d411e0606bebc62a0889ad55" + integrity sha512-V60F7OHPH7vPlgIU73vYyeebKxWjQqCTlge+MvKlVn09PIhCOi/ZotowYdgREHB5S1dyHOr906ui6NheYXjlVQ== + ua-parser-js@^0.7.18: version "0.7.18" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed" @@ -10186,6 +10601,11 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +unorm@^1.3.3: + version "1.4.1" + resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.4.1.tgz#364200d5f13646ca8bcd44490271335614792300" + integrity sha1-NkIA1fE2RsqLzURJAnEzVhR5IwA= + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -10341,6 +10761,13 @@ value-equal@^0.4.0: resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" integrity sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw== +varuint-bitcoin@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-1.1.0.tgz#7a343f50537607af6a3059312b9782a170894540" + integrity sha512-jCEPG+COU/1Rp84neKTyDJQr478/hAfVp5xxYn09QEH0yBjbmPeMfuuQIrp+BUD83hybtYZKhr5elV3bvdV1bA== + dependencies: + safe-buffer "^5.1.1" + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -10530,6 +10957,11 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: dependencies: iconv-lite "0.4.23" +whatwg-fetch@0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.11.0.tgz#46b1d18d0aa99955971ef1a2f5aac506add28815" + integrity sha1-RrHRjQqpmVWXHvGi9arFBq3SiBU= + whatwg-fetch@>=0.10.0: version "3.0.0" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" @@ -10584,6 +11016,13 @@ widest-line@^2.0.0: dependencies: string-width "^2.1.1" +wif@^2.0.1: + version "2.0.6" + resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704" + integrity sha1-CNP1IFbGZnkplyb63g1DKudLRwQ= + dependencies: + bs58check "<3.0.0" + winston@^2.3.1: version "2.4.4" resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.4.tgz#a01e4d1d0a103cf4eada6fc1f886b3110d71c34b"