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

Fix implementation of hd derivation to be bip32 compliant #97

Closed
wants to merge 8 commits into from

Conversation

braydonf
Copy link
Contributor

@braydonf braydonf commented Sep 12, 2016

@axic
Copy link

axic commented Sep 13, 2016

Great work! Can you also include #93?

@braydonf
Copy link
Contributor Author

braydonf commented Sep 13, 2016

There is validation of the private key at: https://github.com/bitpay/bitcore-lib/blob/master/lib/hdprivatekey.js#L416 when the private key is constructed, however I don't see logic for proceeding to the next value for the index.

@braydonf
Copy link
Contributor Author

braydonf commented Sep 13, 2016

Okay, handling of invalid private key derivation is handled. The test that was just added would be broken without the fix, and would give an error message about an invalid private key rather than incrementing the index.

@braydonf
Copy link
Contributor Author

@braydonf
Copy link
Contributor Author

braydonf commented Sep 14, 2016

Just tested the bitcore-lib tests against libbtc here: https://github.com/braydonf/libbtc/tree/cross-ref There seems to be an issue with the fingerprint in the test that still needs to be worked out.
Edit: Fingerprint issue fixed it was using the parent fingerprint in the test

@danfinlay
Copy link

When can we look forward to having this merged & pushed to npm? There are a lot of broken HD wallets out there right now, would be nice to get people using the same algorithms.

@gabegattis
Copy link
Contributor

gabegattis commented Oct 10, 2016

@FlySwatter Hopefully, we can get our 1.0.0 release (which should include this fix) sometime in the next couple of weeks.

I cannot give a certain date though.

This is to avoid any accidental upgrades to a bugfixed version without awareness of the change.
@braydonf
Copy link
Contributor Author

braydonf commented Dec 6, 2016

This PR has been updated from feedback in #115

@matiu
Copy link
Contributor

matiu commented Dec 6, 2016

ACK

@gabegattis
Copy link
Contributor

The plan was to leave .derive() the way it is for the time being.

The 2 new methods would be be for getting a compliant or non-compliant child. We don't want to make .derive() throw util we make breaking changes in v1.0.0.

Also something I mentioned internally, but not on GitHub, I think we should remove the hdkey cache now. We know we want to remove it in v1.0.0. This PR requires changes to the cache code. We can avoid that by just removing the cache. Removing the cache would not be a breaking change. It is not documented and, where it is exposed in index.js, we comment that it is only exposed for internal usage and testing.

@gabegattis
Copy link
Contributor

I opened a new PR that reflects my comment above.
#116

@braydonf
Copy link
Contributor Author

braydonf commented Dec 7, 2016

Removing hdkeycache can be it's own atomic change and is not related to fixing BIP32 derivation.

hardened = !!hardened;
var key = xkey + '/' + number + '/' + hardened;
var key = (nonCompliant ? '1' : '0') + xkey + '/' + number + '/' + hardened;
Copy link

Choose a reason for hiding this comment

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

There should a comment in the code here. Nobody will get what and why this is done.

hardened = !!hardened;
var key = xkey + '/' + number + '/' + hardened;
var key = (nonCompliant ? '1' : '0') + xkey + '/' + number + '/' + hardened;
Copy link

Choose a reason for hiding this comment

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

Same as above. This should be commented. See also http://butunclebob.com/ArticleS.TimOttinger.ApologizeIncode

@@ -127,6 +127,11 @@ HDPrivateKey._getDerivationIndexes = function(path) {
return _.any(indexes, isNaN) ? null : indexes;
};

HDPrivateKey.prototype.derive = function() {
throw new Error('Method has been deprecated, please use deriveChild or deriveNonCompliantChild' +
'and see documentation for more information');
Copy link

Choose a reason for hiding this comment

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

Is this really what one would expect? Why not the remove the function altogether? Also makes the code better readible in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is to make sure that if someone upgrades to 1.0.0, that they will not mistakenly be unaware of the bug that affected the deprecated function.

Copy link

Choose a reason for hiding this comment

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

I unterstand that. So just completely remove the method. This will also throw an error of someone wants to use it, but then it does not show in IDE autocomplete and so on. Just think how annoying this method will be in the future.

*/
HDPrivateKey.prototype.derive = function(arg, hardened) {
HDPrivateKey.prototype.deriveChild = function(arg, hardened, nonCompliant) {
Copy link

Choose a reason for hiding this comment

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

I think "deriveNonComplientChild" and "deriveChild" should both use a function "_derive". In the future everyone will use the function "deriveChild" and they will forever wonder why there is a parameter "nonCompliant". This should not be there. So this should be

HDPrivateKey.prototype.deriveChild = function(arg, hardened) {
  return this._derive(arg, hardened)
}

and in deriveNonCompliantChild

HDPrivateKey.prototype.deriveNonCompliantChild = function(arg, hardened) {
   return this._derive(arg, hardened, true);
 };

Copy link

Choose a reason for hiding this comment

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

This is the most important remark of my review.

Copy link
Contributor

Choose a reason for hiding this comment

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

Does the implementation in #116 satisfy some of your comments? It was based off of this PR, but with some slight changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll take a look at updating, though I agree, nonCompliant likely shouldn't be hanging around on public API method.

* 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.
Copy link

Choose a reason for hiding this comment

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

As much as I like this comment, it is easy to overlook it. One should log a warning when someone is using this function too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I feel like the method name deriveNonCompliantChild is warning enough.

@@ -172,15 +200,23 @@ 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);
Copy link

Choose a reason for hiding this comment

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

Same here. Do we really want to drag this "nonCompliant" parameter through the ages from now on?

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) {
Copy link

Choose a reason for hiding this comment

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

What if it is hardened = false && nonCompliant = true?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The nonCompliant derivation (bug) only affects hardened derivations.

test/hdkeys.js Outdated
});

it('should NOT use full 32 bytes for private key data that is hashed with nonCompliant flag', function() {
// This is to test that the previously implemented non-compliant to BIP32
Copy link

Choose a reason for hiding this comment

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

This is not a proper sentence...

afterEach(function() {
sandbox.restore();
});
it('will handle edge case that derived private key is invalid', function() {
Copy link

Choose a reason for hiding this comment

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

I do not understand what this test is about. Is it feeding in data that triggered the bug and now we have test that the bug is fixed? Maybe say so in the comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not related to the zero padding bug, it has to do with behavior if the derived private key is invalid, which is nearly impossible.

var bn = BN.fromBuffer(new Buffer('9b5a0e8fee1835e21170ce1431f9b6f19b487e67748ed70d8a4462bc031915', 'hex'));
var privkey = new PrivateKey(bn);
var buffer = privkey.toBufferNoPadding();
buffer.length.should.equal(31);
Copy link

Choose a reason for hiding this comment

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

Is this something that one wants? What is the usecase in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the same reason that deriveNonCompliant exists. If there is hashing of privateKey.toBuffer(), the padding will make a difference, and thus there is still a method for it.

@matiu
Copy link
Contributor

matiu commented Feb 28, 2017

ACK. Lets merge this and move forward.

@isocolsky
Copy link
Contributor

ACK

@braydonf
Copy link
Contributor Author

braydonf commented Mar 1, 2017

@matiu @isocolsky Sounds good!

Copy link

@levino levino left a comment

Choose a reason for hiding this comment

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

Yes, much better. Thanks!

@mpolci
Copy link
Contributor

mpolci commented Aug 12, 2017

Why this PR is not yet merged?

@matiu
Copy link
Contributor

matiu commented Aug 12, 2017

This one was replaced by #116

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants