mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-11-16 04:17:25 +00:00
HD Wallets: implement auto upgrade behaviour and refresh the design doc.
This commit is contained in:
@@ -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
|
||||
---------
|
||||
|
||||
Reference in New Issue
Block a user