From 4db281bc9a153f7cf134693e807a8a6a8b8a3924 Mon Sep 17 00:00:00 2001 From: Danny Skubak Date: Fri, 13 Jul 2018 21:14:34 -0400 Subject: [PATCH 1/5] Jenkins - use yarn for building (#2067) --- jenkins/Jenkinsfile.linux.master | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jenkins/Jenkinsfile.linux.master b/jenkins/Jenkinsfile.linux.master index 9f86bd75..7360d6ee 100644 --- a/jenkins/Jenkinsfile.linux.master +++ b/jenkins/Jenkinsfile.linux.master @@ -30,7 +30,7 @@ pipeline { S3_BUCKET_NAME = 'REDACTED' } steps { - sh 'npm run jenkins:build:linux' + sh 'yarn run jenkins:build:linux' } } stage('Upload') { From 5505097c0345a12d0486c2c49f0a41cfc2c478b3 Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Fri, 13 Jul 2018 18:23:18 -0700 Subject: [PATCH 2/5] Update Version to 1.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 607f6e7a..12538f5e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "MyCrypto", "author": "MyCryptoHQ", - "version": "1.2.0", + "version": "1.2.1", "main": "main.js", "description": "MyCrypto web and electron app", "repository": "https://github.com/MyCryptoHQ/MyCrypto", From 96798e71fa915554539bcf636e7f856baee657f9 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Rona Date: Tue, 17 Jul 2018 21:46:41 +0200 Subject: [PATCH 3/5] Add support for the Archos Safe-T mini hardware wallet (#1963) * Add support for the Archos Safe-T mini * Safe-T mini: Fix version requirements * Fix fade-in animation is not hitting the 5th wallet option * Disable safe-t on electron: --- common/assets/images/wallets/safe-t.svg | 135 +++ .../WalletDecrypt/WalletDecrypt.tsx | 12 + .../WalletDecrypt/components/SafeT.scss | 31 + .../WalletDecrypt/components/SafeT.tsx | 153 +++ .../components/WalletButton.scss | 2 +- .../WalletDecrypt/components/index.tsx | 1 + common/components/WalletDecrypt/disables.ts | 3 +- common/config/data.tsx | 7 +- common/config/dpaths.ts | 12 + common/config/links.ts | 5 + .../config/networks/static/reducer.ts | 20 + common/features/config/selectors.ts | 1 + common/features/config/types.ts | 1 + common/features/selectors.ts | 6 +- common/features/wallet/selectors.ts | 4 +- common/libs/wallet/deterministic/enclave.ts | 1 + common/libs/wallet/deterministic/index.ts | 2 + common/libs/wallet/deterministic/safe-t.ts | 116 ++ common/translations/lang/ar.json | 2 + common/translations/lang/de.json | 2 + common/translations/lang/el.json | 2 + common/translations/lang/en.json | 4 + common/translations/lang/es.json | 2 + common/translations/lang/fi.json | 2 + common/translations/lang/fr.json | 4 + common/translations/lang/ht.json | 2 + common/translations/lang/hu.json | 2 + common/translations/lang/id.json | 2 + common/translations/lang/it.json | 2 + common/translations/lang/ja.json | 3 + common/translations/lang/ko.json | 2 + common/translations/lang/nl.json | 2 + common/translations/lang/no.json | 2 + common/translations/lang/pl.json | 2 + common/translations/lang/pt.json | 2 + common/translations/lang/ru.json | 2 + common/translations/lang/sk.json | 2 + common/translations/lang/sl.json | 2 + common/translations/lang/sv.json | 2 + common/translations/lang/tr.json | 2 + common/translations/lang/vi.json | 2 + common/translations/lang/zhcn.json | 2 + common/translations/lang/zhtw.json | 2 + common/typescript/safe-t-connect.d.ts | 69 ++ common/vendor/safe-t-connect.js | 1012 +++++++++++++++++ shared/enclave/server/wallets/index.ts | 2 + shared/enclave/server/wallets/safe-t.ts | 21 + shared/enclave/types.ts | 1 + shared/types/network.d.ts | 1 + 49 files changed, 1668 insertions(+), 5 deletions(-) create mode 100644 common/assets/images/wallets/safe-t.svg create mode 100644 common/components/WalletDecrypt/components/SafeT.scss create mode 100644 common/components/WalletDecrypt/components/SafeT.tsx create mode 100644 common/libs/wallet/deterministic/safe-t.ts create mode 100644 common/typescript/safe-t-connect.d.ts create mode 100644 common/vendor/safe-t-connect.js create mode 100644 shared/enclave/server/wallets/safe-t.ts diff --git a/common/assets/images/wallets/safe-t.svg b/common/assets/images/wallets/safe-t.svg new file mode 100644 index 00000000..103d4651 --- /dev/null +++ b/common/assets/images/wallets/safe-t.svg @@ -0,0 +1,135 @@ + + + + + + image/svg+xml + + trezor + + + + + + trezor + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/common/components/WalletDecrypt/WalletDecrypt.tsx b/common/components/WalletDecrypt/WalletDecrypt.tsx index 9b6e5f72..191e391a 100644 --- a/common/components/WalletDecrypt/WalletDecrypt.tsx +++ b/common/components/WalletDecrypt/WalletDecrypt.tsx @@ -23,6 +23,7 @@ import { transactionFieldsActions } from 'features/transaction'; import { notificationsActions } from 'features/notifications'; import LedgerIcon from 'assets/images/wallets/ledger.svg'; import TrezorIcon from 'assets/images/wallets/trezor.svg'; +import SafeTIcon from 'assets/images/wallets/safe-t.svg'; import ParitySignerIcon from 'assets/images/wallets/parity-signer.svg'; import { Errorable } from 'components'; import { DisabledWallets } from './disables'; @@ -34,6 +35,7 @@ import { PrivateKeyDecrypt, PrivateKeyValue, TrezorDecrypt, + SafeTminiDecrypt, ViewOnlyDecrypt, Web3Decrypt, WalletButton, @@ -141,6 +143,16 @@ const WalletDecrypt = withRouter( helpLink: 'https://support.mycrypto.com/accessing-your-wallet/how-to-use-your-trezor-with-mycrypto.html' }, + [SecureWalletName.SAFE_T]: { + lid: 'X_SAFE_T', + icon: SafeTIcon, + description: 'ADD_HARDWAREDESC', + component: SafeTminiDecrypt, + initialParams: {}, + unlock: this.props.setWallet, + // TODO - Update with the right id once available + helpLink: 'https://www.archos.com/fr/products/crypto/faq.html' + }, [SecureWalletName.PARITY_SIGNER]: { lid: 'X_PARITYSIGNER', icon: ParitySignerIcon, diff --git a/common/components/WalletDecrypt/components/SafeT.scss b/common/components/WalletDecrypt/components/SafeT.scss new file mode 100644 index 00000000..c4e33083 --- /dev/null +++ b/common/components/WalletDecrypt/components/SafeT.scss @@ -0,0 +1,31 @@ +.SafeTminiDecrypt { + text-align: center; + + &-help { + margin-top: 10px; + font-size: 13px; + } + + &-error { + opacity: 0; + transition: none; + + &.is-showing { + opacity: 1; + } + } + + &-buy { + margin: 10px 0; + } + + &-message { + display: flex; + justify-content: center; + align-items: center; + + .Spinner { + margin-right: 16px; + } + } +} diff --git a/common/components/WalletDecrypt/components/SafeT.tsx b/common/components/WalletDecrypt/components/SafeT.tsx new file mode 100644 index 00000000..a1c59ae1 --- /dev/null +++ b/common/components/WalletDecrypt/components/SafeT.tsx @@ -0,0 +1,153 @@ +import React, { PureComponent } from 'react'; +import { connect } from 'react-redux'; + +import { SecureWalletName, safeTReferralURL } from 'config'; +import translate, { translateRaw } from 'translations'; +import { SafeTWallet } from 'libs/wallet'; +import { AppState } from 'features/reducers'; +import { getSingleDPath, getPaths } from 'features/config'; +import { Spinner, NewTabLink } from 'components/ui'; +import UnsupportedNetwork from './UnsupportedNetwork'; +import DeterministicWalletsModal from './DeterministicWalletsModal'; +import './SafeT.scss'; + +//todo: conflicts with comment in walletDecrypt -> onUnlock method +interface OwnProps { + onUnlock(param: any): void; +} + +interface StateProps { + dPath: DPath | undefined; + dPaths: DPath[]; +} + +// todo: nearly duplicates ledger component props +interface State { + publicKey: string; + chainCode: string; + dPath: DPath; + error: string | null; + isLoading: boolean; +} + +type Props = OwnProps & StateProps; + +class SafeTminiDecryptClass extends PureComponent { + public state: State = { + publicKey: '', + chainCode: '', + dPath: this.props.dPath || this.props.dPaths[0], + error: null, + isLoading: false + }; + + public UNSAFE_componentWillReceiveProps(nextProps: Props) { + if (this.props.dPath !== nextProps.dPath && nextProps.dPath) { + this.setState({ dPath: nextProps.dPath }); + } + } + + public render() { + const { dPath, publicKey, chainCode, error, isLoading } = this.state; + const showErr = error ? 'is-showing' : ''; + + if (!dPath) { + return ; + } + + // todo: update help link + return ( +
+ + + + {translate('ORDER_SAFE_T')} + + +
{error || '-'}
+ + +
+ ); + } + + private handlePathChange = (dPath: DPath) => { + this.setState({ dPath }); + this.handleConnect(dPath); + }; + + private handleConnect = (dPath: DPath): void => { + this.setState({ + isLoading: true, + error: null + }); + + SafeTWallet.getChainCode(dPath.value) + .then(res => { + this.setState({ + dPath, + publicKey: res.publicKey, + chainCode: res.chainCode, + isLoading: false + }); + }) + .catch(err => { + this.setState({ + error: err.message, + isLoading: false + }); + }); + }; + + private handleCancel = () => { + this.reset(); + }; + + private handleUnlock = (address: string, index: number) => { + this.props.onUnlock(new SafeTWallet(address, this.state.dPath.value, index)); + this.reset(); + }; + + private handleNullConnect = (): void => { + this.handleConnect(this.state.dPath); + }; + + private reset() { + this.setState({ + publicKey: '', + chainCode: '', + dPath: this.props.dPath || this.props.dPaths[0] + }); + } +} + +function mapStateToProps(state: AppState): StateProps { + return { + dPath: getSingleDPath(state, SecureWalletName.SAFE_T), + dPaths: getPaths(state, SecureWalletName.SAFE_T) + }; +} + +export const SafeTminiDecrypt = connect(mapStateToProps)(SafeTminiDecryptClass); diff --git a/common/components/WalletDecrypt/components/WalletButton.scss b/common/components/WalletDecrypt/components/WalletButton.scss index a093d70f..bc8ed86f 100644 --- a/common/components/WalletDecrypt/components/WalletButton.scss +++ b/common/components/WalletDecrypt/components/WalletButton.scss @@ -30,7 +30,7 @@ animation: wallet-button-enter 400ms ease 1; animation-fill-mode: backwards; - @for $i from 0 to 5 { + @for $i from 0 to 6 { &:nth-child(#{$i}) { animation-delay: 100ms + ($i * 60ms); } diff --git a/common/components/WalletDecrypt/components/index.tsx b/common/components/WalletDecrypt/components/index.tsx index c2aed00d..9efd6af7 100644 --- a/common/components/WalletDecrypt/components/index.tsx +++ b/common/components/WalletDecrypt/components/index.tsx @@ -7,6 +7,7 @@ export * from './Mnemonic'; export * from './ParitySigner'; export * from './PrivateKey'; export * from './Trezor'; +export * from './SafeT'; export * from './ViewOnly'; export * from './WalletButton'; export * from './Web3'; diff --git a/common/components/WalletDecrypt/disables.ts b/common/components/WalletDecrypt/disables.ts index 3ed1fde3..6188a25b 100644 --- a/common/components/WalletDecrypt/disables.ts +++ b/common/components/WalletDecrypt/disables.ts @@ -22,9 +22,10 @@ export const DISABLE_WALLETS: { [key in WalletMode]: DisabledWallets } = { } }, [WalletMode.UNABLE_TO_SIGN]: { - wallets: [SecureWalletName.TREZOR, MiscWalletName.VIEW_ONLY], + wallets: [SecureWalletName.TREZOR, SecureWalletName.SAFE_T, MiscWalletName.VIEW_ONLY], reasons: { [SecureWalletName.TREZOR]: 'This wallet can’t sign messages', + [SecureWalletName.SAFE_T]: 'This wallet can’t sign messages', [MiscWalletName.VIEW_ONLY]: 'This wallet can’t sign messages' } } diff --git a/common/config/data.tsx b/common/config/data.tsx index aa9820fe..155d1492 100644 --- a/common/config/data.tsx +++ b/common/config/data.tsx @@ -55,6 +55,9 @@ export const MINIMUM_PASSWORD_LENGTH = 12; export const knowledgeBaseURL = 'https://support.mycrypto.com'; export const ledgerReferralURL = 'https://www.ledgerwallet.com/r/1985?path=/products/'; export const trezorReferralURL = 'https://shop.trezor.io?a=mycrypto.com'; +// TODO - Update url +export const safeTReferralURL = + 'https://www.archos.com/fr/products/crypto/archos_safetmini/index.html'; export const bitboxReferralURL = 'https://digitalbitbox.com/?ref=mycrypto'; // TODO - Update url, this is MEW's export const bityReferralURL = 'https://bity.com/af/jshkb37v'; @@ -69,12 +72,14 @@ export enum SecureWalletName { WEB3 = 'web3', LEDGER_NANO_S = 'ledgerNanoS', TREZOR = 'trezor', + SAFE_T = 'safeTmini', PARITY_SIGNER = 'paritySigner' } export enum HardwareWalletName { LEDGER_NANO_S = 'ledgerNanoS', - TREZOR = 'trezor' + TREZOR = 'trezor', + SAFE_T = 'safeTmini' } export enum InsecureWalletName { diff --git a/common/config/dpaths.ts b/common/config/dpaths.ts index 5af627f2..544b0c4a 100644 --- a/common/config/dpaths.ts +++ b/common/config/dpaths.ts @@ -8,6 +8,11 @@ export const ETH_TREZOR: DPath = { value: "m/44'/60'/0'/0" }; +export const ETH_SAFE_T: DPath = { + label: 'Safe-T (ETH)', + value: "m/44'/60'/0'/0" +}; + export const ETH_LEDGER: DPath = { label: 'Ledger (ETH)', value: "m/44'/60'/0'" @@ -23,6 +28,11 @@ export const ETC_TREZOR: DPath = { value: "m/44'/61'/0'/0" }; +export const ETC_SAFE_T: DPath = { + label: 'Safe-T (ETC)', + value: "m/44'/61'/0'/0" +}; + export const ETH_TESTNET: DPath = { label: 'Testnet (ETH)', value: "m/44'/1'/0'/0" @@ -106,9 +116,11 @@ export const ESN_DEFAULT: DPath = { export const DPaths: DPath[] = [ ETH_DEFAULT, ETH_TREZOR, + ETH_SAFE_T, ETH_LEDGER, ETC_LEDGER, ETC_TREZOR, + ETC_SAFE_T, ETH_TESTNET, EXP_DEFAULT, UBQ_DEFAULT, diff --git a/common/config/links.ts b/common/config/links.ts index 3e5c979c..0b35dda9 100644 --- a/common/config/links.ts +++ b/common/config/links.ts @@ -3,6 +3,7 @@ import { discordURL, ledgerReferralURL, trezorReferralURL, + safeTReferralURL, ethercardReferralURL, keepkeyReferralURL, steelyReferralURL @@ -84,6 +85,10 @@ export const affiliateLinks: Link[] = [ link: trezorReferralURL, text: translateRaw('TREZOR_REFERAL') }, + { + link: safeTReferralURL, + text: translateRaw('SAFE_T_REFERAL') + }, { link: keepkeyReferralURL, text: translateRaw('KEEPKEY_REFERRAL') diff --git a/common/features/config/networks/static/reducer.ts b/common/features/config/networks/static/reducer.ts index edb903e2..b68a3b24 100644 --- a/common/features/config/networks/static/reducer.ts +++ b/common/features/config/networks/static/reducer.ts @@ -9,10 +9,12 @@ import { ELLA_DEFAULT, ETC_LEDGER, ETC_TREZOR, + ETC_SAFE_T, ETH_DEFAULT, ETH_LEDGER, ETH_TESTNET, ETH_TREZOR, + ETH_SAFE_T, EXP_DEFAULT, POA_DEFAULT, TOMO_DEFAULT, @@ -57,6 +59,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: require('config/contracts/eth.json'), dPathFormats: { [SecureWalletName.TREZOR]: ETH_TREZOR, + [SecureWalletName.SAFE_T]: ETH_SAFE_T, [SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER, [InsecureWalletName.MNEMONIC_PHRASE]: ETH_DEFAULT }, @@ -79,6 +82,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { isTestnet: true, dPathFormats: { [SecureWalletName.TREZOR]: ETH_TESTNET, + [SecureWalletName.SAFE_T]: ETH_TESTNET, [SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER, [InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET }, @@ -100,6 +104,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { isTestnet: true, dPathFormats: { [SecureWalletName.TREZOR]: ETH_TESTNET, + [SecureWalletName.SAFE_T]: ETH_TESTNET, [SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER, [InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET }, @@ -121,6 +126,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { isTestnet: true, dPathFormats: { [SecureWalletName.TREZOR]: ETH_TESTNET, + [SecureWalletName.SAFE_T]: ETH_TESTNET, [SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER, [InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET }, @@ -142,6 +148,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: require('config/contracts/etc.json'), dPathFormats: { [SecureWalletName.TREZOR]: ETC_TREZOR, + [SecureWalletName.SAFE_T]: ETC_SAFE_T, [SecureWalletName.LEDGER_NANO_S]: ETC_LEDGER, [InsecureWalletName.MNEMONIC_PHRASE]: ETC_TREZOR }, @@ -166,6 +173,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: require('config/contracts/ubq.json'), dPathFormats: { [SecureWalletName.TREZOR]: UBQ_DEFAULT, + [SecureWalletName.SAFE_T]: UBQ_DEFAULT, [SecureWalletName.LEDGER_NANO_S]: UBQ_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: UBQ_DEFAULT }, @@ -190,6 +198,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: require('config/contracts/exp.json'), dPathFormats: { [SecureWalletName.TREZOR]: EXP_DEFAULT, + [SecureWalletName.SAFE_T]: EXP_DEFAULT, [SecureWalletName.LEDGER_NANO_S]: EXP_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: EXP_DEFAULT }, @@ -216,6 +225,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: [], dPathFormats: { [SecureWalletName.TREZOR]: POA_DEFAULT, + [SecureWalletName.SAFE_T]: POA_DEFAULT, [SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER, [InsecureWalletName.MNEMONIC_PHRASE]: POA_DEFAULT }, @@ -241,6 +251,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { dPathFormats: { [SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER, [SecureWalletName.TREZOR]: ETH_TREZOR, + [SecureWalletName.SAFE_T]: ETH_SAFE_T, [SecureWalletName.LEDGER_NANO_S]: TOMO_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: TOMO_DEFAULT }, @@ -265,6 +276,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: [], dPathFormats: { [SecureWalletName.TREZOR]: ELLA_DEFAULT, + [SecureWalletName.SAFE_T]: ELLA_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: ELLA_DEFAULT }, gasPriceSettings: { @@ -290,6 +302,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: [], dPathFormats: { [SecureWalletName.TREZOR]: MUSIC_DEFAULT, + [SecureWalletName.SAFE_T]: MUSIC_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: MUSIC_DEFAULT }, gasPriceSettings: { @@ -314,6 +327,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: [], dPathFormats: { [SecureWalletName.TREZOR]: ETSC_DEFAULT, + [SecureWalletName.SAFE_T]: ETSC_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: ETSC_DEFAULT }, gasPriceSettings: { @@ -338,6 +352,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: [], dPathFormats: { [SecureWalletName.TREZOR]: EGEM_DEFAULT, + [SecureWalletName.SAFE_T]: EGEM_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: EGEM_DEFAULT }, gasPriceSettings: { @@ -362,6 +377,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: [], dPathFormats: { [SecureWalletName.TREZOR]: CLO_DEFAULT, + [SecureWalletName.SAFE_T]: CLO_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: CLO_DEFAULT }, gasPriceSettings: { @@ -414,6 +430,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { isTestnet: true, dPathFormats: { [SecureWalletName.TREZOR]: RSK_TESTNET, + [SecureWalletName.SAFE_T]: RSK_TESTNET, [SecureWalletName.LEDGER_NANO_S]: RSK_TESTNET, [InsecureWalletName.MNEMONIC_PHRASE]: RSK_TESTNET }, @@ -440,6 +457,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: [], dPathFormats: { [SecureWalletName.TREZOR]: GO_DEFAULT, + [SecureWalletName.SAFE_T]: GO_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: GO_DEFAULT }, gasPriceSettings: { @@ -464,6 +482,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: [], dPathFormats: { [SecureWalletName.TREZOR]: EOSC_DEFAULT, + [SecureWalletName.SAFE_T]: EOSC_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: EOSC_DEFAULT }, gasPriceSettings: { @@ -487,6 +506,7 @@ export const STATIC_NETWORKS_INITIAL_STATE: StaticNetworksState = { contracts: require('config/contracts/esn.json'), dPathFormats: { [SecureWalletName.TREZOR]: ESN_DEFAULT, + [SecureWalletName.SAFE_T]: ESN_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: ESN_DEFAULT }, gasPriceSettings: { diff --git a/common/features/config/selectors.ts b/common/features/config/selectors.ts index 14ec8d9a..db6547a6 100644 --- a/common/features/config/selectors.ts +++ b/common/features/config/selectors.ts @@ -158,6 +158,7 @@ export function isWalletFormatSupportedOnNetwork(state: AppState, format: Wallet const CHECK_FORMATS: DPathFormat[] = [ SecureWalletName.LEDGER_NANO_S, SecureWalletName.TREZOR, + SecureWalletName.SAFE_T, InsecureWalletName.MNEMONIC_PHRASE ]; diff --git a/common/features/config/types.ts b/common/features/config/types.ts index ceb1cdaf..f2ae1ab3 100644 --- a/common/features/config/types.ts +++ b/common/features/config/types.ts @@ -27,5 +27,6 @@ export type ConfigAction = CustomNetworkAction | CustomNodeAction | NodeAction | export type DPathFormat = | SecureWalletName.TREZOR + | SecureWalletName.SAFE_T | SecureWalletName.LEDGER_NANO_S | InsecureWalletName.MNEMONIC_PHRASE; diff --git a/common/features/selectors.ts b/common/features/selectors.ts index 192138e0..bd5ceb98 100644 --- a/common/features/selectors.ts +++ b/common/features/selectors.ts @@ -69,7 +69,7 @@ export function getDisabledWallets(state: AppState): any { // Some wallets are unavailable offline if (isOffline) { addReason( - [SecureWalletName.WEB3, SecureWalletName.TREZOR], + [SecureWalletName.WEB3, SecureWalletName.TREZOR, SecureWalletName.SAFE_T], 'This wallet cannot be accessed offline' ); } @@ -77,6 +77,10 @@ export function getDisabledWallets(state: AppState): any { // Some wallets are disabled on certain platforms if (process.env.BUILD_ELECTRON) { addReason([SecureWalletName.WEB3], 'This wallet is not supported in the MyCrypto app'); + addReason( + [SecureWalletName.SAFE_T], + 'Coming soon. Please use the MyCrypto.com website in the meantime' + ); } // Dedupe and sort for consistency diff --git a/common/features/wallet/selectors.ts b/common/features/wallet/selectors.ts index 9e00ea11..f355468c 100644 --- a/common/features/wallet/selectors.ts +++ b/common/features/wallet/selectors.ts @@ -3,6 +3,7 @@ import { WalletConfig } from 'libs/wallet/config'; import { IWallet } from 'libs/wallet/IWallet'; import { LedgerWallet } from 'libs/wallet/deterministic/ledger'; import { TrezorWallet } from 'libs/wallet/deterministic/trezor'; +import { SafeTWallet } from 'libs/wallet/deterministic/safe-t'; import Web3Wallet from 'libs/wallet/non-deterministic/web3'; import ParitySignerWallet from 'libs/wallet/non-deterministic/parity'; import { AppState } from 'features/reducers'; @@ -32,8 +33,9 @@ export const getWalletType = (state: AppState): IWalletType => { const isWeb3Wallet = wallet instanceof Web3Wallet; const isLedgerWallet = wallet instanceof LedgerWallet; const isTrezorWallet = wallet instanceof TrezorWallet; + const isSafeTWallet = wallet instanceof SafeTWallet; const isParitySignerWallet = wallet instanceof ParitySignerWallet; - const isHardwareWallet = isLedgerWallet || isTrezorWallet; + const isHardwareWallet = isLedgerWallet || isTrezorWallet || isSafeTWallet; return { isWeb3Wallet, isHardwareWallet, isParitySignerWallet }; }; diff --git a/common/libs/wallet/deterministic/enclave.ts b/common/libs/wallet/deterministic/enclave.ts index bc3cce5d..feceee88 100644 --- a/common/libs/wallet/deterministic/enclave.ts +++ b/common/libs/wallet/deterministic/enclave.ts @@ -9,6 +9,7 @@ import { HardwareWallet, ChainCodeResponse } from './hardware'; const walletTypeNames = { [WalletTypes.LEDGER]: 'X_LEDGER', [WalletTypes.TREZOR]: 'X_TREZOR', + [WalletTypes.SAFE_T]: 'X_SAFE_T', [WalletTypes.KEEPKEY]: 'X_KEEPKEY' }; diff --git a/common/libs/wallet/deterministic/index.ts b/common/libs/wallet/deterministic/index.ts index 7867bf69..e011f7b6 100644 --- a/common/libs/wallet/deterministic/index.ts +++ b/common/libs/wallet/deterministic/index.ts @@ -2,6 +2,7 @@ import { WalletTypes } from 'shared/enclave/client'; import { makeEnclaveWallet } from './enclave'; import { LedgerWallet as LedgerWalletWeb } from './ledger'; import { TrezorWallet as TrezorWalletWeb } from './trezor'; +import { SafeTWallet as SafeTWalletWeb } from './safe-t'; function enclaveOrWallet(type: WalletTypes, lib: T) { return process.env.BUILD_ELECTRON ? makeEnclaveWallet(type) : lib; @@ -11,3 +12,4 @@ export * from './mnemonic'; export * from './hardware'; export const LedgerWallet = enclaveOrWallet(WalletTypes.LEDGER, LedgerWalletWeb); export const TrezorWallet = enclaveOrWallet(WalletTypes.TREZOR, TrezorWalletWeb); +export const SafeTWallet = enclaveOrWallet(WalletTypes.SAFE_T, SafeTWalletWeb); diff --git a/common/libs/wallet/deterministic/safe-t.ts b/common/libs/wallet/deterministic/safe-t.ts new file mode 100644 index 00000000..6091f26c --- /dev/null +++ b/common/libs/wallet/deterministic/safe-t.ts @@ -0,0 +1,116 @@ +import BN from 'bn.js'; +import EthTx, { TxObj } from 'ethereumjs-tx'; +import { addHexPrefix } from 'ethereumjs-util'; +import mapValues from 'lodash/mapValues'; + +import { translateRaw } from 'translations'; +import SafeTConnect from 'vendor/safe-t-connect'; +import { getTransactionFields } from 'libs/transaction'; +import { padLeftEven } from 'libs/values'; +import { stripHexPrefixAndLower } from 'libs/formatters'; +import { HardwareWallet, ChainCodeResponse } from './hardware'; + +export const SAFE_T_MINIMUM_FIRMWARE = '1.0.0'; + +export class SafeTWallet extends HardwareWallet { + public static getChainCode(dpath: string): Promise { + return new Promise(resolve => { + SafeTConnect.getXPubKey( + dpath, + res => { + if (res.success) { + resolve({ + publicKey: res.publicKey, + chainCode: res.chainCode + }); + } else { + throw new Error(res.error); + } + }, + SAFE_T_MINIMUM_FIRMWARE + ); + }); + } + + public signRawTransaction(tx: EthTx): Promise { + return new Promise((resolve, reject) => { + const { chainId, ...strTx } = getTransactionFields(tx); + // stripHexPrefixAndLower identical to ethFuncs.getNakedAddress + const cleanedTx = mapValues(mapValues(strTx, stripHexPrefixAndLower), padLeftEven); + + SafeTConnect.ethereumSignTx( + // Args + this.getPath(), + cleanedTx.nonce, + cleanedTx.gasPrice, + cleanedTx.gasLimit, + cleanedTx.to, + cleanedTx.value, + cleanedTx.data, + chainId, + // Callback + result => { + if (!result.success) { + return reject(Error(result.error)); + } + + // TODO: Explain what's going on here? Add tests? Adapted from: + // https://github.com/kvhnuke/etherwallet/blob/v3.10.2.6/app/scripts/uiFuncs.js#L24 + const txToSerialize: TxObj = { + ...strTx, + v: addHexPrefix(new BN(result.v).toString(16)), + r: addHexPrefix(result.r.toString()), + s: addHexPrefix(result.s) + }; + const eTx = new EthTx(txToSerialize); + const serializedTx = eTx.serialize(); + resolve(serializedTx); + } + ); + }); + } + + public signMessage() { + return Promise.reject(new Error('Signing via Safe-T mini not yet supported.')); + } + + public displayAddress(): Promise { + return new Promise(resolve => { + SafeTConnect.ethereumGetAddress( + `${this.dPath}/${this.index}`, + res => { + if (res.error) { + resolve(false); + } else { + resolve(true); + } + }, + SAFE_T_MINIMUM_FIRMWARE + ); + }); + } + + public getWalletType(): string { + return translateRaw('X_SAFE_T'); + } + + // works, but returns a signature that can only be verified with a Safe-T mini device + /* + public signMessage = (message: string): Promise => { + return new Promise((resolve, reject) => { + SafeTConnect.ethereumSignMessage( + this.getPath(), + message, + response => { + if (response.success) { + resolve(addHexPrefix(response.signature)) + } else{ + console.error(response.error) + reject(response.error) + } + } + ) + }) + } + */ +} diff --git a/common/translations/lang/ar.json b/common/translations/lang/ar.json index 2a834e89..7ded3c23 100644 --- a/common/translations/lang/ar.json +++ b/common/translations/lang/ar.json @@ -282,6 +282,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Connexion au TREZOR ", "ADD_TREZOR_SELECT": "Ceci est une _seed_ TREZOR ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Connexion au Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Réouvrir MyCrypto sur une connexion sécurisée (SSL) ", "ADD_DIGITALBITBOX_0B": "Réouvrir MyCrypto avec [Chrome](https://www.google.com/chrome/browser/desktop/) ou [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/de.json b/common/translations/lang/de.json index f02614bf..9e0f7219 100644 --- a/common/translations/lang/de.json +++ b/common/translations/lang/de.json @@ -295,6 +295,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Zu TREZOR Verbinden ", "ADD_TREZOR_SELECT": "Dies ist ein TREZOR seed ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Zu Safe-T mini Verbinden ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ", "ADD_DIGITALBITBOX_0B": "Re-open MyCrypto using [Chrome](https://www.google.com/chrome/browser/desktop/) or [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/el.json b/common/translations/lang/el.json index c6c3941a..1bbda13c 100644 --- a/common/translations/lang/el.json +++ b/common/translations/lang/el.json @@ -296,6 +296,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Συνδεθείτε στο TREZOR ", "ADD_TREZOR_SELECT": "Αυτός είναι σπόρος του TREZOR ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Συνδεθείτε στο Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ", "ADD_DIGITALBITBOX_0B": "Re-open MyCrypto using [Chrome](https://www.google.com/chrome/browser/desktop/) or [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/en.json b/common/translations/lang/en.json index a935fcbe..f253e707 100644 --- a/common/translations/lang/en.json +++ b/common/translations/lang/en.json @@ -81,6 +81,9 @@ "ORDER_TREZOR": "Don’t have a TREZOR? Order one now!", "HOWTO_TREZOR": "How to use TREZOR with MyCrypto", "X_KEEPKEY": "KeepKey", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Connect to Safe-T mini ", + "ORDER_SAFE_T": "Don’t have a Safe-T mini? Order one now!", "UNLOCK_WALLET": "Unlock your", "X_PARITYSIGNER": "Parity Signer ", "ADD_PARITY_DESC": "Connect & sign via your Parity Signer mobile app ", @@ -460,6 +463,7 @@ "LEDGER_WRONG_APP": "Wrong application selected on your device", "LEDGER_LOCKED": "Your Ledger device is locked", "TREZOR_REFERAL": "Buy a TREZOR", + "SAFE_T_REFERAL": "Buy a Safe-T mini", "KEEPKEY_REFERRAL": "Buy a Keepkey", "STEELY_REFERRAL": "Get a Steely", "ETHERCARD_REFERAL": "Get an ether.card", diff --git a/common/translations/lang/es.json b/common/translations/lang/es.json index 2846381e..e54da8ec 100644 --- a/common/translations/lang/es.json +++ b/common/translations/lang/es.json @@ -295,6 +295,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Conectar a TREZOR ", "ADD_TREZOR_SELECT": "Esto es una semilla TREZOR ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Conectar a Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Volver a abrir MyCrypto en una conexión segura (SSL) ", "ADD_DIGITALBITBOX_0B": "Volver a abrir MyCrypto usando [Chrome](https://www.google.com/chrome/browser/desktop/) u [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/fi.json b/common/translations/lang/fi.json index 7d84d195..0a53002c 100644 --- a/common/translations/lang/fi.json +++ b/common/translations/lang/fi.json @@ -296,6 +296,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Connect to TREZOR ", "ADD_TREZOR_SELECT": "This is a TREZOR seed ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Connect to Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ", "ADD_DIGITALBITBOX_0B": "Re-open MyCrypto using [Chrome](https://www.google.com/chrome/browser/desktop/) or [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/fr.json b/common/translations/lang/fr.json index 8f7e8962..dc99b4e1 100644 --- a/common/translations/lang/fr.json +++ b/common/translations/lang/fr.json @@ -80,6 +80,9 @@ "ADD_TREZOR_SCAN": "Connexion au TREZOR ", "ORDER_TREZOR": "Pas de TREZOR ? Achetez-en un maintenant !", "HOWTO_TREZOR": "Comment utiliser TREZOR avec MyCrypto", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Connexion au Safe-T mini ", + "ORDER_SAFE_T": "Pas de Safe-T mini ? Achetez-en un maintenant !", "X_KEEPKEY": "KeepKey", "UNLOCK_WALLET": "Déverrouillez votre", "X_PARITYSIGNER": "Parity Signer ", @@ -460,6 +463,7 @@ "LEDGER_WRONG_APP": "Mauvaise application sélectionnée sur votre appareil", "LEDGER_LOCKED": "Votre appareil Ledger est verrouillé", "TREZOR_REFERAL": "Acheter un TREZOR", + "SAFE_T_REFERAL": "Acheter un Safe-T mini", "KEEPKEY_REFERRAL": "Acheter un Keepkey", "STEELY_REFERRAL": "Acheter un Steely", "ETHERCARD_REFERAL": "Acheter une ether.card", diff --git a/common/translations/lang/ht.json b/common/translations/lang/ht.json index 54b58585..2f75aa36 100644 --- a/common/translations/lang/ht.json +++ b/common/translations/lang/ht.json @@ -136,6 +136,8 @@ "ADD_METAMASK": "Connect to MetaMask ", "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Connect to TREZOR ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Connect to Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ", "ADD_DIGITALBITBOX_0B": "Re-open MyCrypto using [Chrome](https://www.google.com/chrome/browser/desktop/) or [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/hu.json b/common/translations/lang/hu.json index dfb9c379..9921d0e6 100644 --- a/common/translations/lang/hu.json +++ b/common/translations/lang/hu.json @@ -296,6 +296,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Connect to TREZOR ", "ADD_TREZOR_SELECT": "This is a TREZOR seed ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Connect to Safe-T mini ", "ADD_METAMASK": "Connect to MetaMask ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ", diff --git a/common/translations/lang/id.json b/common/translations/lang/id.json index 7f3cbd53..f63799db 100644 --- a/common/translations/lang/id.json +++ b/common/translations/lang/id.json @@ -160,6 +160,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Hubungkan ke TREZOR ", "ADD_TREZOR_SELECT": "Ini adalah TREZOR seed ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Hubungkan ke Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Buka kembali MyCrypto melalui koneksi (SSL) yang aman ", "ADD_DIGITALBITBOX_0B": "Buka kembali MyCrypto menggunakan [Chrome](https://www.google.com/chrome/browser/desktop/) atau [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/it.json b/common/translations/lang/it.json index 64e79a4d..bbba5275 100644 --- a/common/translations/lang/it.json +++ b/common/translations/lang/it.json @@ -244,6 +244,8 @@ "ADD_METAMASK": "Collegati a MetaMask ", "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Collegati al TREZOR ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Collegati al Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Riapri MyCrypto su una connessione sicura (SSL) ", "ADD_DIGITALBITBOX_0B": "Riapri MyCrypto utilizzando [Chrome](https://www.google.com/chrome/browser/desktop/) o [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/ja.json b/common/translations/lang/ja.json index 02606ab5..afabf737 100644 --- a/common/translations/lang/ja.json +++ b/common/translations/lang/ja.json @@ -61,6 +61,8 @@ "ADD_METAMASK": "MetaMaskに接続 ", "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "TREZORに接続する ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Safe-T miniに接続する ", "X_PARITYSIGNER": "Parity Signer ", "ADD_PARITY_DESC": "Parity Signerモバイルアプリ経由で接続て署名 ", "ADD_PARITY_1": "処理が取り消されました ", @@ -394,6 +396,7 @@ "LEDGER_WRONG_APP": "デバイス上で誤ったアプリが選択されています。", "LEDGER_LOCKED": "Ledgerデバイスがロックされています", "TREZOR_REFERAL": "TREZORを購入", + "SAFE_T_REFERAL": "Safe-T miniを購入", "KEEPKEY_REFERRAL": "Keepkeyを購入", "STEELY_REFERRAL": "Steelyを購入", "ETHERCARD_REFERAL": "ether.cardを購入", diff --git a/common/translations/lang/ko.json b/common/translations/lang/ko.json index cfc719ea..e0d8098e 100644 --- a/common/translations/lang/ko.json +++ b/common/translations/lang/ko.json @@ -78,6 +78,8 @@ "X_HARDWARE_WALLET": "하드웨어 지갑", "X_HARDWARE_WALLET_2": "하드웨어 지갑 ", "ADD_TREZOR_SCAN": "TREZOR에 연결하기 ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Safe-T mini 에 연결하기 ", "X_KEEPKEY": "KeepKey 지갑", "X_PARITYSIGNER": "Parity Signer ", "ADD_PARITY_DESC": "Parity Signer 모바일 앱을 통해 연결 및 서명 ", diff --git a/common/translations/lang/nl.json b/common/translations/lang/nl.json index 86e442dc..f5ca7003 100644 --- a/common/translations/lang/nl.json +++ b/common/translations/lang/nl.json @@ -134,6 +134,8 @@ "ADD_LEDGER_SCAN": "Verbind met Ledger Wallet ", "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Verbind met TREZOR ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Verbind met Safe-T mini ", "ADD_METAMASK": "Verbind met MetaMask ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Her-open MyCrypto met een veilige (SSL) verbinding ", diff --git a/common/translations/lang/no.json b/common/translations/lang/no.json index 105a3cf2..880192d9 100644 --- a/common/translations/lang/no.json +++ b/common/translations/lang/no.json @@ -155,6 +155,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Koble til TREZOR ", "ADD_TREZOR_SELECT": "Dette er en TREZOR seed ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Koble til Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Åpne MyCrypto på nytt på en sikker (SSL) forbindelse. ", "ADD_DIGITALBITBOX_0B": "Åpne MyCrypto på nytt med [Chrome](https://www.google.com/chrome/browser/desktop/) eller [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/pl.json b/common/translations/lang/pl.json index dfeedd6c..d175edac 100644 --- a/common/translations/lang/pl.json +++ b/common/translations/lang/pl.json @@ -259,6 +259,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Połącz z TREZOR ", "ADD_TREZOR_SELECT": "To jest ziarno (seed) TREZOR", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Połącz z Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Otwórz MyCrypto ponownie na bezpiecznym połączeniu (SSL) ", "ADD_DIGITALBITBOX_0B": "Otwórz MyCrypto w [Chrome](https://www.google.com/chrome/browser/desktop/) lub [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/pt.json b/common/translations/lang/pt.json index 62bd4267..c23d85d1 100644 --- a/common/translations/lang/pt.json +++ b/common/translations/lang/pt.json @@ -249,6 +249,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Connect to TREZOR ", "ADD_TREZOR_SELECT": "This is a TREZOR seed ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Connect to Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Re-abra MyCrypto em uma conexão (SSL) segura ", "ADD_DIGITALBITBOX_0B": "Re-abra MyCrypto usando [Chrome](https://www.google.com/chrome/browser/desktop/) ou [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/ru.json b/common/translations/lang/ru.json index ba5f55a3..feaf42a5 100644 --- a/common/translations/lang/ru.json +++ b/common/translations/lang/ru.json @@ -297,6 +297,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Подключиться к TREZOR ", "ADD_TREZOR_SELECT": "Это код восстановления TREZOR ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Подключиться к Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Перезапустите MyCrypto через безопасное (SSL) соединение ", "ADD_DIGITALBITBOX_0B": "Перезапустите MyCrypto с браузере [Chrome](https://www.google.com/chrome/browser/desktop/) или [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/sk.json b/common/translations/lang/sk.json index 1c2a74b0..23c44e57 100644 --- a/common/translations/lang/sk.json +++ b/common/translations/lang/sk.json @@ -292,6 +292,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Connect to TREZOR ", "ADD_TREZOR_SELECT": "This is a TREZOR seed ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Connect to Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ", "ADD_DIGITALBITBOX_0B": diff --git a/common/translations/lang/sl.json b/common/translations/lang/sl.json index ac19cfb6..26ebdd13 100644 --- a/common/translations/lang/sl.json +++ b/common/translations/lang/sl.json @@ -159,6 +159,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Connect to TREZOR ", "ADD_TREZOR_SELECT": "This is a TREZOR seed ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Connect to Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ", "ADD_DIGITALBITBOX_0B": "Re-open MyCrypto using [Chrome](https://www.google.com/chrome/browser/desktop/) or [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/sv.json b/common/translations/lang/sv.json index 686509eb..96d5ee11 100644 --- a/common/translations/lang/sv.json +++ b/common/translations/lang/sv.json @@ -136,6 +136,8 @@ "ADD_METAMASK": "Connect to MetaMask ", "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Connect to TREZOR ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Connect to Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ", "ADD_DIGITALBITBOX_0B": "Re-open MyCrypto using [Chrome](https://www.google.com/chrome/browser/desktop/) or [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/tr.json b/common/translations/lang/tr.json index e03977ed..e590367c 100644 --- a/common/translations/lang/tr.json +++ b/common/translations/lang/tr.json @@ -289,6 +289,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "TREZOR'a bağlan ", "ADD_TREZOR_SELECT": "This is a TREZOR seed ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Safe-T mini'a bağlan ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ", "ADD_DIGITALBITBOX_0B": "Re-open MyCrypto using [Chrome](https://www.google.com/chrome/browser/desktop/) or [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/vi.json b/common/translations/lang/vi.json index 60e280df..595888d9 100644 --- a/common/translations/lang/vi.json +++ b/common/translations/lang/vi.json @@ -256,6 +256,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Connect to TREZOR ", "ADD_TREZOR_SELECT": "This is a TREZOR seed ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Connect to Safe-T mini ", "X_LEDGER": "Ledger ", "ADD_LEDGER_1": "Kết Nối Với Ledger Wallet Của Bạn ", "ADD_LEDGER_2": "Mở Lên Ứng Dụng Của Ethereum (Hoặc một ứng dụng của Hợp Đồng) ", diff --git a/common/translations/lang/zhcn.json b/common/translations/lang/zhcn.json index d74e5d11..cb3516e2 100644 --- a/common/translations/lang/zhcn.json +++ b/common/translations/lang/zhcn.json @@ -296,6 +296,8 @@ "X_TREZOR": "TREZOR ", "ADD_TREZOR_SCAN": "Connect to TREZOR ", "ADD_TREZOR_SELECT": "This is a TREZOR seed ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "Connect to Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ", "ADD_DIGITALBITBOX_0B": "Re-open MyCrypto using [Chrome](https://www.google.com/chrome/browser/desktop/) or [Opera](https://www.opera.com/) ", diff --git a/common/translations/lang/zhtw.json b/common/translations/lang/zhtw.json index 30bab77d..bfd9e037 100644 --- a/common/translations/lang/zhtw.json +++ b/common/translations/lang/zhtw.json @@ -136,6 +136,8 @@ "ADD_METAMASK": "Connect to MetaMask ", "X_TREZOR": "TREZOR 錢包 ", "ADD_TREZOR_SCAN": "連接至 TREZOR ", + "X_SAFE_T": "Safe-T mini ", + "ADD_SAFE_T_SCAN": "連接至 Safe-T mini ", "X_DIGITALBITBOX": "Digital Bitbox ", "ADD_DIGITALBITBOX_0A": "Re-open MyCrypto on a secure (SSL) connection ", "ADD_DIGITALBITBOX_0B": "以 [Chrome](https://www.google.com/chrome/browser/desktop/) 或 [Opera](https://www.opera.com/) 瀏覽器重新開啟MyCrypto", diff --git a/common/typescript/safe-t-connect.d.ts b/common/typescript/safe-t-connect.d.ts new file mode 100644 index 00000000..f6a37d78 --- /dev/null +++ b/common/typescript/safe-t-connect.d.ts @@ -0,0 +1,69 @@ +declare module 'vendor/safe-t-connect' { + type Path = number[] | string; + + interface TxSignature { + r: number; + s: string; + v: string; + } + + interface MessageSignature { + signature: string; + address: string; + } + + interface PublicKey { + xpubkey: string; + path: string; + serializedPath: string; + chainCode: string; + publicKey: string; + } + + interface ErrorResponse { + success: false; + error: string; + } + type SuccessResponse = { + success: true; + error: undefined; + } & T; + type Response = ErrorResponse | SuccessResponse; + + namespace SafeTConnect { + export function getXPubKey( + path: Path, + cb: (res: Response) => void, + minFirmware?: string + ): void; + + export function ethereumSignTx( + path: Path, + nonce: string, + gasPrice: string, + gasLimit: string, + to: string, + value: string, + data: string | null, + chainId: number | null, + cb: (signature: Response) => void, + minFirmware?: string + ): void; + + export function signMessage( + path: Path, + message: string, + cb: (res: Response) => void, + coin?: string, + minFirmware?: string + ): void; + + export function ethereumGetAddress( + path: Path, + cb: (res: Response<{ address: string }>) => void, + minFirmware?: string + ): void; + } + + export default SafeTConnect; +} diff --git a/common/vendor/safe-t-connect.js b/common/vendor/safe-t-connect.js new file mode 100644 index 00000000..9fd67668 --- /dev/null +++ b/common/vendor/safe-t-connect.js @@ -0,0 +1,1012 @@ +/* eslint-ignore */ +/* prettier-ignore */ + +/** + * (C) 2017 SatoshiLabs + * (C) 2018 Archos S.A. + * + * GPLv3 + */ +var SAFE_T_CONNECT_VERSION = 4; + +if (!Array.isArray) { + Array.isArray = function(arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; +} + +var HD_HARDENED = 0x80000000; + +// react sometimes adds some other parameters that should not be there +function _fwStrFix(obj, fw) { + if (typeof fw === 'string') { + obj.requiredFirmware = fw; + } + return obj; +} + + +'use strict'; + +var chrome = window.chrome; +var IS_CHROME_APP = chrome && chrome.app && chrome.app.window; + +var ERR_TIMED_OUT = 'Loading timed out'; +var ERR_WINDOW_CLOSED = 'Window closed'; +var ERR_WINDOW_BLOCKED = 'Window blocked'; +var ERR_ALREADY_WAITING = 'Already waiting for a response'; +var ERR_CHROME_NOT_CONNECTED = 'Internal Chrome popup is not responding.'; + +var DISABLE_LOGIN_BUTTONS = window.SAFE_T_DISABLE_LOGIN_BUTTONS || false; +var CHROME_URL = window.SAFE_T_CHROME_URL || './chrome/wrapper.html'; +var POPUP_ORIGIN = window.SAFE_T_POPUP_ORIGIN || 'https://connect.safe-t.io'; +var POPUP_PATH = window.SAFE_T_POPUP_PATH || POPUP_ORIGIN + '/' + SAFE_T_CONNECT_VERSION; +var POPUP_URL = window.SAFE_T_POPUP_URL || POPUP_PATH + '/popup/popup.html?v=' + new Date().getTime(); + +var POPUP_INIT_TIMEOUT = 15000; + +/** + * Public API. + */ +function SafeTConnect() { + + var manager = new PopupManager(); + + /** + * Popup errors. + */ + this.ERR_TIMED_OUT = ERR_TIMED_OUT; + this.ERR_WINDOW_CLOSED = ERR_WINDOW_CLOSED; + this.ERR_WINDOW_BLOCKED = ERR_WINDOW_BLOCKED; + this.ERR_ALREADY_WAITING = ERR_ALREADY_WAITING; + this.ERR_CHROME_NOT_CONNECTED = ERR_CHROME_NOT_CONNECTED; + + /** + * Open the popup for further communication. All API functions open the + * popup automatically, but if you need to generate some parameters + * asynchronously, use `open` first to avoid popup blockers. + * @param {function(?Error)} callback + */ + this.open = function (callback) { + var onchannel = function (result) { + if (result instanceof Error) { + callback(result); + } else { + callback(); + } + }; + manager.waitForChannel(onchannel); + }; + + /** + * Close the opened popup, if any. + */ + this.close = function () { manager.close(); }; + + /** + * Enable or disable closing the opened popup after a successful call. + * @param {boolean} value + */ + this.closeAfterSuccess = function (value) { manager.closeAfterSuccess = value; }; + + /** + * Enable or disable closing the opened popup after a failed call. + * @param {boolean} value + */ + this.closeAfterFailure = function (value) { manager.closeAfterFailure = value; }; + + /** + * Set bitcore server + * @param {string|Array} value + */ + this.setBitcoreURLS = function(value) { + if (typeof value === 'string') { + manager.bitcoreURLS = [ value ]; + }else if (value instanceof Array) { + manager.bitcoreURLS = value; + } + } + + /** + * Set currency. Human readable coin name + * @param {string|Array} value + */ + this.setCurrency = function(value) { + if (typeof value === 'string') { + manager.currency = value; + } + } + + /** + * Set currency units (mBTC, BTC) + * @param {string|Array} value + */ + this.setCurrencyUnits = function(value) { + if (typeof value === 'string') { + manager.currencyUnits = value; + } + } + + /** + * Set coin info json url + * @param {string|Array} value + */ + this.setCoinInfoURL = function(value) { + if (typeof value === 'string') { + manager.coinInfoURL = value; + } + } + + /** + * Set max. limit for account discovery + * @param {number} value + */ + this.setAccountDiscoveryLimit = function(value) { + if(!isNaN(value)) + manager.accountDiscoveryLimit = value; + } + + /** + * Set max. gap for account discovery + * @param {number} value + */ + this.setAccountDiscoveryGapLength = function(value) { + if(!isNaN(value)) + manager.accountDiscoveryGapLength = value; + } + + /** + * Set discovery BIP44 coin type + * @param {number} value + */ + this.setAccountDiscoveryBip44CoinType = function(value) { + if(!isNaN(value)) + manager.accountDiscoveryBip44CoinType = value; + } + + /** + * @typedef XPubKeyResult + * @param {boolean} success + * @param {?string} error + * @param {?string} xpubkey serialized extended public key + * @param {?string} path BIP32 serializd path of the key + */ + + /** + * Load BIP32 extended public key by path. + * + * Path can be specified either in the string form ("m/44'/1/0") or as + * raw integer array. In case you omit the path, user is asked to select + * a BIP32 account to export, and the result contains m/44'/0'/x' node + * of the account. + * + * @param {?(string|array)} path + * @param {function(XPubKeyResult)} callback + * @param {?(string|array)} requiredFirmware + */ + this.getXPubKey = function (path, callback, requiredFirmware) { + if (typeof path === 'string') { + path = parseHDPath(path); + } + manager.sendWithChannel(_fwStrFix({ + type: 'xpubkey', + path: path + }, requiredFirmware), callback); + }; + + this.getFreshAddress = function (callback, requiredFirmware) { + var wrapperCallback = function (result) { + if (result.success) { + callback({success: true, address: result.freshAddress}); + } else { + callback(result); + } + } + + manager.sendWithChannel(_fwStrFix({ + type: 'accountinfo' + }, requiredFirmware), wrapperCallback); + } + + this.getAccountInfo = function (input, callback, requiredFirmware) { + try { + manager.sendWithChannel(_fwStrFix({ + type: 'accountinfo', + description: input + }, requiredFirmware), callback); + } catch(e) { + callback({success: false, error: e}); + } + } + + this.getAllAccountsInfo = function(callback, requiredFirmware){ + try { + manager.sendWithChannel(_fwStrFix({ + type: 'allaccountsinfo', + description: 'all' + }, requiredFirmware), callback); + } catch(e) { + callback({success: false, error: e}); + } + } + + this.getBalance = function (callback, requiredFirmware) { + manager.sendWithChannel(_fwStrFix({ + type: 'accountinfo' + }, requiredFirmware), callback) + } + + /** + * @typedef SignTxResult + * @param {boolean} success + * @param {?string} error + * @param {?string} serialized_tx serialized tx, in hex, including signatures + * @param {?array} signatures array of input signatures, in hex + */ + + /** + * Sign a transaction in the device and return both serialized + * transaction and the signatures. + * + * @param {array} inputs + * @param {array} outputs + * @param {function(SignTxResult)} callback + * @param {?(string|array)} requiredFirmware + * + * @see https://github.com/archos-safe-t/safe-t-common/blob/master/protob/types.proto + */ + this.signTx = function (inputs, outputs, callback, requiredFirmware, coin) { + manager.sendWithChannel(_fwStrFix({ + type: 'signtx', + inputs: inputs, + outputs: outputs, + coin: coin + }, requiredFirmware), callback); + }; + + // new implementation with ethereum at beginnig + this.ethereumSignTx = function() { + this.signEthereumTx.apply(this, arguments); + } + + // old fallback + this.signEthereumTx = function ( + address_n, + nonce, + gas_price, + gas_limit, + to, + value, + data, + chain_id, + callback, + requiredFirmware + ) { + if (requiredFirmware == null) { + requiredFirmware = '1.0.0'; + } + if (typeof address_n === 'string') { + address_n = parseHDPath(address_n); + } + manager.sendWithChannel(_fwStrFix({ + type: 'signethtx', + address_n: address_n, + nonce: nonce, + gas_price: gas_price, + gas_limit: gas_limit, + to: to, + value: value, + data: data, + chain_id: chain_id, + }, requiredFirmware), callback); + }; + + /** + * @typedef TxRecipient + * @param {number} amount the amount to send, in satoshis + * @param {string} address the address of the recipient + */ + + /** + * Compose a transaction by doing BIP-0044 discovery, letting the user + * select an account, and picking UTXO by internal preferences. + * Transaction is then signed and returned in the same format as + * `signTx`. Only supports BIP-0044 accounts (single-signature). + * + * @param {array} recipients + * @param {function(SignTxResult)} callback + * @param {?(string|array)} requiredFirmware + */ + this.composeAndSignTx = function (recipients, callback, requiredFirmware) { + manager.sendWithChannel(_fwStrFix({ + type: 'composetx', + recipients: recipients + }, requiredFirmware), callback); + }; + + /** + * @typedef RequestLoginResult + * @param {boolean} success + * @param {?string} error + * @param {?string} public_key public key used for signing, in hex + * @param {?string} signature signature, in hex + */ + + /** + * Sign a login challenge for active origin. + * + * @param {?string} hosticon + * @param {string} challenge_hidden + * @param {string} challenge_visual + * @param {string|function(RequestLoginResult)} callback + * @param {?(string|array)} requiredFirmware + * + * @see https://github.com/archos-safe-t/safe-t-common/blob/master/protob/messages.proto + */ + this.requestLogin = function ( + hosticon, + challenge_hidden, + challenge_visual, + callback, + requiredFirmware + ) { + if (typeof callback === 'string') { + // special case for a login through button. + // `callback` is name of global var + callback = window[callback]; + } + if (!callback) { + throw new TypeError('SafeTConnect: login callback not found'); + } + manager.sendWithChannel(_fwStrFix({ + type: 'login', + icon: hosticon, + challenge_hidden: challenge_hidden, + challenge_visual: challenge_visual + }, requiredFirmware), callback); + }; + + /** + * @typedef SignMessageResult + * @param {boolean} success + * @param {?string} error + * @param {?string} address address (in base58check) + * @param {?string} signature signature, in base64 + */ + + /** + * Sign a message + * + * @param {string|array} path + * @param {string} message to sign (ascii) + * @param {string|function(SignMessageResult)} callback + * @param {?string} opt_coin - (optional) name of coin (default Bitcoin) + * @param {?(string|array)} requiredFirmware + * + */ + this.signMessage = function ( + path, + message, + callback, + opt_coin, + requiredFirmware + ) { + if (typeof path === 'string') { + path = parseHDPath(path); + } + if (!opt_coin) { + opt_coin = 'Bitcoin'; + } + if (!callback) { + throw new TypeError('SafeTConnect: callback not found'); + } + manager.sendWithChannel(_fwStrFix({ + type: 'signmsg', + path: path, + message: message, + coin: opt_coin, + }, requiredFirmware), callback); + }; + + /** + * Sign an Ethereum message + * + * @param {string|array} path + * @param {string} message to sign (ascii) + * @param {string|function(SignMessageResult)} callback + * @param {?(string|array)} requiredFirmware + * + */ + this.ethereumSignMessage = function ( + path, + message, + callback, + requiredFirmware + ) { + if (typeof path === 'string') { + path = parseHDPath(path); + } + if (!callback) { + throw new TypeError('SafeTConnect: callback not found'); + } + manager.sendWithChannel(_fwStrFix({ + type: 'signethmsg', + path: path, + message: message, + }, requiredFirmware), callback); + }; + + /** + * Verify message + * + * @param {string} address + * @param {string} signature (base64) + * @param {string} message (string) + * @param {string|function()} callback + * @param {?string} opt_coin - (optional) name of coin (default Bitcoin) + * @param {?(string|array)} requiredFirmware + * + */ + this.verifyMessage = function ( + address, + signature, + message, + callback, + opt_coin, + requiredFirmware + ) { + if (!opt_coin) { + opt_coin = 'Bitcoin'; + } + if (!callback) { + throw new TypeError('SafeTConnect: callback not found'); + } + manager.sendWithChannel(_fwStrFix({ + type: 'verifymsg', + address: address, + signature: signature, + message: message, + coin: {coin_name: opt_coin}, + }, requiredFirmware), callback); + }; + + /** + * Verify ethereum message + * + * @param {string} address + * @param {string} signature (base64) + * @param {string} message (string) + * @param {string|function()} callback + * @param {?(string|array)} requiredFirmware + * + */ + this.ethereumVerifyMessage = function ( + address, + signature, + message, + callback, + requiredFirmware + ) { + if (!callback) { + throw new TypeError('SafeTConnect: callback not found'); + } + manager.sendWithChannel(_fwStrFix({ + type: 'verifyethmsg', + address: address, + signature: signature, + message: message, + }, requiredFirmware), callback); + }; + + /** + * Symmetric key-value encryption + * + * @param {string|array} path + * @param {string} key to show on device display + * @param {string} value hexadecimal value, length a multiple of 16 bytes + * @param {boolean} encrypt / decrypt direction + * @param {boolean} ask_on_encrypt (should user confirm on encrypt?) + * @param {boolean} ask_on_decrypt (should user confirm on decrypt?) + * @param {string|function()} callback + * @param {?(string|array)} requiredFirmware + * + */ + this.cipherKeyValue = function ( + path, + key, + value, + encrypt, + ask_on_encrypt, + ask_on_decrypt, + callback, + requiredFirmware + ) { + if (typeof path === 'string') { + path = parseHDPath(path); + } + if (typeof value !== 'string') { + throw new TypeError('SafeTConnect: Value must be a string'); + } + if (!(/^[0-9A-Fa-f]*$/.test(value))) { + throw new TypeError('SafeTConnect: Value must be hexadecimal'); + } + if (value.length % 32 !== 0) { + // 1 byte == 2 hex strings + throw new TypeError('SafeTConnect: Value length must be multiple of 16 bytes'); + } + if (!callback) { + throw new TypeError('SafeTConnect: callback not found'); + } + manager.sendWithChannel(_fwStrFix({ + type: 'cipherkeyvalue', + path: path, + key: key, + value: value, + encrypt: !!encrypt, + ask_on_encrypt: !!ask_on_encrypt, + ask_on_decrypt: !!ask_on_decrypt + }, requiredFirmware), callback); + }; + + this.nemGetAddress = function ( + address_n, + network, + callback, + requiredFirmware + ) { + if (requiredFirmware == null) { + requiredFirmware = '1.0.0'; + } + if (typeof address_n === 'string') { + address_n = parseHDPath(address_n); + } + manager.sendWithChannel(_fwStrFix({ + type: 'nemGetAddress', + address_n: address_n, + network: network, + }, requiredFirmware), callback); + } + + this.nemSignTx = function ( + address_n, + transaction, + callback, + requiredFirmware + ) { + if (requiredFirmware == null) { + requiredFirmware = '1.0.0'; + } + if (typeof address_n === 'string') { + address_n = parseHDPath(address_n); + } + manager.sendWithChannel(_fwStrFix({ + type: 'nemSignTx', + address_n: address_n, + transaction: transaction + }, requiredFirmware), callback); + } + + this.pushTransaction = function ( + rawTx, + callback + ) { + if (!(/^[0-9A-Fa-f]*$/.test(rawTx))) { + throw new TypeError('SafeTConnect: Transaction must be hexadecimal'); + } + if (!callback) { + throw new TypeError('SafeTConnect: callback not found'); + } + + manager.sendWithChannel({ + type: 'pushtx', + rawTx: rawTx, + }, callback); + } + + /** + * Display address on device + * + * @param {array} address + * @param {string} coin + * @param {boolean} segwit + * @param {?(string|array)} requiredFirmware + * + */ + this.getAddress = function (address, coin, segwit, callback, requiredFirmware) { + + if (typeof address === 'string') { + address = parseHDPath(address); + } + + manager.sendWithChannel(_fwStrFix({ + type: 'getaddress', + address_n: address, + coin: coin, + segwit: segwit + }, requiredFirmware), callback); + } + + /** + * Display ethereum address on device + * + * @param {array} address + * @param {?(string|array)} requiredFirmware + * + */ + this.ethereumGetAddress = function (address, callback, requiredFirmware) { + + if (typeof address === 'string') { + address = parseHDPath(address); + } + + manager.sendWithChannel(_fwStrFix({ + type: 'ethgetaddress', + address_n: address, + }, requiredFirmware), callback); + } + + var LOGIN_CSS = + ''; + + var LOGIN_ONCLICK = + 'SafeTConnect.requestLogin(' + + "'@hosticon@','@challenge_hidden@','@challenge_visual@','@callback@'" + + ')'; + + var LOGIN_HTML = + '
' + + ' ' + + ' ' + + ' @text@' + + ' ' + + ' ' + + ' What is a Safe-T mini?' + + ' ' + + '
'; + + /** + * Find elements and replace them with login buttons. + * It's not required to use these special elements, feel free to call + * `SafeTConnect.requestLogin` directly. + */ + this.renderLoginButtons = function () { + var elements = document.getElementsByTagName('safe-t:login'); + + for (var i = 0; i < elements.length; i++) { + var e = elements[i]; + var text = e.getAttribute('text') || 'Sign in with your SAFE_T'; + var callback = e.getAttribute('callback') || ''; + var hosticon = e.getAttribute('icon') || ''; + var challenge_hidden = e.getAttribute('challenge_hidden') || ''; + var challenge_visual = e.getAttribute('challenge_visual') || ''; + + // it's not valid to put markup into attributes, so let users + // supply a raw text and make Safe-T mini bold + text = text.replace('SAFE_T', 'Safe-T mini'); + e.outerHTML = + (LOGIN_CSS + LOGIN_HTML) + .replace('@text@', text) + .replace('@callback@', callback) + .replace('@hosticon@', hosticon) + .replace('@challenge_hidden@', challenge_hidden) + .replace('@challenge_visual@', challenge_visual) + .replace('@connect_path@', POPUP_PATH); + } + }; +} + +/* + * `getXPubKey()` + */ + +function parseHDPath(string) { + return string + .toLowerCase() + .split('/') + .filter(function (p) { return p !== 'm'; }) + .map(function (p) { + var hardened = false; + if (p[p.length - 1] === "'") { + hardened = true; + p = p.substr(0, p.length - 1); + } + if (isNaN(p)) { + throw new Error('Not a valid path.'); + } + var n = parseInt(p); + if (hardened) { // hardened index + n = (n | 0x80000000) >>> 0; + } + return n; + }); +} + +/* + * Popup management + */ + +function ChromePopup(url, name, width, height) { + var left = (screen.width - width) / 2; + var top = (screen.height - height) / 2; + var opts = { + id: name, + innerBounds: { + width: width, + height: height, + left: left, + top: top + } + }; + + var closed = function () { + if (this.onclose) { + this.onclose(false); // never report as blocked + } + }.bind(this); + + var opened = function (w) { + this.window = w; + this.window.onClosed.addListener(closed); + }.bind(this); + + chrome.app.window.create(url, opts, opened); + + this.name = name; + this.window = null; + this.onclose = null; +} + +function ChromeChannel(popup, waiting) { + var port = null; + + var respond = function (data) { + if (waiting) { + var w = waiting; + waiting = null; + w(data); + } + }; + + var setup = function (p) { + if (p.name === popup.name) { + port = p; + port.onMessage.addListener(respond); + chrome.runtime.onConnect.removeListener(setup); + } + }; + + chrome.runtime.onConnect.addListener(setup); + + this.respond = respond; + + this.close = function () { + chrome.runtime.onConnect.removeListener(setup); + port.onMessage.removeListener(respond); + port.disconnect(); + port = null; + }; + + this.send = function (value, callback) { + if (waiting === null) { + waiting = callback; + + if (port) { + port.postMessage(value); + } else { + throw new Error(ERR_CHROME_NOT_CONNECTED); + } + } else { + throw new Error(ERR_ALREADY_WAITING); + } + }; +} + +function Popup(url, origin, name, width, height) { + var left = (screen.width - width) / 2; + var top = (screen.height - height) / 2; + var opts = + 'width=' + width + + ',height=' + height + + ',left=' + left + + ',top=' + top + + ',menubar=no' + + ',toolbar=no' + + ',location=no' + + ',personalbar=no' + + ',status=no'; + var w = window.open(url, name, opts); + + var interval; + var blocked = w.closed; + var iterate = function () { + if (w.closed) { + clearInterval(interval); + if (this.onclose) { + this.onclose(blocked); + } + } + }.bind(this); + interval = setInterval(iterate, 100); + + this.window = w; + this.origin = origin; + this.onclose = null; +} + +function Channel(popup, waiting) { + + var respond = function (data) { + if (waiting) { + var w = waiting; + waiting = null; + w(data); + } + }; + + var receive = function (event) { + var org1 = event.origin.match(/^.+\:\/\/[^\‌​/]+/)[0]; + var org2 = popup.origin.match(/^.+\:\/\/[^\‌​/]+/)[0]; + //if (event.source === popup.window && event.origin === popup.origin) { + if (event.source === popup.window && org1 === org2) { + respond(event.data); + } + }; + + window.addEventListener('message', receive); + + this.respond = respond; + + this.close = function () { + window.removeEventListener('message', receive); + }; + + this.send = function (value, callback) { + if (waiting === null) { + waiting = callback; + popup.window.postMessage(value, popup.origin); + } else { + throw new Error(ERR_ALREADY_WAITING); + } + }; +} + +function ConnectedChannel(p) { + + var ready = function () { + clearTimeout(this.timeout); + this.popup.onclose = null; + this.ready = true; + this.onready(); + }.bind(this); + + var closed = function (blocked) { + clearTimeout(this.timeout); + this.channel.close(); + if (blocked) { + this.onerror(new Error(ERR_WINDOW_BLOCKED)); + } else { + this.onerror(new Error(ERR_WINDOW_CLOSED)); + } + }.bind(this); + + var timedout = function () { + this.popup.onclose = null; + if (this.popup.window) { + this.popup.window.close(); + } + this.channel.close(); + this.onerror(new Error(ERR_TIMED_OUT)); + }.bind(this); + + if (IS_CHROME_APP) { + this.popup = new ChromePopup(p.chromeUrl, p.name, p.width, p.height); + this.channel = new ChromeChannel(this.popup, ready); + } else { + this.popup = new Popup(p.url, p.origin, p.name, p.width, p.height); + this.channel = new Channel(this.popup, ready); + } + + this.timeout = setTimeout(timedout, POPUP_INIT_TIMEOUT); + + this.popup.onclose = closed; + + this.ready = false; + this.onready = null; + this.onerror = null; +} + +function PopupManager() { + var cc = null; + + var closed = function () { + cc.channel.respond(new Error(ERR_WINDOW_CLOSED)); + cc.channel.close(); + cc = null; + }; + + var open = function (callback) { + cc = new ConnectedChannel({ + name: 'safe-t-connect', + width: 600, + height: 500, + origin: POPUP_ORIGIN, + path: POPUP_PATH, + url: POPUP_URL, + chromeUrl: CHROME_URL + }); + cc.onready = function () { + cc.popup.onclose = closed; + callback(cc.channel); + }; + cc.onerror = function (error) { + cc = null; + callback(error); + }; + }.bind(this); + + this.closeAfterSuccess = true; + this.closeAfterFailure = true; + + this.close = function () { + if (cc && cc.popup.window) { + cc.popup.window.close(); + } + }; + + this.waitForChannel = function (callback) { + if (cc) { + if (cc.ready) { + callback(cc.channel); + } else { + callback(new Error(ERR_ALREADY_WAITING)); + } + } else { + try { + open(callback); + } catch (e) { + callback(new Error(ERR_WINDOW_BLOCKED)); + } + } + }; + + this.sendWithChannel = function (message, callback) { + message.bitcoreURLS = this.bitcoreURLS || null; + message.accountDiscoveryLimit = this.accountDiscoveryLimit || null; + message.accountDiscoveryGapLength = this.accountDiscoveryGapLength || null; + message.accountDiscoveryBip44CoinType = this.accountDiscoveryBip44CoinType || null; + + var respond = function (response) { + var succ = response.success && this.closeAfterSuccess; + var fail = !response.success && this.closeAfterFailure; + if (succ || fail) { + this.close(); + } + callback(response); + }.bind(this); + + var onresponse = function (response) { + if (response instanceof Error) { + var error = response; + respond({ success: false, error: error.message }); + } else { + respond(response); + } + }; + + var onchannel = function (channel) { + if (channel instanceof Error) { + var error = channel; + respond({ success: false, error: error.message }); + } else { + channel.send(message, onresponse); + } + }; + + this.waitForChannel(onchannel); + }; +} + +var connect = new SafeTConnect(); + +module.exports = connect; diff --git a/shared/enclave/server/wallets/index.ts b/shared/enclave/server/wallets/index.ts index 7d091905..31a22d37 100644 --- a/shared/enclave/server/wallets/index.ts +++ b/shared/enclave/server/wallets/index.ts @@ -1,11 +1,13 @@ import { WalletTypes, WalletLib } from 'shared/enclave/types'; import Ledger from './ledger'; import Trezor from './trezor'; +import SafeT from './safe-t'; import KeepKey from './keepkey'; export const wallets: { [key in WalletTypes]: WalletLib } = { [WalletTypes.LEDGER]: Ledger, [WalletTypes.TREZOR]: Trezor, + [WalletTypes.SAFE_T]: SafeT, [WalletTypes.KEEPKEY]: KeepKey }; diff --git a/shared/enclave/server/wallets/safe-t.ts b/shared/enclave/server/wallets/safe-t.ts new file mode 100644 index 00000000..27adcd17 --- /dev/null +++ b/shared/enclave/server/wallets/safe-t.ts @@ -0,0 +1,21 @@ +import { WalletLib } from 'shared/enclave/types'; + +const SafeT: WalletLib = { + async getChainCode() { + throw new Error('Not yet implemented'); + }, + + async signTransaction() { + throw new Error('Not yet implemented'); + }, + + async signMessage() { + throw new Error('Not yet implemented'); + }, + + async displayAddress() { + throw new Error('Not yet implemented'); + } +}; + +export default SafeT; diff --git a/shared/enclave/types.ts b/shared/enclave/types.ts index 8d2859ca..afa66f48 100644 --- a/shared/enclave/types.ts +++ b/shared/enclave/types.ts @@ -9,6 +9,7 @@ export enum EnclaveMethods { export enum WalletTypes { LEDGER = 'ledger', TREZOR = 'trezor', + SAFE_T = 'safe-t', KEEPKEY = 'keepkey' } diff --git a/shared/types/network.d.ts b/shared/types/network.d.ts index fd609964..12a55871 100644 --- a/shared/types/network.d.ts +++ b/shared/types/network.d.ts @@ -44,6 +44,7 @@ interface NetworkContract { interface DPathFormats { trezor?: DPath; + safeTmini?: DPath; ledgerNanoS?: DPath; mnemonicPhrase: DPath; } From e242d0960c2bec2ae6c1da6a8df68b7809b69850 Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Tue, 17 Jul 2018 12:47:07 -0700 Subject: [PATCH 4/5] Update Version to 1.2.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 12538f5e..1780c119 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "MyCrypto", "author": "MyCryptoHQ", - "version": "1.2.1", + "version": "1.2.2", "main": "main.js", "description": "MyCrypto web and electron app", "repository": "https://github.com/MyCryptoHQ/MyCrypto", From 48304699a1292bcb2c3cc5556bba2cdfbfae359b Mon Sep 17 00:00:00 2001 From: mwbailey Date: Wed, 18 Jul 2018 15:05:41 -0700 Subject: [PATCH 5/5] Discard signed message after leaving tab (#2056) --- .../components/SignMessage/index.tsx | 7 ++++++- common/features/message/actions.ts | 7 +++++++ common/features/message/reducer.ts | 8 ++++++++ common/features/message/types.ts | 10 ++++++++-- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/common/containers/Tabs/SignAndVerifyMessage/components/SignMessage/index.tsx b/common/containers/Tabs/SignAndVerifyMessage/components/SignMessage/index.tsx index b2d41fa4..a1f96364 100644 --- a/common/containers/Tabs/SignAndVerifyMessage/components/SignMessage/index.tsx +++ b/common/containers/Tabs/SignAndVerifyMessage/components/SignMessage/index.tsx @@ -18,6 +18,7 @@ interface Props { signMessageRequested: messageActions.TSignMessageRequested; signedMessage: ISignedMessage | null; resetWallet: walletActions.TResetWallet; + resetMessage: messageActions.TResetMessage; } interface State { @@ -35,6 +36,7 @@ export class SignMessage extends Component { public componentWillUnmount() { this.props.resetWallet(); + this.props.resetMessage(); } public render() { @@ -97,6 +99,8 @@ export class SignMessage extends Component { private changeWallet = () => { this.props.resetWallet(); + this.props.resetMessage(); + this.setState(initialState); }; } @@ -107,5 +111,6 @@ const mapStateToProps = (state: AppState) => ({ export default connect(mapStateToProps, { signMessageRequested: messageActions.signMessageRequested, - resetWallet: walletActions.resetWallet + resetWallet: walletActions.resetWallet, + resetMessage: messageActions.resetMessage })(SignMessage); diff --git a/common/features/message/actions.ts b/common/features/message/actions.ts index e9621d29..7f06fc2c 100644 --- a/common/features/message/actions.ts +++ b/common/features/message/actions.ts @@ -25,3 +25,10 @@ export function signMessageFailed(): types.SignMessageFailedAction { type: types.MessageActions.SIGN_FAILED }; } + +export type TResetMessage = typeof resetMessage; +export function resetMessage(): types.ResetMessageAction { + return { + type: types.MessageActions.RESET + }; +} diff --git a/common/features/message/reducer.ts b/common/features/message/reducer.ts index 1e95f155..97ffa316 100644 --- a/common/features/message/reducer.ts +++ b/common/features/message/reducer.ts @@ -21,6 +21,12 @@ function signMessageFailed(state: types.MessageState): types.MessageState { }; } +function resetMessage(): types.MessageState { + return { + ...INITIAL_STATE + }; +} + export function messageReducer( state: types.MessageState = INITIAL_STATE, action: types.MessageAction @@ -30,6 +36,8 @@ export function messageReducer( return signLocalMessageSucceeded(state, action); case types.MessageActions.SIGN_FAILED: return signMessageFailed(state); + case types.MessageActions.RESET: + return resetMessage(); default: return state; } diff --git a/common/features/message/types.ts b/common/features/message/types.ts index ad4810f5..197fc5ed 100644 --- a/common/features/message/types.ts +++ b/common/features/message/types.ts @@ -3,7 +3,8 @@ import { ISignedMessage } from 'libs/signing'; export enum MessageActions { SIGN_REQUESTED = 'MESSAGE_SIGN_REQUESTED', SIGN_LOCAL_SUCCEEDED = 'MESSAGE_SIGN_LOCAL_SUCCEEDED', - SIGN_FAILED = 'MESSAGE_SIGN_FAILED' + SIGN_FAILED = 'MESSAGE_SIGN_FAILED', + RESET = 'MESSAGE_RESET' } export interface MessageState { @@ -24,7 +25,12 @@ export interface SignMessageFailedAction { type: MessageActions.SIGN_FAILED; } +export interface ResetMessageAction { + type: MessageActions.RESET; +} + export type MessageAction = | SignMessageRequestedAction | SignLocalMessageSucceededAction - | SignMessageFailedAction; + | SignMessageFailedAction + | ResetMessageAction;