Skip to content
This repository was archived by the owner on Apr 3, 2019. It is now read-only.
3 changes: 0 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
language: node_js
sudo: false
node_js:
- '0.10'
- '0.12'
- '2.0.0'
- '4'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add v6 to the testing matrix? I don't where this project stands in terms of node versions >= 4

Copy link
Contributor

@braydonf braydonf Dec 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a PR that does this at #79 there are several changes needed in the build to fix this, primarily because of how npm@3 installs dependencies (more flat).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we aren't going to make this PR as a breaking change, it would make sense to keep these versions here.

before_install:
- npm install -g bower
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
**Note**: This is a development/staging branch for 1.0.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed.


Bitcore Library
=======

Expand Down
8 changes: 4 additions & 4 deletions docs/hierarchical.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ var HDPrivateKey = bitcore.HDPrivateKey;

var hdPrivateKey = new HDPrivateKey();
var retrieved = new HDPrivateKey('xpriv...');
var derived = hdPrivateKey.derive("m/0'");
var derivedByNumber = hdPrivateKey.derive(1).derive(2, true);
var derivedByArgument = hdPrivateKey.derive("m/1/2'");
var derived = hdPrivateKey.getChild("m/0'");
var derivedByNumber = hdPrivateKey.getChild(1).getChild(2, true);
var derivedByArgument = hdPrivateKey.getChild("m/1/2'");
assert(derivedByNumber.xprivkey === derivedByArgument.xprivkey);

var address = derived.privateKey.toAddress();
Expand All @@ -39,5 +39,5 @@ try {
}

var address = new Address(hdPublicKey.publicKey, Networks.livenet);
var derivedAddress = new Address(hdPublicKey.derive(100).publicKey, Networks.testnet);
var derivedAddress = new Address(hdPublicKey.getChild(100).publicKey, Networks.testnet);
```
8 changes: 4 additions & 4 deletions lib/hdkeycache.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ module.exports = {
_usedIndex: {},
_CACHE_SIZE: 5000,

get: function(xkey, number, hardened) {
get: function(xkey, number, hardened, nonCompliant) {
hardened = !!hardened;
var key = xkey + '/' + number + '/' + hardened;
var key = (nonCompliant ? '1' : '0') + xkey + '/' + number + '/' + hardened;
if (this._cache[key]) {
this._cacheHit(key);
return this._cache[key];
}
},
set: function(xkey, number, hardened, derived) {
set: function(xkey, number, hardened, derived, nonCompliant) {
hardened = !!hardened;
var key = xkey + '/' + number + '/' + hardened;
var key = (nonCompliant ? '1' : '0') + xkey + '/' + number + '/' + hardened;
this._cache[key] = derived;
this._cacheHit(key);
},
Expand Down
89 changes: 72 additions & 17 deletions lib/hdprivatekey.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,31 @@ HDPrivateKey._getDerivationIndexes = function(path) {
return _.any(indexes, isNaN) ? null : indexes;
};


/**
*
* Get a derived child based on a string or number. This method should not be called directly. Please use: `getChild` or `getNonCompliantChild'.
*
*/
HDPrivateKey.prototype.derive = function(arg, hardened, nonCompliant) {
Copy link
Contributor

@gabegattis gabegattis Dec 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are adding .getNonCompliantChild(), we should leave this method as it was.


// Defaults to the nonCompliant derivation, for backwards compatibility
if (_.isUndefined(nonCompliant))
nonCompliant = true;

if (_.isNumber(arg)) {
return this._deriveWithNumber(arg, hardened, nonCompliant);
} else if (_.isString(arg)) {
return this._deriveFromString(arg, nonCompliant);
} else {
throw new hdErrors.InvalidDerivationArgument(arg);
}
};





/**
* Get a derived child based on a string or number.
*
Expand All @@ -142,25 +167,40 @@ HDPrivateKey._getDerivationIndexes = function(path) {
* @example
* ```javascript
* var parent = new HDPrivateKey('xprv...');
* var child_0_1_2h = parent.derive(0).derive(1).derive(2, true);
* var copy_of_child_0_1_2h = parent.derive("m/0/1/2'");
* var child_0_1_2h = parent.getChild(0).getChild(1).getChild(2, true);
* var copy_of_child_0_1_2h = parent.getChild("m/0/1/2'");
* assert(child_0_1_2h.xprivkey === copy_of_child_0_1_2h);
* ```
*
* @param {string|number} arg
* @param {boolean?} hardened
*
*/
HDPrivateKey.prototype.derive = function(arg, hardened) {
if (_.isNumber(arg)) {
return this._deriveWithNumber(arg, hardened);
} else if (_.isString(arg)) {
return this._deriveFromString(arg);
} else {
throw new hdErrors.InvalidDerivationArgument(arg);
}

HDPrivateKey.prototype.getChild = function(arg, hardened) {
var derived = this.derive(arg, hardened, false);
return derived;
};

/**
* WARNING: If this is a new implementation you should NOT use this method, you should be using
* `getChild` instead.
*
* This method is explicitly for use and compatibility with an implementation that
* was not compliant with BIP32 regarding the derivation algorithm. The private key
* must be 32 bytes hashing, and this implementation will use the non-zero padded
* serialization of a private key, such that it's still possible to derive the privateKey
* to recover those funds.
*
* @param {string|number} arg
* @param {boolean?} hardened
*/
HDPrivateKey.prototype.getNonCompliantChild = function(arg, hardened) {
var derived = this.derive(arg, hardened, true);
return derived;
};

HDPrivateKey.prototype._deriveWithNumber = function(index, hardened) {
HDPrivateKey.prototype._deriveWithNumber = function(index, hardened, nonCompliant) {
/* jshint maxstatements: 20 */
/* jshint maxcomplexity: 10 */
if (!HDPrivateKey.isValidPath(index, hardened)) {
Expand All @@ -172,15 +212,25 @@ HDPrivateKey.prototype._deriveWithNumber = function(index, hardened) {
index += HDPrivateKey.Hardened;
}

var cached = HDKeyCache.get(this.xprivkey, index, hardened);
var cached = HDKeyCache.get(this.xprivkey, index, hardened, nonCompliant);
if (cached) {
return cached;
}

var indexBuffer = BufferUtil.integerAsBuffer(index);
var data;
if (hardened) {
data = BufferUtil.concat([new buffer.Buffer([0]), this.privateKey.toBuffer(), indexBuffer]);
if (hardened && nonCompliant) {
// The private key serialization in this case will not be exactly 32 bytes and can be
// any value less, and the value is not zero-padded.
var nonZeroPadded = this.privateKey.bn.toBuffer();
data = BufferUtil.concat([new buffer.Buffer([0]), nonZeroPadded, indexBuffer]);
} else if (hardened) {
// This will use a 32 byte zero padded serialization of the private key
var privateKeyBuffer = this.privateKey.bn.toBuffer({
size: 32
});
assert(privateKeyBuffer.length === 32, 'length of private key buffer is expected to be 32 bytes');
data = BufferUtil.concat([new buffer.Buffer([0]), privateKeyBuffer, indexBuffer]);
} else {
data = BufferUtil.concat([this.publicKey.toBuffer(), indexBuffer]);
}
Expand All @@ -194,6 +244,11 @@ HDPrivateKey.prototype._deriveWithNumber = function(index, hardened) {
size: 32
});

if (!PrivateKey.isValid(privateKey)) {
// Index at this point is already hardened, we can pass null as the hardened arg
return this._deriveWithNumber(index + 1, null, nonCompliant);
}

var derived = new HDPrivateKey({
network: this.network,
depth: this.depth + 1,
Expand All @@ -202,18 +257,18 @@ HDPrivateKey.prototype._deriveWithNumber = function(index, hardened) {
chainCode: chainCode,
privateKey: privateKey
});
HDKeyCache.set(this.xprivkey, index, hardened, derived);
HDKeyCache.set(this.xprivkey, index, hardened, derived, nonCompliant);
return derived;
};

HDPrivateKey.prototype._deriveFromString = function(path) {
HDPrivateKey.prototype._deriveFromString = function(path, nonCompliant) {
if (!HDPrivateKey.isValidPath(path)) {
throw new hdErrors.InvalidPath(path);
}

var indexes = HDPrivateKey._getDerivationIndexes(path);
var derived = indexes.reduce(function(prev, index) {
return prev._deriveWithNumber(index);
return prev._deriveWithNumber(index, null, nonCompliant);
}, this);

return derived;
Expand Down
33 changes: 24 additions & 9 deletions lib/hdpublickey.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,22 @@ HDPublicKey.isValidPath = function(arg) {
return false;
};


/**
* Internal method to get a derivated child based on a string or number.
*
*/

HDPublicKey.prototype.derive = function(arg, hardened) {
if (_.isNumber(arg)) {
return this._deriveWithNumber(arg, hardened);
} else if (_.isString(arg)) {
return this._deriveFromString(arg);
} else {
throw new hdErrors.InvalidDerivationArgument(arg);
}
};

/**
* Get a derivated child based on a string or number.
*
Expand All @@ -108,14 +124,8 @@ HDPublicKey.isValidPath = function(arg) {
*
* @param {string|number} arg
*/
HDPublicKey.prototype.derive = function(arg, hardened) {
if (_.isNumber(arg)) {
return this._deriveWithNumber(arg, hardened);
} else if (_.isString(arg)) {
return this._deriveFromString(arg);
} else {
throw new hdErrors.InvalidDerivationArgument(arg);
}
HDPublicKey.prototype.getChild = function(arg, hardened) {
return this.derive(arg, hardened);
};

HDPublicKey.prototype._deriveWithNumber = function(index, hardened) {
Expand All @@ -136,7 +146,12 @@ HDPublicKey.prototype._deriveWithNumber = function(index, hardened) {
var leftPart = BN.fromBuffer(hash.slice(0, 32), {size: 32});
var chainCode = hash.slice(32, 64);

var publicKey = PublicKey.fromPoint(Point.getG().mul(leftPart).add(this.publicKey.point));
var publicKey;
try {
publicKey = PublicKey.fromPoint(Point.getG().mul(leftPart).add(this.publicKey.point));
} catch (e) {
return this._deriveWithNumber(index + 1);
}

var derived = new HDPublicKey({
network: this.network,
Expand Down
9 changes: 9 additions & 0 deletions lib/privatekey.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,15 @@ PrivateKey.prototype.toBigNumber = function(){
* @returns {Buffer} A buffer of the private key
*/
PrivateKey.prototype.toBuffer = function(){
return this.bn.toBuffer({ size: 32 });
};

/**
* Will return the private key as a BN buffer without leading zero padding
*
* @returns {Buffer} A buffer of the private key
*/
PrivateKey.prototype.toBufferNoPadding = function() {
return this.bn.toBuffer();
};

Expand Down
34 changes: 17 additions & 17 deletions test/hdkeycache.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,29 @@ describe('HDKey cache', function() {
});

it('saves a derived key', function() {
var child = master.derive(0);
expect(cache._cache[master.xprivkey + '/0/false'].xprivkey).to.equal(child.xprivkey);
var child = master.getChild(0);
expect(cache._cache['0' + master.xprivkey + '/0/false'].xprivkey).to.equal(child.xprivkey);
});
it('starts erasing unused keys', function() {
var child1 = master.derive(0);
var child2 = child1.derive(0);
var child3 = child2.derive(0);
expect(cache._cache[master.xprivkey + '/0/false'].xprivkey).to.equal(child1.xprivkey);
var child4 = child3.derive(0);
expect(cache._cache[master.xprivkey + '/0/false']).to.equal(undefined);
var child1 = master.getChild(0);
var child2 = child1.getChild(0);
var child3 = child2.getChild(0);
expect(cache._cache['0' + master.xprivkey + '/0/false'].xprivkey).to.equal(child1.xprivkey);
var child4 = child3.getChild(0);
expect(cache._cache['0' + master.xprivkey + '/0/false']).to.equal(undefined);
});
it('avoids erasing keys that get cache hits ("hot keys")', function() {
var child1 = master.derive(0);
var child2 = master.derive(0).derive(0);
expect(cache._cache[master.xprivkey + '/0/false'].xprivkey).to.equal(child1.xprivkey);
var child1_copy = master.derive(0);
expect(cache._cache[master.xprivkey + '/0/false'].xprivkey).to.equal(child1.xprivkey);
var child1 = master.getChild(0);
var child2 = master.getChild(0).getChild(0);
expect(cache._cache['0' + master.xprivkey + '/0/false'].xprivkey).to.equal(child1.xprivkey);
var child1_copy = master.getChild(0);
expect(cache._cache['0' + master.xprivkey + '/0/false'].xprivkey).to.equal(child1.xprivkey);
});
it('keeps the size of the cache small', function() {
var child1 = master.derive(0);
var child2 = child1.derive(0);
var child3 = child2.derive(0);
var child4 = child3.derive(0);
var child1 = master.getChild(0);
var child2 = child1.getChild(0);
var child3 = child2.getChild(0);
var child4 = child3.getChild(0);
expect(_.size(cache._cache)).to.equal(3);
});
});
Loading