import & export with compress/encrypt

This commit is contained in:
Ivan Socolsky 2015-03-01 14:05:06 -03:00
parent 18884f3c0f
commit a0019d966c
3 changed files with 146 additions and 66 deletions

View File

@ -9,6 +9,7 @@ var request = require('request')
var events = require('events');
log.debug = log.verbose;
var Bitcore = require('bitcore')
var sjcl = require('sjcl');
var Credentials = require('./credentials');
var WalletUtils = require('../walletutils');
@ -17,6 +18,9 @@ var ServerCompromisedError = require('./servercompromisederror');
var ClientError = require('../clienterror');
var BASE_URL = 'http://localhost:3001/copay/api';
var WALLET_ENCRYPTION_OPTS = {
iter: 5000
};
function _encryptMessage(message, encryptingKey) {
if (!message) return null;
@ -95,6 +99,73 @@ API.prototype.seedFromAirGapped = function(seed) {
this.credentials = Credentials.fromExtendedPublicKey(seed.xPubKey, seed.requestPrivKey);
};
/**
* export
*
* @param opts
* @param opts.compressed
* @param opts.password
*/
API.prototype.export = function(opts) {
$.checkState(this.credentials);
opts = opts || {};
var output;
if (opts.compressed) {
output = this.credentials.exportCompressed();
} else {
output = JSON.stringify(this.credentials.toObj());
}
if (opts.password) {
output = sjcl.encrypt(opts.password, output, WALLET_ENCRYPTION_OPTS);
}
return output;
}
/**
* export
*
* @param opts
* @param opts.compressed
* @param opts.password
*/
API.prototype.import = function(str, opts) {
opts = opts || {};
var input = str;
if (opts.password) {
try {
input = sjcl.decrypt(opts.password, input);
} catch (ex) {
throw new Error('Incorrect password');
}
}
try {
if (opts.compressed) {
this.credentials = Credentials.importCompressed(input);
// TODO: complete missing fields that live on the server only such as: walletId, walletName, copayerName
} else {
this.credentials = Credentials.fromObj(JSON.parse(input));
}
} catch (ex) {
throw new Error('Error importing from source');
}
};
API.prototype.toString = function(password) {
$.checkState(this.credentials);
return this.credentials.toObject();
};
API.prototype.fromString = function(str) {
this.credentials = Credentials.fromObject(str);
};
API.prototype._doRequest = function(method, url, args, cb) {
$.checkState(this.credentials);
@ -340,19 +411,6 @@ API.prototype.getBalance = function(cb) {
self._doGetRequest('/v1/balance/', cb);
};
/**
* Exports the wallet as it is now.
*/
API.prototype.export = function() {
$.checkState(this.credentials);
return this.credentials.exportCompressed();
}
API.prototype.import = function(str) {
this.credentials = Credentials.importCompressed(str);
};
/**
*

View File

@ -133,40 +133,6 @@ WalletUtils.privateKeyToAESKey = function(privKey) {
return Bitcore.crypto.Hash.sha256(pk.toBuffer()).slice(0, 16).toString('base64');
};
WalletUtils.decryptWallet = function(data, password) {
$.checkArgument(data.enc);
var extraFields = JSON.parse(sjcl.decrypt(password, data.enc));
delete data.enc;
return _.extend(data, extraFields);
};
WalletUtils.sjclOpts = {
iter: 5000,
};
WalletUtils.encryptWallet = function(data, accessWithoutEncrytion, password) {
// Fields to encrypt, given the NOPASSWD access level
var fieldsEncryptByLevel = {
none: _.keys(data),
readonly: ['xPrivKey', 'requestPrivKey', 'publicKeyRing'],
readwrite: ['xPrivKey', ],
full: [],
};
var fieldsEncrypt = fieldsEncryptByLevel[accessWithoutEncrytion];
$.checkState(!_.isUndefined(fieldsEncrypt));
var toEncrypt = _.pick(data, fieldsEncrypt);
var enc = sjcl.encrypt(password, JSON.stringify(toEncrypt), WalletUtils.sjclOpts);
var ret = _.omit(data, fieldsEncrypt);
ret.enc = enc;
return ret;
};
WalletUtils.signTxp = function(txp, xPrivKey) {
var self = this;

View File

@ -865,29 +865,85 @@ describe('client API ', function() {
});
describe('Export & Import', function() {
it('should export & import', function(done) {
var address, importedClient;
beforeEach(function(done) {
importedClient = null;
helpers.createAndJoinWallet(clients, 1, 1, function() {
clients[0].createAddress(function(err, address) {
clients[0].createAddress(function(err, addr) {
should.not.exist(err);
should.exist(address.address);
var exported = clients[0].export();
var importedClient = new Client({
request: helpers.getRequest(app),
});
importedClient.import(exported);
importedClient.getMainAddresses({}, function(err, list) {
should.not.exist(err);
should.exist(list);
list.length.should.equal(1);
list[0].address.should.equal(address.address);
done();
});
should.exist(addr.address);
address = addr.address;
done();
});
})
});
});
afterEach(function(done) {
importedClient.getMainAddresses({}, function(err, list) {
should.not.exist(err);
should.exist(list);
list.length.should.equal(1);
list[0].address.should.equal(address);
done();
});
});
it('should export & import', function() {
var exported = clients[0].export();
importedClient = new Client({
request: helpers.getRequest(app),
});
importedClient.import(exported);
});
it.skip('should export & import compressed', function() {
var walletId = clients[0].credentials.walletId;
var walletName = clients[0].credentials.walletName;
var copayerName = clients[0].credentials.copayerName;
var exported = clients[0].export({
compressed: true
});
importedClient = new Client({
request: helpers.getRequest(app),
});
importedClient.import(exported, {
compressed: true
});
importedClient.credentials.walletId.should.equal(walletId);
importedClient.credentials.walletName.should.equal(walletName);
importedClient.credentials.copayerName.should.equal(copayerName);
});
it('should export & import encrypted', function() {
var exported = clients[0].export({
password: '123'
});
importedClient = new Client({
request: helpers.getRequest(app),
});
importedClient.import(exported, {
password: '123'
});
});
it('should export & import compressed & encrypted', function() {
var exported = clients[0].export({
compressed: true,
password: '123'
});
importedClient = new Client({
request: helpers.getRequest(app),
});
importedClient.import(exported, {
compressed: true,
password: '123'
});
});
it.skip('should fail to export compressed & import uncompressed', function() {});
it.skip('should fail to export uncompressed & import compressed', function() {});
it.skip('should fail to export unencrypted & import with password', function() {});
it.skip('should fail to export encrypted & import with incorrect password', function() {});
});
describe('Air gapped related flows', function() {