Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hdkeychain: Introduce v2 module. #1696

Merged
merged 13 commits into from
Mar 29, 2019
Merged

Conversation

davecgh
Copy link
Member

@davecgh davecgh commented Mar 20, 2019

NOTE: This currently contains a module replacement directive since it relies on an unreleased version of the chaincfg module. It will need to be removed once the requisite modules is released before this can be merged. However, it is otherwise ready for review.

The hdkeychain package currently has some less-than ideal APIs and behaviors that have been retained up to this point in order to avoid breaking existing caller code. However, now that versioned module support is available and in full use, these items can be addressed.

A quick overview of the issues with hdkeychain this aims to address are:

  • It relies on side effects of the magic global-state registration capabilities of chaincfg which are slated to be removed
  • It directly accepts *chaincfg.Params in its API funcs which tightly couples it to the specific version of chaincfg. The forces major version bumps of the hdkeychain module when the major version of chaincfg is bumped even if nothing in it that hdkeychain actually relies on changed.
  • Address only return addresses of a specific type and script version. Since there ultimately will be multiple address types and script versions, it no longer makes sense to return an address which probably isn't the one the caller actually needs. Instead, the caller can make use of the ECPubKey method to obtain the public key and create the desired address from there.
  • The handling of networks is error prone and could lead to subtle bugs:
    • SetNet allows changing the network after keys have already been derived and then deriving more keys which will not result in the same derivation as starting with the target network to begin with. There is also no practical use for changing a network in the middle of a derivation or providing an extended key serialized for a network that the rest of the derivation does not apply to.
    • NewKeyFromString does not accept the network the serialized extended key is intended to be for and instead parses for any recognized network. This results in requiring the caller ensuring it is for the correct network via the separate IsForNet which leads to to the next point. Instead, NewKeyFromString should take the required network as a parameter and error if the serialized extended key is not for that network.
    • IsForNet is easy for a caller to forget which means it could lead to them inadvertently using keys for other networks.

Consequently, this introduces hdkeychain/v2 which addresses the aforementioned items. A series of individual commits to make the review process easier. Each commit message more thoroughly describes its purpose, but primarily they consist of the following:

  • Freeze version 1 of the hdkeychain module
  • Remove the root module hdkeychain override so building the software will still produce binaries based on the v1 module until the v2 module is fully released
  • Correct the keys used for the benchmarks and example docs to be correct for Decred
  • Consolidate tests in the main package
  • Remove Address
  • Remove SetNet
  • Used locally-scoped network params in the tests to permit easier mocks
  • Modify NewKeyFromString to accept required network parameters and error in the case the provided serialized extended key does not correspond to the network
  • Remove IsForNet
  • Refactor internals to avoid relying on global chaincfg state which also leads to a slight optimization (benchmarks below)
  • Introduce a new NetworkParams interface that is limited specifically to the parameters needed and update all APIs that previously accepted *chaincfg.Params to use the new interface instead.
  • Convert the tests to use mock params that satisfy the interface thus preventing test failures if chaincfg changes
  • Bump the module version to v2
  • Update docs for the new module version

The following is a before and after comparison of the allocations and performance as a side effect of the changes:

benchmark                 old ns/op   new ns/op   delta
--------------------------------------------------------
BenchmarkDeriveHardened   5795        5600        -3.36%
BenchmarkDeriveNormal     6585        6263        -4.89%
BenchmarkPrivToPub        137         118         -13.87%
BenchmarkDeserialize      17618       17618       +0.00%
BenchmarkSerialize        21672       21061       -2.82%

benchmark                 old bytes   new bytes   delta
--------------------------------------------------------
BenchmarkDeriveHardened   1552        1536        -1.03%
BenchmarkDeriveNormal     1601        1585        -1.00%
BenchmarkPrivToPub        128         112         -12.50%
BenchmarkDeserialize      1640        1624        -0.98%
BenchmarkSerialize        1712        1712        +0.00%

Finally, it should also be noted that this only introduces the new module and does not update anything to make use of it yet, so building the software will still produce binaries based on the v1 module.

@davecgh davecgh added this to the 1.5.0 milestone Mar 20, 2019
hdkeychain/doc.go Outdated Show resolved Hide resolved
@matheusd
Copy link
Member

matheusd commented Mar 22, 2019

An idea: also make the NetParams return the masterKey to be used when generating a new extended key. This would allow the package to be used in networks other than bitcoin/decred and remove another global magic constant.

@davecgh davecgh force-pushed the hdkeychain_introduce_v2 branch from 45db951 to a368dbf Compare March 22, 2019 17:45
@davecgh
Copy link
Member Author

davecgh commented Mar 22, 2019

That seems like a good idea. It does mean the parameter should first be added to chaincfg and also the appropriate method introduced there so that the various chaincfg parameters implicitly satisfy the interface. Otherwise, it would require the callers to write conversion code to get it to satisfy the interface which I'd like to avoid.

@davecgh
Copy link
Member Author

davecgh commented Mar 22, 2019

After discussing this with @jrick and considering it a bit more, changing the master key would actually break multi-currency wallets since the BIP defines the seed as a specific value and thus BIP44 implicitly relies on it.

@davecgh
Copy link
Member Author

davecgh commented Mar 25, 2019

Updated to account for renamed HD extended key accessor funcs in chaincfg.

davecgh added 13 commits March 29, 2019 11:26
This serves as the final release of version 1 of the hdkeychain module.
All future releases will be moving to version 2 of the module.

Since there have been no changes since the last release version, this
merely removes the hdkeychain override in the root module and updates it
so building the software will still produce binaries based on the v1
module until the v2 module is fully released.
This corrects the master extended private key used in the benchmarks for
Decred.  This appears to have been missed when porting the code.
This corrects the serialized extended key examples in the package
documentation.  This appears to have been missed when porting the code.
Putting the test code in the same package makes it easier for forks
since they don't have to change the import paths as much and makes it
more consistent with the rest of the code base.
This removes the Address method from the ExtendedKey struct since
ultimately there will be multiple address types and therefore it no
longer makes sense to return an address which probably isn't the one the
caller actually needs.  Instead, the caller can make use of the ECPubKey
method to obtain the public key and create the desired address from
there.

It also removes the related tests, updates the example to show the
address conversion process for a standard pay-to-pubkey-hash address,
and updates the README.md and doc.go files accordingly.
This removes the SetNet method from the ExtendedKey struct since its use
in practice could lead to subtle bugs due to the fact that changing the
network after keys have already been derived and then deriving more keys
will not result in the same derivation as starting with the target
network to begin with.  It is better to simply avoid the possibility of
misuse.

It also removes the related tests.
This modifies the tests to use locals for the network parameter
references to make it easier to mock the parameters in the future.
This modifies NewKeyFromString to accept the required network parameters
for the provided encoded extended key and introduces a new error to
indicate the provide extended key is for the wrong network.

This means that NewKeyFromString will now return ErrWrongNetwork if the
extended key being decoded is not for the provided network.  This
differs from the previous behavior relied on globally-registered
networks in chaincfg to determine which network the encoded key was for
followed by requiring the caller to check upon if the key was for the
specific network it desired upon return.

Finally, it removes the no longer necessary IsForNet method and updates
the documentation accordingly.
This refactors the the extended key internals to keep track of the
public and private network ids instead of a version slice in order to
remove the need to rely on global state for looking up the extended
public key ID for the network.

Since the public and private network ids are 8 bytes and the version
slice was 24 bytes, this also has the nice side effect of slightly
optimizing the performance, particularly in the private to public case,
as shown by the following benchmark comparison:

benchmark                 old ns/op   new ns/op   delta
--------------------------------------------------------
BenchmarkDeriveHardened   5795        5600        -3.36%
BenchmarkDeriveNormal     6585        6263        -4.89%
BenchmarkPrivToPub        137         118         -13.87%
BenchmarkDeserialize      17618       17618       +0.00%
BenchmarkSerialize        21672       21061       -2.82%

benchmark                 old bytes   new bytes   delta
--------------------------------------------------------
BenchmarkDeriveHardened   1552        1536        -1.03%
BenchmarkDeriveNormal     1601        1585        -1.00%
BenchmarkPrivToPub        128         112         -12.50%
BenchmarkDeserialize      1640        1624        -0.98%
BenchmarkSerialize        1712        1712        +0.00%
This introduces a new interface named NetworkParams and updates the
functions that currently take a pointer to a chaincfg.Params struct to
accept the interface instead.

This removes the tight coupling between the two packages at the API
boundary and allows callers to easily provide custom values without
having to create and register and entire chaincfg network as previous
required.

Finally, the README.md is updated accordingly.
This implements a mock network params struct that implements the new
interface and modifies the test code to make use of the mock params
instead of chaincfg.  This completely decouples the tests, with the
exception of the example code, from chaincfg and therefore any updates
to it such as introducing new test networks will not require changes to
the tests.
This updates the docs/README.md file, module hierarchy graphviz, and
module hierarchy diagram to reflect the new module version
@davecgh davecgh force-pushed the hdkeychain_introduce_v2 branch from 0c81833 to c1cf192 Compare March 29, 2019 16:40
@davecgh
Copy link
Member Author

davecgh commented Mar 29, 2019

This is ready for final review and ready to be merged.

@davecgh davecgh merged commit c1cf192 into decred:master Mar 29, 2019
@davecgh davecgh deleted the hdkeychain_introduce_v2 branch March 29, 2019 18:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants