create/join using mnemonic working

This commit is contained in:
Matias Alejo Garcia 2015-09-02 15:56:00 -03:00
commit 452d9e1bda
9 changed files with 187 additions and 151 deletions

View file

@ -7,16 +7,42 @@
<div class="content p20v" ng-controller="wordsController as wordsC"> <div class="content p20v" ng-controller="wordsController as wordsC">
<div class="row"> <div class="row" ng-show="index.n==1">
<div class="m10t columns size-14 text-gray" translate> <div class="m10t columns size-14 text-gray" translate>
<span translate> <span translate>
In order to restore your Copay wallet you will need these 12 backup words. You will need these backup words to restore this personal wallet.
</span> </span>
<span translate class="text-bold"> <span translate class="text-bold">
Write them down and keep them somethere safe Write them down and keep them somewhere safe.
</span> </span>
</div> </div>
</div> </div>
<div class="row" ng-show="(index.n>1 && index.m != index.n )">
<div class="m10t columns size-14 text-gray" translate>
<span translate>
To restore this {{index.m}}-{{index.n}} shared wallet you will need:
</span>:
<ol class="m10t columns size-14 text-gray">
<li> Your backup words and access to the wallet service where your wallet is registered
<li> <b>OR</b> the backup words of <b>all</b> copayers in the wallet
<li> <b>OR</b> 1 wallet export file and the remaining quorum of backup words (e.g. in a 3-5 wallet: 1 wallet export file + 2 backup words of any of the other copayers).
</ol>
</span>
</div>
</div>
<div class="row" ng-show="(index.n>1 && index.m == index.n )">
<div class="m10t columns size-14 text-gray" translate>
<span translate>
To restore this {{index.m}}-{{index.n}} <b>shared</b> wallet you will need
</span>:
<ol class="m10t columns size-14 text-gray">
<li> Your backup words and access to the wallet service where wallet is registered
<li> <b>OR</b> the backups words of <b>all</b> copayers in the wallet
</ol>
</span>
</div>
</div>
<div class="row m20t" ng-show="!wordsC.mywords"> <div class="row m20t" ng-show="!wordsC.mywords">
<div class="columns size-14 text-gray" translate> <div class="columns size-14 text-gray" translate>
The backup words had been deleted from this device The backup words had been deleted from this device
@ -37,10 +63,11 @@
</div> </div>
<div class="row enable_text_select" ng-show="show" > <div class="row enable_text_select" ng-show="show" >
<span class="m10" ng-repeat="word in wordsC.mywords"> <div class="small-centered p10t p10b large-centered medium-centered large-8 medium-8 small-10 columns enable_text_select" style="border:1px solid gray; background:#eee;
{{word}} ">
</span> <span class="m10r" ng-repeat="word in wordsC.mywords">{{word}} </span>
<div class="m10 text-center"> </div>
<div class="m10 text-center columns">
<div class="m10 size-14 text-gray" translate> <div class="m10 size-14 text-gray" translate>
<span translate> <span translate>
Once you have wrote your backup words, it is recommended to delete them from this device. Once you have wrote your backup words, it is recommended to delete them from this device.
@ -54,7 +81,9 @@
</button> </button>
</div> </div>
</div> </div>
<div class="row m20t">
<!-- hide this in multisig just to show less text -->
<div class="row m20t" ng-show="index.n==1">
<div class="columns size-14 text-gray" translate> <div class="columns size-14 text-gray" translate>
You can safely install your backup on another device and use your wallet from multiple devices at the same time. <a href="#" ng-click="$root.openExternalLink('https://github.com/bitpay/copay#backups')"> You can safely install your backup on another device and use your wallet from multiple devices at the same time. <a href="#" ng-click="$root.openExternalLink('https://github.com/bitpay/copay#backups')">
<span translate>Learn more about Copay backups</span> <span translate>Learn more about Copay backups</span>

View file

@ -118,10 +118,10 @@
</label> </label>
<label for="ext-master" class="m10t"> <label for="ext-master" class="m10t">
<span translate>Backup Words (BIP39 seed)</span> <span translate>Backup Words (BIP39 seed)</span>
<small translate>If not given, a secure key will be generated</small> <small translate>If not given, a random secure key will be generated</small>
<input id="ext-master" <input id="ext-master"
type="text" type="text"
placeholder="{{'BIP32 master extended private key'|translate}}" placeholder="{{'Backup words'|translate}}"
name="privateKey" ng-model="privateKey"> name="privateKey" ng-model="privateKey">
</label> </label>
</div> </div>

View file

@ -20,21 +20,21 @@
<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-4 medium-4 large-4"> <div class="tab-container small-6 medium-6 large-6">
<a href <a href
ng-class="{'selected': type =='12'}" ng-class="{'selected': type =='12'}"
ng-click="import.setType('12')" translate>12 Words Backup</a> ng-click="import.setType('12')" translate>12 Words Backup</a>
</div> </div>
<div class="tab-container small-4 medium-4 large-4"> <div class="tab-container small-6 medium-6 large-6">
<a href <a href
ng-class="{'selected': type=='file'}" ng-class="{'selected': type=='file'}"
ng-click="import.setType('file')" translate>File/Text Backup</a> ng-click="import.setType('file')" translate>File/Text Backup</a>
</div> </div>
<div class="tab-container small-4 medium-4 large-4"> <!-- <div class="tab&#45;container small&#45;4 medium&#45;4 large&#45;4"> -->
<a href <!-- <a href -->
ng-class="{'selected': type=='qr'}" <!-- ng&#45;class="{'selected': type=='qr'}" -->
ng-click="import.setType('qr')" translate>QR Code</a> <!-- ng&#45;click="import.setType('qr')" translate>QR Code</a> -->
</div> <!-- </div> -->
</div> </div>
@ -55,7 +55,7 @@
<label for="words"> <label for="words">
<span translate>Type the 12 backup words here</span>: <span translate>Type the 12 backup words here</span>:
</label> </label>
<textarea class="form-control" name="words" ng-model="import.words" rows="5"></textarea> <textarea class="form-control" name="words" ng-model="import.words" rows="2"></textarea>
</div> </div>
<div class="m10t oh" ng-init="hideAdv=true"> <div class="m10t oh" ng-init="hideAdv=true">
@ -69,12 +69,18 @@
</div> </div>
<div ng-hide="hideAdv" class="row"> <div ng-hide="hideAdv" class="row">
<div class="large-12 columns"> <div class="large-12 columns">
<label for="passphrase" class="line-b oh"><span translate>Passphrase</span> <small translate>Mnemonics could required a passphrase to be imported</small> <label for="passphrase" class="line-b oh"><span translate>Passphrase</span> <small translate>Mnemonics could required a passphrase to be imported</small>
<div class="input"> <div class="input">
<input type="password" class="form-control" placeholder="{{'Your backup passhrase'|translate}}" <input type="password" class="form-control" placeholder="{{'Your backup passhrase'|translate}}"
name="passphrase" ng-model="import.passphrase"> name="passphrase" ng-model="import.passphrase">
</div> </div>
</label> </label>
<label for="network-name" class="line-b oh">
<span translate>Testnet</span>
<switch id="network-name" name="isTestnet" ng-model="isTestnet" class="green right m5t m10b"></switch>
</label>
</div> </div>
</div> </div>

View file

@ -99,11 +99,12 @@
</label> </label>
</div> </div>
<div class="large-12 columns"> <div class="large-12 columns">
<label for="ext-master">{{'Master extended private key'|translate}} <label for="ext-master">
<small translate>If not given, a secure key will be generated</small> <span translate>Backup Words (BIP39 seed)</span>
<small translate>If not given, a random secure key will be generated</small>
<input id="ext-master" <input id="ext-master"
type="text" type="text"
placeholder="{{'BIP32 master extended private key'|translate}}" placeholder="{{'Backup words'|translate}}"
name="privateKey" ng-model="privateKey"> name="privateKey" ng-model="privateKey">
</label> </label>
</div> </div>

View file

@ -215,16 +215,14 @@
<!-- Address--> <!-- Address-->
<div class="large-12 columns"> <div class="large-12 columns">
<h2 class="text-center m10t" translate>My Bitcoin address</h2> <h2 class="text-center m10t" translate>My Bitcoin address</h2>
<div > <div>
<div class="box-notification" ng-show="home.addrError">
<span class="text-warning">
{{home.addrError|translate}}
</span>
</div>
<div class="box-notification" ng-show="home.addrError"> <div class="text-center" ng-click="home.copyAddress(home.addr[index.walletId])" ng-show="home.addr[index.walletId] || home.generatingAddress">
<span class="text-warning">
{{home.addrError|translate}}
</span>
</div>
<div class="text-center" ng-click="home.copyAddress(home.addr[index.walletId])">
<qrcode size="220" data="bitcoin:{{home.addr[index.walletId]}}"></qrcode> <qrcode size="220" data="bitcoin:{{home.addr[index.walletId]}}"></qrcode>
<div ng-show="home.generatingAddress" style="position:relative; top:-226px; height:0px"> <div ng-show="home.generatingAddress" style="position:relative; top:-226px; height:0px">
<div style="height:220px; width:220px; margin:auto; background: url(img/qr.png) white"> <div style="height:220px; width:220px; margin:auto; background: url(img/qr.png) white">
@ -246,15 +244,17 @@
</div> </div>
</div> </div>
<div class="m10t text-center" ng-show="index.isCordova"> <div ng-show="home.addr[index.walletId]">
<span class="button outline dark-gray tiny round" <div class="m10t text-center" ng-show="index.isCordova">
ng-click="home.shareAddress(home.addr[index.walletId])"> <span class="button outline dark-gray tiny round"
<i class="fi-share"></i> ng-click="home.shareAddress(home.addr[index.walletId])">
<span translate>Share address</span> <i class="fi-share"></i>
</span> <span translate>Share address</span>
</div> </span>
<div class="line-t size-12" translate> </div>
Share this wallet address to receive payments. To protect your privacy, new addresses are generated automatically once you use them. <div class="line-t size-12" translate>
Share this wallet address to receive payments. To protect your privacy, new addresses are generated automatically once you use them.
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -18,6 +18,7 @@ angular.module('copayApp.controllers').controller('importController',
this.setType = function(type) { this.setType = function(type) {
$scope.type = type; $scope.type = type;
this.error = null;
$timeout(function() { $timeout(function() {
$rootScope.$apply(); $rootScope.$apply();
}); });
@ -63,11 +64,8 @@ angular.module('copayApp.controllers').controller('importController',
var _importMnemonic = function(words, passphrase, opts) { var _importMnemonic = function(words, passphrase, opts) {
self.loading = true; self.loading = true;
console.log('[import.js.64:opts:]', opts); //TODO
$timeout(function() { $timeout(function() {
profileService.importWalletMnemonic(words, { profileService.importWalletMnemonic(words, opts, function(err, ret) {
passphrase: passphrase,
}, function(err, ret) {
self.loading = false; self.loading = false;
if (err) { if (err) {
self.error = err; self.error = err;
@ -83,13 +81,13 @@ angular.module('copayApp.controllers').controller('importController',
}, 100); }, 100);
}; };
// { // {
// network: opts.network, // network: opts.network,
// m: opts.m, // m: opts.m,
// n: opts.n, // n: opts.n,
// publicKeyRing: opts.publicKeyRing, // publicKeyRing: opts.publicKeyRing,
// }, // },
// //
$scope.getFile = function() { $scope.getFile = function() {
// If we use onloadend, we need to check the readyState. // If we use onloadend, we need to check the readyState.
reader.onloadend = function(evt) { reader.onloadend = function(evt) {
@ -144,18 +142,28 @@ angular.module('copayApp.controllers').controller('importController',
var passphrase = form.passphrase.$modelValue; var passphrase = form.passphrase.$modelValue;
var words = form.words.$modelValue; var words = form.words.$modelValue;
this.error = null;
if (!words || words.split(' ').map(function(v) { if (!words) {
return lodash.trim(v); this.error = gettext('Please enter the backup words');
}).length != 12) { } else {
this.error = gettext('Please input 12 backup words'); var wordList = words.split(/ /).filter(function(v){ return v.length>0; });
if (wordList.length != 12)
this.error = gettext('Please enter 12 backup words');
else
words = wordList.join(' ');
}
if (this.error) {
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
return; return;
} }
opts.passphrase = form.passphrase.$modelValue || null; opts.passphrase = form.passphrase.$modelValue || null;
opts.networkName = form.isTestnet.$modelValue ? 'testnet' : 'livenet';
_importMnemonic(words, passphrase, opts); _importMnemonic(words, passphrase, opts);
}; };

View file

@ -29,12 +29,4 @@ angular.module('copayApp.controllers').controller('splashController',
}); });
}, 100); }, 100);
}; };
console.log('[splash.js.32]'); //TODO
var a = bwcService.getClient();
console.log('[splash.js.34]'); //TODO
a.seedFromMnemonic('glare benefit approve speak post afford spot cancel argue cushion unaware kitchen');
console.log("LISTO", a.credentials);
}); });

View file

@ -84,6 +84,21 @@ angular.module('copayApp.services')
case 'WALLET_NOT_FOUND': case 'WALLET_NOT_FOUND':
body = gettextCatalog.getString('Wallet not found'); body = gettextCatalog.getString('Wallet not found');
break; break;
case 'SERVER_COMPROMISED':
body = gettextCatalog.getString('Server response could not be verified');
break;
case 'WALLET_DOES_NOT_EXIST':
body = gettextCatalog.getString('This wallet is not registed at the wallet service. Please create it from "Create Wallet" using adding for backup words');
break;
case 'INVALID_BACKUP':
body = gettextCatalog.getString('Backup words are invalid');
break;
default:
$log.warn('Unknown error type:', err.code);
body = err.code + ':' + err.message;
break;
} }
} }

View file

@ -165,52 +165,14 @@ angular.module('copayApp.services')
}); });
}; };
root._seedWallet = function(walletClient, network) { root._seedWallet = function(opts, cb) {
var lang = uxLanguage.getCurrentLanguage(); opts = opts || {};
console.log('[profileService.js.170]'); //TODO
try {
walletClient.seedFromRandomWithMnemonic(network, null, lang);
console.log('[profileService.js.174]'); //TODO
} catch (e) {
$log.info('Error creating seed: ' + e.message);
if (e.message.indexOf('language') > 0) {
$log.info('Using default language for mnemonic');
walletClient.seedFromRandomWithMnemonic(network);
} else {
throw (e);
}
}
};
root._createNewProfile = function(opts, cb) {
if (opts.noWallet) {
return cb(null, Profile.create());
}
var walletClient = bwcService.getClient(); var walletClient = bwcService.getClient();
root._seedWallet(walletClient, 'livenet'); var network = opts.networkName || 'livenet';
walletClient.createWallet('Personal Wallet', 'me', 1, 1, {
network: 'livenet'
}, function(err) {
if (err) return bwsError.cb(err, gettext('Error creating wallet'), cb);
var p = Profile.create({
credentials: [JSON.parse(walletClient.export())],
});
return cb(null, p);
})
};
root.createWallet = function(opts, cb) {
var walletClient = bwcService.getClient();
$log.debug('Creating Wallet:', opts);
if (opts.mnemonic) { if (opts.mnemonic) {
try { try {
walletClient.seedFromMnemonic(opts.mnemonic); walletClient.seedFromMnemonic(opts.mnemonic, opts.passphrase, network);
} catch (ex) { } catch (ex) {
$log.info(ex); $log.info(ex);
return cb(gettext('Could not create: Invalid Backup Words')); return cb(gettext('Could not create: Invalid Backup Words'));
@ -221,41 +183,69 @@ console.log('[profileService.js.174]'); //TODO
} 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'));
} }
} else {
var lang = uxLanguage.getCurrentLanguage();
try {
walletClient.seedFromRandomWithMnemonic(network, opts.passphrase, lang);
} catch (e) {
$log.info('Error creating seed: ' + e.message);
if (e.message.indexOf('language') > 0) {
$log.info('Using default language for mnemonic');
walletClient.seedFromRandomWithMnemonic(network, opts.passphrase);
} else {
return cb(e);
}
}
} }
root._seedWallet(walletClient, opts.networkName); return cb(null, walletClient);
};
walletClient.createWallet(opts.name, opts.myName || 'me', opts.m, opts.n, { root._createNewProfile = function(opts, cb) {
network: opts.networkName
}, function(err, secret) {
if (err) return bwsError.cb(err, gettext('Error creating wallet'), cb);
root.profile.credentials.push(JSON.parse(walletClient.export())); if (opts.noWallet) {
root.setWalletClients(); return cb(null, Profile.create());
}
root.setAndStoreFocus(walletClient.credentials.walletId, function() { root._seedWallet({}, function(err, walletClient) {
storageService.storeProfile(root.profile, function(err) { if (err) return cb(err);
return cb(null, secret);
walletClient.createWallet('Personal Wallet', 'me', 1, 1, {
network: 'livenet'
}, function(err) {
if (err) return bwsError.cb(err, gettext('Error creating wallet'), cb);
var p = Profile.create({
credentials: [JSON.parse(walletClient.export())],
}); });
return cb(null, p);
}); });
}) })
}; };
root.createWallet = function(opts, cb) {
$log.debug('Creating Wallet:', opts);
root._seedWallet(opts, function(err, walletClient) {
if (err) return cb(err);
walletClient.createWallet(opts.name, opts.myName || 'me', opts.m, opts.n, {
network: opts.networkName
}, function(err, secret) {
if (err) return bwsError.cb(err, gettext('Error creating wallet'), cb);
root.profile.credentials.push(JSON.parse(walletClient.export()));
root.setWalletClients();
root.setAndStoreFocus(walletClient.credentials.walletId, function() {
storageService.storeProfile(root.profile, function(err) {
return cb(null, secret);
});
});
})
});
};
root.joinWallet = function(opts, cb) { root.joinWallet = function(opts, cb) {
var walletClient = bwcService.getClient(); var walletClient = bwcService.getClient();
$log.debug('Joining Wallet:', opts); $log.debug('Joining Wallet:', opts);
if (opts.extendedPrivateKey) {
try {
walletClient.seedFromExtendedPrivateKey(opts.extendedPrivateKey);
} catch (ex) {
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);
@ -269,18 +259,24 @@ console.log('[profileService.js.174]'); //TODO
} catch (ex) { } catch (ex) {
return cb(gettext('Bad wallet invitation')); return cb(gettext('Bad wallet invitation'));
} }
opts.networkName = walletData.network;
$log.debug('Joining Wallet:', opts);
walletClient.joinWallet(opts.secret, opts.myName || 'me', function(err) { root._seedWallet(opts, function(err, walletClient) {
if (err) return bwsError.cb(err, gettext('Could not join wallet'), cb); if (err) return cb(err);
root.profile.credentials.push(JSON.parse(walletClient.export())); walletClient.joinWallet(opts.secret, opts.myName || 'me', function(err) {
root.setWalletClients(); if (err) return bwsError.cb(err, gettext('Could not join wallet'), cb);
root.setAndStoreFocus(walletClient.credentials.walletId, function() { root.profile.credentials.push(JSON.parse(walletClient.export()));
storageService.storeProfile(root.profile, function(err) { root.setWalletClients();
return cb(null, opts.secret);
root.setAndStoreFocus(walletClient.credentials.walletId, function() {
storageService.storeProfile(root.profile, function(err) {
return cb();
});
}); });
}); })
}) })
}; };
@ -351,9 +347,13 @@ console.log('[profileService.js.174]'); //TODO
var walletClient = bwcService.getClient(); var walletClient = bwcService.getClient();
$log.debug('Importing Wallet Mnemonic'); $log.debug('Importing Wallet Mnemonic');
console.log('[profileService.js.340]', words, opts); //TODO
walletClient.importFromMnemonic(words, { walletClient.importFromMnemonic(words, {
network: opts.networkName,
passphrase: opts.passphrase, passphrase: opts.passphrase,
}, function(err) { }, function(err) {
console.log('[profileService.js.342:err:]',err); //TODO
console.log('[profileService.js.341:walletClient:]',walletClient.credentials.credentials); //TODO
if (err) if (err)
return bwsError.cb(err, gettext('Could not import'), cb); return bwsError.cb(err, gettext('Could not import'), cb);
@ -361,21 +361,6 @@ console.log('[profileService.js.174]'); //TODO
}); });
}; };
root.importWalletMnemonicEx = function(words, opts, cb) {
var walletClient = bwcService.getClient();
$log.debug('Importing Wallet Mnemonic EX', opts);
walletClient.importFromMnemonic(words, opts,
function(err) {
if (err)
return bwsError.cb(err, gettext('Could not import'), cb);
root._addWalletClient(walletClient, cb);
});
};
root.create = function(opts, cb) { root.create = function(opts, cb) {
$log.info('Creating profile'); $log.info('Creating profile');
configService.get(function(err) { configService.get(function(err) {