HD Wallets: implement auto upgrade behaviour and refresh the design doc.

This commit is contained in:
Mike Hearn
2014-06-12 18:54:57 +02:00
parent 57105f52e6
commit 443d556909
13 changed files with 419 additions and 65 deletions

View File

@@ -25,29 +25,32 @@ Create a new KeyChain interface and provide BasicKeyChain, DeterministicKeyChain
Wallets may contain multiple key chains. However only the last one is "active" in the sense that it will be used to
create new keys. There's no way to change that.
Wallet API changes to have an importKey method that works like addKey does today, and forwards to the basic key chain.
There's also a getKey method that forwards to the active key chain (which after upgrade will always be deterministic)
and requests a key for a specific purpose, specified by an enum parameter. The getKey method supports requesting keys
for the following purposes:
The Wallet class has most key handling code refactored out into KeyChainGroup, which handles multiplexing a
BasicKeyChain (for random keys, if any), and zero or more DeterministicKeyChain. Wallet ends up just forwarding method
calls to this class most of the time. Thus in this section where the Wallet API is discussed, it can be assumed that
KeyChainGroup has the same API. Although individual key chain objects have their own locks and are expected to be thread
safe, KeyChainGroup itself is not and is not exposed directly by Wallet: it's an implementation detail, and locked under
the Wallet lock.
The Wallet API changes to have an importKey method that works like addKey does today, and forwards to the BasicKeyChain.
There's also a freshKey method that forwards to the active HD chain and requests a key for a specific purpose,
specified by an enum parameter. The freshKey method supports requesting keys for the following purposes:
- CHANGE
- RECEIVE_FUNDS
and may in future also have additional purposes like for micropayment channels, etc. These map to the notion of
"accounts" as defined in the BIP32 spec, but otherwise should not be exposed in any user interfaces. getKey is not
guaranteed to return a freshly generated key: it may return the same key repeatedly if the underlying keychain either
does not support auto-extension (basic) or does not believe the key was used yet (deterministic). In cases where the
user knows they need a fresh key even though earlier keys were not yet used, a newKey method takes the same purpose
parameter as getKey, but tells the keychain to ignore usage heuristics and always generate a new key.
"accounts" as defined in the BIP32 spec, but otherwise should not be exposed in any user interfaces. freshKey is
guaranteed to return a freshly generated key: it will not return the same key repeatedly. There is also a currentKey
method that returns a stable key suitable for display in the user interface: it will be changed automatically when
it's observed being used in a transaction.
There can be multiple key chains. There is always:
<=1 basic key chain
>=0 deterministic key chains
* 1 basic key chain, though it may be empty.
* >=0 deterministic key chains
Thus it's possible to have more than one deterministic key chain, but not more than one basic key chain. New wallets
will not have a basic key chain unless an attempt to import a key is made. Old wallets will have both a basic and
(after upgrade) one deterministic key chain, and this is expected to be the normal state of operation.
Thus it's possible to have more than one deterministic key chain, but not more than one basic key chain.
Multiple deterministic key chains become relevant when key rotation happens. Individual keys in a deterministic
heirarchy do not rotate. Instead the rotation time is applied only to the seed. Either the whole key chain rotates or
@@ -198,21 +201,23 @@ private derivation (see the BIP32 spec for more information on this).
Upgrade
-------
HD wallets are superior to regular wallets, there are no reasons why you would want not want to use them. Therefore
wallets generated by older versions of bitcoinj will be upgraded in place at the first opportunity. This process should
be transparent to end users. The wallet seed, which would normally be randomly generated, will for upgraded wallets
be set to the private key bytes of the oldest non-rotating key. This selection ensures that the key is least likely to
be compromised and most likely to be backed up. Assuming the private key really is random, this gives security of the
HD wallet as well. It also means that the user does not necessarily need to make a new backup after the upgrade,
although creation of one would be recommended anyway.
HD wallets are strictly superior to old random wallets, thus by default all new wallets will be HD. The deterministic
key chain will be created on demand by the KeyChainGroup, which allows the default parameters like lookahead size to
be configured after construction of the wallet but before the DeterministicKeyChain is constructed.
Encrypted wallets cannot be upgraded at load time. They must wait until the decryption key has been made available.
This may happen on explicit decrypt by an end user, or more likely, the first time they spend money or click "add
address" with the new wallet app. The wallet class will have a maybeUpgradeToDeterministic method which will be called
at various places where private key bytes might become available, like just after deserialization or a method being
called which has an AES key parameter. The method will check if an upgrade is necessary, and if so add a
DeterministicKeyChain to the internal list of chains.
For old wallets that contain random keys, attempts to use any methods that rely on an HD chain being present will
either automatically upgrade the wallet to HD, or if encrypted, throw an unchecked exception until the API user invokes
an upgrade method that takes the users encryption key. The upgrade will select the oldest non-rotating private key,
truncate it to 128 bits and use that as the seed for the new HD chain. We truncate and thus lose entropy because
128 bits is more than enough, and people like to write down their seeds on paper. 128 bit seeds using the BIP 39
mnemonic code specification yields 12 words, which is a convenient size.
As part of migrating to deterministic wallets, if the wallet is encrypted the wallet author is expected to test
after load whether the wallet needs an upgrade, and call the upgrade method explicitly with the password.
Note that attempting to create a spend will fail if the wallet is not upgraded, because it will attempt to retrieve a
change key which is done deterministically in the new version of the code: thus an non-upgraded wallet is not very
useful for more than viewing (unless the API caller explicitly overrides the change address behaviour using the
relevant field in SendRequest of course).
Test plan
---------