import & export with compress/encrypt
This commit is contained in:
parent
18884f3c0f
commit
a0019d966c
|
@ -9,6 +9,7 @@ var request = require('request')
|
||||||
var events = require('events');
|
var events = require('events');
|
||||||
log.debug = log.verbose;
|
log.debug = log.verbose;
|
||||||
var Bitcore = require('bitcore')
|
var Bitcore = require('bitcore')
|
||||||
|
var sjcl = require('sjcl');
|
||||||
|
|
||||||
var Credentials = require('./credentials');
|
var Credentials = require('./credentials');
|
||||||
var WalletUtils = require('../walletutils');
|
var WalletUtils = require('../walletutils');
|
||||||
|
@ -17,6 +18,9 @@ var ServerCompromisedError = require('./servercompromisederror');
|
||||||
var ClientError = require('../clienterror');
|
var ClientError = require('../clienterror');
|
||||||
|
|
||||||
var BASE_URL = 'http://localhost:3001/copay/api';
|
var BASE_URL = 'http://localhost:3001/copay/api';
|
||||||
|
var WALLET_ENCRYPTION_OPTS = {
|
||||||
|
iter: 5000
|
||||||
|
};
|
||||||
|
|
||||||
function _encryptMessage(message, encryptingKey) {
|
function _encryptMessage(message, encryptingKey) {
|
||||||
if (!message) return null;
|
if (!message) return null;
|
||||||
|
@ -95,6 +99,73 @@ API.prototype.seedFromAirGapped = function(seed) {
|
||||||
this.credentials = Credentials.fromExtendedPublicKey(seed.xPubKey, seed.requestPrivKey);
|
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) {
|
API.prototype._doRequest = function(method, url, args, cb) {
|
||||||
$.checkState(this.credentials);
|
$.checkState(this.credentials);
|
||||||
|
|
||||||
|
@ -340,19 +411,6 @@ API.prototype.getBalance = function(cb) {
|
||||||
self._doGetRequest('/v1/balance/', 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);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
|
@ -133,40 +133,6 @@ WalletUtils.privateKeyToAESKey = function(privKey) {
|
||||||
return Bitcore.crypto.Hash.sha256(pk.toBuffer()).slice(0, 16).toString('base64');
|
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) {
|
WalletUtils.signTxp = function(txp, xPrivKey) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
|
@ -865,29 +865,85 @@ describe('client API ', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Export & Import', 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() {
|
helpers.createAndJoinWallet(clients, 1, 1, function() {
|
||||||
clients[0].createAddress(function(err, address) {
|
clients[0].createAddress(function(err, addr) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
should.exist(address.address);
|
should.exist(addr.address);
|
||||||
|
address = addr.address;
|
||||||
var exported = clients[0].export();
|
done();
|
||||||
|
|
||||||
var importedClient = new Client({
|
|
||||||
request: helpers.getRequest(app),
|
|
||||||
});
|
});
|
||||||
importedClient.import(exported);
|
});
|
||||||
|
});
|
||||||
|
afterEach(function(done) {
|
||||||
importedClient.getMainAddresses({}, function(err, list) {
|
importedClient.getMainAddresses({}, function(err, list) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
should.exist(list);
|
should.exist(list);
|
||||||
list.length.should.equal(1);
|
list.length.should.equal(1);
|
||||||
list[0].address.should.equal(address.address);
|
list[0].address.should.equal(address);
|
||||||
done();
|
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() {
|
describe('Air gapped related flows', function() {
|
||||||
|
|
Loading…
Reference in New Issue