public class KeyChainGroup extends java.lang.Object implements KeyBag
A KeyChainGroup is used by the Wallet
and manages: a BasicKeyChain
object
(which will normally be empty), and zero or more DeterministicKeyChain
s. The last added
deterministic keychain is always the default active keychain, that's the one we normally derive keys and
addresses from.
There can be active keychains for each output script type. However this class almost entirely only works on
the default active keychain (see getActiveKeyChain()
). The other active keychains
(see #getActiveKeyChain(ScriptType, long)
) are meant as fallback for if a sender doesn't understand a
certain new script type (e.g. P2WPKH which comes with the new Bech32 address format). Active keychains
share the same seed, so that upgrading the wallet
(see #upgradeToDeterministic(ScriptType, KeyChainGroupStructure, long, KeyParameter)
) to understand
a new script type doesn't require a fresh backup.
If a key rotation time is set, it may be necessary to add a new DeterministicKeyChain with a fresh seed and also preserve the old one, so funds can be swept from the rotating keys. In this case, there may be more than one deterministic chain. The latest chain is called the active chain and is where new keys are served from.
The wallet delegates most key management tasks to this class. It is not thread safe and requires external locking, i.e. by the wallet lock. The group then in turn delegates most operations to the key chain objects, combining their responses together when necessary.
Deterministic key chains have a concept of a lookahead size and threshold. Please see the discussion in the
class docs for DeterministicKeyChain
for more information on this topic.
Modifier and Type | Class and Description |
---|---|
static class |
KeyChainGroup.Builder
Builder for
KeyChainGroup . |
Modifier and Type | Field and Description |
---|---|
protected java.util.LinkedList<DeterministicKeyChain> |
chains |
Modifier and Type | Method and Description |
---|---|
void |
addAndActivateHDChain(DeterministicKeyChain chain)
Adds an HD chain to the chains list, and make it the default chain (from which keys are issued).
|
void |
addEventListener(KeyChainEventListener listener)
Adds a listener for events that are run when keys are added, on the user thread.
|
void |
addEventListener(KeyChainEventListener listener,
java.util.concurrent.Executor executor)
Adds a listener for events that are run when keys are added, on the given executor.
|
static KeyChainGroup.Builder |
builder(NetworkParameters params) |
static KeyChainGroup.Builder |
builder(NetworkParameters params,
KeyChainGroupStructure structure) |
boolean |
checkAESKey(org.bouncycastle.crypto.params.KeyParameter aesKey) |
boolean |
checkPassword(java.lang.CharSequence password) |
static KeyChainGroup |
createBasic(NetworkParameters params)
Creates a keychain group with just a basic chain.
|
Address |
currentAddress(KeyChain.KeyPurpose purpose)
Returns address for a
currentKey(KeyChain.KeyPurpose) |
DeterministicKey |
currentKey(KeyChain.KeyPurpose purpose)
Returns a key that hasn't been seen in a transaction yet, and which is suitable for displaying in a wallet
user interface as "a convenient key to receive funds on" when the purpose parameter is
KeyChain.KeyPurpose.RECEIVE_FUNDS . |
void |
decrypt(org.bouncycastle.crypto.params.KeyParameter aesKey)
Decrypt the keys in the group using the previously given key crypter and the AES key.
|
void |
encrypt(KeyCrypter keyCrypter,
org.bouncycastle.crypto.params.KeyParameter aesKey)
Encrypt the keys in the group using the KeyCrypter and the AES key.
|
ECKey |
findKeyFromPubKey(byte[] pubKey)
Locates a keypair from the keychain given the raw public key bytes.
|
ECKey |
findKeyFromPubKeyHash(byte[] pubKeyHash,
Script.ScriptType scriptType)
Locates a keypair from the keychain given the hash of the public key, and (optionally) by usage for a specific
script type.
|
RedeemData |
findRedeemDataFromScriptHash(byte[] scriptHash)
Locates a redeem data (redeem script and keys) from the keychain given the hash of the script.
|
Address |
freshAddress(KeyChain.KeyPurpose purpose)
Returns address for a
freshKey(KeyChain.KeyPurpose) |
Address |
freshAddress(KeyChain.KeyPurpose purpose,
Script.ScriptType outputScriptType,
long keyRotationTimeSecs)
Returns a fresh address for a given
KeyChain.KeyPurpose and of a given
Script.ScriptType . |
DeterministicKey |
freshKey(KeyChain.KeyPurpose purpose)
Returns a key that has not been returned by this method before (fresh).
|
java.util.List<DeterministicKey> |
freshKeys(KeyChain.KeyPurpose purpose,
int numberOfKeys)
Returns a key/s that have not been returned by this method before (fresh).
|
static KeyChainGroup |
fromProtobufEncrypted(NetworkParameters params,
java.util.List<Protos.Key> keys,
KeyCrypter crypter,
KeyChainFactory factory) |
static KeyChainGroup |
fromProtobufUnencrypted(NetworkParameters params,
java.util.List<Protos.Key> keys,
KeyChainFactory factory) |
DeterministicKeyChain |
getActiveKeyChain()
Returns the key chain that's used for generation of default fresh/current keys.
|
DeterministicKeyChain |
getActiveKeyChain(Script.ScriptType outputScriptType,
long keyRotationTimeSecs)
Returns the key chain that's used for generation of fresh/current keys of the given type.
|
java.util.List<DeterministicKeyChain> |
getActiveKeyChains(long keyRotationTimeSecs)
Returns the key chains that are used for generation of fresh/current keys, in the order of how they
were added.
|
BloomFilter |
getBloomFilter(int size,
double falsePositiveRate,
long nTweak) |
int |
getBloomFilterElementCount() |
int |
getCombinedKeyLookaheadEpochs()
Returns a counter that increases (by an arbitrary amount) each time new keys have been calculated due to
lookahead and thus the Bloom filter that was previously calculated has become stale.
|
java.util.List<DeterministicKeyChain> |
getDeterministicKeyChains()
Returns a copy of the current list of chains.
|
long |
getEarliestKeyCreationTime() |
java.util.List<ECKey> |
getImportedKeys()
Returns a list of the non-deterministic keys that have been imported into the wallet, or the empty list if none.
|
KeyCrypter |
getKeyCrypter()
Returns the key crypter or null if the group is not encrypted.
|
int |
getLookaheadSize()
Gets the current lookahead size being used for ALL deterministic key chains.
|
int |
getLookaheadThreshold()
Gets the current lookahead threshold being used for ALL deterministic key chains.
|
boolean |
hasKey(ECKey key) |
int |
importKeys(ECKey... keys)
Imports the given keys into the basic chain, creating it if necessary.
|
int |
importKeys(java.util.List<ECKey> keys)
Imports the given keys into the basic chain, creating it if necessary.
|
int |
importKeysAndEncrypt(java.util.List<ECKey> keys,
org.bouncycastle.crypto.params.KeyParameter aesKey)
Imports the given unencrypted keys into the basic chain, encrypting them along the way with the given key.
|
boolean |
isDeterministicUpgradeRequired(Script.ScriptType preferredScriptType,
long keyRotationTimeSecs)
Returns true if a call to
#upgradeToDeterministic(ScriptType, KeyChainGroupStructure, long, KeyParameter) is required
in order to have an active deterministic keychain of the desired script type. |
boolean |
isEncrypted()
Returns true if the group is encrypted.
|
boolean |
isMarried()
Whether the active keychain is married.
|
boolean |
isRequiringUpdateAllBloomFilter() |
boolean |
isSupportsDeterministicChains()
Returns true if it contains any deterministic keychain or one could be created.
|
boolean |
isWatching()
Returns whether this chain has only watching keys (unencrypted keys with no private part).
|
void |
markP2SHAddressAsUsed(LegacyAddress address) |
void |
markPubKeyAsUsed(byte[] pubkey)
Mark the DeterministicKeys as used, if they match the pubkey
See
DeterministicKeyChain.markKeyAsUsed(DeterministicKey) for more info on this. |
void |
markPubKeyHashAsUsed(byte[] pubKeyHash)
Mark the DeterministicKeys as used, if they match the pubKeyHash
See
DeterministicKeyChain.markKeyAsUsed(DeterministicKey) for more info on this. |
void |
mergeActiveKeyChains(KeyChainGroup from,
long keyRotationTimeSecs)
Merge all active chains from the given keychain group into this keychain group.
|
int |
numKeys()
Returns the number of keys managed by this group, including the lookahead buffers.
|
boolean |
removeEventListener(KeyChainEventListener listener)
Removes a listener for events that are run when keys are added.
|
boolean |
removeImportedKey(ECKey key)
Removes a key that was imported into the basic key chain.
|
java.util.List<Protos.Key> |
serializeToProtobuf()
Returns a list of key protobufs obtained by merging the chains.
|
java.lang.String |
toString(boolean includeLookahead,
boolean includePrivateKeys,
org.bouncycastle.crypto.params.KeyParameter aesKey) |
void |
upgradeToDeterministic(Script.ScriptType preferredScriptType,
KeyChainGroupStructure structure,
long keyRotationTimeSecs,
org.bouncycastle.crypto.params.KeyParameter aesKey)
This method will upgrade the wallet along the following path:
Basic --> P2PKH --> P2WPKH |
@Nullable protected final java.util.LinkedList<DeterministicKeyChain> chains
public static KeyChainGroup createBasic(NetworkParameters params)
public static KeyChainGroup.Builder builder(NetworkParameters params)
public static KeyChainGroup.Builder builder(NetworkParameters params, KeyChainGroupStructure structure)
public boolean isSupportsDeterministicChains()
public void addAndActivateHDChain(DeterministicKeyChain chain)
public DeterministicKey currentKey(KeyChain.KeyPurpose purpose)
KeyChain.KeyPurpose.RECEIVE_FUNDS
. The returned key is stable until
it's actually seen in a pending or confirmed transaction, at which point this method will start returning
a different key (for each purpose independently).
This method is not supposed to be used for married keychains and will throw UnsupportedOperationException if
the active chain is married.
For married keychains use currentAddress(KeyChain.KeyPurpose)
to get a proper P2SH address
public Address currentAddress(KeyChain.KeyPurpose purpose)
currentKey(KeyChain.KeyPurpose)
public DeterministicKey freshKey(KeyChain.KeyPurpose purpose)
DeterministicKeyChain
. When the parameter is
KeyChain.KeyPurpose.RECEIVE_FUNDS
the returned key is suitable for being put
into a receive coins wizard type UI. You should use this when the user is definitely going to hand this key out
to someone who wishes to send money.
This method is not supposed to be used for married keychains and will throw UnsupportedOperationException if
the active chain is married.
For married keychains use freshAddress(KeyChain.KeyPurpose)
to get a proper P2SH address
public java.util.List<DeterministicKey> freshKeys(KeyChain.KeyPurpose purpose, int numberOfKeys)
DeterministicKeyChain
. When the parameter is
KeyChain.KeyPurpose.RECEIVE_FUNDS
the returned key is suitable for being put
into a receive coins wizard type UI. You should use this when the user is definitely going to hand this key out
to someone who wishes to send money.
This method is not supposed to be used for married keychains and will throw UnsupportedOperationException if
the active chain is married.
For married keychains use freshAddress(KeyChain.KeyPurpose)
to get a proper P2SH address
public Address freshAddress(KeyChain.KeyPurpose purpose, Script.ScriptType outputScriptType, long keyRotationTimeSecs)
Returns a fresh address for a given KeyChain.KeyPurpose
and of a given
Script.ScriptType
.
This method is meant for when you really need a fallback address. Normally, you should be
using freshAddress(KeyChain.KeyPurpose)
or
currentAddress(KeyChain.KeyPurpose)
.
public Address freshAddress(KeyChain.KeyPurpose purpose)
freshKey(KeyChain.KeyPurpose)
public java.util.List<DeterministicKeyChain> getActiveKeyChains(long keyRotationTimeSecs)
public final DeterministicKeyChain getActiveKeyChain(Script.ScriptType outputScriptType, long keyRotationTimeSecs)
null
is returned. No upgrade or downgrade is tried.public final DeterministicKeyChain getActiveKeyChain()
public final void mergeActiveKeyChains(KeyChainGroup from, long keyRotationTimeSecs)
public int getLookaheadSize()
DeterministicKeyChain.setLookaheadSize(int)
for more information.public int getLookaheadThreshold()
DeterministicKeyChain.setLookaheadThreshold(int)
for more information.public int importKeys(java.util.List<ECKey> keys)
public int importKeys(ECKey... keys)
public boolean checkPassword(java.lang.CharSequence password)
public boolean checkAESKey(org.bouncycastle.crypto.params.KeyParameter aesKey)
public int importKeysAndEncrypt(java.util.List<ECKey> keys, org.bouncycastle.crypto.params.KeyParameter aesKey)
@Nullable public RedeemData findRedeemDataFromScriptHash(byte[] scriptHash)
KeyBag
findRedeemDataFromScriptHash
in interface KeyBag
public void markP2SHAddressAsUsed(LegacyAddress address)
@Nullable public ECKey findKeyFromPubKeyHash(byte[] pubKeyHash, @Nullable Script.ScriptType scriptType)
KeyBag
findKeyFromPubKeyHash
in interface KeyBag
pubKeyHash
- hash of the keypair to look forscriptType
- only look for given usage (currently Script.ScriptType#P2PKH
or
Script.ScriptType#P2WPKH
) or null
if we don't carepublic void markPubKeyHashAsUsed(byte[] pubKeyHash)
DeterministicKeyChain.markKeyAsUsed(DeterministicKey)
for more info on this.public boolean hasKey(ECKey key)
@Nullable public ECKey findKeyFromPubKey(byte[] pubKey)
KeyBag
findKeyFromPubKey
in interface KeyBag
public void markPubKeyAsUsed(byte[] pubkey)
DeterministicKeyChain.markKeyAsUsed(DeterministicKey)
for more info on this.public int numKeys()
public boolean removeImportedKey(ECKey key)
java.lang.IllegalArgumentException
- if the key is deterministic.public final boolean isMarried()
MarriedKeyChain
public void encrypt(KeyCrypter keyCrypter, org.bouncycastle.crypto.params.KeyParameter aesKey)
KeyCrypterScrypt
.KeyCrypterException
- Thrown if the wallet encryption fails for some reason,
leaving the group unchanged.DeterministicUpgradeRequiredException
- Thrown if there are random keys but no HD chain.public void decrypt(org.bouncycastle.crypto.params.KeyParameter aesKey)
KeyCrypterScrypt
.KeyCrypterException
- Thrown if the wallet decryption fails for some reason, leaving the group unchanged.public boolean isEncrypted()
public boolean isWatching()
java.lang.IllegalStateException
- if there are no keys, or if there is a mix between watching and non-watching keys.@Nullable public KeyCrypter getKeyCrypter()
public java.util.List<ECKey> getImportedKeys()
public long getEarliestKeyCreationTime()
public int getBloomFilterElementCount()
public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak)
public boolean isRequiringUpdateAllBloomFilter()
public void addEventListener(KeyChainEventListener listener)
public void addEventListener(KeyChainEventListener listener, java.util.concurrent.Executor executor)
public boolean removeEventListener(KeyChainEventListener listener)
public java.util.List<Protos.Key> serializeToProtobuf()
public static KeyChainGroup fromProtobufUnencrypted(NetworkParameters params, java.util.List<Protos.Key> keys, KeyChainFactory factory) throws UnreadableWalletException
UnreadableWalletException
public static KeyChainGroup fromProtobufEncrypted(NetworkParameters params, java.util.List<Protos.Key> keys, KeyCrypter crypter, KeyChainFactory factory) throws UnreadableWalletException
UnreadableWalletException
public void upgradeToDeterministic(Script.ScriptType preferredScriptType, KeyChainGroupStructure structure, long keyRotationTimeSecs, @Nullable org.bouncycastle.crypto.params.KeyParameter aesKey) throws DeterministicUpgradeRequiresPassword, AllRandomKeysRotating
This method will upgrade the wallet along the following path: Basic --> P2PKH --> P2WPKH
It won't skip any steps in that upgrade path because the user might be restoring from a backup and still expects money on the P2PKH chain.
It will extract and reuse the seed from the current wallet, so that a fresh backup isn't required after upgrading. If coming from a basic chain containing only random keys this means it will pick the oldest non-rotating private key as a seed.
Note that for upgrading an encrypted wallet, the decryption key is needed. In future, we could skip
that requirement for a P2PKH --> P2WPKH
upgrade and just clone the encryped seed, but currently
the key is needed even for that.
preferredScriptType
- desired script type for the active keychainstructure
- keychain group structure to derive an account path fromkeyRotationTimeSecs
- If non-zero, UNIX time for which keys created before this are assumed to be
compromised or weak, those keys will not be used for deterministic upgrade.aesKey
- If non-null, the encryption key the keychain is encrypted under. If the keychain is encrypted
and this is not supplied, an exception is thrown letting you know you should ask the user for
their password, turn it into a key, and then try again.java.lang.IllegalStateException
- if there is already a deterministic key chain present or if there are
no random keys (i.e. this is not an upgrade scenario), or if aesKey is
provided but the wallet is not encrypted.java.lang.IllegalArgumentException
- if the rotation time specified excludes all keys.DeterministicUpgradeRequiresPassword
- if the key chain group is encrypted
and you should provide the users encryption key.AllRandomKeysRotating
public boolean isDeterministicUpgradeRequired(Script.ScriptType preferredScriptType, long keyRotationTimeSecs)
#upgradeToDeterministic(ScriptType, KeyChainGroupStructure, long, KeyParameter)
is required
in order to have an active deterministic keychain of the desired script type.public java.lang.String toString(boolean includeLookahead, boolean includePrivateKeys, @Nullable org.bouncycastle.crypto.params.KeyParameter aesKey)
public java.util.List<DeterministicKeyChain> getDeterministicKeyChains()
public int getCombinedKeyLookaheadEpochs()