updates ledger integration

This commit is contained in:
Matias Alejo Garcia 2015-09-04 10:17:59 -03:00
commit fe7a628edd
9 changed files with 282 additions and 244 deletions

View file

@ -117,7 +117,7 @@
<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 for="seed" class="oh"> <label ng-hide="hwLedger" for="seed" class="oh">
<span translate>Specify your wallet seed</span> <span translate>Specify your wallet seed</span>
<switch id="seed" name="setSeed" ng-model="setSeed" class="green right m5t m10b"></switch> <switch id="seed" name="setSeed" ng-model="setSeed" class="green right m5t m10b"></switch>
</label> </label>
@ -129,14 +129,14 @@
</div> </div>
</label> </label>
<label for="ext-master" class="m10t" ng-show="setSeed"> <label for="ext-master" class="m10t" ng-show="setSeed && !hwLedger">
<span translate>Wallet Seed</span> <span translate>Wallet Seed</span>
<small translate>Enter the 12 words seed (BIP39)</small> <small translate>Enter the 12 words seed (BIP39)</small>
<input id="ext-master" <input id="ext-master"
type="text" type="text"
name="privateKey" ng-model="privateKey"> name="privateKey" ng-model="privateKey">
</label> </label>
<label for="passphrase" class="line-b oh" ng-show="setSeed"><span translate>Seed Passphrase</span> <small translate>The seed could require a passphrase to be imported</small> <label for="passphrase" class="line-b oh" ng-show="setSeed && !hwLedger"><span translate>Seed Passphrase</span> <small translate>The seed could require a passphrase to be imported</small>
<div class="input"> <div class="input">
<input type="text" class="form-control" name="passphrase" ng-model="passphrase"> <input type="text" class="form-control" name="passphrase" ng-model="passphrase">
</div> </div>
@ -155,11 +155,11 @@
<button type="submit" class="button round black expand m0" ng-show="totalCopayers != 1" ng-disabled="setupForm.$invalid || create.loading"> <button type="submit" class="button round black expand m0" ng-show="totalCopayers != 1" ng-disabled="setupForm.$invalid || create.loading || create.ledger">
<span translate>Create {{requiredCopayers}}-of-{{totalCopayers}} wallet</span> <span translate>Create {{requiredCopayers}}-of-{{totalCopayers}} wallet</span>
</button> </button>
<button type="submit" class="button round black expand m0" ng-show="totalCopayers == 1" ng-disabled="setupForm.$invalid || create.loading"> <button type="submit" class="button round black expand m0" ng-show="totalCopayers == 1" ng-disabled="setupForm.$invalid || create.loading || create.ledger">
<span translate>Create new wallet</span> <span translate>Create new wallet</span>
</button> </button>
</div> </div>

View file

@ -36,19 +36,18 @@
<span ng-style="{'color':index.backgroundColor}">&block;</span> <span ng-style="{'color':index.backgroundColor}">&block;</span>
</span> </span>
</li> </li>
<li class="line-b p20" ng-hide="index.isPrivKeyExternal">
<li class="line-b p20" ng-hide="preferences.externalIndex >= 0">
<span translate>Request Password for Spending Funds</span> <span translate>Request Password for Spending Funds</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"> <li class="line-b p20" ng-show="index.isPrivKeyExternal">
<span translate>Hardware wallet</span> <span translate>Hardware wallet</span>
<span class="right text-gray"> <span class="right text-gray">
{{preferences.externalSource}} {{preferences.externalSource}}
(index {{preferences.externalIndex}}) (index {{preferences.externalIndex}})
</span> </span>
</li> </li>
<li class="line-b p20" ng-click="$root.go('backup')" ng-show="preferences.externalIndex >= 0"> <li class="line-b p20" ng-click="$root.go('backup')" ng-hide="index.isPrivKeyExternal">
<i class="icon-arrow-right3 size-24 right text-gray"></i> <i class="icon-arrow-right3 size-24 right text-gray"></i>
<span class="text-warning right" ng-show="index.needsBackup"> <span class="text-warning right" ng-show="index.needsBackup">
<i class="fi-alert"></i> <span translate> Still not done</span> <i class="fi-alert"></i> <span translate> Still not done</span>

View file

@ -125,8 +125,7 @@
<div class="size-12 text-gray"> <div class="size-12 text-gray">
<span translate>Multisignature wallet</span> <span translate>Multisignature wallet</span>
(<span translate>{{index.m}}-of-{{index.n}}</span>) (<span translate>{{index.m}}-of-{{index.n}}</span>)
<span ng-if="index.network != 'livenet'">- Testnet</span> <span ng-include="'views/includes/walletInfo.html'"></span>
<span ng-if="!index.canSign"> - <span translate>No Private key</span></span>
</div> </div>
</div> </div>
<div ng-show="!index.isShared"> <div ng-show="!index.isShared">
@ -134,8 +133,7 @@
{{(index.alias || index.walletName)}} {{(index.alias || index.walletName)}}
</p> </p>
<div class="size-12 text-gray"> <div class="size-12 text-gray">
<span ng-if="index.network != 'livenet'"> Testnet </span> <span ng-include="'views/includes/walletInfo.html'"></span>
<span ng-if="!index.canSign" translate>No Private key</span>
</div> </div>
</div> </div>
</div> </div>

View file

@ -35,7 +35,7 @@ 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.externalIndexValues = lodash.range(0,20); this.externalIndexValues = lodash.range(0,ledger.MAX_SLOT);
$scope.externalIndex = 0; $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;
@ -73,24 +73,17 @@ angular.module('copayApp.controllers').controller('createController',
return; return;
} }
self.loading = true;
if (form.hwLedger.$modelValue) { if (form.hwLedger.$modelValue) {
self.ledger = true; self.ledger = true;
ledger.getXPubKey($scope.externalIndex, function(data) { ledger.getInfoForNewWallet($scope.externalIndex, function(err, lopts) {
self.ledger = false; self.ledger = false;
if (data.success) { if (err) {
self.error = err;
$scope.$apply(); $scope.$apply();
opts.extendedPublicKey = data.xpubkey; return;
opts.externalSource = 'ledger';
opts.externalIndex = $scope.externalIndex;
self._create(opts);
} else {
self.loading = false;
self.error = data.message;
$scope.$apply();
$log.debug(data.message);
} }
opts = lodash.assign(lopts, opts);
self._create(opts);
}); });
} else { } else {
self._create(opts); self._create(opts);
@ -98,6 +91,7 @@ angular.module('copayApp.controllers').controller('createController',
}; };
this._create = function (opts) { this._create = function (opts) {
self.loading = true;
$timeout(function() { $timeout(function() {
profileService.createWallet(opts, function(err, secret, walletId) { profileService.createWallet(opts, function(err, secret, walletId) {
self.loading = false; self.loading = false;
@ -105,14 +99,14 @@ angular.module('copayApp.controllers').controller('createController',
if (err == "Error creating wallet" && opts.extendedPublicKey) { if (err == "Error creating wallet" && opts.extendedPublicKey) {
err = gettext("This xpub index is already used by another wallet. Please select another index."); err = gettext("This xpub index is already used by another wallet. Please select another index.");
} }
$log.debug(err); $log.warn(err);
self.error = err; self.error = err;
$timeout(function() { $timeout(function() {
$rootScope.$apply(); $rootScope.$apply();
}); });
} }
else { else {
if (opts.mnemonic && opts.n==1) { if ( ( opts.mnemonic && opts.n==1) || otps.externalSource ) {
$rootScope.$emit('Local/WalletImported', walletId); $rootScope.$emit('Local/WalletImported', walletId);
} else { } else {
go.walletHome(); go.walletHome();

View file

@ -101,6 +101,8 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.walletId = fc.credentials.walletId; self.walletId = fc.credentials.walletId;
self.isComplete = fc.isComplete(); self.isComplete = fc.isComplete();
self.canSign = fc.canSign(); self.canSign = fc.canSign();
self.isPrivKeyExternal = fc.isPrivKeyExternal();
self.externalSource = fc.getPrivKeyExternalSourceName();
self.txps = []; self.txps = [];
self.copayers = []; self.copayers = [];
self.updateColor(); self.updateColor();

View file

@ -204,7 +204,7 @@ angular.module('copayApp.controllers').controller('joinController',
} }
$timeout(function() { $timeout(function() {
var fc = profileService.focusedClient; var fc = profileService.focusedClient;
if (opts.mnemonic && fc.isComplete()) { if ( ( opts.mnemonic || opts.externalSource ) && fc.isComplete()) {
$rootScope.$emit('Local/WalletImported', fc.credentials.walletId); $rootScope.$emit('Local/WalletImported', fc.credentials.walletId);
} else { } else {
go.walletHome(); go.walletHome();

View file

@ -184,7 +184,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
$scope.error = null; $scope.error = null;
$scope.copayers = copayers $scope.copayers = copayers
$scope.copayerId = fc.credentials.copayerId; $scope.copayerId = fc.credentials.copayerId;
$scope.canSign = fc.canSign(); $scope.canSign = fc.canSign() || fc.isPrivKeyExternal();
$scope.loading = null; $scope.loading = null;
$scope.color = fc.backgroundColor; $scope.color = fc.backgroundColor;
@ -244,7 +244,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
$scope.sign = function(txp) { $scope.sign = function(txp) {
var fc = profileService.focusedClient; var fc = profileService.focusedClient;
if (!fc.canSign()) if (!fc.canSign() && !fc.isPrivKeyExternal())
return; return;
if (fc.isPrivKeyEncrypted()) { if (fc.isPrivKeyEncrypted()) {
@ -801,7 +801,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
return self.setSendError(err); return self.setSendError(err);
} }
if (!fc.canSign()) { if (!fc.canSign() && !fc.isPrivKeyExternal()) {
$log.info('No signing proposal: No private key') $log.info('No signing proposal: No private key')
self.setOngoingProcess(); self.setOngoingProcess();
self.resetForm(); self.resetForm();

View file

@ -1,21 +1,70 @@
'use strict'; 'use strict';
angular.module('copayApp.services') angular.module('copayApp.services')
.factory('ledger', function(bwcService) { .factory('ledger', function($log, bwcService, gettext) {
var root = {}; var root = {};
var LEDGER_CHROME_ID = "kkdpmhnladdopljabkgpacgpliggeeaf"; var LEDGER_CHROME_ID = "kkdpmhnladdopljabkgpacgpliggeeaf";
root.MAX_SLOT = 20;
root.ENTROPY_INDEX_START = 1000;
root.callbacks = {}; root.callbacks = {};
root.hasSession = function() { root.hasSession = function() {
root._message({ command:"has_session" }); root._message({
command: "has_session"
});
} }
root.getXPubKey = function(index, callback) { root.getEntropySource = function(index, callback) {
root.callbacks["get_xpubkey"] = callback; index = index + root.ENTROPY_INDEX_START;
root._messageAfterSession({ command:"get_xpubkey", path:root._getPath(index) }) var xpub = root.getXPubKey("m/" + index + "'", function(data) {
if (!data.success) {
$log.warn(data.message);
return callback(data);
}
var b = bwcService.getBitcore();
var x = b.HDPublicKey(data.xpubkey);
data.entropySource = x.publicKey.toString();
return callback(data);
});
}; };
root.getXPubKeyForAddresses = function(index, callback) {
return root.getXPubKey(root._getPath(index), callback);
};
root.getXPubKey = function(path, callback) {
root.callbacks["get_xpubkey"] = callback;
root._messageAfterSession({
command: "get_xpubkey",
path: path
})
};
root.getInfoForNewWallet = function(slot, callback) {
var opts = {};
root.getEntropySource(slot, function(data) {
if (!data.success) {
$log.warn(data.message);
return callback(data.message);
}
opts.entropySource = data.entropySource;
root.getXPubKeyForAddresses(slot, function(data) {
if (!data.success) {
$log.warn(data.message);
return callback(data);
}
opts.extendedPublicKey = data.xpubkey;
opts.externalSource = 'ledger';
opts.externalIndex = slot;
return callback(null, opts);
});
});
};
root.signTx = function(txp, index, callback) { root.signTx = function(txp, index, callback) {
root.callbacks["sign_p2sh"] = callback; root.callbacks["sign_p2sh"] = callback;
var redeemScripts = []; var redeemScripts = [];
@ -40,13 +89,15 @@ angular.module('copayApp.services')
scripts: redeemScripts, scripts: redeemScripts,
outputs_number: splitTransaction.outputs.length, outputs_number: splitTransaction.outputs.length,
outputs_script: splitTransaction.outputScript.toString(), outputs_script: splitTransaction.outputScript.toString(),
paths: paths }); paths: paths
});
} }
root._message = function(data) { root._message = function(data) {
chrome.runtime.sendMessage( chrome.runtime.sendMessage(
LEDGER_CHROME_ID, LEDGER_CHROME_ID, {
{ request: data }, request: data
},
function(response) { function(response) {
root._callback(response); root._callback(response);
} }
@ -55,7 +106,9 @@ angular.module('copayApp.services')
root._messageAfterSession = function(data) { root._messageAfterSession = function(data) {
root._after_session = data; root._after_session = data;
root._message({ command:"launch" }); root._message({
command: "launch"
});
root._should_poll_session = true; root._should_poll_session = true;
root._do_poll_session(); root._do_poll_session();
} }
@ -79,7 +132,10 @@ angular.module('copayApp.services')
} else { } else {
root._should_poll_session = false; root._should_poll_session = false;
Object.keys(root.callbacks).forEach(function(key) { Object.keys(root.callbacks).forEach(function(key) {
root.callbacks[key]({ success: false, message: "The Ledger Chrome application is not installed" }); root.callbacks[key]({
success: false,
message: gettext("The Ledger Chrome application is not installed"),
});
}); });
} }
} }
@ -168,7 +224,9 @@ var Convert = {};
Convert.stringToHex = function(src) { Convert.stringToHex = function(src) {
var r = ""; var r = "";
var hexes = new Array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"); 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];} for (var i = 0; i < src.length; i++) {
r += hexes[src.charCodeAt(i) >> 4] + hexes[src.charCodeAt(i) & 0xf];
}
return r; return r;
} }
@ -287,14 +345,12 @@ var ByteString = function(value, encoding) {
if (this.hasBuffer && (value instanceof Buffer)) { if (this.hasBuffer && (value instanceof Buffer)) {
this.value = value; this.value = value;
this.encoding = GP.HEX; this.encoding = GP.HEX;
} } else {
else {
switch (encoding) { switch (encoding) {
case GP.HEX: case GP.HEX:
if (!this.hasBuffer) { if (!this.hasBuffer) {
this.value = Convert.hexToBin(value); this.value = Convert.hexToBin(value);
} } else {
else {
this.value = new Buffer(value, 'hex'); this.value = new Buffer(value, 'hex');
} }
break; break;
@ -302,8 +358,7 @@ var ByteString = function(value, encoding) {
case GP.ASCII: case GP.ASCII:
if (!this.hasBuffer) { if (!this.hasBuffer) {
this.value = value; this.value = value;
} } else {
else {
this.value = new Buffer(value, 'ascii'); this.value = new Buffer(value, 'ascii');
} }
break; break;
@ -332,8 +387,7 @@ ByteString.prototype.byteAt = function(index) {
} }
if (!this.hasBuffer) { if (!this.hasBuffer) {
return Convert.readHexDigit(Convert.stringToHex(this.value.substring(index, index + 1))); return Convert.readHexDigit(Convert.stringToHex(this.value.substring(index, index + 1)));
} } else {
else {
return this.value[index]; return this.value[index];
} }
} }
@ -362,30 +416,25 @@ ByteString.prototype.bytes = function(offset, count) {
} }
if (!this.hasBuffer) { if (!this.hasBuffer) {
result = new ByteString(this.value.substring(offset, offset + count), GP.ASCII); result = new ByteString(this.value.substring(offset, offset + count), GP.ASCII);
} } else {
else {
result = new Buffer(count); result = new Buffer(count);
this.value.copy(result, 0, offset, offset + count); this.value.copy(result, 0, offset, offset + count);
} }
} } else
else
if (typeof count == "undefined") { if (typeof count == "undefined") {
if (!this.hasBuffer) { if (!this.hasBuffer) {
result = new ByteString(this.value.substring(offset), GP.ASCII); result = new ByteString(this.value.substring(offset), GP.ASCII);
} } else {
else {
result = new Buffer(this.value.length - offset); result = new Buffer(this.value.length - offset);
this.value.copy(result, 0, offset, this.value.length); this.value.copy(result, 0, offset, this.value.length);
} }
} } else {
else {
throw "Invalid count"; throw "Invalid count";
} }
if (!this.hasBuffer) { if (!this.hasBuffer) {
result.encoding = this.encoding; result.encoding = this.encoding;
return result; return result;
} } else {
else {
return new ByteString(result, GP.HEX); return new ByteString(result, GP.HEX);
} }
} }
@ -407,8 +456,7 @@ ByteString.prototype.concat = function(target) {
var x = new ByteString(result, GP.ASCII); var x = new ByteString(result, GP.ASCII);
x.encoding = this.encoding; x.encoding = this.encoding;
return x; return x;
} } else {
else {
var result = Buffer.concat([this.value, target.value]); var result = Buffer.concat([this.value, target.value]);
return new ByteString(result, GP.HEX); return new ByteString(result, GP.HEX);
} }
@ -428,8 +476,7 @@ ByteString.prototype.equals = function(target) {
} }
if (!this.hasBuffer) { if (!this.hasBuffer) {
return (this.value == target.value); return (this.value == target.value);
} } else {
else {
return Buffer.equals(this.value, target.value); return Buffer.equals(this.value, target.value);
} }
} }
@ -461,15 +508,13 @@ ByteString.prototype.toString = function(encoding) {
case GP.HEX: case GP.HEX:
if (!this.hasBuffer) { if (!this.hasBuffer) {
return Convert.stringToHex(this.value); return Convert.stringToHex(this.value);
} } else {
else {
return this.value.toString('hex'); return this.value.toString('hex');
} }
case GP.ASCII: case GP.ASCII:
if (!this.hasBuffer) { if (!this.hasBuffer) {
return this.value; return this.value;
} } else {
else {
return this.value.toString(); return this.value.toString();
} }
default: default:

View file

@ -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, ledger, uxLanguage) { .factory('profileService', function profileServiceFactory($rootScope, $location, $timeout, $filter, $log, lodash, storageService, bwcService, configService, notificationService, isChromeApp, isCordova, gettext, nodeWebkit, bwsError, uxLanguage, ledger) {
var root = {}; var root = {};
@ -179,7 +179,7 @@ angular.module('copayApp.services')
} }
} else if (opts.extendedPublicKey) { } else if (opts.extendedPublicKey) {
try { try {
walletClient.seedFromExternalWalletPublicKey(opts.extendedPublicKey, opts.externalSource, opts.externalIndex); walletClient.seedFromExternalWalletPublicKey(opts.extendedPublicKey, opts.externalSource, opts.externalIndex, opts.entropySource);
} catch (ex) { } catch (ex) {
return cb(gettext('Could not create using the specified extended public key')); return cb(gettext('Could not create using the specified extended public key'));
} }