Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

Commit

Permalink
Merge pull request #124 from matiu/feat/importFromExtendedPublicKey
Browse files Browse the repository at this point in the history
adds importFromExtendedPublicKey
  • Loading branch information
matiu committed Sep 4, 2015
2 parents 99a722e + 9fb146f commit 2b7a59e
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 30 deletions.
56 changes: 41 additions & 15 deletions bitcore-wallet-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ function API(opts) {
this.doNotVerifyPayPro = opts.doNotVerifyPayPro;

this.transports = opts.transports || ['polling', 'websocket'];
this.timeout = opts.timeout || 50000;


if (this.verbose) {
Expand Down Expand Up @@ -290,8 +291,8 @@ API.prototype.seedFromMnemonic = function(words, passphrase, network) {
* @param {Number} index - index of the external key
* @param {String} entropySourceHex - an HEX string containing random data, that can be reproducible by the device. NOTE: It should not be possible to derive entropySourceHex from xPubkey.
*/
API.prototype.seedFromExternalWalletPublicKey = function(xPubKey, source, index, entropySourceHex) {
this.credentials = Credentials.fromExternalWalletPublicKey(xPubKey, source, index, entropySourceHex);
API.prototype.seedFromExtendedPublicKey = function(xPubKey, source, index, entropySourceHex) {
this.credentials = Credentials.fromExtendedPublicKey(xPubKey, source, index, entropySourceHex);
}


Expand Down Expand Up @@ -360,8 +361,8 @@ API.prototype._import = function(cb) {
// it worked?
if (!err) return cb(null, ret);

// Is the error other than "copayer was not found"?
if (err.code != 'NOT_AUTHORIZED')
// Is the error other than "copayer was not found"? || or no priv key.
if (err.code != 'NOT_AUTHORIZED' || self.isPrivKeyExternal())
return cb(err);

//Second option, lets try to add an access
Expand Down Expand Up @@ -402,6 +403,20 @@ API.prototype.importFromExtendedPrivateKey = function(xPrivKey, cb) {
this._import(cb);
};


API.prototype.importFromExtendedPublicKey = function(xPubKey, source, index, entropySourceHex, cb) {
log.debug('Importing from Extended Private Key');
try {
this.credentials = Credentials.fromExtendedPublicKey(xPubKey, source, index, entropySourceHex);
} catch (e) {
log.info('xPriv error:', e);
return cb(Errors.INVALID_BACKUP);
};

this._import(cb);
};


/**
* Open a wallet and try to complete the public key ring.
*
Expand Down Expand Up @@ -484,7 +499,7 @@ API.prototype._doRequest = function(method, url, args, cb) {
body: args,
json: true,
withCredentials: false,
timeout: 10000
timeout: this.timeout,
};

log.debug('Request Args', util.inspect(args, {
Expand Down Expand Up @@ -1732,23 +1747,35 @@ Credentials.fromMnemonic = function(words, passphrase, network) {
* For external sources, this derivation should be done before
* call fromExternalWalletPublicKey
*
* entropySource should be a HEX string containing random data, that can
* be reproducible from
* entropySource should be a HEX string containing pseudorandom data, that can
* be deterministic derived from the xPrivKey, and should not be derived from xPubKey
*
*/
Credentials.fromExternalWalletPublicKey = function(xPubKey, source, index, entropySourceHex) {
Credentials.fromExtendedPublicKey = function(xPubKey, source, index, entropySourceHex) {
$.checkArgument(entropySourceHex);

var entropyBuffer = new Buffer(entropySourceHex, 'hex');
//require at least 112 bits of entropy
$.checkArgument(entropyBuffer.length >= 14, 'No enough entropy')

var x = new Credentials();
x.xPubKey = xPubKey;
x.entropySource = Bitcore.crypto.Hash.ripemd160(Bitcore.crypto.Hash.sha256(new Buffer(entropySourceHex, 'hex'))).toString('hex');
x.entropySource = Bitcore.crypto.Hash.sha256sha256(entropyBuffer).toString('hex');

x.externalSource = source;
x.externalIndex = index;
x._expand();
return x;
};

Credentials.prototype._hashFromEntropy = function(prefix, length) {
$.checkState(prefix);
var b = new Buffer(this.entropySource, 'hex');
var b2 = Bitcore.crypto.Hash.sha256hmac(b, new Buffer(prefix));
return b2.slice(0, length);
};


Credentials.prototype._expand = function() {
$.checkState(this.xPrivKey || (this.xPubKey && this.entropySource));

Expand All @@ -1765,16 +1792,15 @@ Credentials.prototype._expand = function() {
var pubKey = requestDerivation.publicKey;
this.requestPubKey = pubKey.toString();

this.entropySource = Bitcore.crypto.Hash.ripemd160(Bitcore.crypto.Hash.sha256(requestDerivation.privateKey.toBuffer())).toString('hex');
this.entropySource = Bitcore.crypto.Hash.sha256(requestDerivation.privateKey.toBuffer()).toString('hex');
} else {
// Random priv key
var privKey = new Bitcore.PrivateKey(network);
var seed = this._hashFromEntropy('reqPrivKey', 32);
var privKey = new Bitcore.PrivateKey(seed.toString('hex'), network);
this.requestPrivKey = privKey.toString();
this.requestPubKey = privKey.toPublicKey().toString();
}

this.personalEncryptingKey = Bitcore.crypto.Hash.sha256(new Buffer(this.entropySource, 'hex')).slice(0, 16).toString('base64');

this.personalEncryptingKey = this._hashFromEntropy('personalKey', 16).toString('base64');

var network = WalletUtils.getNetworkFromXPubKey(this.xPubKey);
if (this.network) {
Expand Down Expand Up @@ -114650,7 +114676,7 @@ module.exports={
"name": "bitcore-wallet-client",
"description": "Client for bitcore-wallet-service",
"author": "BitPay Inc",
"version": "0.2.0",
"version": "0.2.2",
"keywords": [
"bitcoin",
"copay",
Expand Down
6 changes: 3 additions & 3 deletions bitcore-wallet-client.min.js

Large diffs are not rendered by default.

18 changes: 16 additions & 2 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,8 @@ API.prototype._import = function(cb) {
// it worked?
if (!err) return cb(null, ret);

// Is the error other than "copayer was not found"?
if (err.code != 'NOT_AUTHORIZED')
// Is the error other than "copayer was not found"? || or no priv key.
if (err.code != 'NOT_AUTHORIZED' || self.isPrivKeyExternal())
return cb(err);

//Second option, lets try to add an access
Expand Down Expand Up @@ -397,6 +397,20 @@ API.prototype.importFromExtendedPrivateKey = function(xPrivKey, cb) {
this._import(cb);
};


API.prototype.importFromExtendedPublicKey = function(xPubKey, source, index, entropySourceHex, cb) {
log.debug('Importing from Extended Private Key');
try {
this.credentials = Credentials.fromExtendedPublicKey(xPubKey, source, index, entropySourceHex);
} catch (e) {
log.info('xPriv error:', e);
return cb(Errors.INVALID_BACKUP);
};

this._import(cb);
};


/**
* Open a wallet and try to complete the public key ring.
*
Expand Down
2 changes: 1 addition & 1 deletion lib/credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Credentials.fromExtendedPublicKey = function(xPubKey, source, index, entropySour

var entropyBuffer = new Buffer(entropySourceHex, 'hex');
//require at least 112 bits of entropy
$.checkArgument(entropyBuffer.length >= 14, 'No enough entropy')
$.checkArgument(entropyBuffer.length >= 14, 'At least 112 bits of entropy are needed')

var x = new Credentials();
x.xPubKey = xPubKey;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "bitcore-wallet-client",
"description": "Client for bitcore-wallet-service",
"author": "BitPay Inc",
"version": "0.2.1",
"version": "0.2.2",
"keywords": [
"bitcoin",
"copay",
Expand Down
43 changes: 35 additions & 8 deletions test/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -539,8 +539,8 @@ describe('client API', function() {

var wkey = clients[0].credentials.walletPrivKey;
var skey = clients[0].credentials.sharedEncryptingKey;
delete clients[0].credentials.walletPrivKey;
delete clients[0].credentials.sharedEncryptingKey;
delete clients[0].credentials.walletPrivKey;
delete clients[0].credentials.sharedEncryptingKey;
should.not.exist(err);
clients[0].getStatus({
includeExtendedInfo: true
Expand Down Expand Up @@ -584,7 +584,7 @@ describe('client API', function() {


it('should create a 1-1 wallet with given mnemonic', function(done) {
var words= 'forget announce travel fury farm alpha chaos choice talent sting eagle supreme';
var words = 'forget announce travel fury farm alpha chaos choice talent sting eagle supreme';
clients[0].seedFromMnemonic(words);
clients[0].createWallet('wallet name', 'creator', 1, 1, {
network: 'livenet'
Expand All @@ -602,7 +602,7 @@ describe('client API', function() {


it('should create a 2-3 wallet with given mnemonic', function(done) {
var words= 'forget announce travel fury farm alpha chaos choice talent sting eagle supreme';
var words = 'forget announce travel fury farm alpha chaos choice talent sting eagle supreme';
clients[0].seedFromMnemonic(words);
clients[0].createWallet('wallet name', 'creator', 2, 3, {
network: 'livenet'
Expand All @@ -619,7 +619,7 @@ describe('client API', function() {
});
});


});

describe('Network fees', function() {
Expand Down Expand Up @@ -1972,7 +1972,6 @@ describe('client API', function() {
done();
});
});

});

describe('Mnemonic related tests', function() {
Expand Down Expand Up @@ -2003,7 +2002,35 @@ describe('client API', function() {
done();
});
});

it('should import with external priv key', function(done) {
var client = helpers.newClient(app);
client.seedFromExtendedPublicKey('xpub661MyMwAqRbcGVyYUcHbZi9KNhN9Tdj8qHi9ZdoUXP1VeKiXDGGrE9tSoJKYhGFE2rimteYdwvoP6e87zS5LsgcEvsvdrpPBEmeWz9EeAUq', 'ledger', 2, '1a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f00');
client.createWallet('wallet name', 'creator', 1, 1, {
network: 'livenet'
}, function(err) {
should.not.exist(err);
var c = client.credentials;
importedClient = helpers.newClient(app);
importedClient.importFromExtendedPublicKey('xpub661MyMwAqRbcGVyYUcHbZi9KNhN9Tdj8qHi9ZdoUXP1VeKiXDGGrE9tSoJKYhGFE2rimteYdwvoP6e87zS5LsgcEvsvdrpPBEmeWz9EeAUq', 'ledger', 2, '1a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f001a1f00', function(err) {
should.not.exist(err);
var c2 = importedClient.credentials;
c2.xPubKey.should.equal(client.credentials.xPubKey);
c2.personalEncryptingKey.should.equal(c.personalEncryptingKey);
c2.walletId.should.equal(c.walletId);
c2.walletName.should.equal(c.walletName);
c2.copayerName.should.equal(c.copayerName);
done();
});
});
});
it('should fail to import with external priv key when not enought entropy', function() {
var client = helpers.newClient(app);
(function() {
client.seedFromExtendedPublicKey('xpub661MyMwAqRbcGVyYUcHbZi9KNhN9Tdj8qHi9ZdoUXP1VeKiXDGGrE9tSoJKYhGFE2rimteYdwvoP6e87zS5LsgcEvsvdrpPBEmeWz9EeAUq', 'ledger', 2, '1a1f00');
}).should.throw('entropy');
});


});

describe('Recovery', function() {
Expand Down Expand Up @@ -2064,7 +2091,7 @@ describe('client API', function() {
});
});
});

it('should be able to recreate wallet 2-2', function(done) {
helpers.createAndJoinWallet(clients, 2, 2, function() {
clients[0].createAddress(function(err, addr) {
Expand Down

0 comments on commit 2b7a59e

Please sign in to comment.