Merge pull request #5797 from ajp8164/feat/intel-tee

Baseline Intel TEE support
This commit is contained in:
Javier Donadío 2017-04-17 13:57:25 -03:00 committed by GitHub
commit 803a9ca365
27 changed files with 635 additions and 102 deletions

View file

@ -1,11 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('createController',
function($scope, $rootScope, $timeout, $log, lodash, $state, $ionicScrollDelegate, $ionicHistory, profileService, configService, gettextCatalog, ledger, trezor, platformInfo, derivationPathHelper, ongoingProcess, walletService, storageService, popupService, appConfigService) {
var isChromeApp = platformInfo.isChromeApp;
var isCordova = platformInfo.isCordova;
var isDevel = platformInfo.isDevel;
function($scope, $rootScope, $timeout, $log, lodash, $state, $ionicScrollDelegate, $ionicHistory, profileService, configService, gettextCatalog, ledger, trezor, intelTEE, derivationPathHelper, ongoingProcess, walletService, storageService, popupService, appConfigService) {
/* For compressed keys, m*73 + n*34 <= 496 */
var COPAYER_PAIR_LIMITS = {
@ -67,9 +63,11 @@ angular.module('copayApp.controllers').controller('createController',
var seedOptions = [{
id: 'new',
label: gettextCatalog.getString('Random'),
}, {
supportsTestnet: true
}, {
id: 'set',
label: gettextCatalog.getString('Specify Recovery Phrase...'),
supportsTestnet: false
}];
$scope.seedSource = seedOptions[0];
@ -81,16 +79,26 @@ angular.module('copayApp.controllers').controller('createController',
*/
if (appConfigService.name == 'copay') {
if (n > 1 && isChromeApp) {
if (n > 1 && walletService.externalSource.ledger.supported)
seedOptions.push({
id: 'ledger',
label: 'Ledger Hardware Wallet',
id: walletService.externalSource.ledger.id,
label: walletService.externalSource.ledger.longName,
supportsTestnet: walletService.externalSource.ledger.supportsTestnet
});
if (walletService.externalSource.trezor.supported) {
seedOptions.push({
id: walletService.externalSource.trezor.id,
label: walletService.externalSource.trezor.longName,
supportsTestnet: walletService.externalSource.trezor.supportsTestnet
});
}
if (isChromeApp || isDevel) {
if (walletService.externalSource.intelTEE.supported) {
seedOptions.push({
id: 'trezor',
label: 'Trezor Hardware Wallet',
id: walletService.externalSource.intelTEE.id,
label: walletService.externalSource.intelTEE.longName,
supportsTestnet: walletService.externalSource.intelTEE.supportsTestnet
});
}
}
@ -151,23 +159,37 @@ angular.module('copayApp.controllers').controller('createController',
return;
}
if ($scope.seedSource.id == 'ledger' || $scope.seedSource.id == 'trezor') {
if ($scope.seedSource.id == walletService.externalSource.ledger.id || $scope.seedSource.id == walletService.externalSource.trezor.id || $scope.seedSource.id == walletService.externalSource.intelTEE.id) {
var account = $scope.formData.account;
if (!account || account < 1) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid account number'));
return;
}
if ($scope.seedSource.id == 'trezor')
if ($scope.seedSource.id == walletService.externalSource.trezor.id || $scope.seedSource.id == walletService.externalSource.intelTEE.id)
account = account - 1;
opts.account = account;
ongoingProcess.set('connecting' + $scope.seedSource.id, true);
ongoingProcess.set('connecting ' + $scope.seedSource.id, true);
var src = $scope.seedSource.id == 'ledger' ? ledger : trezor;
var src;
switch ($scope.seedSource.id) {
case walletService.externalSource.ledger.id:
src = ledger;
break;
case walletService.externalSource.trezor.id:
src = trezor;
break;
case walletService.externalSource.intelTEE.id:
src = intelTEE;
break;
default:
this.error = gettextCatalog.getString('Invalid seed source id: ' + $scope.seedSource.id);
return;
}
src.getInfoForNewWallet(opts.n > 1, account, function(err, lopts) {
ongoingProcess.set('connecting' + $scope.seedSource.id, false);
src.getInfoForNewWallet(opts.n > 1, account, opts.networkName, function(err, lopts) {
ongoingProcess.set('connecting ' + $scope.seedSource.id, false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;

View file

@ -8,8 +8,8 @@ angular.module('copayApp.controllers').controller('importController',
var errors = bwcService.getErrors();
$scope.init = function() {
$scope.isDevel = platformInfo.isDevel;
$scope.isChromeApp = platformInfo.isChromeApp;
$scope.supportsLedger = platformInfo.supportsLedger;
$scope.supportsTrezor = platformInfo.supportsTrezor;
$scope.isCordova = platformInfo.isCordova;
$scope.formData = {};
$scope.formData.bwsurl = defaults.bws.url;
@ -23,17 +23,17 @@ angular.module('copayApp.controllers').controller('importController',
$scope.seedOptions = [];
if ($scope.isChromeApp) {
if ($scope.supportsLedger) {
$scope.seedOptions.push({
id: 'ledger',
label: 'Ledger Hardware Wallet',
id: walletService.externalSource.ledger.id,
label: walletService.externalSource.ledger.longName,
});
}
if ($scope.isChromeApp || $scope.isDevel) {
if ($scope.supportsTrezor) {
$scope.seedOptions.push({
id: 'trezor',
label: 'Trezor Hardware Wallet',
id: walletService.externalSource.trezor.id,
label: walletService.externalSource.trezor.longName,
});
$scope.formData.seedSource = $scope.seedOptions[0];
}
@ -260,14 +260,14 @@ angular.module('copayApp.controllers').controller('importController',
};
$scope.importTrezor = function(account, isMultisig) {
trezor.getInfoForNewWallet(isMultisig, account, function(err, lopts) {
trezor.getInfoForNewWallet(isMultisig, account, 'livenet', function(err, lopts) {
ongoingProcess.clear();
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
lopts.externalSource = 'trezor';
lopts.externalSource = walletService.externalSource.trezor.id;
lopts.bwsurl = $scope.formData.bwsurl;
ongoingProcess.set('importingWallet', true);
$log.debug('Import opts', lopts);
@ -293,7 +293,7 @@ angular.module('copayApp.controllers').controller('importController',
var account = $scope.formData.account;
if ($scope.formData.seedSource.id == 'trezor') {
if ($scope.formData.seedSource.id == walletService.externalSource.trezor.id) {
if (account < 1) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid account number'));
return;
@ -302,11 +302,11 @@ angular.module('copayApp.controllers').controller('importController',
}
switch ($scope.formData.seedSource.id) {
case ('ledger'):
case (walletService.externalSource.ledger.id):
ongoingProcess.set('connectingledger', true);
$scope.importLedger(account);
break;
case ('trezor'):
case (walletService.externalSource.trezor.id):
ongoingProcess.set('connectingtrezor', true);
$scope.importTrezor(account, $scope.formData.isMultisig);
break;
@ -316,14 +316,14 @@ angular.module('copayApp.controllers').controller('importController',
};
$scope.importLedger = function(account) {
ledger.getInfoForNewWallet(true, account, function(err, lopts) {
ledger.getInfoForNewWallet(true, account, 'livenet', function(err, lopts) {
ongoingProcess.clear();
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
lopts.externalSource = 'ledger';
lopts.externalSource = lopts.externalSource = walletService.externalSource.ledger.id;
lopts.bwsurl = $scope.formData.bwsurl;
ongoingProcess.set('importingWallet', true);
$log.debug('Import opts', lopts);

View file

@ -1,10 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('joinController',
function($scope, $rootScope, $timeout, $state, $ionicHistory, $ionicScrollDelegate, profileService, configService, storageService, applicationService, gettextCatalog, lodash, ledger, trezor, platformInfo, derivationPathHelper, ongoingProcess, walletService, $log, $stateParams, popupService, appConfigService) {
var isChromeApp = platformInfo.isChromeApp;
var isDevel = platformInfo.isDevel;
function($scope, $rootScope, $timeout, $state, $ionicHistory, $ionicScrollDelegate, profileService, configService, storageService, applicationService, gettextCatalog, lodash, ledger, trezor, intelTEE, derivationPathHelper, ongoingProcess, walletService, $log, $stateParams, popupService, appConfigService) {
var self = this;
var defaults = configService.getDefaults();
@ -64,17 +61,24 @@ angular.module('copayApp.controllers').controller('joinController',
*/
if (appConfigService.name == 'copay') {
if (isChromeApp) {
if (walletService.externalSource.ledger.supported) {
self.seedOptions.push({
id: 'ledger',
label: 'Ledger Hardware Wallet',
id: walletService.externalSource.ledger.id,
label: walletService.externalSource.ledger.longName
});
}
if (isChromeApp || isDevel) {
if (walletService.externalSource.trezor.supported) {
self.seedOptions.push({
id: 'trezor',
label: 'Trezor Hardware Wallet',
id: walletService.externalSource.trezor.id,
label: walletService.externalSource.trezor.longName
});
}
if (walletService.externalSource.intelTEE.supported) {
seedOptions.push({
id: walletService.externalSource.intelTEE.id,
label: walletService.externalSource.intelTEE.longName
});
}
}
@ -97,7 +101,7 @@ angular.module('copayApp.controllers').controller('joinController',
var opts = {
secret: form.secret.$modelValue,
myName: form.myName.$modelValue,
bwsurl: $scope.bwsurl,
bwsurl: $scope.bwsurl
}
var setSeed = self.seedSourceId == 'set';
@ -130,21 +134,38 @@ angular.module('copayApp.controllers').controller('joinController',
return;
}
if (self.seedSourceId == 'ledger' || self.seedSourceId == 'trezor') {
if (self.seedSourceId == walletService.externalSource.ledger.id || self.seedSourceId == walletService.externalSource.trezor.id || self.seedSourceId == walletService.externalSource.intelTEE.id) {
var account = $scope.account;
if (!account || account < 1) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid account number'));
return;
}
if (self.seedSourceId == 'trezor')
if (self.seedSourceId == walletService.externalSource.trezor.id || self.seedSourceId == walletService.externalSource.intelTEE.id)
account = account - 1;
opts.account = account;
opts.isMultisig = true;
ongoingProcess.set('connecting' + self.seedSourceId, true);
var src = self.seedSourceId == 'ledger' ? ledger : trezor;
src.getInfoForNewWallet(true, account, function(err, lopts) {
var src;
switch (self.seedSourceId) {
case walletService.externalSource.ledger.id:
src = ledger;
break;
case walletService.externalSource.trezor.id:
src = trezor;
break;
case walletService.externalSource.intelTEE.id:
src = intelTEE;
break;
default:
this.error = gettextCatalog.getString('Invalid seed source id: ' + self.seedSourceId);
return;
}
// TODO: cannot currently join an intelTEE testnet wallet (need to detect from the secret)
src.getInfoForNewWallet(true, account, 'livenet', function(err, lopts) {
ongoingProcess.set('connecting' + self.seedSourceId, false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);

View file

@ -89,9 +89,6 @@ angular.module('copayApp.controllers').controller('preferencesController',
value: $scope.wallet.balanceHidden
};
if (wallet.isPrivKeyExternal)
$scope.externalSource = wallet.getPrivKeyExternalSourceName() == 'ledger' ? 'Ledger' : 'Trezor';
$scope.touchIdAvailable = fingerprintService.isAvailable();
$scope.touchIdEnabled = {
value: config.touchIdFor ? config.touchIdFor[walletId] : null

View file

@ -0,0 +1,28 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesExternalController', function($scope, $stateParams, lodash, gettextCatalog, popupService, profileService, walletService) {
var wallet = profileService.getWallet($stateParams.walletId);
$scope.externalSource = lodash.find(walletService.externalSource, function(source) {
return source.id == wallet.getPrivKeyExternalSourceName();
});
if ($scope.externalSource.isEmbeddedHardware) {
$scope.hardwareConnected = $scope.externalSource.version.length > 0;
$scope.showMneumonicFromHardwarePopup = function() {
var title = gettextCatalog.getString('Warning!');
var message = gettextCatalog.getString('Are you being watched? Anyone with your recovery phrase can access or spend your bitcoin.');
popupService.showConfirm(title, message, null, null, function(res) {
if (res) {
walletService.showMneumonicFromHardware(wallet, function(err) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err.message || err);
}
});
}
});
};
}
});

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesInformation',
function($scope, $log, $ionicHistory, platformInfo, lodash, profileService, configService, $stateParams, $state) {
function($scope, $log, $ionicHistory, platformInfo, lodash, profileService, configService, $stateParams, $state, walletService) {
var wallet = profileService.getWallet($stateParams.walletId);
$scope.wallet = wallet;
@ -44,5 +44,13 @@ angular.module('copayApp.controllers').controller('preferencesInformation',
$scope.M = c.m;
$scope.N = c.n;
$scope.pubKeys = lodash.pluck(c.publicKeyRing, 'xPubKey');
$scope.externalSource = null;
if (wallet.isPrivKeyExternal()) {
$scope.externalSource = lodash.find(walletService.externalSource, function(source) {
return source.id == wallet.getPrivKeyExternalSourceName();
}).name;
}
});
});

View file

@ -145,6 +145,22 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
});
};
$scope.shouldShowReceiveAddressFromHardware = function() {
var wallet = $scope.wallet;
if (wallet.isPrivKeyExternal() && wallet.credentials.hwInfo) {
return (wallet.credentials.hwInfo.name == walletService.externalSource.intelTEE.id);
} else {
return false;
}
};
$scope.showReceiveAddressFromHardware = function() {
var wallet = $scope.wallet;
if (wallet.isPrivKeyExternal() && wallet.credentials.hwInfo) {
walletService.showReceiveAddressFromHardware(wallet, $scope.addr, function(){});
}
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.wallets = profileService.getWallets();

View file

@ -579,6 +579,15 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
}
})
.state('tabs.preferences.preferencesExternal', {
url: '/preferencesExternal',
views: {
'tab-settings@tabs': {
controller: 'preferencesExternalController',
templateUrl: 'views/preferencesExternal.html'
}
}
})
.state('tabs.preferences.delete', {
url: '/delete',
views: {

View file

@ -6,9 +6,11 @@ angular.module('copayApp.services')
// Ledger magic number to get xPub without user confirmation
root.ENTROPY_INDEX_PATH = "0xb11e/";
root.M = 'm/';
root.UNISIG_ROOTPATH = 44;
root.MULTISIG_ROOTPATH = 48;
root.LIVENET_PATH = 0;
root.TESTNET_PATH = 1;
root._err = function(data) {
var msg = data.error || data.message || 'unknown';
@ -17,26 +19,49 @@ angular.module('copayApp.services')
root.getRootPath = function(device, isMultisig, account) {
if (!isMultisig) return root.UNISIG_ROOTPATH;
// Compat
if (device == 'ledger' && account == 0) return root.UNISIG_ROOTPATH;
return root.MULTISIG_ROOTPATH;
var path;
if (isMultisig) {
path = root.MULTISIG_ROOTPATH;
} else {
if (device == 'ledger' && account > 0) {
path = root.MULTISIG_ROOTPATH;
} else {
path = root.UNISIG_ROOTPATH;
}
}
if (device == 'intelTEE') {
path = root.M + path;
}
return path;
};
root.getAddressPath = function(device, isMultisig, account) {
return root.getRootPath(device, isMultisig, account) + "'/" + root.LIVENET_PATH + "'/" + account + "'";
}
root.getAddressPath = function(device, isMultisig, account, network) {
network = network || 'livenet';
var networkPath = root.LIVENET_PATH;
if (network == 'testnet') {
networkPath = root.TESTNET_PATH;
}
return root.getRootPath(device, isMultisig, account) + "'/" + networkPath + "'/" + account + "'";
};
root.getEntropyPath = function(device, isMultisig, account) {
var path;
var path = root.ENTROPY_INDEX_PATH;
if (isMultisig) {
path = path + "48'/"
} else {
path = path + "44'/"
}
// Old ledger wallet compat
if (device == 'ledger' && account == 0)
return root.ENTROPY_INDEX_PATH + "0'";
if (device == 'ledger' && account == 0) {
return path + "0'/";
}
return root.ENTROPY_INDEX_PATH + root.getRootPath(device, isMultisig, account) + "'/" + account + "'";
if (device == 'intelTEE') {
path = root.M + path;
}
return path + account + "'";
};
root.pubKeyToEntropySource = function(xPubKey) {

195
src/js/services/intelTEE.js Normal file
View file

@ -0,0 +1,195 @@
'use strict';
angular.module('copayApp.services')
.factory('intelTEE', function($log, $timeout, gettext, lodash, bitcore, hwWallet, bwcService, platformInfo) {
var root = {};
root.description = {
supported: platformInfo.supportsIntelTEE,
version: platformInfo.versionIntelTEE,
id: 'intelTEE',
name: 'Intel TEE',
longName: 'Intel TEE Hardware Wallet',
derivationStrategy: 'BIP44',
isEmbeddedHardware: true,
supportsTestnet: true
};
if (!root.description.supported) {
return root;
}
var IntelWallet = require('intelWalletCon');
var TEE_APP_ID = '63279de1b6cb4dcf8c206716bd318092f8c206716bd31809263279de1b6cb4dc';
root.walletEnclave = new IntelWallet.Wallet();
var walletEnclaveStatus = root.walletEnclave.initializeEnclave();
if (walletEnclaveStatus != 0) {
$log.error('Failed to create Intel Wallet enclave');
}
root.getInfoForNewWallet = function(isMultisig, account, networkName, callback) {
var opts = {};
initSource(opts, function(err, opts) {
if (err) return callback(err);
root.getEntropySource(opts.hwInfo.id, isMultisig, account, function(err, entropySource) {
if (err) return callback(err);
opts.entropySource = entropySource;
root.getXPubKey(opts.hwInfo.id, hwWallet.getAddressPath(root.description.id, isMultisig, account, networkName), function(data) {
if (!data.success) {
$log.warn(data.message);
return callback(data);
}
opts.extendedPublicKey = data.xpubkey;
opts.externalSource = root.description.id;
opts.derivationStrategy = root.description.derivationStrategy;
return callback(null, opts);
});
});
});
};
root.getXPubKey = function(teeWalletId, path, callback) {
$log.debug('TEE deriving xPub path:', path);
// Expected to be a extended public key.
var xpubkey = root.walletEnclave.getPublicKey(teeWalletId, path);
// Error messages returned in value.
var result = {
success: false,
message: xpubkey.ExtendedPublicKey
};
// Success indicated by status being equal to the tee wallet id.
if (xpubkey.Status == teeWalletId) {
result.success = true;
result.message = 'OK';
result.xpubkey = xpubkey.ExtendedPublicKey;
} else {
$log.error('Failed to get xpubkey from TEE wallet: ' + result.message);
}
callback(result);
};
root.getEntropySource = function(teeWalletId, isMultisig, account, callback) {
root.getXPubKey(teeWalletId, hwWallet.getEntropyPath(root.description.id, isMultisig, account), function(data) {
if (!data.success)
return callback(hwWallet._err(data));
return callback(null, hwWallet.pubKeyToEntropySource(data.xpubkey));
});
};
root.showMneumonic = function(teeWalletId, cb) {
var result = root.walletEnclave.displayWordList(teeWalletId, 'en');
if (result != teeWalletId) {
cb(result);
} else {
cb();
}
};
root.showReceiveAddress = function(teeWalletId, address, cb) {
var isMultisig = false; // TODO
var account = 0; // TODO
var basePath = hwWallet.getAddressPath(root.description.id, isMultisig, account, address.network);
var keyPath = address.path.replace('m', basePath);
var result = root.walletEnclave.displayReceiveAddress(teeWalletId, keyPath);
if (result != teeWalletId) {
cb(result);
} else {
cb();
}
};
root.signTx = function(teeWalletId, txp, callback) {
var account = 0; // TODO
var isMultisig = txp.requiredSignatures > 1;
var basePath = hwWallet.getAddressPath(root.description.id, isMultisig, account, txp.network);
var rawTx = bwcService.Client.getRawTx(txp);
var keypaths = lodash.map(lodash.pluck(txp.inputs, 'path'), function(path) {
return path.replace('m', basePath);
});
var publicKeys = lodash.pluck(txp.inputs, 'publicKeys');
var changePublicKeys = txp.changeAddress.publicKeys;
publicKeys.push(changePublicKeys);
var changeaddrpath;
if (txp.changeAddress) {
changeaddrpath = txp.changeAddress.path.replace('m', basePath);
}
var result;
if (txp.requiredSignatures == 1) {
result = root.walletEnclave.signTransaction(teeWalletId, rawTx, changeaddrpath, keypaths);
} else {
result = root.walletEnclave.signTransaction(teeWalletId, rawTx, changeaddrpath, keypaths, publicKeys, txp.requiredSignatures, changePublicKeys, txp.requiredSignatures);
}
if (result.Status != teeWalletId) {
return callback('TEE failed to sign transction: ' + result.Status);
}
return callback(null, result);
};
function initSource(opts, callback) {
var args = {
"Testnet" : (opts.networkName == 'livenet'? false : true),
"PINUnlockRequired" : false,
"PINSignatureDataRequired" : false,
"PINSignatureTransaction" : 0,
"ExportCount" : 10,
"MaxPINAttempts" : 3,
"PINTimeout" : 30
};
var teeStatus = root.walletEnclave.createWallet(TEE_APP_ID, args);
switch (teeStatus) {
case "CREATE WALLET FAILURE":
case "CREATE WALLET FAILED TO INITIALIZE":
case "CREATE WALLET FAILURE BAD INPUT":
case "CREATE WALLET FAILURE case SERIALIZATION":
case "DELETE_WALLET_AUTHORIZATION_UNSUCCESSFUL":
case "LOAD_WALLET_FAILTURE":
case "IMPORT WORD LIST FAILTURE":
case "IMPORT WORD LIST FAILURE BAD INPUT":
case "IMPORT WORD NOT IN DICTIONARY":
case "INVALID PIN":
case "INVALID APPLICATION ID":
case "DISPLAY WORD LIST FAILURE":
case "DELETE WALLET NO SUCH APPLICATION ID":
case "SIGN DATA FAILURE":
case "SIGN DATA INVALID HASH":
case "SIGN DATA BUFFER TOO SMALL":
case "SIGN DATA INVALID PIN":
case "RECEIVE ADDRESS INVALID INPUT":
case "RECEIVE ADDRESS NULL":
case "RECEIVE ADDRESS BUFFER TOO SMALL":
case "PUBLIC KEY BUFFER TOO SMALL":
case "LOAD WALLET FAILURE":
case "PUBLIC KEY FAILURE":
case "PUBLIC KEY FAIL TO SERIALIZE":
case "UKNOWN ERROR CODE":
$log.error(teeStatus);
return callback(teeStatus); // TODO: translate error text for display
break;
default:
opts.hwInfo = {
name: root.description.id,
id: teeStatus
};
$log.debug('TEE wallet created: ' + opts.hwInfo.id);
return callback(null, opts);
}
};
return root;
});

View file

@ -1,10 +1,19 @@
'use strict';
angular.module('copayApp.services')
.factory('ledger', function($log, bwcService, gettext, hwWallet) {
.factory('ledger', function($log, bwcService, gettext, hwWallet, platformInfo) {
var root = {};
var LEDGER_CHROME_ID = "kkdpmhnladdopljabkgpacgpliggeeaf";
root.description = {
supported: platformInfo.supportsLedger,
id: 'ledger',
name: 'Ledger',
longName: 'Ledger Hardware Wallet',
isEmbeddedHardware: false,
supportsTestnet: false
};
root.callbacks = {};
root.hasSession = function() {
root._message({
@ -13,7 +22,7 @@ angular.module('copayApp.services')
}
root.getEntropySource = function(isMultisig, account, callback) {
root.getXPubKey(hwWallet.getEntropyPath('ledger', isMultisig, account), function(data) {
root.getXPubKey(hwWallet.getEntropyPath(root.description.id, isMultisig, account), function(data) {
if (!data.success)
return callback(hwWallet._err(data));
@ -30,21 +39,28 @@ angular.module('copayApp.services')
});
};
root.getInfoForNewWallet = function(isMultisig, account, callback) {
root.initSource = function(opts, callback) {
// No initialization for this hardware source.
return callback(opts);
};
root.getInfoForNewWallet = function(isMultisig, account, networkName, callback) {
// networkName not used for this hardware (always livenet)
root.getEntropySource(isMultisig, account, function(err, entropySource) {
if (err) return callback(err);
root.getXPubKey(hwWallet.getAddressPath('ledger', isMultisig, account), function(data) {
if (!data.success) return callback(data);
var opts = {};
opts.entropySource = entropySource;
var opts = {};
opts.entropySource = entropySource;
root.getXPubKey(hwWallet.getAddressPath(root.description.id, isMultisig, account), function(data) {
if (!data.success) {
$log.warn(data.message);
return callback(data);
}
opts.extendedPublicKey = data.xpubkey;
opts.externalSource = 'ledger';
opts.account = account;
opts.externalSource = root.description.id;
// Old ledger compat
opts.derivationStrategy = account ? 'BIP48' : 'BIP44';
opts.derivationStrategy = opts.account ? 'BIP48' : 'BIP44';
return callback(null, opts);
});
});
@ -57,7 +73,7 @@ angular.module('copayApp.services')
var tx = bwcService.getUtils().buildTx(txp);
for (var i = 0; i < tx.inputs.length; i++) {
redeemScripts.push(new ByteString(tx.inputs[i].redeemScript.toBuffer().toString('hex'), GP.HEX).toString());
paths.push(hwWallet.getAddressPath('ledger', isMultisig, account) + txp.inputs[i].path.substring(1));
paths.push(hwWallet.getAddressPath(root.description.id, isMultisig, account) + txp.inputs[i].path.substring(1));
}
var splitTransaction = root._splitTransaction(new ByteString(tx.toString(), GP.HEX));
var inputs = [];

View file

@ -23,6 +23,27 @@ angular.module('copayApp.services').factory('platformInfo', function($window) {
}
};
var getVersionIntelTee = function() {
var v = '';
var isWindows = navigator.platform.indexOf('Win') > -1;
if (!isNodeWebkit() || !isWindows) {
return v;
}
try {
var IntelWallet = require('intelWalletCon');
if (IntelWallet.getVersion) {
v = IntelWallet.getVersion();
} else {
v = 'Alpha';
}
if (v.length > 0) {
$log.info('Intel TEE library ' + v);
}
} catch (e) {}
return v;
};
// Detect mobile devices
var ret = {
@ -39,5 +60,11 @@ angular.module('copayApp.services').factory('platformInfo', function($window) {
ret.isChromeApp = $window.chrome && chrome.runtime && chrome.runtime.id && !ret.isNW;
ret.isDevel = !ret.isMobile && !ret.isChromeApp && !ret.isNW;
ret.supportsLedger = ret.isChromeApp;
ret.supportsTrezor = ret.isChromeApp || ret.isDevel;
ret.versionIntelTEE = getVersionIntelTee();
ret.supportsIntelTEE = ret.versionIntelTEE.length > 0;
return ret;
});

View file

@ -347,6 +347,7 @@ angular.module('copayApp.services')
account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
});
walletClient.credentials.hwInfo = opts.hwInfo;
} catch (ex) {
$log.warn("Creating wallet from Extended Public Key Arg:", ex, opts);
return cb(gettextCatalog.getString('Could not create using the specified extended public key'));

View file

@ -1,14 +1,24 @@
'use strict';
angular.module('copayApp.services')
.factory('trezor', function($log, $timeout, lodash, bitcore, hwWallet) {
.factory('trezor', function($log, $timeout, lodash, bitcore, hwWallet, platformInfo) {
var root = {};
var SETTLE_TIME = 3000;
root.callbacks = {};
root.description = {
supported: platformInfo.supportsTrezor,
id: 'trezor',
name: 'Trezor',
longName: 'Trezor Hardware Wallet',
derivationStrategy: 'BIP48',
isEmbeddedHardware: false,
supportsTestnet: false
};
root.getEntropySource = function(isMultisig, account, callback) {
root.getXPubKey(hwWallet.getEntropyPath('trezor', isMultisig, account), function(data) {
root.getXPubKey(hwWallet.getEntropyPath(root.description.id, isMultisig, account), function(data) {
if (!data.success)
return callback(hwWallet._err(data));
@ -26,8 +36,13 @@ angular.module('copayApp.services')
}
};
root.initSource = function(opts, callback) {
// No initialization for this hardware source.
return callback(opts);
};
root.getInfoForNewWallet = function(isMultisig, account, callback) {
root.getInfoForNewWallet = function(isMultisig, account, networkName, callback) {
// networkName not used for this hardware (always livenet)
var opts = {};
root.getEntropySource(isMultisig, account, function(err, data) {
if (err) return callback(err);
@ -35,13 +50,12 @@ angular.module('copayApp.services')
$log.debug('Waiting TREZOR to settle...');
$timeout(function() {
root.getXPubKey(hwWallet.getAddressPath('trezor', isMultisig, account), function(data) {
root.getXPubKey(hwWallet.getAddressPath(root.description.id, isMultisig, account), function(data) {
if (!data.success)
return callback(hwWallet._err(data));
opts.extendedPublicKey = data.xpubkey;
opts.externalSource = 'trezor';
opts.account = account;
opts.externalSource = root.description.id;
if (isMultisig)
opts.derivationStrategy = 'BIP48';

View file

@ -1,10 +1,16 @@
'use strict';
angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, storageService, configService, rateService, uxLanguage, $filter, gettextCatalog, bwcError, $ionicPopup, fingerprintService, ongoingProcess, gettext, $rootScope, txFormatService, $ionicModal, $state, bwcService, bitcore, popupService) {
angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, intelTEE, storageService, configService, rateService, uxLanguage, $filter, gettextCatalog, bwcError, $ionicPopup, fingerprintService, ongoingProcess, gettext, $rootScope, txFormatService, $ionicModal, $state, bwcService, bitcore, popupService) {
// `wallet` is a decorated version of client.
var root = {};
root.externalSource = {
ledger: ledger.description,
trezor: trezor.description,
intelTEE: intelTEE.description
}
root.WALLET_STATUS_MAX_TRIES = 7;
root.WALLET_STATUS_DELAY_BETWEEN_TRIES = 1.4 * 1000;
root.SOFT_CONFIRMATION_LIMIT = 12;
@ -40,6 +46,43 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
var _signWithIntelTEE = function(wallet, txp, cb) {
$log.info('Requesting Intel TEE to sign the transaction');
intelTEE.signTx(wallet.credentials.hwInfo.id, txp, function(err, result) {
if (err) return cb(err);
$log.debug('Intel TEE response', result);
txp.signatures = result.Signatures;
return wallet.signTxProposal(txp, cb);
});
};
root.showMneumonicFromHardware = function(wallet, cb) {
switch (wallet.getPrivKeyExternalSourceName()) {
case root.externalSource.intelTEE.id:
return intelTEE.showMneumonic(wallet.credentials.hwInfo.id, cb);
break;
default:
cb('Error: unrecognized external source');
break;
}
};
root.showReceiveAddressFromHardware = function(wallet, address, cb) {
switch (wallet.getPrivKeyExternalSourceName()) {
case root.externalSource.intelTEE.id:
root.getAddressObj(wallet, address, function(err, addrObj) {
if (err) return cb(err);
return intelTEE.showReceiveAddress(wallet.credentials.hwInfo.id, addrObj, cb);
});
break;
default:
cb('Error: unrecognized external source');
break;
}
};
root.invalidateCache = function(wallet) {
if (wallet.cachedStatus)
wallet.cachedStatus.isValid = false;
@ -629,10 +672,12 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
if (wallet.isPrivKeyExternal()) {
switch (wallet.getPrivKeyExternalSourceName()) {
case 'ledger':
case root.externalSource.ledger.id:
return _signWithLedger(wallet, txp, cb);
case 'trezor':
case root.externalSource.trezor.id:
return _signWithTrezor(wallet, txp, cb);
case root.externalSource.intelTEE.id:
return _signWithIntelTEE(wallet, txp, cb);
default:
var msg = 'Unsupported External Key:' + wallet.getPrivKeyExternalSourceName();
$log.error(msg);
@ -845,6 +890,21 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
root.getAddressObj = function(wallet, address, cb) {
wallet.getMainAddresses({
reverse: true
}, function(err, addr) {
if (err) return cb(err);
var addrObj = lodash.find(addr, function(a) {
return a.address == address;
});
var err = null;
if (!addrObj) {
err = 'Error: specified address not in wallet';
}
return cb(err, addrObj);
});
};
root.isReady = function(wallet, cb) {
if (!wallet.isComplete())

View file

@ -162,6 +162,17 @@
transform: translate(100%, -40%);
}
}
.overlay {
position: absolute;
width: 220px;
height: 100%;
background-color: rgba(255,255,255,0.8);
button {
width: 100%;
top: 50%;
transform: translateY(-50%);
}
}
@media(max-height: 700px) {
padding: 10vh 0 4vh;
}

View file

@ -3,6 +3,13 @@
.icon-bitpay {
background-image: url("../img/icon-bitpay.svg");
}
.warning {
color: $v-warning-color;
}
.centered {
width: 100%;
text-align: center;
}
.disabled {
color: $v-light-gray;
}