diff --git a/public/views/create.html b/public/views/create.html
index 982430718..f1340cb6b 100644
--- a/public/views/create.html
+++ b/public/views/create.html
@@ -117,7 +117,7 @@
-
+
Specify your wallet seed
@@ -129,14 +129,14 @@
-
+
Wallet Seed
Enter the 12 words seed (BIP39)
- Seed Passphrase The seed could require a passphrase to be imported
+ Seed Passphrase The seed could require a passphrase to be imported
@@ -155,11 +155,11 @@
-
+
Create {{requiredCopayers}}-of-{{totalCopayers}} wallet
-
+
Create new wallet
diff --git a/public/views/preferences.html b/public/views/preferences.html
index b07c2dc35..2bdc0b71e 100644
--- a/public/views/preferences.html
+++ b/public/views/preferences.html
@@ -36,19 +36,18 @@
█
-
-
+
Request Password for Spending Funds
-
+
Hardware wallet
{{preferences.externalSource}}
(index {{preferences.externalIndex}})
-
+
Still not done
diff --git a/public/views/walletHome.html b/public/views/walletHome.html
index c5772a71e..294a1cd8c 100644
--- a/public/views/walletHome.html
+++ b/public/views/walletHome.html
@@ -123,10 +123,9 @@
{{(index.alias || index.walletName)}}
- Multisignature wallet
+ Multisignature wallet
({{index.m}}-of-{{index.n}} )
- - Testnet
- - No Private key
+
@@ -134,8 +133,7 @@
{{(index.alias || index.walletName)}}
- Testnet
- No Private key
+
diff --git a/src/js/controllers/create.js b/src/js/controllers/create.js
index e77b4dd36..a210bf5e5 100644
--- a/src/js/controllers/create.js
+++ b/src/js/controllers/create.js
@@ -35,7 +35,7 @@ angular.module('copayApp.controllers').controller('createController',
$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;
this.TCValues = lodash.range(2, defaults.limits.totalCopayers + 1);
$scope.totalCopayers = defaults.wallet.totalCopayers;
@@ -73,24 +73,17 @@ angular.module('copayApp.controllers').controller('createController',
return;
}
- self.loading = true;
-
if (form.hwLedger.$modelValue) {
self.ledger = true;
- ledger.getXPubKey($scope.externalIndex, function(data) {
+ ledger.getInfoForNewWallet($scope.externalIndex, function(err, lopts) {
self.ledger = false;
- if (data.success) {
+ if (err) {
+ self.error = err;
$scope.$apply();
- opts.extendedPublicKey = data.xpubkey;
- opts.externalSource = 'ledger';
- opts.externalIndex = $scope.externalIndex;
- self._create(opts);
- } else {
- self.loading = false;
- self.error = data.message;
- $scope.$apply();
- $log.debug(data.message);
+ return;
}
+ opts = lodash.assign(lopts, opts);
+ self._create(opts);
});
} else {
self._create(opts);
@@ -98,6 +91,7 @@ angular.module('copayApp.controllers').controller('createController',
};
this._create = function (opts) {
+ self.loading = true;
$timeout(function() {
profileService.createWallet(opts, function(err, secret, walletId) {
self.loading = false;
@@ -105,14 +99,14 @@ angular.module('copayApp.controllers').controller('createController',
if (err == "Error creating wallet" && opts.extendedPublicKey) {
err = gettext("This xpub index is already used by another wallet. Please select another index.");
}
- $log.debug(err);
+ $log.warn(err);
self.error = err;
$timeout(function() {
$rootScope.$apply();
});
}
else {
- if (opts.mnemonic && opts.n==1) {
+ if ( ( opts.mnemonic && opts.n==1) || otps.externalSource ) {
$rootScope.$emit('Local/WalletImported', walletId);
} else {
go.walletHome();
diff --git a/src/js/controllers/index.js b/src/js/controllers/index.js
index 72b62a98a..a1defdf67 100644
--- a/src/js/controllers/index.js
+++ b/src/js/controllers/index.js
@@ -101,6 +101,8 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.walletId = fc.credentials.walletId;
self.isComplete = fc.isComplete();
self.canSign = fc.canSign();
+ self.isPrivKeyExternal = fc.isPrivKeyExternal();
+ self.externalSource = fc.getPrivKeyExternalSourceName();
self.txps = [];
self.copayers = [];
self.updateColor();
diff --git a/src/js/controllers/join.js b/src/js/controllers/join.js
index 337db1446..9a66b61e2 100644
--- a/src/js/controllers/join.js
+++ b/src/js/controllers/join.js
@@ -204,7 +204,7 @@ angular.module('copayApp.controllers').controller('joinController',
}
$timeout(function() {
var fc = profileService.focusedClient;
- if (opts.mnemonic && fc.isComplete()) {
+ if ( ( opts.mnemonic || opts.externalSource ) && fc.isComplete()) {
$rootScope.$emit('Local/WalletImported', fc.credentials.walletId);
} else {
go.walletHome();
diff --git a/src/js/controllers/walletHome.js b/src/js/controllers/walletHome.js
index 20f9948bf..fe6af8ef3 100644
--- a/src/js/controllers/walletHome.js
+++ b/src/js/controllers/walletHome.js
@@ -184,7 +184,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
$scope.error = null;
$scope.copayers = copayers
$scope.copayerId = fc.credentials.copayerId;
- $scope.canSign = fc.canSign();
+ $scope.canSign = fc.canSign() || fc.isPrivKeyExternal();
$scope.loading = null;
$scope.color = fc.backgroundColor;
@@ -244,7 +244,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
$scope.sign = function(txp) {
var fc = profileService.focusedClient;
- if (!fc.canSign())
+ if (!fc.canSign() && !fc.isPrivKeyExternal())
return;
if (fc.isPrivKeyEncrypted()) {
@@ -801,7 +801,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
return self.setSendError(err);
}
- if (!fc.canSign()) {
+ if (!fc.canSign() && !fc.isPrivKeyExternal()) {
$log.info('No signing proposal: No private key')
self.setOngoingProcess();
self.resetForm();
diff --git a/src/js/services/ledger.js b/src/js/services/ledger.js
index 00c31542b..fa05bc8f6 100644
--- a/src/js/services/ledger.js
+++ b/src/js/services/ledger.js
@@ -1,53 +1,104 @@
'use strict';
angular.module('copayApp.services')
- .factory('ledger', function(bwcService) {
+ .factory('ledger', function($log, bwcService, gettext) {
var root = {};
var LEDGER_CHROME_ID = "kkdpmhnladdopljabkgpacgpliggeeaf";
+ root.MAX_SLOT = 20;
+ root.ENTROPY_INDEX_START = 1000;
root.callbacks = {};
root.hasSession = function() {
- root._message({ command:"has_session" });
+ 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.getEntropySource = function(index, callback) {
+ index = index + root.ENTROPY_INDEX_START;
+ 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.callbacks["sign_p2sh"] = callback;
var redeemScripts = [];
var paths = [];
var tx = bwcService.getUtils().buildTx(txp);
- for (var i=0; i=0; i--) {
+ for (var i = x.length - 1; i >= 0; i--) {
res += Convert.toHexByte(x.byteAt(i));
}
return new ByteString(res, GP.HEX);
@@ -157,7 +213,7 @@ angular.module('copayApp.services')
return root;
});
-var Convert = {};
+var Convert = {};
/**
* Convert a binary string to his hexadecimal representation
@@ -167,9 +223,11 @@ var Convert = {};
*/
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> 4] + hexes [src.charCodeAt(i) & 0xf];}
- return 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;
}
/**
@@ -180,24 +238,24 @@ Convert.stringToHex = function(src) {
* @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> 24) & 0xff) + Convert.toHexDigit((number >> 16) & 0xff) +
- Convert.toHexDigit((number >> 8) & 0xff) + Convert.toHexDigit(number & 0xff);
+ Convert.toHexDigit((number >> 8) & 0xff) + Convert.toHexDigit(number & 0xff);
}
@@ -275,209 +333,196 @@ 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
-*/
+ * @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;
+ 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";
}
- 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;
+ }
+ 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];
- }
+ 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
-*/
+ * @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 {
+ 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.encoding = this.encoding;
- return result;
+ 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 {
- return new ByteString(result, GP.HEX);
+ } 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);
- }
+ 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);
- }
+ 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;
+ var targetEncoding = this.encoding;
+ if (arguments.length >= 1) {
+ if (typeof encoding != "number") {
+ throw "Invalid encoding";
}
- switch(targetEncoding) {
+ switch (encoding) {
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();
- }
+ targetEncoding = encoding;
+ break;
+
default:
- throw "Unsupported";
- }
+ throw "Unsupported arguments";
+ }
+ targetEncoding = encoding;
}
-
-ByteString.prototype.toStringIE = function(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);
}
diff --git a/src/js/services/profileService.js b/src/js/services/profileService.js
index ef1ddd292..33a620a10 100644
--- a/src/js/services/profileService.js
+++ b/src/js/services/profileService.js
@@ -1,6 +1,6 @@
'use strict';
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 = {};
@@ -179,7 +179,7 @@ angular.module('copayApp.services')
}
} else if (opts.extendedPublicKey) {
try {
- walletClient.seedFromExternalWalletPublicKey(opts.extendedPublicKey, opts.externalSource, opts.externalIndex);
+ walletClient.seedFromExternalWalletPublicKey(opts.extendedPublicKey, opts.externalSource, opts.externalIndex, opts.entropySource);
} catch (ex) {
return cb(gettext('Could not create using the specified extended public key'));
}