-
Notifications
You must be signed in to change notification settings - Fork 1k
Fix implementation of hd derivation to be bip32 compliant #97
Conversation
Great work! Can you also include #93? |
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. |
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. |
There may also be this case: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#public-parent-key--public-child-key |
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. |
1291b99
to
e9d1237
Compare
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. |
@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.
This PR has been updated from feedback in #115 |
ACK |
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. |
I opened a new PR that reflects my comment above. |
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
lib/hdprivatekey.js
Outdated
*/ | ||
HDPrivateKey.prototype.derive = function(arg, hardened) { | ||
HDPrivateKey.prototype.deriveChild = function(arg, hardened, nonCompliant) { |
There was a problem hiding this comment.
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);
};
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
ACK. Lets merge this and move forward. |
ACK |
@matiu @isocolsky Sounds good! |
There was a problem hiding this 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!
Why this PR is not yet merged? |
This one was replaced by #116 |
https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
Closes: #94
Closes: #93