From dab863ab01978dce5b50a82d94c2c2e9df67648e Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Fri, 18 May 2018 17:41:17 -0400 Subject: [PATCH 1/7] adding long derivation paths (courtesy of blockstack.js) and test --- app/js/auth/components/AuthModal.js | 8 ++--- app/js/utils/account-utils.js | 36 ++++++++++++++++++++ app/js/utils/index.js | 1 + tests/utils/account-utils.test.js | 53 +++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 4 deletions(-) diff --git a/app/js/auth/components/AuthModal.js b/app/js/auth/components/AuthModal.js index 246f7b2d6..396c50bd8 100644 --- a/app/js/auth/components/AuthModal.js +++ b/app/js/auth/components/AuthModal.js @@ -11,10 +11,9 @@ import { getAppBucketUrl, isLaterVersion } from 'blockstack' import Image from '../../components/Image' -import { AppsNode } from '../../utils/account-utils' +import { getCorrectAppPrivateKey } from '../../utils/account-utils' import { fetchProfileLocations, getDefaultProfileUrl } from '../../utils/profile-utils' import { getTokenFileUrlFromZoneFile } from '../../utils/zone-utils' -import { HDNode } from 'bitcoinjs-lib' import { validateScopes, appRequestSupportsDirectHub } from '../utils' import ToolTip from '../../components/ToolTip' import log4js from 'log4js' @@ -164,8 +163,9 @@ class AuthModal extends Component { const privateKey = profileSigningKeypair.key const appsNodeKey = profileSigningKeypair.appsNodeKey const salt = profileSigningKeypair.salt - const appsNode = new AppsNode(HDNode.fromBase58(appsNodeKey), salt) - const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey() + + const appPrivateKey = getCorrectAppPrivateKey(this.state.scopes, profile, appsNodeKey, + salt, appDomain) let profileUrlPromise diff --git a/app/js/utils/account-utils.js b/app/js/utils/account-utils.js index 791968648..7fcf6d922 100644 --- a/app/js/utils/account-utils.js +++ b/app/js/utils/account-utils.js @@ -3,6 +3,7 @@ import { decrypt } from './encryption-utils' import { HDNode } from 'bitcoinjs-lib' import crypto from 'crypto' import log4js from 'log4js' +import { BlockstackWallet, publicKeyToAddress, getPublicKeyFromPrivate } from 'blockstack' const logger = log4js.getLogger('utils/account-utils.js') @@ -20,6 +21,13 @@ function hashCode(string) { const APPS_NODE_INDEX = 0 const SIGNING_NODE_INDEX = 1 const ENCRYPTION_NODE_INDEX = 2 +const SINGLE_PLAYER_APP_DOMAIN_LEGACY_LIST = ['https://blockstack-todos.appartisan.com', + 'https://use.coinsapp.co', + 'http://www.coinstack.one', + 'http://www.blockportfol.io', + 'http://lioapp.io', + 'http://coindexapp.herokuapp.com', + 'https://peachyportfolio.com'] export const MAX_TRUST_LEVEL = 99 export class AppNode { @@ -407,3 +415,31 @@ export function findAddressIndex(address, identityAddresses) { } return null } + +export function getCorrectAppPrivateKey(scopes, profile, appsNodeKey, salt, appDomain) { + const appPrivateKey = BlockstackWallet.getAppPrivateKey(appsNodeKey, salt, appDomain) + const legacyAppPrivateKey = BlockstackWallet.getLegacyAppPrivateKey( + appsNodeKey, salt, appDomain) + if (scopes.publishData) { + // multiplayer application, let's check if there's a legacy signin. + if (!profile || !profile.apps || !profile.apps[appDomain]) { + // first login with this app, use normal private key + return appPrivateKey + } + let appBucketUrl = profile.apps[appDomain] + if (appBucketUrl.endsWith('/')) { + appBucketUrl = appBucketUrl.slice(0, -1) + } + const legacyAddress = publicKeyToAddress(getPublicKeyFromPrivate(legacyAppPrivateKey)) + if (appBucketUrl.endsWith(`/${legacyAddress}`)) { + return legacyAppPrivateKey + } + return appPrivateKey + } else { + if (SINGLE_PLAYER_APP_DOMAIN_LEGACY_LIST.includes(appDomain)) { + return legacyAppPrivateKey + } else { + return appPrivateKey + } + } +} diff --git a/app/js/utils/index.js b/app/js/utils/index.js index ba8c88a87..26e5ff929 100644 --- a/app/js/utils/index.js +++ b/app/js/utils/index.js @@ -1,4 +1,5 @@ export { + getCorrectAppPrivateKey, decryptMasterKeychain, deriveIdentityKeyPair, getBitcoinPrivateKeychain, diff --git a/tests/utils/account-utils.test.js b/tests/utils/account-utils.test.js index 72236758d..070632d94 100644 --- a/tests/utils/account-utils.test.js +++ b/tests/utils/account-utils.test.js @@ -5,6 +5,7 @@ import { getIdentityOwnerAddressNode, findAddressIndex, decryptMasterKeychain, + getCorrectAppPrivateKey, encrypt } from '../../app/js/utils' @@ -21,6 +22,58 @@ describe('account-utils', () => { masterKeychain = null }) + describe('test app private keys', () => { + it('should correctly generate a legacy app private key', () => { + const appsNodeKey = 'xprvA1bXJqMaKqHFnYB3LyLmtJMXgpCKisknF2EuVBrNs6UkvR3U4W2vtdEK9' + + 'ESFx82YoX6Xi491prYxxbhFDhEjyRTsjdjFkhPPhRQQQbz92Qh' + const salt = 'e61f9eb10842fc3e237fba6319947de93fb630df963342914339b96790563a5a' + + const legacyAppSK = 'b21aaf76b684cc1b6bf8c48bcec5df1adb68a7a6970e66c86fc0e86e09b4d244' + const newAppSK = '3168aefff6aa53959a002112821384c41f39f538c0e8727a798bb40beb62ca5e' + + const expectLegacy1 = getCorrectAppPrivateKey( + { publishData: true }, + { apps: { 'https://blockstack-todos.appartisan.com': + 'https://gaia.blockstack.org/hub/18Dg8z4zH13HTpMSERjc3iZTQsyzF3373R/' } }, + appsNodeKey, + salt, + 'https://blockstack-todos.appartisan.com') + const expectLegacy2 = getCorrectAppPrivateKey( + { publishData: false }, + {}, + appsNodeKey, + salt, + 'https://blockstack-todos.appartisan.com') + const expectLegacy3 = getCorrectAppPrivateKey( + { publishData: true }, + { apps: { 'https://blockstack-todos.appartisan.com': + 'https://gaia.blockstack.org/hub/18Dg8z4zH13HTpMSERjc3iZTQsyzF3373R' } }, + appsNodeKey, + salt, + 'https://blockstack-todos.appartisan.com') + const expectNew1 = getCorrectAppPrivateKey( + { publishData: false }, + {}, + appsNodeKey, + 'potato potato', + 'carrot carrot carrot') + const expectNew2 = getCorrectAppPrivateKey( + { publishData: true }, + {}, appsNodeKey, 'potato potato', 'carrot carrot carrot') + const expectNew3 = getCorrectAppPrivateKey( + { publishData: true }, + { apps: { 'carrot carrot carrot': 'abc.co/abcd.c/'} }, + appsNodeKey, 'potato potato', 'carrot carrot carrot') + + assert.equal(expectLegacy1, legacyAppSK) + assert.equal(expectLegacy2, legacyAppSK) + assert.equal(expectLegacy3, legacyAppSK) + assert.equal(expectNew1, newAppSK) + assert.equal(expectNew2, newAppSK) + assert.equal(expectNew3, newAppSK) + }) + }) + describe('getIdentityOwnerAddressNode', () => { it('should generate app key tree', () => { const identityPrivateKeychainNode = getIdentityPrivateKeychain(masterKeychain) From 08af48b5a91e47c7e29b455f946c5ff78a3f5271 Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Tue, 3 Jul 2018 08:41:31 -0500 Subject: [PATCH 2/7] fix syntax error from merge --- app/js/utils/account-utils.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/js/utils/account-utils.js b/app/js/utils/account-utils.js index a8be3dc78..fedb3d726 100644 --- a/app/js/utils/account-utils.js +++ b/app/js/utils/account-utils.js @@ -441,6 +441,8 @@ export function getCorrectAppPrivateKey(scopes, profile, appsNodeKey, salt, appD } else { return appPrivateKey } + } +} export function getBlockchainIdentities(masterKeychain, identitiesToGenerate) { const identityPrivateKeychainNode = getIdentityPrivateKeychain(masterKeychain) From 130bd31a65c7db598bfe8ee7ded6d2c6dc94a969 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Tue, 10 Jul 2018 14:12:00 -0400 Subject: [PATCH 3/7] Replace account-utils functions with BlockstackWallet functions --- app/js/utils/account-utils.js | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/app/js/utils/account-utils.js b/app/js/utils/account-utils.js index fedb3d726..478836157 100644 --- a/app/js/utils/account-utils.js +++ b/app/js/utils/account-utils.js @@ -157,21 +157,15 @@ export function decryptMasterKeychain(password, encryptedBackupPhrase) { } const EXTERNAL_ADDRESS = 'EXTERNAL_ADDRESS' -const CHANGE_ADDRESS = 'CHANGE_ADDRESS' export function getBitcoinPrivateKeychain(masterKeychain) { - const BIP_44_PURPOSE = 44 - const BITCOIN_COIN_TYPE = 0 - const ACCOUNT_INDEX = 0 - - return masterKeychain - .deriveHardened(BIP_44_PURPOSE) - .deriveHardened(BITCOIN_COIN_TYPE) - .deriveHardened(ACCOUNT_INDEX) + return new BlockstackWallet(masterKeychain).getBitcoinPrivateKeychain() } export function getBitcoinPublicKeychain(masterKeychain) { - return getBitcoinPrivateKeychain(masterKeychain).neutered() + return new BlockstackWallet(masterKeychain) + .getBitcoinPrivateKeychain() + .neutered() } export function getBitcoinAddressNode( @@ -179,17 +173,9 @@ export function getBitcoinAddressNode( addressIndex = 0, chainType = EXTERNAL_ADDRESS ) { - let chain = null - - if (chainType === EXTERNAL_ADDRESS) { - chain = 0 - } else if (chainType === CHANGE_ADDRESS) { - chain = 1 - } else { - throw new Error('Invalid chain type') - } - - return bitcoinKeychain.derive(chain).derive(addressIndex) + return BlockstackWallet.getNodeFromBitcoinKeychain( + bitcoinKeychain.toBase58(), addressIndex, chainType + ) } export function decryptBitcoinPrivateKey(password, encryptedBackupPhrase) { From 2739512bffc5c0476b812875a68a93d02ba2d4b8 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Tue, 17 Jul 2018 12:07:09 -0400 Subject: [PATCH 4/7] Remove encryption / decryption code in lieu of blockstack.js, convert as much as possible to BlockstackWallet. --- app/js/UpdateStatePage.js | 11 +- app/js/account/BackupAccountPage.js | 7 +- app/js/account/ChangePasswordPage.js | 5 +- app/js/account/store/account/actions.js | 20 ++-- app/js/profiles/store/identity/actions.js | 6 +- app/js/seed/index.js | 4 +- app/js/sign-in/index.js | 5 +- app/js/update/index.js | 6 +- app/js/utils/account-utils.js | 44 +++----- app/js/utils/encryption-utils.js | 117 --------------------- app/js/utils/index.js | 2 - app/js/welcome/WelcomeModal.js | 5 +- package-lock.json | 70 +++--------- package.json | 1 - test/account/store/account/actions.test.js | 3 +- test/mocha.opts | 2 + test/utils/account-utils.test.js | 8 +- test/utils/encryption-utils.test.js | 45 -------- 18 files changed, 64 insertions(+), 297 deletions(-) delete mode 100644 app/js/utils/encryption-utils.js delete mode 100644 test/utils/encryption-utils.test.js diff --git a/app/js/UpdateStatePage.js b/app/js/UpdateStatePage.js index 76bf78efa..696edfc2f 100644 --- a/app/js/UpdateStatePage.js +++ b/app/js/UpdateStatePage.js @@ -3,11 +3,11 @@ import React, { Component } from 'react' import Modal from 'react-modal' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' +import { decryptMnemonic } from 'blockstack' import Alert from './components/Alert' import InputGroup from './components/InputGroup' import { AccountActions } from './account/store/account' import { IdentityActions } from './profiles/store/identity' -import { decrypt } from './utils' import { CURRENT_VERSION, updateState, @@ -138,7 +138,7 @@ class UpdateStatePage extends Component { - upgradeBlockstackState(event) { + upgradeBlockstackState(event) { logger.trace('upgradeBlockstackState') event.preventDefault() this.setState({ upgradeInProgress: true }) @@ -146,13 +146,10 @@ class UpdateStatePage extends Component { // number of identities to generate // default identity // copy api settings - const { encryptedBackupPhrase } = this.props + const { password } = this.state - const dataBuffer = new Buffer(encryptedBackupPhrase, 'hex') - const password = this.state.password - - decrypt(dataBuffer, password) + decryptMnemonic(encryptedBackupPhrase, password) .then(backupPhraseBuffer => { const backupPhrase = backupPhraseBuffer.toString() logger.debug('upgradeBlockstackState: correct password!') diff --git a/app/js/account/BackupAccountPage.js b/app/js/account/BackupAccountPage.js index dbd18115a..85765ef7f 100644 --- a/app/js/account/BackupAccountPage.js +++ b/app/js/account/BackupAccountPage.js @@ -4,10 +4,10 @@ import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import bip39 from 'bip39' import { HDNode } from 'bitcoinjs-lib' +import { decryptMnemonic } from 'blockstack' import Alert from '@components/Alert' import InputGroup from '@components/InputGroup' -import { decrypt } from '@utils' import log4js from 'log4js' import { AccountActions } from './store/account' @@ -63,11 +63,8 @@ class BackupAccountPage extends Component { decryptBackupPhrase() { logger.trace('decryptBackupPhrase') - - const password = this.state.password - const dataBuffer = new Buffer(this.props.encryptedBackupPhrase, 'hex') logger.debug('Trying to decrypt recovery phrase...') - decrypt(dataBuffer, password).then( + decryptMnemonic(this.props.encryptedBackupPhrase, this.state.password).then( plaintextBuffer => { logger.debug('Keychain phrase successfully decrypted') this.updateAlert('success', 'Keychain phrase decrypted') diff --git a/app/js/account/ChangePasswordPage.js b/app/js/account/ChangePasswordPage.js index 8d59042e7..46d540869 100644 --- a/app/js/account/ChangePasswordPage.js +++ b/app/js/account/ChangePasswordPage.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types' import React, { Component } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' +import { encryptMnemonic, decryptMnemonic } from 'blockstack' import Alert from '@components/Alert' import InputGroup from '@components/InputGroup' @@ -67,7 +68,7 @@ class ChangePasswordPage extends Component { const newPassword2 = this.state.newPassword2 const dataBuffer = new Buffer(this.props.encryptedBackupPhrase, 'hex') logger.debug('Trying to decrypt recovery phrase...') - decrypt(dataBuffer, currentPassword).then( + decryptMnemonic(dataBuffer, currentPassword).then( plaintextBuffer => { logger.debug('Recovery phrase successfully decrypted') if (newPassword.length < 8) { @@ -77,7 +78,7 @@ class ChangePasswordPage extends Component { this.updateAlert('danger', 'New passwords must match') } else { logger.debug('Trying to re-encrypt recovery phrase with new password...') - encrypt(plaintextBuffer, newPassword).then(ciphertextBuffer => { + encryptMnemonic(plaintextBuffer, newPassword).then(ciphertextBuffer => { this.props.updateBackupPhrase(ciphertextBuffer.toString('hex')) this.updateAlert('success', 'Password updated!') this.setState({ diff --git a/app/js/account/store/account/actions.js b/app/js/account/store/account/actions.js index e99755c4a..46a57655b 100644 --- a/app/js/account/store/account/actions.js +++ b/app/js/account/store/account/actions.js @@ -1,16 +1,14 @@ import { HDNode } from 'bitcoinjs-lib' import bip39 from 'bip39' -import { randomBytes } from 'crypto' import { authorizationHeaderValue, btcToSatoshis, satoshisToBtc, - encrypt, getInsightUrls, getBlockchainIdentities } from '@utils' import { isCoreEndpointDisabled } from '@utils/window-utils' -import { transactions, config, network } from 'blockstack' +import { transactions, config, network, BlockstackWallet } from 'blockstack' import roundTo from 'round-to' import * as types from './types' @@ -452,17 +450,17 @@ const initializeWallet = ( masterKeychain = HDNode.fromSeedBuffer(seedBuffer) } else { // Create a new wallet - const STRENGTH = 128 // 128 bits generates a 12 word mnemonic - backupPhrase = bip39.generateMnemonic(STRENGTH, randomBytes) + backupPhrase = BlockstackWallet.generateMnemonic() const seedBuffer = bip39.mnemonicToSeed(backupPhrase) masterKeychain = HDNode.fromSeedBuffer(seedBuffer) } - return encrypt(new Buffer(backupPhrase), password).then(ciphertextBuffer => { - const encryptedBackupPhrase = ciphertextBuffer.toString('hex') - return dispatch( - createAccount(encryptedBackupPhrase, masterKeychain, identitiesToGenerate) - ) - }) + return BlockstackWallet.encryptMnemonic(backupPhrase, password) + .then(ciphertextBuffer => { + const encryptedBackupPhrase = ciphertextBuffer.toString('hex') + return dispatch( + createAccount(encryptedBackupPhrase, masterKeychain, identitiesToGenerate) + ) + }) } function newBitcoinAddress() { diff --git a/app/js/profiles/store/identity/actions.js b/app/js/profiles/store/identity/actions.js index 29db0bc1e..8178a5924 100644 --- a/app/js/profiles/store/identity/actions.js +++ b/app/js/profiles/store/identity/actions.js @@ -2,10 +2,9 @@ import { HDNode } from 'bitcoinjs-lib' import bip39 from 'bip39' import * as types from './types' -import { validateProofs } from 'blockstack' +import { validateProofs, decryptMnemonic } from 'blockstack' import { authorizationHeaderValue, - decrypt, deriveIdentityKeyPair, getIdentityOwnerAddressNode, getIdentityPrivateKeychain, @@ -211,9 +210,8 @@ function createNewProfile( return (dispatch: Dispatch<*>): Promise<*> => { logger.trace('createNewProfile') // Decrypt master keychain - const dataBuffer = new Buffer(encryptedBackupPhrase, 'hex') logger.debug('createNewProfile: Trying to decrypt backup phrase...') - return decrypt(dataBuffer, password).then( + return decryptMnemonic(encryptedBackupPhrase, password).then( plaintextBuffer => { logger.debug('createNewProfile: Backup phrase successfully decrypted') const backupPhrase = plaintextBuffer.toString() diff --git a/app/js/seed/index.js b/app/js/seed/index.js index 741b9b4bc..f8154052d 100644 --- a/app/js/seed/index.js +++ b/app/js/seed/index.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' +import { decryptMnemonic } from 'blockstack' import { Initial, Password, Seed, SeedConfirm, Success } from './views' -import { decrypt } from '@utils/encryption-utils' import { withRouter, browserHistory } from 'react-router' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' @@ -117,7 +117,7 @@ class SeedContainer extends Component { const buffer = new Buffer(encryptedBackupPhrase, method) - return decrypt(buffer, password).then(result => { + return decryptMnemonic(buffer, password).then(result => { if (this.state.seed !== result.toString()) { return this.setState( { diff --git a/app/js/sign-in/index.js b/app/js/sign-in/index.js index 4c7cdf2fc..20c47611f 100644 --- a/app/js/sign-in/index.js +++ b/app/js/sign-in/index.js @@ -1,6 +1,7 @@ import React from 'react' import PropTypes from 'prop-types' -import { decrypt, isBackupPhraseValid } from '@utils' +import { decryptMnemonic } from 'blockstack' +import { isBackupPhraseValid } from '@utils' import { browserHistory, withRouter } from 'react-router' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' @@ -156,7 +157,7 @@ class SignIn extends React.Component { { decrypting: true }, () => setTimeout(() => - decrypt( + decryptMnemonic( new Buffer(this.state.encryptedKey, 'base64'), this.state.password ) diff --git a/app/js/update/index.js b/app/js/update/index.js index d31e36d4e..f682a5768 100644 --- a/app/js/update/index.js +++ b/app/js/update/index.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types' import { withRouter, browserHistory } from 'react-router' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' +import { decryptMnemonic } from 'blockstack' import { AccountActions } from '../account/store/account' import { IdentityActions } from '../profiles/store/identity' import { Initial, Success, NoUpdate } from './views' @@ -28,7 +29,6 @@ import { hasLegacyCoreStateVersion, migrateLegacyCoreEndpoints } from '@utils/api-utils' -import { decrypt } from '@utils' const VIEWS = { INITIAL: 0, SUCCESS: 1, @@ -135,10 +135,8 @@ class UpdatePage extends React.Component { console.error('No encryptedBackupPhrase, cannot continue') return null } - const dataBuffer = new Buffer(encryptedBackupPhrase, 'hex') - const { password } = this.state - return decrypt(dataBuffer, password) + return decryptMnemonic(encryptedBackupPhrase, this.state.password) .then(backupPhraseBuffer => { this.setState( { diff --git a/app/js/utils/account-utils.js b/app/js/utils/account-utils.js index 478836157..d743f5f17 100644 --- a/app/js/utils/account-utils.js +++ b/app/js/utils/account-utils.js @@ -1,6 +1,4 @@ import bip39 from 'bip39' -import { decrypt } from './encryption-utils' -import { HDNode } from 'bitcoinjs-lib' import crypto from 'crypto' import log4js from 'log4js' import { BlockstackWallet, publicKeyToAddress, getPublicKeyFromPrivate } from 'blockstack' @@ -139,20 +137,14 @@ export function isBackupPhraseValid(backupPhrase) { export function decryptMasterKeychain(password, encryptedBackupPhrase) { return new Promise((resolve, reject) => { - const dataBuffer = new Buffer(encryptedBackupPhrase, 'hex') - decrypt(dataBuffer, password).then( - plaintextBuffer => { - const backupPhrase = plaintextBuffer.toString() - const seed = bip39.mnemonicToSeed(backupPhrase) - const masterKeychain = HDNode.fromSeedBuffer(seed) - logger.trace('decryptMasterKeychain: decrypted!') - resolve(masterKeychain) - }, - error => { - logger.error('decryptMasterKeychain: error', error) + BlockstackWallet.fromEncryptedMnemonic(encryptedBackupPhrase, password) + .then((wallet) => { + resolve(wallet.rootNode) + }) + .catch(err => { + logger.error('decryptMasterKeychain: error', err) reject(new Error('Incorrect password')) - } - ) + }) }) } @@ -178,29 +170,17 @@ export function getBitcoinAddressNode( ) } -export function decryptBitcoinPrivateKey(password, encryptedBackupPhrase) { - return new Promise((resolve, reject) => - decryptMasterKeychain(password, encryptedBackupPhrase) - .then(masterKeychain => { - const bitcoinPrivateKeychain = getBitcoinPrivateKeychain(masterKeychain) - const bitcoinAddressHDNode = getBitcoinAddressNode(bitcoinPrivateKeychain, 0) - const privateKey = bitcoinAddressHDNode.keyPair.d.toBuffer(32).toString('hex') - resolve(privateKey) - }) - .catch(error => { - reject(error) - }) - ) +export async function decryptBitcoinPrivateKey(password, encryptedBackupPhrase) { + const wallet = await BlockstackWallet.fromEncryptedMnemonic(encryptedBackupPhrase, password) + return wallet.getBitcoinPrivateKey(0) } -const IDENTITY_KEYCHAIN = 888 -const BLOCKSTACK_ON_BITCOIN = 0 export function getIdentityPrivateKeychain(masterKeychain) { - return masterKeychain.deriveHardened(IDENTITY_KEYCHAIN).deriveHardened(BLOCKSTACK_ON_BITCOIN) + return new BlockstackWallet(masterKeychain).getIdentityPrivateKeychain() } export function getIdentityPublicKeychain(masterKeychain) { - return getIdentityPrivateKeychain(masterKeychain).neutered() + return new BlockstackWallet(masterKeychain).getIdentityPublicKeychain() } export function getIdentityOwnerAddressNode(identityPrivateKeychain, identityIndex = 0) { diff --git a/app/js/utils/encryption-utils.js b/app/js/utils/encryption-utils.js deleted file mode 100644 index 279034492..000000000 --- a/app/js/utils/encryption-utils.js +++ /dev/null @@ -1,117 +0,0 @@ -import bip39 from 'bip39' -import crypto from 'crypto' -import triplesec from 'triplesec' - -function normalizeMnemonic(mnemonic) { - return bip39.mnemonicToEntropy(mnemonic).toString('hex') -} - -function denormalizeMnemonic(normalizedMnemonic) { - return bip39.entropyToMnemonic(normalizedMnemonic) -} - -function encryptMnemonic(plaintextBuffer, password) { - return Promise.resolve().then(() => { - // must be bip39 mnemonic - if (!bip39.validateMnemonic(plaintextBuffer.toString())) { - throw new Error('Not a valid bip39 nmemonic') - } - - // normalize plaintext to fixed length byte string - const plaintextNormalized = Buffer.from( - normalizeMnemonic(plaintextBuffer.toString()), 'hex') - - // AES-128-CBC with SHA256 HMAC - const salt = crypto.randomBytes(16) - const keysAndIV = crypto.pbkdf2Sync(password, salt, 100000, 48, 'sha512') - const encKey = keysAndIV.slice(0, 16) - const macKey = keysAndIV.slice(16, 32) - const iv = keysAndIV.slice(32, 48) - - const cipher = crypto.createCipheriv('aes-128-cbc', encKey, iv) - let cipherText = cipher.update(plaintextNormalized, '', 'hex') - cipherText += cipher.final('hex') - - const hmacPayload = Buffer.concat([salt, Buffer.from(cipherText, 'hex')]) - - const hmac = crypto.createHmac('sha256', macKey) - hmac.write(hmacPayload) - const hmacDigest = hmac.digest() - - const payload = Buffer.concat([salt, hmacDigest, Buffer.from(cipherText, 'hex')]) - return payload - }) -} - -function decryptMnemonic(dataBuffer, password) { - return Promise.resolve().then(() => { - const salt = dataBuffer.slice(0, 16) - const hmacSig = dataBuffer.slice(16, 48) // 32 bytes - const cipherText = dataBuffer.slice(48) - const hmacPayload = Buffer.concat([salt, cipherText]) - - const keysAndIV = crypto.pbkdf2Sync(password, salt, 100000, 48, 'sha512') - const encKey = keysAndIV.slice(0, 16) - const macKey = keysAndIV.slice(16, 32) - const iv = keysAndIV.slice(32, 48) - - const decipher = crypto.createDecipheriv('aes-128-cbc', encKey, iv) - let plaintext = decipher.update(cipherText, '', 'hex') - plaintext += decipher.final('hex') - - const hmac = crypto.createHmac('sha256', macKey) - hmac.write(hmacPayload) - const hmacDigest = hmac.digest() - - // hash both hmacSig and hmacDigest so string comparison time - // is uncorrelated to the ciphertext - const hmacSigHash = crypto.createHash('sha256') - .update(hmacSig) - .digest() - .toString('hex') - - const hmacDigestHash = crypto.createHash('sha256') - .update(hmacDigest) - .digest() - .toString('hex') - - if (hmacSigHash !== hmacDigestHash) { - // not authentic - throw new Error('Wrong password (HMAC mismatch)') - } - - const mnemonic = denormalizeMnemonic(plaintext) - if (!bip39.validateMnemonic(mnemonic)) { - throw new Error('Wrong password (invalid plaintext)') - } - - return mnemonic - }) -} - - -export function encrypt(plaintextBuffer, password) { - return encryptMnemonic(plaintextBuffer, password) -} - -export function decrypt(dataBuffer, password) { - return decryptMnemonic(dataBuffer, password) - .catch(() => // try the old way - new Promise((resolve, reject) => { - triplesec.decrypt( - { - key: new Buffer(password), - data: dataBuffer - }, - (err, plaintextBuffer) => { - if (!err) { - resolve(plaintextBuffer) - } else { - reject(err) - } - } - ) - }) - ) -} - diff --git a/app/js/utils/index.js b/app/js/utils/index.js index e0a8f908e..d0d893e1a 100644 --- a/app/js/utils/index.js +++ b/app/js/utils/index.js @@ -30,8 +30,6 @@ export { export { getNumberOfVerifications, compareProfilesByVerifications } from './search-utils' -export { encrypt, decrypt } from './encryption-utils' - export { isABlockstackName, hasNameBeenPreordered, diff --git a/app/js/welcome/WelcomeModal.js b/app/js/welcome/WelcomeModal.js index 276c59140..a5f73d7a6 100644 --- a/app/js/welcome/WelcomeModal.js +++ b/app/js/welcome/WelcomeModal.js @@ -3,9 +3,10 @@ import React, { Component } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import Modal from 'react-modal' +import { decryptMnemonic } from 'blockstack' import Alert from '@components/Alert' import { isWebAppBuild } from '@utils/window-utils' -import { decrypt, isBackupPhraseValid } from '@utils' +import { isBackupPhraseValid } from '@utils' import { AccountActions } from '../account/store/account' import { IdentityActions } from '../profiles/store/identity' @@ -169,7 +170,7 @@ class WelcomeModal extends Component { this.setState({ updateInProgress }) if (!updateInProgress && nextProps.accountCreated && !this.props.accountCreated) { logger.debug('account created - checking for valid password in component state') - decrypt(new Buffer(this.props.encryptedBackupPhrase, 'hex'), this.state.password) + decryptMnemonic(this.props.encryptedBackupPhrase, this.state.password) .then((identityKeyPhraseBuffer) => { logger.debug('Backup phrase successfully decrypted. Storing keychain phrase.') this.setState({ identityKeyPhrase: identityKeyPhraseBuffer.toString() }) diff --git a/package-lock.json b/package-lock.json index e7b8b0edd..5f5028d6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "blockstack-browser", - "version": "0.29.1", + "version": "0.29.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -9612,7 +9612,6 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", "dev": true, - "optional": true, "requires": { "delayed-stream": "~1.0.0" } @@ -9684,8 +9683,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "optional": true + "dev": true }, "delegates": { "version": "1.0.0", @@ -9738,9 +9736,9 @@ "dev": true, "optional": true, "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.12" } }, "fs.realpath": { @@ -9843,8 +9841,8 @@ "dev": true, "optional": true, "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" + "ajv": "^4.9.1", + "har-schema": "^1.0.5" } }, "has-unicode": { @@ -9913,7 +9911,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-typedarray": { @@ -10010,17 +10008,15 @@ "version": "1.27.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", - "dev": true, - "optional": true + "dev": true }, "mime-types": { "version": "2.1.15", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", "dev": true, - "optional": true, "requires": { - "mime-db": "1.27.0" + "mime-db": "~1.27.0" } }, "minimatch": { @@ -10029,7 +10025,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.7" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -10102,8 +10098,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "optional": true + "dev": true }, "oauth-sign": { "version": "0.8.2", @@ -12289,24 +12284,6 @@ "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", "dev": true }, - "iced-error": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/iced-error/-/iced-error-0.0.12.tgz", - "integrity": "sha1-4KhhRigXzwzpdLE/ymEtOg1dEL4=" - }, - "iced-lock": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/iced-lock/-/iced-lock-1.1.0.tgz", - "integrity": "sha1-YRbvHKs6zW5rEIk7snumIv0/3nI=", - "requires": { - "iced-runtime": "^1.0.0" - } - }, - "iced-runtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/iced-runtime/-/iced-runtime-1.0.3.tgz", - "integrity": "sha1-LU9PuZmreqVDCxk8d6f85BGDGc4=" - }, "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", @@ -15438,14 +15415,6 @@ "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8=", "dev": true }, - "more-entropy": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/more-entropy/-/more-entropy-0.0.7.tgz", - "integrity": "sha1-Z7/G96hvJvvDeqyD/UbYjGHRCbU=", - "requires": { - "iced-runtime": ">=0.0.1" - } - }, "mout": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/mout/-/mout-0.9.1.tgz", @@ -19603,7 +19572,8 @@ "progress": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=" + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true }, "promise": { "version": "7.3.1", @@ -23084,18 +23054,6 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, - "triplesec": { - "version": "3.0.26", - "resolved": "https://registry.npmjs.org/triplesec/-/triplesec-3.0.26.tgz", - "integrity": "sha1-3/K7R1ikIzcuc5o5fYmR8Fl9CsE=", - "requires": { - "iced-error": ">=0.0.9", - "iced-lock": "^1.0.1", - "iced-runtime": "^1.0.2", - "more-entropy": ">=0.0.7", - "progress": "~1.1.2" - } - }, "tryit": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", diff --git a/package.json b/package.json index 060f1d7fb..a9ff87149 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,6 @@ "round-to": "^2.0.0", "styled-components": "^3.2.3", "styled-system": "^2.2.1", - "triplesec": "^3.0.25", "yup": "^0.24.1", "zone-file": "^0.2.2", "zxcvbn": "^4.4.2" diff --git a/test/account/store/account/actions.test.js b/test/account/store/account/actions.test.js index dec8772da..a270085af 100644 --- a/test/account/store/account/actions.test.js +++ b/test/account/store/account/actions.test.js @@ -1,5 +1,6 @@ import configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' +import { decryptMnemonic } from 'blockstack' import { decrypt } from '../../../../app/js/utils/encryption-utils' import AccountActions from '../../../../app/js/account/store/account/actions' import { @@ -192,7 +193,7 @@ describe('AccountActions', () => { const identityPublicKeychain = actions1[0].identityPublicKeychain const bitcoinPublicKeychain = actions1[0].bitcoinPublicKeychain - return decrypt( + return decryptMnemonic( new Buffer(encryptedBackupPhrase, 'hex'), password ).then(plaintextBuffer => { diff --git a/test/mocha.opts b/test/mocha.opts index 6f8301b14..57e4e9844 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,6 +1,8 @@ --require babel-register +--require babel-polyfill --require test/init.js --require assert --reporter spec --file test/helper.js --recursive +--timeout 5000 diff --git a/test/utils/account-utils.test.js b/test/utils/account-utils.test.js index 070632d94..70e7ffea0 100644 --- a/test/utils/account-utils.test.js +++ b/test/utils/account-utils.test.js @@ -1,12 +1,12 @@ import { HDNode } from 'bitcoinjs-lib' import bip39 from 'bip39' +import { BlockstackWallet } from 'blockstack' import { getIdentityPrivateKeychain, getIdentityOwnerAddressNode, findAddressIndex, decryptMasterKeychain, - getCorrectAppPrivateKey, - encrypt + getCorrectAppPrivateKey } from '../../app/js/utils' let masterKeychain = null @@ -123,8 +123,8 @@ describe('account-utils', () => { const password = 'password123' beforeEach((done) => { - encrypt(new Buffer(backupPhrase), password).then(ciphertextBuffer => { - encryptedBackupPhrase = ciphertextBuffer.toString('hex') + BlockstackWallet.encryptMnemonic(backupPhrase, password).then(hex => { + encryptedBackupPhrase = hex done() }) }) diff --git a/test/utils/encryption-utils.test.js b/test/utils/encryption-utils.test.js deleted file mode 100644 index f5e479d4e..000000000 --- a/test/utils/encryption-utils.test.js +++ /dev/null @@ -1,45 +0,0 @@ -import { encrypt, decrypt } from '../../app/js/utils/encryption-utils' - -describe('encryption-utils', () => { - beforeEach(() => { - }) - - afterEach(() => { - }) - - describe('encrypt & decrypt', () => { - it('should encrypt & decrypt the phrase', () => { - const phrase = 'vivid oxygen neutral wheat find thumb cigar wheel board kiwi portion business' - const password = 'supersecret' - return encrypt(new Buffer(phrase), password) - .then((encryptedTextBuffer) => { - assert(encryptedTextBuffer) - const encryptedText = encryptedTextBuffer.toString('hex') - return decrypt(new Buffer(encryptedText, 'hex'), password) - .then((plaintextBuffer) => { - assert(plaintextBuffer) - assert.equal(plaintextBuffer.toString(), phrase) - }) - }) - }) - }) - - describe('decrypt legacy', () => { - it('should decrypt legacy encryption', () => { - const legacyCiphertext = '1c94d7de0000000304d583f007c71e6e5fef354c046e8c64b1adebd6904dcb' + - '007a1222f07313643873455ab2a3ab3819e99d518cc7d33c18bde02494aa74efc35a8970b2007b2fc715f' + - '6067cee27f5c92d020b1806b0444994aab80050a6732131d2947a51bacb3952fb9286124b3c2b3196ff7e' + - 'dce66dee0dbd9eb59558e0044bddb3a78f48a66cf8d78bb46bb472bd2d5ec420c831fc384293252459524' + - 'ee2d668869f33c586a94467d0ce8671260f4cc2e87140c873b6ca79fb86c6d77d134d7beb2018845a9e71' + - 'e6c7ecdedacd8a676f1f873c5f9c708cc6070642d44d2505aa9cdba26c50ad6f8d3e547fb0cba710a7f7b' + - 'e54ff7ea7e98a809ddee5ef85f6f259b3a17a8d8dbaac618b80fe266a1e63ec19e476bee9177b51894e' - const password = 'supersecret' - const phrase = 'vivid oxygen neutral wheat find thumb cigar wheel board kiwi portion business' - return decrypt(new Buffer(legacyCiphertext, 'hex'), password) - .then((plaintextBuffer) => { - assert(plaintextBuffer) - assert.equal(plaintextBuffer.toString(), phrase) - }) - }) - }) -}) From b3c07a0da868defc8e25ead53b9bcdda0441a3f5 Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Mon, 6 Aug 2018 16:21:48 -0500 Subject: [PATCH 5/7] patch account-utils and its dependents to use BlockstackWallet --- .flowconfig | 1 + app/js/account/ChangePasswordPage.js | 1 - app/js/account/store/account/actions.js | 11 +- app/js/account/store/account/reducer.js | 9 +- app/js/profiles/store/identity/actions.js | 21 +-- app/js/utils/account-utils.js | 145 +-------------------- app/js/utils/index.js | 3 - package-lock.json | 21 +-- package.json | 2 +- test/account/store/account/actions.test.js | 1 - test/utils/account-utils.test.js | 38 ++++-- 11 files changed, 60 insertions(+), 193 deletions(-) diff --git a/.flowconfig b/.flowconfig index 28b1ce0e5..ba0d7d47a 100644 --- a/.flowconfig +++ b/.flowconfig @@ -9,6 +9,7 @@ .*/node_modules/immutable/* .*/blockstack.js/node_modules/* .*/blockstack.js/src/* +.*/blockstack.js/.git/* .*/tmp/nexe/* .*/node_modules/styled-components/.* diff --git a/app/js/account/ChangePasswordPage.js b/app/js/account/ChangePasswordPage.js index 46d540869..aa3bc032d 100644 --- a/app/js/account/ChangePasswordPage.js +++ b/app/js/account/ChangePasswordPage.js @@ -7,7 +7,6 @@ import { encryptMnemonic, decryptMnemonic } from 'blockstack' import Alert from '@components/Alert' import InputGroup from '@components/InputGroup' import { AccountActions } from './store/account' -import { decrypt, encrypt } from '@utils' import log4js from 'log4js' const logger = log4js.getLogger('account/ChangePasswordPage.js') diff --git a/app/js/account/store/account/actions.js b/app/js/account/store/account/actions.js index 46a57655b..5ab7e1a3b 100644 --- a/app/js/account/store/account/actions.js +++ b/app/js/account/store/account/actions.js @@ -1,4 +1,3 @@ -import { HDNode } from 'bitcoinjs-lib' import bip39 from 'bip39' import { authorizationHeaderValue, @@ -29,7 +28,7 @@ const updateEmail = email => dispatch => function createAccount( encryptedBackupPhrase, - masterKeychain, + keychainB58, identitiesToGenerate ) { logger.debug(`createAccount: identitiesToGenerate: ${identitiesToGenerate}`) @@ -40,7 +39,7 @@ function createAccount( firstBitcoinAddress, identityAddresses, identityKeypairs - } = getBlockchainIdentities(masterKeychain, identitiesToGenerate) + } = getBlockchainIdentities(keychainB58, identitiesToGenerate) return { type: types.CREATE_ACCOUNT, @@ -447,12 +446,14 @@ const initializeWallet = ( let masterKeychain = null if (backupPhrase && bip39.validateMnemonic(backupPhrase)) { const seedBuffer = bip39.mnemonicToSeed(backupPhrase) - masterKeychain = HDNode.fromSeedBuffer(seedBuffer) + masterKeychain = BlockstackWallet.fromSeedBuffer(seedBuffer) + .toBase58() } else { // Create a new wallet backupPhrase = BlockstackWallet.generateMnemonic() const seedBuffer = bip39.mnemonicToSeed(backupPhrase) - masterKeychain = HDNode.fromSeedBuffer(seedBuffer) + masterKeychain = BlockstackWallet.fromSeedBuffer(seedBuffer) + .toBase58() } return BlockstackWallet.encryptMnemonic(backupPhrase, password) .then(ciphertextBuffer => { diff --git a/app/js/account/store/account/reducer.js b/app/js/account/store/account/reducer.js index 8b08ab10c..627745e16 100644 --- a/app/js/account/store/account/reducer.js +++ b/app/js/account/store/account/reducer.js @@ -1,6 +1,5 @@ import * as types from './types' -import { HDNode } from 'bitcoinjs-lib' -import { getBitcoinAddressNode } from '@utils' +import { BlockstackWallet } from 'blockstack' const initialState = { accountCreated: false, // persist @@ -86,10 +85,10 @@ function AccountReducer(state = initialState, action) { publicKeychain: state.bitcoinAccount.publicKeychain, addresses: [ ...state.bitcoinAccount.addresses, - getBitcoinAddressNode( - HDNode.fromBase58(state.bitcoinAccount.publicKeychain), + BlockstackWallet.getAddressFromBitcoinKeychain( + state.bitcoinAccount.publicKeychain, state.bitcoinAccount.addressIndex + 1 - ).getAddress() + ) ], addressIndex: state.bitcoinAccount.addressIndex + 1, balances: state.bitcoinAccount.balances diff --git a/app/js/profiles/store/identity/actions.js b/app/js/profiles/store/identity/actions.js index 8178a5924..fc9ae46c6 100644 --- a/app/js/profiles/store/identity/actions.js +++ b/app/js/profiles/store/identity/actions.js @@ -1,13 +1,9 @@ // @flow -import { HDNode } from 'bitcoinjs-lib' import bip39 from 'bip39' import * as types from './types' -import { validateProofs, decryptMnemonic } from 'blockstack' +import { validateProofs, decryptMnemonic, BlockstackWallet } from 'blockstack' import { authorizationHeaderValue, - deriveIdentityKeyPair, - getIdentityOwnerAddressNode, - getIdentityPrivateKeychain, resolveZoneFileToProfile } from '@utils/index' import { DEFAULT_PROFILE, fetchProfileLocations } from '@utils/profile-utils' @@ -216,18 +212,11 @@ function createNewProfile( logger.debug('createNewProfile: Backup phrase successfully decrypted') const backupPhrase = plaintextBuffer.toString() const seedBuffer = bip39.mnemonicToSeed(backupPhrase) - const masterKeychain = HDNode.fromSeedBuffer(seedBuffer) - const identityPrivateKeychainNode = getIdentityPrivateKeychain( - masterKeychain - ) + const wallet = BlockstackWallet.fromSeedBuffer(seedBuffer) + const index = nextUnusedAddressIndex - const identityOwnerAddressNode = getIdentityOwnerAddressNode( - identityPrivateKeychainNode, - index - ) - const newIdentityKeypair = deriveIdentityKeyPair( - identityOwnerAddressNode - ) + const newIdentityKeypair = wallet.getIdentityKeyPair(index, true) + logger.debug( `createNewProfile: new identity: ${newIdentityKeypair.address}` ) diff --git a/app/js/utils/account-utils.js b/app/js/utils/account-utils.js index d743f5f17..4d38c3c75 100644 --- a/app/js/utils/account-utils.js +++ b/app/js/utils/account-utils.js @@ -1,24 +1,9 @@ import bip39 from 'bip39' -import crypto from 'crypto' import log4js from 'log4js' import { BlockstackWallet, publicKeyToAddress, getPublicKeyFromPrivate } from 'blockstack' const logger = log4js.getLogger('utils/account-utils.js') -function hashCode(string) { - let hash = 0 - if (string.length === 0) return hash - for (let i = 0; i < string.length; i++) { - const character = string.charCodeAt(i) - hash = (hash << 5) - hash + character - hash = hash & hash - } - return hash & 0x7fffffff -} - -const APPS_NODE_INDEX = 0 -const SIGNING_NODE_INDEX = 1 -const ENCRYPTION_NODE_INDEX = 2 const SINGLE_PLAYER_APP_DOMAIN_LEGACY_LIST = ['https://blockstack-todos.appartisan.com', 'https://use.coinsapp.co', 'http://www.coinstack.one', @@ -43,74 +28,6 @@ export class AppNode { } } -export class AppsNode { - constructor(appsHdNode, salt) { - this.hdNode = appsHdNode - this.salt = salt - } - - getNode() { - return this.hdNode - } - - getAppNode(appDomain) { - const hash = crypto - .createHash('sha256') - .update(`${appDomain}${this.salt}`) - .digest('hex') - const appIndex = hashCode(hash) - const appNode = this.hdNode.deriveHardened(appIndex) - return new AppNode(appNode, appDomain) - } - - toBase58() { - return this.hdNode.toBase58() - } - - getSalt() { - return this.salt - } -} - -class IdentityAddressOwnerNode { - constructor(ownerHdNode, salt) { - this.hdNode = ownerHdNode - this.salt = salt - } - - getNode() { - return this.hdNode - } - - getSalt() { - return this.salt - } - - getIdentityKey() { - return this.hdNode.keyPair.d.toBuffer(32).toString('hex') - } - - getIdentityKeyID() { - return this.hdNode.keyPair.getPublicKeyBuffer().toString('hex') - } - - getAppsNode() { - return new AppsNode(this.hdNode.deriveHardened(APPS_NODE_INDEX), this.salt) - } - - getAddress() { - return this.hdNode.getAddress() - } - - getEncryptionNode() { - return this.hdNode.deriveHardened(ENCRYPTION_NODE_INDEX) - } - - getSigningNode() { - return this.hdNode.deriveHardened(SIGNING_NODE_INDEX) - } -} - export function isPasswordValid(password) { let isValid = false let error = null @@ -154,12 +71,6 @@ export function getBitcoinPrivateKeychain(masterKeychain) { return new BlockstackWallet(masterKeychain).getBitcoinPrivateKeychain() } -export function getBitcoinPublicKeychain(masterKeychain) { - return new BlockstackWallet(masterKeychain) - .getBitcoinPrivateKeychain() - .neutered() -} - export function getBitcoinAddressNode( bitcoinKeychain, addressIndex = 0, @@ -175,43 +86,6 @@ export async function decryptBitcoinPrivateKey(password, encryptedBackupPhrase) return wallet.getBitcoinPrivateKey(0) } -export function getIdentityPrivateKeychain(masterKeychain) { - return new BlockstackWallet(masterKeychain).getIdentityPrivateKeychain() -} - -export function getIdentityPublicKeychain(masterKeychain) { - return new BlockstackWallet(masterKeychain).getIdentityPublicKeychain() -} - -export function getIdentityOwnerAddressNode(identityPrivateKeychain, identityIndex = 0) { - if (identityPrivateKeychain.isNeutered()) { - throw new Error('You need the private key to generate identity addresses') - } - - const publicKeyHex = identityPrivateKeychain.keyPair.getPublicKeyBuffer().toString('hex') - const salt = crypto - .createHash('sha256') - .update(publicKeyHex) - .digest('hex') - - return new IdentityAddressOwnerNode(identityPrivateKeychain.deriveHardened(identityIndex), salt) -} - -export function deriveIdentityKeyPair(identityOwnerAddressNode) { - const address = identityOwnerAddressNode.getAddress() - const identityKey = identityOwnerAddressNode.getIdentityKey() - const identityKeyID = identityOwnerAddressNode.getIdentityKeyID() - const appsNode = identityOwnerAddressNode.getAppsNode() - const keyPair = { - key: identityKey, - keyID: identityKeyID, - address, - appsNodeKey: appsNode.toBase58(), - salt: appsNode.getSalt() - } - return keyPair -} - export function getWebAccountTypes(api) { const webAccountTypes = { twitter: { @@ -410,19 +284,16 @@ export function getCorrectAppPrivateKey(scopes, profile, appsNodeKey, salt, appD } } -export function getBlockchainIdentities(masterKeychain, identitiesToGenerate) { - const identityPrivateKeychainNode = getIdentityPrivateKeychain(masterKeychain) - const bitcoinPrivateKeychainNode = getBitcoinPrivateKeychain(masterKeychain) +export function getBlockchainIdentities(keychainB58, identitiesToGenerate) { + const wallet = BlockstackWallet.fromBase58(keychainB58) - const identityPublicKeychainNode = identityPrivateKeychainNode.neutered() + const identityPublicKeychainNode = wallet.getIdentityPublicKeychain() const identityPublicKeychain = identityPublicKeychainNode.toBase58() - const bitcoinPublicKeychainNode = bitcoinPrivateKeychainNode.neutered() + const bitcoinPublicKeychainNode = wallet.getBitcoinPublicKeychain() const bitcoinPublicKeychain = bitcoinPublicKeychainNode.toBase58() - const firstBitcoinAddress = getBitcoinAddressNode( - bitcoinPublicKeychainNode - ).getAddress() + const firstBitcoinAddress = wallet.getBitcoinAddress(0) const identityAddresses = [] const identityKeypairs = [] @@ -434,11 +305,7 @@ export function getBlockchainIdentities(masterKeychain, identitiesToGenerate) { addressIndex < identitiesToGenerate; addressIndex++ ) { - const identityOwnerAddressNode = getIdentityOwnerAddressNode( - identityPrivateKeychainNode, - addressIndex - ) - const identityKeyPair = deriveIdentityKeyPair(identityOwnerAddressNode) + const identityKeyPair = wallet.getIdentityKeyPair(addressIndex, true) identityKeypairs.push(identityKeyPair) identityAddresses.push(identityKeyPair.address) logger.debug(`createAccount: identity index: ${addressIndex}`) diff --git a/app/js/utils/index.js b/app/js/utils/index.js index d0d893e1a..aaa004a0a 100644 --- a/app/js/utils/index.js +++ b/app/js/utils/index.js @@ -3,9 +3,6 @@ export { decryptMasterKeychain, deriveIdentityKeyPair, getBitcoinPrivateKeychain, - getBitcoinPublicKeychain, - getIdentityPrivateKeychain, - getIdentityPublicKeychain, getWebAccountTypes, isPasswordValid, isBackupPhraseValid, diff --git a/package-lock.json b/package-lock.json index 5f5028d6f..514f5ce32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5973,11 +5973,6 @@ } } }, - "crypto": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-0.0.3.tgz", - "integrity": "sha1-RwqBuGvkxe4XrMggeh9TFa4g27A=" - }, "crypto-browserify": { "version": "3.11.1", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz", @@ -13790,16 +13785,22 @@ "dev": true }, "jsontokens": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/jsontokens/-/jsontokens-0.7.6.tgz", - "integrity": "sha1-4j3XXqELECjqnV11EtyyScWY1K0=", + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/jsontokens/-/jsontokens-0.7.8.tgz", + "integrity": "sha512-vTZ06cDSDoFeXJUnZNFHsh8aP+L3Xz5Ke7XPb6nyT3n5wb9uxyq4TE8gUl0H4hmzSe/YIrDi+mneuHIh37AMiA==", "requires": { "asn1.js": "^4.9.1", - "base64url": "^2.0.0", - "crypto": "0.0.3", + "base64url": "^3.0.0", "elliptic": "^6.3.2", "key-encoder": "^1.1.6", "validator": "^7.0.0" + }, + "dependencies": { + "base64url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.0.tgz", + "integrity": "sha512-LIVmqIrIWuiqTvn4RzcrwCOuHo2DD6tKmKBPXXlr4p4n4l6BZBkwFTIa3zu1XkX5MbZgro4a6BvPi+n2Mns5Gg==" + } } }, "jsprim": { diff --git a/package.json b/package.json index a9ff87149..0bf2107f5 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "inert-polyfill": "^0.2.5", "is-retina": "^1.0.3", "isomorphic-fetch": "^2.2.1", - "jsontokens": "^0.7.6", + "jsontokens": "^0.7.8", "lodash": "^4.17.2", "lodash.debounce": "^4.0.8", "log4js": "^1.1.1", diff --git a/test/account/store/account/actions.test.js b/test/account/store/account/actions.test.js index a270085af..6a2f94b47 100644 --- a/test/account/store/account/actions.test.js +++ b/test/account/store/account/actions.test.js @@ -1,7 +1,6 @@ import configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import { decryptMnemonic } from 'blockstack' -import { decrypt } from '../../../../app/js/utils/encryption-utils' import AccountActions from '../../../../app/js/account/store/account/actions' import { CREATE_ACCOUNT, diff --git a/test/utils/account-utils.test.js b/test/utils/account-utils.test.js index 70e7ffea0..293424636 100644 --- a/test/utils/account-utils.test.js +++ b/test/utils/account-utils.test.js @@ -1,6 +1,5 @@ -import { HDNode } from 'bitcoinjs-lib' import bip39 from 'bip39' -import { BlockstackWallet } from 'blockstack' +import { BlockstackWallet, publicKeyToAddress, getPublicKeyFromPrivate } from 'blockstack' import { getIdentityPrivateKeychain, getIdentityOwnerAddressNode, @@ -15,7 +14,8 @@ const backupPhrase = 'sound idle panel often situate develop unit text design an describe('account-utils', () => { beforeEach(() => { const seedBuffer = bip39.mnemonicToSeed(backupPhrase) - masterKeychain = HDNode.fromSeedBuffer(seedBuffer) + masterKeychain = BlockstackWallet.fromSeedBuffer(seedBuffer) + .toBase58() }) afterEach(() => { @@ -76,24 +76,25 @@ describe('account-utils', () => { describe('getIdentityOwnerAddressNode', () => { it('should generate app key tree', () => { - const identityPrivateKeychainNode = getIdentityPrivateKeychain(masterKeychain) + const wallet = BlockstackWallet.fromBase58(masterKeychain) const addressIndex = 0 - const identityOwnerAddressNode = getIdentityOwnerAddressNode(identityPrivateKeychainNode, addressIndex) + const identityKeyPair = wallet.getIdentityKeyPair(addressIndex, true) const expectedSalt = 'c15619adafe7e75a195a1a2b5788ca42e585a3fd181ae2ff009c6089de54ed9e' - const actualSalt = identityOwnerAddressNode.getSalt() + const actualSalt = wallet.getIdentitySalt() assert.equal(actualSalt, expectedSalt) const expectedAddress = '1JeTQ5cQjsD57YGcsVFhwT7iuQUXJR6BSk' - const actualAddress = identityOwnerAddressNode.getAddress() + const actualAddress = identityKeyPair.address assert.equal(actualAddress, expectedAddress) - const appsNode = identityOwnerAddressNode.getAppsNode() + const appsNodeKey = identityKeyPair.appsNodeKey const origin = 'https://amazing.app:443' - const appNode = appsNode.getAppNode(origin) + const appNodeKey = BlockstackWallet.getLegacyAppPrivateKey( + appsNodeKey, identityKeyPair.salt, origin) const expectedAppNodeAddress = '1A9NEhnXq5jDp9BRT4DrwadRP5jbBK896X' - const actualAppNodeAddress = appNode.getAddress() + const actualAppNodeAddress = publicKeyToAddress(getPublicKeyFromPrivate(appNodeKey)) assert.equal(actualAppNodeAddress, expectedAppNodeAddress) }) }) @@ -125,6 +126,13 @@ describe('account-utils', () => { beforeEach((done) => { BlockstackWallet.encryptMnemonic(backupPhrase, password).then(hex => { encryptedBackupPhrase = hex + const seedBuffer = bip39.mnemonicToSeed(backupPhrase) + masterKeychain = BlockstackWallet.fromSeedBuffer(seedBuffer) + .toBase58() + done() + }).catch((err) => { + console.log(err) + assert.false() done() }) }) @@ -134,8 +142,14 @@ describe('account-utils', () => { }) it('should return the decrypted master keychain', (done) => { - decryptMasterKeychain('password123', encryptedBackupPhrase).then((keychain) => { - assert.equal(masterKeychain.getIdentifier().toString('hex'), keychain.getIdentifier().toString('hex')) + decryptMasterKeychain('password123', encryptedBackupPhrase).then((actualMnemonic) => { + const seedBuffer = bip39.mnemonicToSeed(backupPhrase) + const keychain = BlockstackWallet.fromSeedBuffer(seedBuffer).toBase58() + assert.equal(masterKeychain, keychain) + done() + }).catch((err) => { + console.log(err) + assert.false() done() }) }) From 9a546cdc0a125c7395bedd3dce3713f478ee10a4 Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Mon, 6 Aug 2018 16:49:38 -0500 Subject: [PATCH 6/7] finish refactoring out wallet functions from account-utils --- app/js/utils/account-utils.js | 44 -------------------------------- app/js/utils/index.js | 5 ---- app/js/wallet/SendPage.js | 14 +++------- test/utils/account-utils.test.js | 14 ++++------ 4 files changed, 8 insertions(+), 69 deletions(-) diff --git a/app/js/utils/account-utils.js b/app/js/utils/account-utils.js index 4d38c3c75..9b80fbdb1 100644 --- a/app/js/utils/account-utils.js +++ b/app/js/utils/account-utils.js @@ -13,21 +13,6 @@ const SINGLE_PLAYER_APP_DOMAIN_LEGACY_LIST = ['https://blockstack-todos.appartis 'https://peachyportfolio.com'] export const MAX_TRUST_LEVEL = 99 -export class AppNode { - constructor(hdNode, appDomain) { - this.hdNode = hdNode - this.appDomain = appDomain - } - - getAppPrivateKey() { - return this.hdNode.keyPair.d.toBuffer(32).toString('hex') - } - - getAddress() { - return this.hdNode.getAddress() - } -} - export function isPasswordValid(password) { let isValid = false let error = null @@ -52,35 +37,6 @@ export function isBackupPhraseValid(backupPhrase) { return { isValid, error } } -export function decryptMasterKeychain(password, encryptedBackupPhrase) { - return new Promise((resolve, reject) => { - BlockstackWallet.fromEncryptedMnemonic(encryptedBackupPhrase, password) - .then((wallet) => { - resolve(wallet.rootNode) - }) - .catch(err => { - logger.error('decryptMasterKeychain: error', err) - reject(new Error('Incorrect password')) - }) - }) -} - -const EXTERNAL_ADDRESS = 'EXTERNAL_ADDRESS' - -export function getBitcoinPrivateKeychain(masterKeychain) { - return new BlockstackWallet(masterKeychain).getBitcoinPrivateKeychain() -} - -export function getBitcoinAddressNode( - bitcoinKeychain, - addressIndex = 0, - chainType = EXTERNAL_ADDRESS -) { - return BlockstackWallet.getNodeFromBitcoinKeychain( - bitcoinKeychain.toBase58(), addressIndex, chainType - ) -} - export async function decryptBitcoinPrivateKey(password, encryptedBackupPhrase) { const wallet = await BlockstackWallet.fromEncryptedMnemonic(encryptedBackupPhrase, password) return wallet.getBitcoinPrivateKey(0) diff --git a/app/js/utils/index.js b/app/js/utils/index.js index aaa004a0a..c9fbd24b1 100644 --- a/app/js/utils/index.js +++ b/app/js/utils/index.js @@ -1,13 +1,8 @@ export { getCorrectAppPrivateKey, - decryptMasterKeychain, - deriveIdentityKeyPair, - getBitcoinPrivateKeychain, getWebAccountTypes, isPasswordValid, isBackupPhraseValid, - getIdentityOwnerAddressNode, - getBitcoinAddressNode, findAddressIndex, decryptBitcoinPrivateKey, calculateTrustLevel, diff --git a/app/js/wallet/SendPage.js b/app/js/wallet/SendPage.js index f2c9f2768..87f8fe56c 100644 --- a/app/js/wallet/SendPage.js +++ b/app/js/wallet/SendPage.js @@ -2,12 +2,7 @@ import PropTypes from 'prop-types' import React, { Component } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' -import { - decryptMasterKeychain, - getBitcoinPrivateKeychain, - getBitcoinAddressNode -} from '../utils' - +import { BlockstackWallet } from 'blockstack' import { AccountActions } from '../account/store/account' import Alert from '@components/Alert' @@ -90,11 +85,8 @@ class SendPage extends Component { }) const password = this.state.password const encryptedBackupPhrase = this.props.account.encryptedBackupPhrase - decryptMasterKeychain(password, encryptedBackupPhrase) - .then((masterKeychain) => { - const bitcoinPrivateKeychain = getBitcoinPrivateKeychain(masterKeychain) - const bitcoinAddressHDNode = getBitcoinAddressNode(bitcoinPrivateKeychain, 0) - const paymentKey = bitcoinAddressHDNode.keyPair.d.toBuffer(32).toString('hex') + BlockstackWallet.fromEncryptedMnemonic(encryptedBackupPhrase, password).then((wallet) => { + const paymentKey = wallet.getBitcoinPrivateKey(0) const amount = this.state.amount const recipientAddress = this.state.recipientAddress diff --git a/test/utils/account-utils.test.js b/test/utils/account-utils.test.js index 293424636..b83518c87 100644 --- a/test/utils/account-utils.test.js +++ b/test/utils/account-utils.test.js @@ -1,10 +1,7 @@ import bip39 from 'bip39' import { BlockstackWallet, publicKeyToAddress, getPublicKeyFromPrivate } from 'blockstack' import { - getIdentityPrivateKeychain, - getIdentityOwnerAddressNode, findAddressIndex, - decryptMasterKeychain, getCorrectAppPrivateKey } from '../../app/js/utils' @@ -132,7 +129,7 @@ describe('account-utils', () => { done() }).catch((err) => { console.log(err) - assert.false() + assert.equal(true, false) done() }) }) @@ -142,20 +139,19 @@ describe('account-utils', () => { }) it('should return the decrypted master keychain', (done) => { - decryptMasterKeychain('password123', encryptedBackupPhrase).then((actualMnemonic) => { - const seedBuffer = bip39.mnemonicToSeed(backupPhrase) - const keychain = BlockstackWallet.fromSeedBuffer(seedBuffer).toBase58() + BlockstackWallet.fromEncryptedMnemonic(encryptedBackupPhrase, 'password123').then((wallet) => { + const keychain = wallet.toBase58() assert.equal(masterKeychain, keychain) done() }).catch((err) => { console.log(err) - assert.false() + assert.equal(true, false) done() }) }) it('should return an error object if something goes wrong', (done) => { - decryptMasterKeychain('badpass', encryptedBackupPhrase).catch((err) => { + BlockstackWallet.fromEncryptedMnemonic(encryptedBackupPhrase, 'badpass').catch((err) => { assert.isTrue(err instanceof Error) assert.equal(err.message, 'Incorrect password') done() From 118cc3a6feafb44091c4e625d34a541b08d3562f Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Tue, 7 Aug 2018 09:24:49 -0500 Subject: [PATCH 7/7] add note riot to the legacy app list --- app/js/utils/account-utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/js/utils/account-utils.js b/app/js/utils/account-utils.js index 9b80fbdb1..f93b421f0 100644 --- a/app/js/utils/account-utils.js +++ b/app/js/utils/account-utils.js @@ -5,6 +5,7 @@ import { BlockstackWallet, publicKeyToAddress, getPublicKeyFromPrivate } from 'b const logger = log4js.getLogger('utils/account-utils.js') const SINGLE_PLAYER_APP_DOMAIN_LEGACY_LIST = ['https://blockstack-todos.appartisan.com', + 'https://note.riot.ai', 'https://use.coinsapp.co', 'http://www.coinstack.one', 'http://www.blockportfol.io',