diff options
author | frankiebee <frankie.diamond@gmail.com> | 2018-03-14 06:27:26 +0800 |
---|---|---|
committer | frankiebee <frankie.diamond@gmail.com> | 2018-03-14 06:27:26 +0800 |
commit | a2c14ad02b6f080efe18535d64efe4acdaa5f310 (patch) | |
tree | a4f10bab758b35c5344557907c7c95a396e4fbb9 /app | |
parent | c465d510b100fdf9926413751df04cbd59de68eb (diff) | |
parent | c83a9ceb04a485149fe65fbb2b44f0adeda696b1 (diff) | |
download | tangerine-wallet-browser-a2c14ad02b6f080efe18535d64efe4acdaa5f310.tar tangerine-wallet-browser-a2c14ad02b6f080efe18535d64efe4acdaa5f310.tar.gz tangerine-wallet-browser-a2c14ad02b6f080efe18535d64efe4acdaa5f310.tar.bz2 tangerine-wallet-browser-a2c14ad02b6f080efe18535d64efe4acdaa5f310.tar.lz tangerine-wallet-browser-a2c14ad02b6f080efe18535d64efe4acdaa5f310.tar.xz tangerine-wallet-browser-a2c14ad02b6f080efe18535d64efe4acdaa5f310.tar.zst tangerine-wallet-browser-a2c14ad02b6f080efe18535d64efe4acdaa5f310.zip |
Merge remote-tracking branch 'origin/i#3509' into i#3509
Diffstat (limited to 'app')
-rw-r--r-- | app/_locales/en/messages.json | 605 | ||||
-rw-r--r-- | app/manifest.json | 12 | ||||
-rw-r--r-- | app/scripts/controllers/transactions.js | 4 | ||||
-rw-r--r-- | app/scripts/lib/seed-phrase-verifier.js | 48 | ||||
-rw-r--r-- | app/scripts/lib/tx-state-manager.js | 2 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 53 |
6 files changed, 703 insertions, 21 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 8c28f1c43..1ca31427d 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1,10 +1,609 @@ { + "accept": { + "message": "Accept" + }, + "account": { + "message": "Account" + }, + "accountDetails": { + "message": "Account Details" + }, + "accountName": { + "message": "Account Name" + }, + "address": { + "message": "Address" + }, + "addToken": { + "message": "Add Token" + }, + "amount": { + "message": "Amount" + }, + "amountPlusGas": { + "message": "Amount + Gas" + }, + "appDescription": { + "message": "Ethereum Browser Extension", + "description": "The description of the application" + }, "appName": { "message": "MetaMask", "description": "The name of the application" }, - "appDescription": { - "message": "Ethereum Identity Management", - "description": "The description of the application" + "attemptingConnect": { + "message": "Attempting to connect to blockchain." + }, + "available": { + "message": "Available" + }, + "back": { + "message": "Back" + }, + "balance": { + "message": "Balance:" + }, + "balanceIsInsufficientGas": { + "message": "Insufficient balance for current gas total" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "must be greater than or equal to $1 and less than or equal to $2.", + "description": "helper for inputting hex as decimal input" + }, + "borrowDharma": { + "message": "Borrow With Dharma (Beta)" + }, + "buy": { + "message": "Buy" + }, + "buyCoinbase": { + "message": "Buy on Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin." + }, + "cancel": { + "message": "Cancel" + }, + "clickCopy": { + "message": "Click to Copy" + }, + "confirm": { + "message": "Confirm" + }, + "confirmContract": { + "message": "Confirm Contract" + }, + "confirmPassword": { + "message": "Confirm Password" + }, + "confirmTransaction": { + "message": "Confirm Transaction" + }, + "continueToCoinbase": { + "message": "Continue to Coinbase" + }, + "contractDeployment": { + "message": "Contract Deployment" + }, + "conversionProgress": { + "message": "Conversion in progress" + }, + "copiedButton": { + "message": "Copied" + }, + "copiedClipboard": { + "message": "Copied to Clipboard" + }, + "copiedExclamation": { + "message": "Copied!" + }, + "copy": { + "message": "Copy" + }, + "copyToClipboard": { + "message": "Copy to clipboard" + }, + "copyButton": { + "message": " Copy " + }, + "copyPrivateKey": { + "message": "This is your private key (click to copy)" + }, + "create": { + "message": "Create" + }, + "createAccount": { + "message": "Create Account" + }, + "createDen": { + "message": "Create" + }, + "crypto": { + "message": "Crypto", + "description": "Exchange type (cryptocurrencies)" + }, + "customGas": { + "message": "Customize Gas" + }, + "customize": { + "message": "Customize" + }, + "customRPC": { + "message": "Custom RPC" + }, + "defaultNetwork": { + "message": "The default network for Ether transactions is Main Net." + }, + "denExplainer": { + "message": "Your DEN is your password-encrypted storage within MetaMask." + }, + "deposit": { + "message": "Deposit" + }, + "depositBTC": { + "message": "Deposit your BTC to the address below:" + }, + "depositCoin": { + "message": "Deposit your $1 to the address below", + "description": "Tells the user what coin they have selected to deposit with shapeshift" + }, + "depositEth": { + "message": "Deposit Eth" + }, + "depositEther": { + "message": "Deposit Ether" + }, + "depositFiat": { + "message": "Deposit with Fiat" + }, + "depositFromAccount": { + "message": "Deposit from another account" + }, + "depositShapeShift": { + "message": "Deposit with ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "If you own other cryptocurrencies, you can trade and deposit Ether directly into your MetaMask wallet. No Account Needed." + }, + "details": { + "message": "Details" + }, + "directDeposit": { + "message": "Direct Deposit" + }, + "directDepositEther": { + "message": "Directly Deposit Ether" + }, + "directDepositEtherExplainer": { + "message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit." + }, + "done": { + "message": "Done" + }, + "edit": { + "message": "Edit" + }, + "editAccountName": { + "message": "Edit Account Name" + }, + "encryptNewDen": { + "message": "Encrypt your new DEN" + }, + "enterPassword": { + "message": "Enter password" + }, + "etherscanView": { + "message": "View account on Etherscan" + }, + "exchangeRate": { + "message": "Exchange Rate" + }, + "exportPrivateKey": { + "message": "Export Private Key" + }, + "exportPrivateKeyWarning": { + "message": "Export private keys at your own risk." + }, + "failed": { + "message": "Failed" + }, + "fiat": { + "message": "FIAT", + "description": "Exchange type" + }, + "fileImportFail": { + "message": "File import not working? Click here!", + "description": "Helps user import their account from a JSON file" + }, + "from": { + "message": "From" + }, + "fromShapeShift": { + "message": "From ShapeShift" + }, + "gas": { + "message": "Gas", + "description": "Short indication of gas cost" + }, + "gasFee": { + "message": "Gas Fee" + }, + "gasLimit": { + "message": "Gas Limit" + }, + "gasLimitCalculation": { + "message": "We calculate the suggested gas limit based on network success rates." + }, + "gasLimitRequired": { + "message": "Gas Limit Required" + }, + "gasLimitTooLow": { + "message": "Gas limit must be at least 21000" + }, + "gasPrice": { + "message": "Gas Price (GWEI)" + }, + "gasPriceCalculation": { + "message": "We calculate the suggested gas prices based on network success rates." + }, + "gasPriceRequired": { + "message": "Gas Price Required" + }, + "getEther": { + "message": "Get Ether" + }, + "getEtherFromFaucet": { + "message": "Get Ether from a faucet for the $1", + "description": "Displays network name for Ether faucet" + }, + "greaterThanMin": { + "message": "must be greater than or equal to $1.", + "description": "helper for inputting hex as decimal input" + }, + "here": { + "message": "here", + "description": "as in -click here- for more information (goes with troubleTokenBalances)" + }, + "hide": { + "message": "Hide" + }, + "hideToken": { + "message": "Hide Token" + }, + "hideTokenPrompt": { + "message": "Hide Token?" + }, + "howToDeposit": { + "message": "How would you like to deposit Ether?" + }, + "import": { + "message": "Import", + "description": "Button to import an account from a selected file" + }, + "importAccount": { + "message": "Import Account" + }, + "importAnAccount": { + "message": "Import an account" + }, + "importDen": { + "message": "Import Existing DEN" + }, + "imported": { + "message": "Imported", + "description": "status showing that an account has been fully loaded into the keyring" + }, + "infoHelp": { + "message": "Info & Help" + }, + "invalidAddress": { + "message": "Invalid address" + }, + "invalidGasParams": { + "message": "Invalid Gas Parameters" + }, + "invalidInput": { + "message": "Invalid input." + }, + "invalidRequest": { + "message": "Invalid Request" + }, + "jsonFile": { + "message": "JSON File", + "description": "format for importing an account" + }, + "kovan": { + "message": "Kovan Test Network" + }, + "lessThanMax": { + "message": "must be less than or equal to $1.", + "description": "helper for inputting hex as decimal input" + }, + "limit": { + "message": "Limit" + }, + "loading": { + "message": "Loading..." + }, + "loadingTokens": { + "message": "Loading Tokens..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "logout": { + "message": "Log out" + }, + "loose": { + "message": "Loose" + }, + "mainnet": { + "message": "Main Ethereum Network" + }, + "message": { + "message": "Message" + }, + "min": { + "message": "Minimum" + }, + "myAccounts": { + "message": "My Accounts" + }, + "needEtherInWallet": { + "message": "To interact with decentralized applications using MetaMask, you’ll need Ether in your wallet." + }, + "needImportFile": { + "message": "You must select a file to import.", + "description": "User is important an account and needs to add a file to continue" + }, + "needImportPassword": { + "message": "You must enter a password for the selected file.", + "description": "Password and file needed to import an account" + }, + "networks": { + "message": "Networks" + }, + "newAccount": { + "message": "New Account" + }, + "newAccountNumberName": { + "message": "Account $1", + "description": "Default name of next account to be created on create account screen" + }, + "newContract": { + "message": "New Contract" + }, + "newPassword": { + "message": "New Password (min 8 chars)" + }, + "newRecipient": { + "message": "New Recipient" + }, + "next": { + "message": "Next" + }, + "noAddressForName": { + "message": "No address has been set for this name." + }, + "noDeposits": { + "message": "No deposits received" + }, + "noTransactionHistory": { + "message": "No transaction history." + }, + "noTransactions": { + "message": "No Transactions" + }, + "notStarted": { + "message": "Not Started" + }, + "oldUI": { + "message": "Old UI" + }, + "oldUIMessage": { + "message": "You have returned to the old UI. You can switch back to the New UI through the option in the top right dropdown menu." + }, + "or": { + "message": "or", + "description": "choice between creating or importing a new account" + }, + "passwordMismatch": { + "message": "passwords don't match", + "description": "in password creation process, the two new password fields did not match" + }, + "passwordShort": { + "message": "password not long enough", + "description": "in password creation process, the password is not long enough to be secure" + }, + "pastePrivateKey": { + "message": "Paste your private key string here:", + "description": "For importing an account from a private key" + }, + "pasteSeed": { + "message": "Paste your seed phrase here!" + }, + "pleaseReviewTransaction": { + "message": "Please review your transaction." + }, + "privateKey": { + "message": "Private Key", + "description": "select this type of file to use to import an account" + }, + "privateKeyWarning": { + "message": "Warning: Never disclose this key. Anyone with your private keys can take steal any assets held in your account." + }, + "privateNetwork": { + "message": "Private Network" + }, + "qrCode": { + "message": "Show QR Code" + }, + "readdToken": { + "message": "You can add this token back in the future by going go to “Add token” in your accounts options menu." + }, + "readMore": { + "message": "Read more here." + }, + "receive": { + "message": "Receive" + }, + "recipientAddress": { + "message": "Recipient Address" + }, + "refundAddress": { + "message": "Your Refund Address" + }, + "rejected": { + "message": "Rejected" + }, + "required": { + "message": "Required" + }, + "retryWithMoreGas": { + "message": "Retry with a higher gas price here" + }, + "revert": { + "message": "Revert" + }, + "rinkeby": { + "message": "Rinkeby Test Network" + }, + "ropsten": { + "message": "Ropsten Test Network" + }, + "sampleAccountName": { + "message": "E.g. My new account", + "description": "Help user understand concept of adding a human-readable name to their account" + }, + "save": { + "message": "Save" + }, + "saveAsFile": { + "message": "Save as File", + "description": "Account export process" + }, + "selectService": { + "message": "Select Service" + }, + "send": { + "message": "Send" + }, + "sendTokens": { + "message": "Send Tokens" + }, + "sendTokensAnywhere": { + "message": "Send Tokens to anyone with an Ethereum account" + }, + "settings": { + "message": "Settings" + }, + "shapeshiftBuy": { + "message": "Buy with Shapeshift" + }, + "showPrivateKeys": { + "message": "Show Private Keys" + }, + "showQRCode": { + "message": "Show QR Code" + }, + "sign": { + "message": "Sign" + }, + "signMessage": { + "message": "Sign Message" + }, + "signNotice": { + "message": "Signing this message can have \ndangerous side effects. Only sign messages from \nsites you fully trust with your entire account.\n This dangerous method will be removed in a future version. " + }, + "sigRequest": { + "message": "Signature Request" + }, + "sigRequested": { + "message": "Signature Requested" + }, + "status": { + "message": "Status" + }, + "submit": { + "message": "Submit" + }, + "takesTooLong": { + "message": "Taking too long?" + }, + "testFaucet": { + "message": "Test Faucet" + }, + "to": { + "message": "To" + }, + "toETHviaShapeShift": { + "message": "$1 to ETH via ShapeShift", + "description": "system will fill in deposit type in start of message" + }, + "tokenBalance": { + "message": "Your Token Balance is:" + }, + "total": { + "message": "Total" + }, + "transactionMemo": { + "message": "Transaction memo (optional)" + }, + "transactionNumber": { + "message": "Transaction Number" + }, + "transfers": { + "message": "Transfers" + }, + "troubleTokenBalances": { + "message": "We had trouble loading your token balances. You can view them ", + "description": "Followed by a link (here) to view token balances" + }, + "typePassword": { + "message": "Type Your Password" + }, + "uiWelcome": { + "message": "Welcome to the New UI (Beta)" + }, + "uiWelcomeMessage": { + "message": "You are now using the new Metamask UI. Take a look around, try out new features like sending tokens, and let us know if you have any issues." + }, + "unavailable": { + "message": "Unavailable" + }, + "unknown": { + "message": "Unknown" + }, + "unknownNetwork": { + "message": "Unknown Private Network" + }, + "unknownNetworkId": { + "message": "Unknown network ID" + }, + "usaOnly": { + "message": "USA only", + "description": "Using this exchange is limited to people inside the USA" + }, + "usedByClients": { + "message": "Used by a variety of different clients" + }, + "viewAccount": { + "message": "View Account" + }, + "warning": { + "message": "Warning" + }, + "whatsThis": { + "message": "What's this?" + }, + "yourSigRequested": { + "message": "Your signature is being requested" + }, + "youSign": { + "message": "You are signing" } } diff --git a/app/manifest.json b/app/manifest.json index 2b3acf1b5..6c5ed686b 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,10 +1,10 @@ { - "name": "MetaMask", - "short_name": "Metamask", - "version": "4.1.3", + "name": "__MSG_appName__", + "short_name": "__MSG_appName__", + "version": "4.2.0", "manifest_version": 2, "author": "https://metamask.io", - "description": "Ethereum Browser Extension", + "description": "__MSG_appDescription__", "commands": { "_execute_browser_action": { "suggested_key": { @@ -59,7 +59,7 @@ "clipboardWrite", "http://localhost:8545/", "https://*.infura.io/" - ], + ], "web_accessible_resources": [ "scripts/inpage.js" ], @@ -68,4 +68,4 @@ "https://metamask.io/*" ] } -} +}
\ No newline at end of file diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index ef5578d5a..9c2ca0dc8 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -3,7 +3,7 @@ const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const Transaction = require('ethereumjs-tx') const EthQuery = require('ethjs-query') -const TransactionStateManger = require('../lib/tx-state-manager') +const TransactionStateManager = require('../lib/tx-state-manager') const TxGasUtil = require('../lib/tx-gas-utils') const PendingTransactionTracker = require('../lib/pending-tx-tracker') const createId = require('../lib/random-id') @@ -38,7 +38,7 @@ module.exports = class TransactionController extends EventEmitter { this.query = new EthQuery(this.provider) this.txGasUtil = new TxGasUtil(this.provider) - this.txStateManager = new TransactionStateManger({ + this.txStateManager = new TransactionStateManager({ initState: opts.initState, txHistoryLimit: opts.txHistoryLimit, getNetwork: this.getNetwork.bind(this), diff --git a/app/scripts/lib/seed-phrase-verifier.js b/app/scripts/lib/seed-phrase-verifier.js new file mode 100644 index 000000000..9cea22029 --- /dev/null +++ b/app/scripts/lib/seed-phrase-verifier.js @@ -0,0 +1,48 @@ +const KeyringController = require('eth-keyring-controller') + +const seedPhraseVerifier = { + + // Verifies if the seed words can restore the accounts. + // + // The seed words can recreate the primary keyring and the accounts belonging to it. + // The created accounts in the primary keyring are always the same. + // The keyring always creates the accounts in the same sequence. + verifyAccounts (createdAccounts, seedWords) { + + return new Promise((resolve, reject) => { + + if (!createdAccounts || createdAccounts.length < 1) { + return reject(new Error('No created accounts defined.')) + } + + const keyringController = new KeyringController({}) + const Keyring = keyringController.getKeyringClassForType('HD Key Tree') + const opts = { + mnemonic: seedWords, + numberOfAccounts: createdAccounts.length, + } + + const keyring = new Keyring(opts) + keyring.getAccounts() + .then((restoredAccounts) => { + + log.debug('Created accounts: ' + JSON.stringify(createdAccounts)) + log.debug('Restored accounts: ' + JSON.stringify(restoredAccounts)) + + if (restoredAccounts.length !== createdAccounts.length) { + // this should not happen... + return reject(new Error('Wrong number of accounts')) + } + + for (let i = 0; i < restoredAccounts.length; i++) { + if (restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase()) { + return reject(new Error('Not identical accounts! Original: ' + createdAccounts[i] + ', Restored: ' + restoredAccounts[i])) + } + } + return resolve() + }) + }) + }, +} + +module.exports = seedPhraseVerifier diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js index 051efd247..2eb006380 100644 --- a/app/scripts/lib/tx-state-manager.js +++ b/app/scripts/lib/tx-state-manager.js @@ -4,7 +4,7 @@ const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const txStateHistoryHelper = require('./tx-state-history-helper') -module.exports = class TransactionStateManger extends EventEmitter { +module.exports = class TransactionStateManager extends EventEmitter { constructor ({ initState, txHistoryLimit, getNetwork }) { super() diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ad4e71792..0a5c1d36f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -37,6 +37,7 @@ const version = require('../manifest.json').version const BN = require('ethereumjs-util').BN const GWEI_BN = new BN('1000000000') const percentile = require('percentile') +const seedPhraseVerifier = require('./lib/seed-phrase-verifier') module.exports = class MetamaskController extends EventEmitter { @@ -344,6 +345,7 @@ module.exports = class MetamaskController extends EventEmitter { // primary HD keyring management addNewAccount: nodeify(this.addNewAccount, this), placeSeedWords: this.placeSeedWords.bind(this), + verifySeedPhrase: nodeify(this.verifySeedPhrase, this), clearSeedWordCache: this.clearSeedWordCache.bind(this), resetAccount: this.resetAccount.bind(this), importAccountWithStrategy: this.importAccountWithStrategy.bind(this), @@ -565,14 +567,18 @@ module.exports = class MetamaskController extends EventEmitter { // Opinionated Keyring Management // - async addNewAccount (cb) { + async addNewAccount () { const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] - if (!primaryKeyring) return cb(new Error('MetamaskController - No HD Key Tree found')) + if (!primaryKeyring) { + throw new Error('MetamaskController - No HD Key Tree found') + } const keyringController = this.keyringController const oldAccounts = await keyringController.getAccounts() const keyState = await keyringController.addNewAccount(primaryKeyring) const newAccounts = await keyringController.getAccounts() + await this.verifySeedPhrase() + newAccounts.forEach((address) => { if (!oldAccounts.includes(address)) { this.preferencesController.setSelectedAddress(address) @@ -587,14 +593,43 @@ module.exports = class MetamaskController extends EventEmitter { // Used when creating a first vault, to allow confirmation. // Also used when revealing the seed words in the confirmation view. placeSeedWords (cb) { + + this.verifySeedPhrase() + .then((seedWords) => { + this.configManager.setSeedWords(seedWords) + return cb(null, seedWords) + }) + .catch((err) => { + return cb(err) + }) + } + + // Verifies the current vault's seed words if they can restore the + // accounts belonging to the current vault. + // + // Called when the first account is created and on unlocking the vault. + async verifySeedPhrase () { + const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] - if (!primaryKeyring) return cb(new Error('MetamaskController - No HD Key Tree found')) - primaryKeyring.serialize() - .then((serialized) => { - const seedWords = serialized.mnemonic - this.configManager.setSeedWords(seedWords) - cb(null, seedWords) - }) + if (!primaryKeyring) { + throw new Error('MetamaskController - No HD Key Tree found') + } + + const serialized = await primaryKeyring.serialize() + const seedWords = serialized.mnemonic + + const accounts = await primaryKeyring.getAccounts() + if (accounts.length < 1) { + throw new Error('MetamaskController - No accounts found') + } + + try { + await seedPhraseVerifier.verifyAccounts(accounts, seedWords) + return seedWords + } catch (err) { + log.error(err.message) + throw err + } } // ClearSeedWordCache |