diff --git a/baseDerivation.md b/baseDerivation.md new file mode 100644 index 000000000..164a708fe --- /dev/null +++ b/baseDerivation.md @@ -0,0 +1,26 @@ + +Copay accepts three base derivation paths: + + * m/44' + * m/48' (only used for MULTISIGNATURE, HARDWARE Wallets) + * m/45' (deprecated and it is only supported for old wallets) + +Both m/44 and m/48 follow the BIP44 standard: + +m/XX'/'/ + +Supported cointypes are: 0: Livenet, and 1: Testnet + +If you need to import a wallet from a mnemonic using an account different +from the default (0), use, for example: + + m/44'/0'/11' + +to import account 11. + +In case you have a multisignature wallet originally created from a hardware device, and you had loose access to the device, you will need to enter the 24 mnemonic backup (from the device) and a path like: + + + m/48'/0'/8' + +for a multisignature wallet, account 8. diff --git a/public/views/create.html b/public/views/create.html index d666209d9..79f17bf97 100644 --- a/public/views/create.html +++ b/public/views/create.html @@ -116,12 +116,9 @@
- -
@@ -161,21 +158,11 @@
- -
Multiple accounts can be derived from the same seed. Specify which account to use
-
- -
-
- -
+
- - -
- -
Multiple accounts can be derived from the same seed. Specify which account to import
-
+
+ +
+
+
+ + +
+
-
- - -
Multiple wallets accounts are supported on the device simultaneously. Select which account should be imported
-
-
-
-
- -
diff --git a/src/js/controllers/create.js b/src/js/controllers/create.js index 44a11f67b..0a19e1726 100644 --- a/src/js/controllers/create.js +++ b/src/js/controllers/create.js @@ -1,11 +1,12 @@ 'use strict'; angular.module('copayApp.controllers').controller('createController', - function($scope, $rootScope, $location, $timeout, $log, lodash, go, profileService, configService, isCordova, gettext, ledger, trezor, isMobile, isChromeApp, isDevel) { + function($scope, $rootScope, $location, $timeout, $log, lodash, go, profileService, configService, isCordova, gettext, ledger, trezor, isMobile, isChromeApp, isDevel, derivationPathHelper) { var self = this; var defaults = configService.getDefaults(); this.isWindowsPhoneApp = isMobile.Windows() && isCordova; + $scope.account = 1; /* For compressed keys, m*73 + n*34 <= 496 */ var COPAYER_PAIR_LIMITS = { @@ -25,8 +26,7 @@ angular.module('copayApp.controllers').controller('createController', var defaults = configService.getDefaults(); $scope.bwsurl = defaults.bws.url; - self.accountValuesForSeed = lodash.range(0, 100); - $scope.accountForSeed = 0; + $scope.derivationPath = derivationPathHelper.default; // ng-repeat defined number of times instead of repeating over array? this.getNumber = function(num) { @@ -77,7 +77,6 @@ angular.module('copayApp.controllers').controller('createController', this.setSeedSource = function(src) { self.seedSourceId = $scope.seedSource.id; - self.accountValues = lodash.range(1, 100); $timeout(function() { $rootScope.$apply(); @@ -89,6 +88,7 @@ angular.module('copayApp.controllers').controller('createController', this.error = gettext('Please enter the required fields'); return; } + var opts = { m: $scope.requiredCopayers, n: $scope.totalCopayers, @@ -96,18 +96,29 @@ angular.module('copayApp.controllers').controller('createController', myName: $scope.totalCopayers > 1 ? form.myName.$modelValue : null, networkName: form.isTestnet.$modelValue ? 'testnet' : 'livenet', bwsurl: $scope.bwsurl, - account: $scope.accountForSeed || 0, use48: $scope.fromHardware, }; - var setSeed = self.seedSourceId =='set'; + var setSeed = self.seedSourceId == 'set'; if (setSeed) { - var words = form.privateKey.$modelValue; + + var words = form.privateKey.$modelValue || ''; if (words.indexOf(' ') == -1 && words.indexOf('prv') == 1 && words.length > 108) { opts.extendedPrivateKey = words; } else { opts.mnemonic = words; } opts.passphrase = form.passphrase.$modelValue; + + var pathData = derivationPathHelper.parse($scope.derivationPath); + if (!pathData) { + this.error = gettext('Invalid derivation path'); + return; + } + + opts.account = pathData.account; + opts.networkName = pathData.networkName; + opts.derivationStrategy = pathData.derivationStrategy; + } else { opts.passphrase = form.createPassphrase.$modelValue; } @@ -119,11 +130,15 @@ angular.module('copayApp.controllers').controller('createController', if (self.seedSourceId == 'ledger' || self.seedSourceId == 'trezor') { var account = $scope.account; - if (!account) { - this.error = gettext('Please select account'); + if (!account || account < 1) { + this.error = gettext('Invalid account number'); return; } - opts.account = account; + + if ( self.seedSourceId == 'trezor') + account = account - 1; + + opts.account = account; self.hwWallet = self.seedSourceId == 'ledger' ? 'Ledger' : 'Trezor'; var src = self.seedSourceId == 'ledger' ? ledger : trezor; diff --git a/src/js/controllers/import.js b/src/js/controllers/import.js index 8eeec31c4..f89c3dd21 100644 --- a/src/js/controllers/import.js +++ b/src/js/controllers/import.js @@ -1,14 +1,14 @@ 'use strict'; angular.module('copayApp.controllers').controller('importController', - function($scope, $rootScope, $location, $timeout, $log, profileService, configService, notification, go, sjcl, gettext, lodash, ledger, trezor, isChromeApp, isDevel) { + function($scope, $rootScope, $location, $timeout, $log, profileService, configService, notification, go, sjcl, gettext, lodash, ledger, trezor, isChromeApp, isDevel, derivationPathHelper) { var self = this; var reader = new FileReader(); var defaults = configService.getDefaults(); $scope.bwsurl = defaults.bws.url; - $scope.accountForSeed = 0; - self.accountValuesForSeed = lodash.range(0, 100); + $scope.derivationPath = derivationPathHelper.default; + $scope.account = 1; window.ignoreMobilePause = true; $scope.$on('$destroy', function() { @@ -196,8 +196,16 @@ angular.module('copayApp.controllers').controller('importController', } opts.passphrase = form.passphrase.$modelValue || null; - opts.networkName = form.isTestnet.$modelValue ? 'testnet' : 'livenet'; - opts.account = $scope.accountForSeed; + + var pathData = derivationPathHelper.parse($scope.derivationPath); + if (!pathData) { + this.error = gettext('Invalid derivation path'); + return; + } + opts.account = pathData.account; + opts.networkName = pathData.networkName; + opts.derivationStrategy = pathData.derivationStrategy; + _importMnemonic(words, opts); }; @@ -233,15 +241,24 @@ angular.module('copayApp.controllers').controller('importController', }; this.importHW = function(form) { - if (form.$invalid) { + if (form.$invalid || $scope.account < 0 ) { this.error = gettext('There is an error in the form'); $timeout(function() { $scope.$apply(); }); return; } + this.error = ''; - var account = $scope.account; + var account = + $scope.account; + + if (self.seedSourceId == 'trezor') { + if ( account < 1) { + this.error = gettext('Invalid account number'); + return; + } + account = account - 1; + } var isMultisig = form.isMultisig.$modelValue; switch (self.seedSourceId) { @@ -261,7 +278,6 @@ angular.module('copayApp.controllers').controller('importController', this.setSeedSource = function() { if (!$scope.seedSource) return; self.seedSourceId = $scope.seedSource.id; - self.accountValues = lodash.range(1, 100); $timeout(function() { $rootScope.$apply(); diff --git a/src/js/controllers/join.js b/src/js/controllers/join.js index 376a60100..31ae0158d 100644 --- a/src/js/controllers/join.js +++ b/src/js/controllers/join.js @@ -1,13 +1,12 @@ 'use strict'; angular.module('copayApp.controllers').controller('joinController', - function($scope, $rootScope, $timeout, go, notification, profileService, configService, isCordova, storageService, applicationService, $modal, gettext, lodash, ledger, trezor, isChromeApp, isDevel) { + function($scope, $rootScope, $timeout, go, notification, profileService, configService, isCordova, storageService, applicationService, $modal, gettext, lodash, ledger, trezor, isChromeApp, isDevel,derivationPathHelper) { var self = this; var defaults = configService.getDefaults(); $scope.bwsurl = defaults.bws.url; - self.accountValuesForSeed = lodash.range(0, 100); - $scope.accountForSeed = 0; + $scope.derivationPath = derivationPathHelper.default; this.onQrCodeScanned = function(data) { $scope.secret = data; @@ -62,7 +61,6 @@ angular.module('copayApp.controllers').controller('joinController', secret: form.secret.$modelValue, myName: form.myName.$modelValue, bwsurl: $scope.bwsurl, - account: $scope.accountForSeed || 0, } var setSeed = self.seedSourceId =='set'; @@ -74,6 +72,15 @@ angular.module('copayApp.controllers').controller('joinController', opts.mnemonic = words; } opts.passphrase = form.passphrase.$modelValue; + + var pathData = derivationPathHelper.parse($scope.derivationPath); + if (!pathData) { + this.error = gettext('Invalid derivation path'); + return; + } + opts.account = pathData.account; + opts.networkName = pathData.networkName; + opts.derivationStrategy = pathData.derivationStrategy; } else { opts.passphrase = form.createPassphrase.$modelValue; } @@ -85,10 +92,14 @@ angular.module('copayApp.controllers').controller('joinController', if (self.seedSourceId == 'ledger' || self.seedSourceId == 'trezor') { var account = $scope.account; - if (!account) { - this.error = gettext('Please select account'); + if (!account || account < 1) { + this.error = gettext('Invalid account number'); return; } + + if ( self.seedSourceId == 'trezor') + account = account - 1; + opts.account = account; self.hwWallet = self.seedSourceId == 'ledger' ? 'Ledger' : 'Trezor'; var src = self.seedSourceId == 'ledger' ? ledger : trezor; diff --git a/src/js/routes.js b/src/js/routes.js index 1533ba74a..326527899 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -21,13 +21,14 @@ angular $logProvider.debugEnabled(true); $provide.decorator('$log', ['$delegate', - function($delegate) { + function($delegate, isDevel) { var historicLog = historicLogProvider.$get(); ['debug', 'info', 'warn', 'error', 'log'].forEach(function(level) { + if (isDevel && level == 'error') return; + var orig = $delegate[level]; $delegate[level] = function() { - if (level == 'error') console.log(arguments); diff --git a/src/js/services/derivationPathHelper.js b/src/js/services/derivationPathHelper.js new file mode 100644 index 000000000..04588692d --- /dev/null +++ b/src/js/services/derivationPathHelper.js @@ -0,0 +1,46 @@ +'use strict'; + +angular.module('copayApp.services').factory('derivationPathHelper', function(lodash) { + var root = {}; + + root.default = "m/44'/0'/0'" + root.parse = function(str) { + var arr = str.split('/'); + + var ret = {}; + + if (arr[0] != 'm') + return false; + + switch (arr[1]) { + case "44'": + ret.derivationStrategy = 'BIP44'; + break; + case "48'": + ret.derivationStrategy = 'BIP48'; + break; + default: + return false; + }; + + switch (arr[2]) { + case "0'": + ret.networkName = 'livenet'; + break; + case "1'": + ret.networkName = 'testnet'; + break; + default: + return false; + }; + + var match = arr[3].match(/(\d+)'/); + if (!match) + return false; + ret.account = + match[1] + + return ret; + }; + + return root; +}); diff --git a/src/js/services/hwWallet.js b/src/js/services/hwWallet.js index 7e696046d..cf81f71f1 100644 --- a/src/js/services/hwWallet.js +++ b/src/js/services/hwWallet.js @@ -13,7 +13,7 @@ angular.module('copayApp.services') root._err = function(data) { var msg = 'Hardware Wallet Error: ' + (data.error || data.message || 'unknown'); $log.warn(msg); - return JSON.parse(JSON.stringify(msg)); + return msg; }; root.getAddressPath = function(isMultisig, account) { diff --git a/src/js/services/trezor.js b/src/js/services/trezor.js index 295b0f05f..4a16df8de 100644 --- a/src/js/services/trezor.js +++ b/src/js/services/trezor.js @@ -24,7 +24,6 @@ angular.module('copayApp.services') root.getInfoForNewWallet = function(isMultisig, account, callback) { - account = account - 1; var opts = {}; root.getEntropySource(isMultisig, account, function(err, data) { if (err) return callback(err);