Ledger hardware wallet support
This commit is contained in:
parent
e3831fe9c6
commit
d3f77b37ad
9 changed files with 693 additions and 23 deletions
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<div class="content p20v" ng-controller="createController as create" ng-init="create.setTotalCopayers(1)">
|
<div class="content p20v" ng-controller="createController as create" ng-init="create.setTotalCopayers(1)">
|
||||||
|
|
||||||
<div class="onGoingProcess" ng-show="create.loading">
|
<div class="onGoingProcess" ng-show="create.loading && !create.ledger">
|
||||||
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
|
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
|
||||||
<div class="spinner">
|
<div class="spinner">
|
||||||
<div class="rect1"></div>
|
<div class="rect1"></div>
|
||||||
|
|
@ -21,6 +21,19 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="onGoingProcess" ng-show="create.ledger">
|
||||||
|
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
|
||||||
|
<div class="spinner">
|
||||||
|
<div class="rect1"></div>
|
||||||
|
<div class="rect2"></div>
|
||||||
|
<div class="rect3"></div>
|
||||||
|
<div class="rect4"></div>
|
||||||
|
<div class="rect5"></div>
|
||||||
|
</div>
|
||||||
|
<span translate>Connecting to Ledger Wallet...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="create-tab small-only-text-center" ng-hide="create.hideTabs">
|
<div class="create-tab small-only-text-center" ng-hide="create.hideTabs">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="tab-container small-6 medium-3 large-2">
|
<div class="tab-container small-6 medium-3 large-2">
|
||||||
|
|
@ -87,12 +100,22 @@
|
||||||
<i ng-if="!hideAdv" class="icon-arrow-up4"></i>
|
<i ng-if="!hideAdv" class="icon-arrow-up4"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div ng-hide="hideAdv" class="row">
|
<div ng-hide="hideAdv" class="row" ng-init="hideSlots=true">
|
||||||
<div class="large-12 columns">
|
<div class="large-12 columns">
|
||||||
<label for="network-name" class="line-b oh">
|
<label for="network-name" class="line-b oh">
|
||||||
<span translate>Testnet</span>
|
<span translate>Testnet</span>
|
||||||
<switch id="network-name" name="isTestnet" ng-model="isTestnet" class="green right m5t m10b"></switch>
|
<switch id="network-name" name="isTestnet" ng-model="isTestnet" class="green right m5t m10b"></switch>
|
||||||
</label>
|
</label>
|
||||||
|
<label ng-show="create.isChromeApp()" for="hw-ledger" class="line-b oh">
|
||||||
|
<span translate>Use Ledger hardware wallet</span>
|
||||||
|
<switch id="hw-ledger" name="hwLedger" ng-model="hwLedger" class="green right m5t m10b" ng-change="hideSlots=false"></switch>
|
||||||
|
</label>
|
||||||
|
<div ng-hide="hideSlots">
|
||||||
|
<label class="line-b oh"><span translate>Select slot number for Ledger key</span>
|
||||||
|
<select class="m10t" ng-model="externalIndex" ng-options="externalIndex as externalIndex for externalIndex in create.externatIndexValues">
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<label for="ext-master" class="m10t">
|
<label for="ext-master" class="m10t">
|
||||||
<span translate>Master extended private key</span>
|
<span translate>Master extended private key</span>
|
||||||
<small translate>If not given, a secure key will be generated</small>
|
<small translate>If not given, a secure key will be generated</small>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div class="content p20v" ng-controller="joinController as join">
|
<div class="content p20v" ng-controller="joinController as join">
|
||||||
<div class="onGoingProcess" ng-show="join.loading">
|
<div class="onGoingProcess" ng-show="join.loading && !join.ledger">
|
||||||
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
|
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
|
||||||
<div class="spinner">
|
<div class="spinner">
|
||||||
<div class="rect1"></div>
|
<div class="rect1"></div>
|
||||||
|
|
@ -19,6 +19,19 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="onGoingProcess" ng-show="join.ledger">
|
||||||
|
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
|
||||||
|
<div class="spinner">
|
||||||
|
<div class="rect1"></div>
|
||||||
|
<div class="rect2"></div>
|
||||||
|
<div class="rect3"></div>
|
||||||
|
<div class="rect4"></div>
|
||||||
|
<div class="rect5"></div>
|
||||||
|
</div>
|
||||||
|
<span translate>Connecting to Ledger Wallet...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="large-12 columns">
|
<div class="large-12 columns">
|
||||||
|
|
||||||
|
|
@ -71,7 +84,20 @@
|
||||||
<i ng-show="!join.hideAdv" class="icon-arrow-down4"></i>
|
<i ng-show="!join.hideAdv" class="icon-arrow-down4"></i>
|
||||||
<i ng-show="join.hideAdv" class="icon-arrow-up4"></i>
|
<i ng-show="join.hideAdv" class="icon-arrow-up4"></i>
|
||||||
</a>
|
</a>
|
||||||
<div ng-show="join.hideAdv" class="row">
|
<div ng-show="join.hideAdv" class="row" ng-init="hideSlots=true">
|
||||||
|
<div class="large-12 columns" ng-show="join.isChromeApp()">
|
||||||
|
<label for="hw-ledger" class="line-b oh">
|
||||||
|
<span translate>Use Ledger hardware wallet</span>
|
||||||
|
<switch id="hw-ledger" name="hwLedger" ng-model="hwLedger" class="green right m5t m10b" ng-change="hideSlots=false"></switch>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="large-12 columns" ng-hide="hideSlots">
|
||||||
|
<label class="line-b oh">
|
||||||
|
<span translate>Select slot number for Ledger key</span>
|
||||||
|
<select class="m10t" ng-model="externalIndex" ng-options="externalIndex as externalIndex for externalIndex in join.externatIndexValues">
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="large-12 columns">
|
<div class="large-12 columns">
|
||||||
<label for="ext-master">{{'Master extended private key'|translate}}
|
<label for="ext-master">{{'Master extended private key'|translate}}
|
||||||
<small translate>If not given, a secure key will be generated</small>
|
<small translate>If not given, a secure key will be generated</small>
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,17 @@
|
||||||
<span ng-style="{'color':index.backgroundColor}">█</span>
|
<span ng-style="{'color':index.backgroundColor}">█</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="line-b p20">
|
<li class="line-b p20" ng-hide="preferences.externalIndex >= 0">
|
||||||
<span translate>Encrypt Private Key</span>
|
<span translate>Encrypt Private Key</span>
|
||||||
<switch id="network-name" name="encrypt" ng-model="encrypt" class="green right"></switch>
|
<switch id="network-name" name="encrypt" ng-model="encrypt" class="green right"></switch>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="line-b p20" ng-show="preferences.externalIndex >= 0">
|
||||||
|
<span translate>Hardware wallet</span>
|
||||||
|
<span class="right text-gray">
|
||||||
|
{{preferences.externalSource}}
|
||||||
|
(index {{preferences.externalIndex}})
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
<h4 class="title m0"> </h4>
|
<h4 class="title m0"> </h4>
|
||||||
<li class="line-b p20" ng-click="$root.go('backup')">
|
<li class="line-b p20" ng-click="$root.go('backup')">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('createController',
|
angular.module('copayApp.controllers').controller('createController',
|
||||||
function($scope, $rootScope, $location, $timeout, $log, lodash, go, profileService, configService, isMobile, isCordova, gettext) {
|
function($scope, $rootScope, $location, $timeout, $log, lodash, go, profileService, configService, isMobile, isCordova, gettext, isChromeApp, ledger) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var defaults = configService.getDefaults();
|
var defaults = configService.getDefaults();
|
||||||
|
|
@ -35,6 +35,8 @@ angular.module('copayApp.controllers').controller('createController',
|
||||||
$scope.requiredCopayers = Math.min(parseInt(n / 2 + 1), maxReq);
|
$scope.requiredCopayers = Math.min(parseInt(n / 2 + 1), maxReq);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.externatIndexValues = lodash.range(0,20);
|
||||||
|
$scope.externalIndex = 0;
|
||||||
this.TCValues = lodash.range(2, defaults.limits.totalCopayers + 1);
|
this.TCValues = lodash.range(2, defaults.limits.totalCopayers + 1);
|
||||||
$scope.totalCopayers = defaults.wallet.totalCopayers;
|
$scope.totalCopayers = defaults.wallet.totalCopayers;
|
||||||
|
|
||||||
|
|
@ -42,6 +44,10 @@ angular.module('copayApp.controllers').controller('createController',
|
||||||
updateRCSelect(tc);
|
updateRCSelect(tc);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.isChromeApp = function() {
|
||||||
|
return isChromeApp;
|
||||||
|
};
|
||||||
|
|
||||||
this.create = function(form) {
|
this.create = function(form) {
|
||||||
if (form && form.$invalid) {
|
if (form && form.$invalid) {
|
||||||
this.error = gettext('Please enter the required fields');
|
this.error = gettext('Please enter the required fields');
|
||||||
|
|
@ -53,14 +59,40 @@ angular.module('copayApp.controllers').controller('createController',
|
||||||
name: form.walletName.$modelValue,
|
name: form.walletName.$modelValue,
|
||||||
extendedPrivateKey: form.privateKey.$modelValue,
|
extendedPrivateKey: form.privateKey.$modelValue,
|
||||||
myName: $scope.totalCopayers > 1 ? form.myName.$modelValue : null,
|
myName: $scope.totalCopayers > 1 ? form.myName.$modelValue : null,
|
||||||
networkName: form.isTestnet.$modelValue ? 'testnet' : 'livenet'
|
networkName: form.isTestnet.$modelValue ? 'testnet' : 'livenet',
|
||||||
};
|
};
|
||||||
self.loading = true;
|
self.loading = true;
|
||||||
|
|
||||||
|
if (form.hwLedger.$modelValue) {
|
||||||
|
self.ledger = true;
|
||||||
|
ledger.getXPubKey($scope.externalIndex, function(data) {
|
||||||
|
self.ledger = false;
|
||||||
|
$scope.$apply();
|
||||||
|
if (data.success) {
|
||||||
|
opts.extendedPublicKey = data.xpubkey;
|
||||||
|
opts.externalSource = 'ledger';
|
||||||
|
opts.externalIndex = $scope.externalIndex;
|
||||||
|
self._create(opts);
|
||||||
|
} else {
|
||||||
|
self.loading = false;
|
||||||
|
$log.debug(data.message);
|
||||||
|
self.error = data.message;
|
||||||
|
$scope.$apply();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self._create(opts);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this._create = function (opts) {
|
||||||
$timeout(function() {
|
$timeout(function() {
|
||||||
profileService.createWallet(opts, function(err, secret) {
|
profileService.createWallet(opts, function(err, secret) {
|
||||||
self.loading = false;
|
self.loading = false;
|
||||||
if (err) {
|
if (err) {
|
||||||
|
if (err == "Error creating wallet" && opts.extendedPublicKey) {
|
||||||
|
err = "This xpub index is already used by another wallet. Please select another index."
|
||||||
|
}
|
||||||
$log.debug(err);
|
$log.debug(err);
|
||||||
self.error = err;
|
self.error = err;
|
||||||
$timeout(function() {
|
$timeout(function() {
|
||||||
|
|
@ -72,7 +104,7 @@ angular.module('copayApp.controllers').controller('createController',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
}
|
||||||
|
|
||||||
this.formFocus = function(what) {
|
this.formFocus = function(what) {
|
||||||
if (!this.isWindowsPhoneApp) return
|
if (!this.isWindowsPhoneApp) return
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('joinController',
|
angular.module('copayApp.controllers').controller('joinController',
|
||||||
function($scope, $rootScope, $timeout, go, isMobile, notification, profileService, isCordova, $modal, gettext) {
|
function($scope, $rootScope, $timeout, go, isMobile, notification, profileService, isCordova, isChromeApp, $modal, gettext, lodash, ledger) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
this.externatIndexValues = lodash.range(0,20);
|
||||||
|
$scope.externalIndex = 0;
|
||||||
|
|
||||||
|
this.isChromeApp = function() {
|
||||||
|
return isChromeApp;
|
||||||
|
};
|
||||||
|
|
||||||
//TODO : make one function - this was copied from topbar.js
|
//TODO : make one function - this was copied from topbar.js
|
||||||
var cordovaOpenScanner = function() {
|
var cordovaOpenScanner = function() {
|
||||||
|
|
@ -145,12 +151,37 @@ angular.module('copayApp.controllers').controller('joinController',
|
||||||
}
|
}
|
||||||
self.loading = true;
|
self.loading = true;
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
secret: form.secret.$modelValue,
|
||||||
|
extendedPrivateKey: form.privateKey.$modelValue,
|
||||||
|
myName: form.myName.$modelValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form.hwLedger.$modelValue) {
|
||||||
|
self.ledger = true;
|
||||||
|
ledger.getXPubKey($scope.externalIndex, function(data) {
|
||||||
|
self.ledger = false;
|
||||||
|
$scope.$apply();
|
||||||
|
if (data.success) {
|
||||||
|
opts.extendedPublicKey = data.xpubkey;
|
||||||
|
opts.externalSource = 'ledger';
|
||||||
|
opts.externalIndex = $scope.externalIndex;
|
||||||
|
self._join(opts);
|
||||||
|
} else {
|
||||||
|
self.loading = false;
|
||||||
|
$log.debug(data.message);
|
||||||
|
self.error = data.message;
|
||||||
|
$scope.$apply();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self._join(opts);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this._join = function(opts) {
|
||||||
$timeout(function() {
|
$timeout(function() {
|
||||||
profileService.joinWallet({
|
profileService.joinWallet(opts, function(err) {
|
||||||
secret: form.secret.$modelValue,
|
|
||||||
extendedPrivateKey: form.privateKey.$modelValue,
|
|
||||||
myName: form.myName.$modelValue
|
|
||||||
}, function(err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
self.loading = false;
|
self.loading = false;
|
||||||
self.error = err;
|
self.error = err;
|
||||||
|
|
@ -162,5 +193,5 @@ angular.module('copayApp.controllers').controller('joinController',
|
||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,11 @@ angular.module('copayApp.controllers').controller('preferencesController',
|
||||||
$scope.glideraEnabled = config.glidera.enabled;
|
$scope.glideraEnabled = config.glidera.enabled;
|
||||||
$scope.glideraTestnet = config.glidera.testnet;
|
$scope.glideraTestnet = config.glidera.testnet;
|
||||||
var fc = profileService.focusedClient;
|
var fc = profileService.focusedClient;
|
||||||
if (fc)
|
if (fc) {
|
||||||
$scope.encrypt = fc.hasPrivKeyEncrypted();
|
$scope.encrypt = fc.hasPrivKeyEncrypted();
|
||||||
|
this.externalSource = fc.getPrivKeyExternalSourceName() == 'ledger' ? "Ledger" : null;
|
||||||
|
this.externalIndex = fc.getExternalIndex();
|
||||||
|
}
|
||||||
|
|
||||||
var unwatchSpendUnconfirmed = $scope.$watch('spendUnconfirmed', function(newVal, oldVal) {
|
var unwatchSpendUnconfirmed = $scope.$watch('spendUnconfirmed', function(newVal, oldVal) {
|
||||||
if (newVal == oldVal) return;
|
if (newVal == oldVal) return;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('walletHomeController', function($scope, $rootScope, $timeout, $filter, $modal, $log, notification, txStatus, isCordova, profileService, lodash, configService, rateService, storageService, bitcore, isChromeApp, gettext, gettextCatalog, nodeWebkit, addressService, feeService, bwsError, txFormatService) {
|
angular.module('copayApp.controllers').controller('walletHomeController', function($scope, $rootScope, $timeout, $filter, $modal, $log, notification, txStatus, isCordova, profileService, lodash, configService, rateService, storageService, bitcore, isChromeApp, gettext, gettextCatalog, nodeWebkit, addressService, ledger, feeService, bwsError, utilService) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
$rootScope.hideMenuBar = false;
|
$rootScope.hideMenuBar = false;
|
||||||
|
|
@ -258,6 +258,33 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (fc.isPrivKeyExternal()) {
|
||||||
|
if (fc.getPrivKeyExternalSourceName() == 'ledger') {
|
||||||
|
$log.debug('Requesting Ledger Chrome app to sign the transaction');
|
||||||
|
self.setOngoingProcess(gettext('Requesting Ledger Wallet to sign'));
|
||||||
|
$scope.loading = true;
|
||||||
|
$scope.error = null;
|
||||||
|
ledger.signTx(txp, fc.getExternalIndex(), function(result) {
|
||||||
|
if (result.success) {
|
||||||
|
txp.signatures = [];
|
||||||
|
for (var i=0; i<result.signatures.length; i++) {
|
||||||
|
txp.signatures.push(result.signatures[i].substring(0, result.signatures[i].length - 2));
|
||||||
|
}
|
||||||
|
$scope._doSign(txp);
|
||||||
|
} else {
|
||||||
|
$scope.loading = false;
|
||||||
|
$scope.error = result.message;
|
||||||
|
self.setOngoingProcess();
|
||||||
|
$scope.$digest();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$scope._doSign(txp);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope._doSign = function(txp) {
|
||||||
self.setOngoingProcess(gettext('Signing payment'));
|
self.setOngoingProcess(gettext('Signing payment'));
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
$scope.error = null;
|
$scope.error = null;
|
||||||
|
|
@ -804,6 +831,30 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
|
||||||
|
|
||||||
this.signAndBroadcast = function(txp, cb) {
|
this.signAndBroadcast = function(txp, cb) {
|
||||||
var fc = profileService.focusedClient;
|
var fc = profileService.focusedClient;
|
||||||
|
|
||||||
|
if (fc.isPrivKeyExternal()) {
|
||||||
|
if (fc.getPrivKeyExternalSourceName() == 'ledger') {
|
||||||
|
$log.debug('Requesting Ledger Chrome app to sign the transaction');
|
||||||
|
self.setOngoingProcess(gettext('Requesting Ledger Wallet to sign'));
|
||||||
|
ledger.signTx(txp, fc.getExternalIndex(), function(result) {
|
||||||
|
if (result.success) {
|
||||||
|
txp.signatures = [];
|
||||||
|
for (var i=0; i<result.signatures.length; i++) {
|
||||||
|
txp.signatures.push(result.signatures[i].substring(0, result.signatures[i].length - 2));
|
||||||
|
}
|
||||||
|
self._doSignAndBroadcast(txp, cb);
|
||||||
|
} else {
|
||||||
|
return cb(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self._doSignAndBroadcast(txp, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this._doSignAndBroadcast = function(txp, cb) {
|
||||||
|
var fc = profileService.focusedClient;
|
||||||
self.setOngoingProcess(gettext('Signing transaction'));
|
self.setOngoingProcess(gettext('Signing transaction'));
|
||||||
fc.signTxProposal(txp, function(err, signedTx) {
|
fc.signTxProposal(txp, function(err, signedTx) {
|
||||||
profileService.lockFC();
|
profileService.lockFC();
|
||||||
|
|
|
||||||
486
src/js/services/ledger.js
Normal file
486
src/js/services/ledger.js
Normal file
|
|
@ -0,0 +1,486 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('copayApp.services')
|
||||||
|
.factory('ledger', function(bwcService) {
|
||||||
|
var root = {};
|
||||||
|
var LEDGER_CHROME_ID = "kkdpmhnladdopljabkgpacgpliggeeaf";
|
||||||
|
|
||||||
|
root.callbacks = {};
|
||||||
|
|
||||||
|
root.hasSession = function() {
|
||||||
|
root._message({ command:"has_session" });
|
||||||
|
}
|
||||||
|
|
||||||
|
root.getXPubKey = function(index, callback) {
|
||||||
|
root.callbacks["get_xpubkey"] = callback;
|
||||||
|
root._messageAfterSession({ command:"get_xpubkey", path:root._getPath(index) })
|
||||||
|
};
|
||||||
|
|
||||||
|
root.signTx = function(txp, index, callback) {
|
||||||
|
root.callbacks["sign_p2sh"] = callback;
|
||||||
|
var redeemScripts = [];
|
||||||
|
var paths = [];
|
||||||
|
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(root._getPath(index) + txp.inputs[i].path.substring(1));
|
||||||
|
}
|
||||||
|
var splitTransaction = root._splitTransaction(new ByteString(tx.toString(), GP.HEX));
|
||||||
|
var inputs = [];
|
||||||
|
for (var i=0; i<splitTransaction.inputs.length; i++) {
|
||||||
|
var input = splitTransaction.inputs[i];
|
||||||
|
inputs.push([
|
||||||
|
root._reverseBytestring(input.prevout.bytes(0, 32)).toString(),
|
||||||
|
root._reverseBytestring(input.prevout.bytes(32)).toString()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
root._messageAfterSession({
|
||||||
|
command:"sign_p2sh",
|
||||||
|
inputs: inputs,
|
||||||
|
scripts: redeemScripts,
|
||||||
|
outputs_number: splitTransaction.outputs.length,
|
||||||
|
outputs_script: splitTransaction.outputScript.toString(),
|
||||||
|
paths: paths });
|
||||||
|
}
|
||||||
|
|
||||||
|
root._message = function(data) {
|
||||||
|
chrome.runtime.sendMessage(
|
||||||
|
LEDGER_CHROME_ID,
|
||||||
|
{ request: data },
|
||||||
|
function(response) {
|
||||||
|
root._callback(response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
root._messageAfterSession = function(data) {
|
||||||
|
root._after_session = data;
|
||||||
|
root._message({ command:"launch" });
|
||||||
|
root._should_poll_session = true;
|
||||||
|
root._do_poll_session();
|
||||||
|
}
|
||||||
|
|
||||||
|
root._do_poll_session = function() {
|
||||||
|
root.hasSession();
|
||||||
|
if (root._should_poll_session) {
|
||||||
|
setTimeout(root._do_poll_session, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root._callback = function(data) {
|
||||||
|
if (typeof data == "object") {
|
||||||
|
if (data.command == "has_session" && data.success) {
|
||||||
|
root._message(root._after_session);
|
||||||
|
root._after_session = null;
|
||||||
|
root._should_poll_session = false;
|
||||||
|
} else if (typeof root.callbacks[data.command] == "function") {
|
||||||
|
root.callbacks[data.command](data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
root._should_poll_session = false;
|
||||||
|
Object.keys(root.callbacks).forEach(function (key) {
|
||||||
|
root.callbacks[key]({ success: false, message: "The Ledger Chrome application is not installed" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root._getPath = function(index) {
|
||||||
|
return bwcService.getUtils().PATHS.BASE_ADDRESS_DERIVATION + "/" + index;
|
||||||
|
}
|
||||||
|
|
||||||
|
root._splitTransaction = function(transaction) {
|
||||||
|
var result = {};
|
||||||
|
var inputs = [];
|
||||||
|
var outputs = [];
|
||||||
|
var offset = 0;
|
||||||
|
var version = transaction.bytes(offset, 4);
|
||||||
|
offset += 4;
|
||||||
|
var varint = root._getVarint(transaction, offset);
|
||||||
|
var numberInputs = varint[0];
|
||||||
|
offset += varint[1];
|
||||||
|
for (var i=0; i<numberInputs; i++) {
|
||||||
|
var input = {};
|
||||||
|
input['prevout'] = transaction.bytes(offset, 36);
|
||||||
|
offset += 36;
|
||||||
|
varint = root._getVarint(transaction, offset);
|
||||||
|
offset += varint[1];
|
||||||
|
input['script'] = transaction.bytes(offset, varint[0]);
|
||||||
|
offset += varint[0];
|
||||||
|
input['sequence'] = transaction.bytes(offset, 4);
|
||||||
|
offset += 4;
|
||||||
|
inputs.push(input);
|
||||||
|
}
|
||||||
|
varint = root._getVarint(transaction, offset);
|
||||||
|
var numberOutputs = varint[0];
|
||||||
|
offset += varint[1];
|
||||||
|
var outputStartOffset = offset;
|
||||||
|
for (var i=0; i<numberOutputs; i++) {
|
||||||
|
var output = {};
|
||||||
|
output['amount'] = transaction.bytes(offset, 8);
|
||||||
|
offset += 8;
|
||||||
|
varint = root._getVarint(transaction, offset);
|
||||||
|
offset += varint[1];
|
||||||
|
output['script'] = transaction.bytes(offset, varint[0]);
|
||||||
|
offset += varint[0];
|
||||||
|
outputs.push(output);
|
||||||
|
}
|
||||||
|
var locktime = transaction.bytes(offset, 4);
|
||||||
|
result['version'] = version;
|
||||||
|
result['inputs'] = inputs;
|
||||||
|
result['outputs'] = outputs;
|
||||||
|
result['locktime'] = locktime;
|
||||||
|
result['outputScript'] = transaction.bytes(outputStartOffset, offset - outputStartOffset);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
root._getVarint = function(data, offset) {
|
||||||
|
if (data.byteAt(offset) < 0xfd) {
|
||||||
|
return [ data.byteAt(offset), 1 ];
|
||||||
|
}
|
||||||
|
if (data.byteAt(offset) == 0xfd) {
|
||||||
|
return [ ((data.byteAt(offset + 2) << 8) + data.byteAt(offset + 1)), 3 ];
|
||||||
|
}
|
||||||
|
if (data.byteAt(offset) == 0xfe) {
|
||||||
|
return [ ((data.byteAt(offset + 4) << 24) + (data.byteAt(offset + 3) << 16) +
|
||||||
|
(data.byteAt(offset + 2) << 8) + data.byteAt(offset + 1)), 5 ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root._reverseBytestring = function(x) {
|
||||||
|
var res = "";
|
||||||
|
for (var i=x.length - 1; i>=0; i--) {
|
||||||
|
res += Convert.toHexByte(x.byteAt(i));
|
||||||
|
}
|
||||||
|
return new ByteString(res, GP.HEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
});
|
||||||
|
|
||||||
|
var Convert = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a binary string to his hexadecimal representation
|
||||||
|
* @param {String} src binary string
|
||||||
|
* @static
|
||||||
|
* @returns {String} hexadecimal representation
|
||||||
|
*/
|
||||||
|
Convert.stringToHex = function(src) {
|
||||||
|
var r = "";
|
||||||
|
var hexes = new Array ("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f");
|
||||||
|
for (var i=0; i<src.length; i++) {r += hexes [src.charCodeAt(i) >> 4] + hexes [src.charCodeAt(i) & 0xf];}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an hexadecimal string to its binary representation
|
||||||
|
* @param {String} src hexadecimal string
|
||||||
|
* @static
|
||||||
|
* @return {Array} byte array
|
||||||
|
* @throws {InvalidString} if the string isn't properly formatted
|
||||||
|
*/
|
||||||
|
Convert.hexToBin = function(src) {
|
||||||
|
var result = "";
|
||||||
|
var digits = "0123456789ABCDEF";
|
||||||
|
if ((src.length % 2) != 0) {
|
||||||
|
throw "Invalid string";
|
||||||
|
}
|
||||||
|
src = src.toUpperCase();
|
||||||
|
for (var i=0; i<src.length; i+=2) {
|
||||||
|
var x1 = digits.indexOf(src.charAt(i));
|
||||||
|
if (x1 < 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
var x2 = digits.indexOf(src.charAt(i + 1));
|
||||||
|
if (x2 < 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
result += String.fromCharCode((x1 << 4) + x2);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a double digit hexadecimal number to an integer
|
||||||
|
* @static
|
||||||
|
* @param {String} data buffer containing the digit to parse
|
||||||
|
* @param {Number} offset offset to the digit (default is 0)
|
||||||
|
* @returns {Number} converted digit
|
||||||
|
*/
|
||||||
|
Convert.readHexDigit = function(data, offset) {
|
||||||
|
var digits = '0123456789ABCDEF';
|
||||||
|
if (typeof offset == "undefined") {
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
return (digits.indexOf(data.substring(offset, offset + 1).toUpperCase()) << 4) + (digits.indexOf(data.substring(offset + 1, offset + 2).toUpperCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a number to a two digits hexadecimal string (deprecated)
|
||||||
|
* @static
|
||||||
|
* @param {Number} number number to convert
|
||||||
|
* @returns {String} converted number
|
||||||
|
*/
|
||||||
|
Convert.toHexDigit = function(number) {
|
||||||
|
var digits = '0123456789abcdef';
|
||||||
|
return digits.charAt(number >> 4) + digits.charAt(number & 0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a number to a two digits hexadecimal string (similar to toHexDigit)
|
||||||
|
* @static
|
||||||
|
* @param {Number} number number to convert
|
||||||
|
* @returns {String} converted number
|
||||||
|
*/
|
||||||
|
Convert.toHexByte = function(number) {
|
||||||
|
return Convert.toHexDigit(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a BCD number to a two digits hexadecimal string
|
||||||
|
* @static
|
||||||
|
* @param {Number} number number to convert
|
||||||
|
* @returns {String} converted number
|
||||||
|
*/
|
||||||
|
Convert.toHexByteBCD = function(numberBCD) {
|
||||||
|
var number = ((numberBCD / 10) * 16) + (numberBCD % 10);
|
||||||
|
return Convert.toHexDigit(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a number to an hexadecimal short number
|
||||||
|
* @static
|
||||||
|
* @param {Number} number number to convert
|
||||||
|
* @returns {String} converted number
|
||||||
|
*/
|
||||||
|
Convert.toHexShort = function(number) {
|
||||||
|
return Convert.toHexDigit((number >> 8) & 0xff) + Convert.toHexDigit(number & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a number to an hexadecimal int number
|
||||||
|
* @static
|
||||||
|
* @param {Number} number number to convert
|
||||||
|
* @returns {String} converted number
|
||||||
|
*/
|
||||||
|
Convert.toHexInt = function(number) {
|
||||||
|
return Convert.toHexDigit((number >> 24) & 0xff) + Convert.toHexDigit((number >> 16) & 0xff) +
|
||||||
|
Convert.toHexDigit((number >> 8) & 0xff) + Convert.toHexDigit(number & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var GP = {};
|
||||||
|
GP.ASCII = 1;
|
||||||
|
GP.HEX = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class GPScript ByteString implementation
|
||||||
|
* @param {String} value initial value
|
||||||
|
* @param {HEX|ASCII} encoding encoding to use
|
||||||
|
* @property {Number} length length of the ByteString
|
||||||
|
* @constructs
|
||||||
|
*/
|
||||||
|
var ByteString = function(value, encoding) {
|
||||||
|
this.encoding = encoding;
|
||||||
|
this.hasBuffer = (typeof Buffer != 'undefined');
|
||||||
|
if (this.hasBuffer && (value instanceof Buffer)) {
|
||||||
|
this.value = value;
|
||||||
|
this.encoding = GP.HEX;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch(encoding) {
|
||||||
|
case GP.HEX:
|
||||||
|
if (!this.hasBuffer) {
|
||||||
|
this.value = Convert.hexToBin(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.value = new Buffer(value, 'hex');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GP.ASCII:
|
||||||
|
if (!this.hasBuffer) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.value = new Buffer(value, 'ascii');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw "Invalid arguments";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.length = this.value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the byte value at the given index
|
||||||
|
* @param {Number} index index
|
||||||
|
* @returns {Number} byte value
|
||||||
|
*/
|
||||||
|
ByteString.prototype.byteAt = function(index) {
|
||||||
|
if (arguments.length < 1) {
|
||||||
|
throw "Argument missing";
|
||||||
|
}
|
||||||
|
if (typeof index != "number") {
|
||||||
|
throw "Invalid index";
|
||||||
|
}
|
||||||
|
if ((index < 0) || (index >= this.value.length)) {
|
||||||
|
throw "Invalid index offset";
|
||||||
|
}
|
||||||
|
if (!this.hasBuffer) {
|
||||||
|
return Convert.readHexDigit(Convert.stringToHex(this.value.substring(index, index + 1)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return this.value[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a subset of the ByteString
|
||||||
|
* @param {Number} offset offset to start at
|
||||||
|
* @param {Number} [count] size of the target ByteString (default : use the remaining length)
|
||||||
|
* @returns {ByteString} subset of the original ByteString
|
||||||
|
*/
|
||||||
|
ByteString.prototype.bytes = function(offset, count) {
|
||||||
|
var result;
|
||||||
|
if (arguments.length < 1) {
|
||||||
|
throw "Argument missing";
|
||||||
|
}
|
||||||
|
if (typeof offset != "number") {
|
||||||
|
throw "Invalid offset";
|
||||||
|
}
|
||||||
|
//if ((offset < 0) || (offset >= this.value.length)) {
|
||||||
|
if (offset < 0) {
|
||||||
|
throw "Invalid offset";
|
||||||
|
}
|
||||||
|
if (typeof count == "number") {
|
||||||
|
if (count < 0) {
|
||||||
|
throw "Invalid count";
|
||||||
|
}
|
||||||
|
if (!this.hasBuffer) {
|
||||||
|
result = new ByteString(this.value.substring(offset, offset + count), GP.ASCII);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = new Buffer(count);
|
||||||
|
this.value.copy(result, 0, offset, offset + count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (typeof count == "undefined") {
|
||||||
|
if (!this.hasBuffer) {
|
||||||
|
result = new ByteString(this.value.substring(offset), GP.ASCII);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = new Buffer(this.value.length - offset);
|
||||||
|
this.value.copy(result, 0, offset, this.value.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw "Invalid count";
|
||||||
|
}
|
||||||
|
if (!this.hasBuffer) {
|
||||||
|
result.encoding = this.encoding;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new ByteString(result, GP.HEX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends two ByteString
|
||||||
|
* @param {ByteString} target ByteString to append
|
||||||
|
* @returns {ByteString} result of the concatenation
|
||||||
|
*/
|
||||||
|
ByteString.prototype.concat = function(target) {
|
||||||
|
if (arguments.length < 1) {
|
||||||
|
throw "Not enough arguments";
|
||||||
|
}
|
||||||
|
if (!(target instanceof ByteString)) {
|
||||||
|
throw "Invalid argument";
|
||||||
|
}
|
||||||
|
if (!this.hasBuffer) {
|
||||||
|
var result = this.value + target.value;
|
||||||
|
var x = new ByteString(result, GP.ASCII);
|
||||||
|
x.encoding = this.encoding;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var result = Buffer.concat([this.value, target.value]);
|
||||||
|
return new ByteString(result, GP.HEX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if two ByteString are equal
|
||||||
|
* @param {ByteString} target ByteString to check against
|
||||||
|
* @returns {Boolean} true if the two ByteString are equal
|
||||||
|
*/
|
||||||
|
ByteString.prototype.equals = function(target) {
|
||||||
|
if (arguments.length < 1) {
|
||||||
|
throw "Not enough arguments";
|
||||||
|
}
|
||||||
|
if (!(target instanceof ByteString)) {
|
||||||
|
throw "Invalid argument";
|
||||||
|
}
|
||||||
|
if (!this.hasBuffer) {
|
||||||
|
return (this.value == target.value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Buffer.equals(this.value, target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the ByteString to a String using the given encoding
|
||||||
|
* @param {HEX|ASCII|UTF8|BASE64|CN} encoding encoding to use
|
||||||
|
* @return {String} converted content
|
||||||
|
*/
|
||||||
|
ByteString.prototype.toString = function(encoding) {
|
||||||
|
var targetEncoding = this.encoding;
|
||||||
|
if (arguments.length >= 1) {
|
||||||
|
if (typeof encoding != "number") {
|
||||||
|
throw "Invalid encoding";
|
||||||
|
}
|
||||||
|
switch(encoding) {
|
||||||
|
case GP.HEX:
|
||||||
|
case GP.ASCII:
|
||||||
|
targetEncoding = encoding;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw "Unsupported arguments";
|
||||||
|
}
|
||||||
|
targetEncoding = encoding;
|
||||||
|
}
|
||||||
|
switch(targetEncoding) {
|
||||||
|
case GP.HEX:
|
||||||
|
if (!this.hasBuffer) {
|
||||||
|
return Convert.stringToHex(this.value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return this.value.toString('hex');
|
||||||
|
}
|
||||||
|
case GP.ASCII:
|
||||||
|
if (!this.hasBuffer) {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return this.value.toString();
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw "Unsupported";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteString.prototype.toStringIE = function(encoding) {
|
||||||
|
return this.toString(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteString.prototype.toBuffer = function() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
angular.module('copayApp.services')
|
angular.module('copayApp.services')
|
||||||
.factory('profileService', function profileServiceFactory($rootScope, $location, $timeout, $filter, $log, lodash, storageService, bwcService, configService, notificationService, isChromeApp, isCordova, gettext, nodeWebkit, bwsError) {
|
.factory('profileService', function profileServiceFactory($rootScope, $location, $timeout, $filter, $log, lodash, storageService, bwcService, configService, notificationService, isChromeApp, isCordova, gettext, nodeWebkit, bwsError, ledger) {
|
||||||
|
|
||||||
var root = {};
|
var root = {};
|
||||||
|
|
||||||
|
|
@ -193,6 +193,12 @@ angular.module('copayApp.services')
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return cb(gettext('Could not create using the specified extended private key'));
|
return cb(gettext('Could not create using the specified extended private key'));
|
||||||
}
|
}
|
||||||
|
} else if (opts.extendedPublicKey) {
|
||||||
|
try {
|
||||||
|
walletClient.seedFromExternalWalletPublicKey(opts.extendedPublicKey, opts.externalSource, opts.externalIndex);
|
||||||
|
} catch (ex) {
|
||||||
|
return cb(gettext('Could not create using the specified extended public key'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
walletClient.createWallet(opts.name, opts.myName || 'me', opts.m, opts.n, {
|
walletClient.createWallet(opts.name, opts.myName || 'me', opts.m, opts.n, {
|
||||||
network: opts.networkName
|
network: opts.networkName
|
||||||
|
|
@ -219,9 +225,14 @@ angular.module('copayApp.services')
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return cb(gettext('Could not join using the specified extended private key'));
|
return cb(gettext('Could not join using the specified extended private key'));
|
||||||
}
|
}
|
||||||
|
} else if (opts.extendedPublicKey) {
|
||||||
|
try {
|
||||||
|
walletClient.seedFromExternalWalletPublicKey(opts.extendedPublicKey, opts.externalSource, opts.externalIndex);
|
||||||
|
} catch (ex) {
|
||||||
|
return cb(gettext('Could not create using the specified extended public key'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var walletData = this.getUtils().fromSecret(opts.secret);
|
var walletData = this.getUtils().fromSecret(opts.secret);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue