Browse Source

add access levels to import / export

activeAddress
Matias Alejo Garcia 10 years ago
parent
commit
e12b6b8158
  1. 48
      bit-wallet/bit-export
  2. 2
      bit-wallet/bit-import
  3. 55
      lib/client/api.js
  4. 10
      lib/walletutils.js
  5. 75
      test/integration/clientApi.js

48
bit-wallet/bit-export

@ -1,27 +1,57 @@
#!/usr/bin/env node
var program = require('commander');
var qr = require('qr-image');
var qr = require('qr-image');
var fs = require('fs');
var _ = require('lodash');
var Client = require('../lib/client');
var utils = require('./cli-utils');
var utils = require('./cli-utils');
program = utils.configureCommander(program);
program
.option('-q, --qr')
.option('-a, --access [level]', 'access privileges for exported data (full, readwrite, readonly)', 'full')
.option('-q, --qr', 'export a QR code')
.option('-o, --output [filename]', 'output file');
program.on('--help', function() {
console.log(' Access Levels:');
console.log('');
console.log(' readonly : allows to read wallet data: balance, tx proposals ');
console.log(' readwrite: + allows to create addresses and unsigned tx prposals ');
console.log(' full : + allows sign tx prposals ');
console.log('');
});
program
.parse(process.argv);
var args = program.args;
var client = utils.getClient(program);
client.export(function(err, x) {
if (!_.contains(['full', 'readwrite', 'readonly'], program.access)) {
program.help();
}
var msg = ' Access Level: ' + program.access;
client.export({
access: program.access
}, function(err, x) {
utils.die(err);
if (program.qr) {
var filename = program.config + '.svg';
var qr_svg = qr.image(x, { type: 'svg' });
qr_svg.pipe(require('fs').createWriteStream(filename));
console.log('Wallet Critical Data: exported to ' + filename);
var filename = program.file + '.svg';
var qr_svg = qr.image(x, {
type: 'svg'
});
qr_svg.pipe(fs.createWriteStream(filename));
console.log('Wallet Critical Data: exported to %s. %s\n',filename, msg);
} else {
console.log('Wallet Critical Data:\n', x);
if (program.output) {
fs.writeFileSync(program.output, x);
console.log('Wallet Critical Data saved at %s. %s\n', program.output, msg);
} else {
console.log('Wallet Critical Data (%s)\n%s', msg, x);
}
}
});

2
bit-wallet/bit-import

@ -22,5 +22,5 @@ var str = fs.readFileSync(args[0]);
client.import(str, function(err, x) {
utils.die(err);
console.log('Wallet Imported');
console.log('Wallet Imported. Access level:' + x);
});

55
lib/client/api.js

@ -16,6 +16,7 @@ var ServerCompromisedError = require('./servercompromisederror')
var BASE_URL = 'http://localhost:3001/copay/api';
var WALLET_CRITICAL_DATA = ['xPrivKey', 'm', 'publicKeyRing', 'sharedEncryptingKey'];
var WALLET_EXTRA_DATA = ['copayerId', 'roPrivKey', 'rwPrivKey'];
function _encryptMessage(message, encryptingKey) {
if (!message) return null;
@ -398,8 +399,16 @@ API.prototype.getBalance = function(cb) {
});
};
API.prototype.export = function(cb) {
/**
* export
*
* @param opts.access =['full', 'readonly', 'readwrite']
*/
API.prototype.export = function(opts, cb) {
var self = this;
$.shouldBeFunction(cb);
opts = opts || {};
var access = opts.access || 'full';
this._loadAndCheck(function(err, data) {
if (err) return cb(err);
@ -409,13 +418,29 @@ API.prototype.export = function(cb) {
_.each(WALLET_CRITICAL_DATA, function(k) {
var d;
if (k === 'publicKeyRing') {
if (access != 'full' && k === 'xPrivKey') {
v.push(null);
return;
}
// Skips own pub key IF priv key is exported
if (access == 'full' && k === 'publicKeyRing') {
d = _.without(data[k], myXPubKey);
} else {
d = data[k];
}
v.push(d);
});
if (access != 'full') {
v.push(data.copayerId);
v.push(data.roPrivKey);
if (access == 'readwrite') {
v.push(data.rwPrivKey);
}
}
return cb(null, JSON.stringify(v));
});
}
@ -433,30 +458,28 @@ API.prototype.import = function(str, cb) {
var inData = JSON.parse(str);
var i = 0;
_.each(WALLET_CRITICAL_DATA, function(k) {
_.each(WALLET_CRITICAL_DATA.concat(WALLET_EXTRA_DATA), function(k) {
data[k] = inData[i++];
if (!data[k])
return cb('Invalid wallet data');
});
if (data.xPrivKey) {
var xPubKey = (new Bitcore.HDPublicKey(data.xPrivKey)).toString();
var xpriv = new Bitcore.HDPrivateKey(data.xPrivKey);
var xPubKey = new Bitcore.HDPublicKey(xpriv).toString();
data.publicKeyRing.unshift(xPubKey);
data.copayerId = WalletUtils.xPubToCopayerId(xPubKey);
} else {
data.copayerId = inData[i];
data.roPrivKey = xpriv.derive('m/1/0').privateKey.toWIF();
data.rwPrivKey = xpriv.derive('m/1/1').privateKey.toWIF();
}
if (!data.copayerId)
return cb('Invalid source data');
data.n = data.publicKeyRing.length;
var xpriv = data.xPrivKey ? new Bitcore.HDPrivateKey(data.xPrivKey) : null;
data.roPrivKey = inData.roPrivKey || (xpriv ? xpriv.derive('m/1/0').privateKey.toWIF() : null);
data.rwPrivKey = inData.rwPrivKey || (xpriv ? xpriv.derive('m/1/1').privateKey.toWIF() : null);
data.network = data.xPrivKey.substr(0, 4) === 'tprv' ? 'testnet' : 'livenet';
self.storage.save(data, cb);
if (!data.copayerId || !data.n || !data.m)
return cb('Invalid source data');
data.network = data.publicKeyRing[0].substr(0, 4) == 'tpub' ? 'testnet' : 'livenet';
self.storage.save(data, function(err) {
return cb(err, WalletUtils.accessFromData(data));
});
});
};

10
lib/walletutils.js

@ -31,6 +31,16 @@ WalletUtils.signMessage = function(text, privKey) {
};
WalletUtils.accessFromData = function(data) {
if (data.xPrivKey)
return 'full';
if (data.rwPrivKey)
return 'readwrite';
return 'readonly';
};
WalletUtils.verifyMessage = function(text, signature, pubKey) {
$.checkArgument(text);
$.checkArgument(pubKey);

75
test/integration/clientApi.js

@ -277,7 +277,7 @@ describe('client API ', function() {
});
});
describe('Access control', function() {
describe('Access control & export', function() {
it('should not be able to create address if not rwPubKey', function(done) {
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
should.not.exist(err);
@ -292,8 +292,77 @@ describe('client API ', function() {
});
});
});
});
it('should not be able to create address from a ro export', function(done) {
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
should.not.exist(err);
clients[0].export({
access: 'readonly'
}, function(err, str) {
should.not.exist(err);
clients[1].import(str, function(err, wallet) {
should.not.exist(err);
clients[1].createAddress(function(err, x0) {
err.code.should.equal('NOTAUTHORIZED');
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
done();
});
});
});
});
});
});
it('should be able to create address from a rw export', function(done) {
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
should.not.exist(err);
clients[0].export({
access: 'readwrite'
}, function(err, str) {
should.not.exist(err);
clients[1].import(str, function(err, wallet) {
should.not.exist(err);
clients[1].createAddress(function(err, x0) {
should.not.exist(err);
done();
});
});
});
});
});
it('should not be able to create tx proposals from a rw export', function(done) {
helpers.createAndJoinWallet(clients, 1, 1, function(err, w) {
should.not.exist(err);
clients[0].export({
access: 'readwrite'
}, function(err, str) {
clients[1].import(str, function(err, wallet) {
should.not.exist(err);
clients[1].createAddress(function(err, x0) {
should.not.exist(err);
blockExplorerMock.setUtxo(x0, 1, 1);
var opts = {
amount: 10000000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello 1-1',
};
clients[1].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
clients[1].signTxProposal(x, function(err, tx) {
err.code.should.be.equal('BADSIGNATURES');
clients[1].getTxProposals({}, function(err, txs) {
should.not.exist(err);
txs[0].status.should.equal('pending');
done();
});
});
});
});
});
});
});
});
});
describe('Address Creation', function() {
it('should be able to create address in all copayers in a 2-3 wallet', function(done) {
@ -395,7 +464,7 @@ describe('client API ', function() {
it('round trip #import #export', function(done) {
helpers.createAndJoinWallet(clients, 2, 2, function(err, w) {
should.not.exist(err);
clients[0].export(function(err, str) {
clients[0].export({}, function(err, str) {
should.not.exist(err);
var original = JSON.parse(fsmock._get('client0'));
clients[2].import(str, function(err, wallet) {

Loading…
Cancel
Save