Class KeyChainGroup

  • All Implemented Interfaces:
    KeyBag

    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 DeterministicKeyChains. 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.

    • Method Detail

      • createBasic

        public static KeyChainGroup createBasic​(NetworkParameters params)
        Creates a keychain group with just a basic chain. No deterministic chains will be created automatically.
      • isSupportsDeterministicChains

        public boolean isSupportsDeterministicChains()
        Returns true if it contains any deterministic keychain or one could be created.
      • addAndActivateHDChain

        public void addAndActivateHDChain​(DeterministicKeyChain chain)
        Adds an HD chain to the chains list, and make it the default chain (from which keys are issued). Useful for adding a complex pre-configured keychain, such as a married wallet.
      • currentKey

        public 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. 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

      • freshKey

        public DeterministicKey freshKey​(KeyChain.KeyPurpose purpose)
        Returns a key that has not been returned by this method before (fresh). You can think of this as being a newly created key, although the notion of "create" is not really valid for a 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

      • freshKeys

        public java.util.List<DeterministicKey> freshKeys​(KeyChain.KeyPurpose purpose,
                                                          int numberOfKeys)
        Returns a key/s that have not been returned by this method before (fresh). You can think of this as being newly created key/s, although the notion of "create" is not really valid for a 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

      • getActiveKeyChains

        public 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. The default active chain will come last in the list.
      • getActiveKeyChain

        public final DeterministicKeyChain getActiveKeyChain​(Script.ScriptType outputScriptType,
                                                             long keyRotationTimeSecs)
        Returns the key chain that's used for generation of fresh/current keys of the given type. If it's not the default type and no active chain for this type exists, null is returned. No upgrade or downgrade is tried.
      • getActiveKeyChain

        public final DeterministicKeyChain getActiveKeyChain()
        Returns the key chain that's used for generation of default fresh/current keys. This is always the newest deterministic chain. If no deterministic chain is present but imported keys instead, a deterministic upgrate is tried.
      • mergeActiveKeyChains

        public final void mergeActiveKeyChains​(KeyChainGroup from,
                                               long keyRotationTimeSecs)
        Merge all active chains from the given keychain group into this keychain group.
      • importKeys

        public int importKeys​(java.util.List<ECKey> keys)
        Imports the given keys into the basic chain, creating it if necessary.
      • importKeys

        public int importKeys​(ECKey... keys)
        Imports the given keys into the basic chain, creating it if necessary.
      • checkPassword

        public boolean checkPassword​(java.lang.CharSequence password)
      • checkAESKey

        public boolean checkAESKey​(org.bouncycastle.crypto.params.KeyParameter aesKey)
      • importKeysAndEncrypt

        public 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.
      • findRedeemDataFromScriptHash

        @Nullable
        public RedeemData findRedeemDataFromScriptHash​(byte[] scriptHash)
        Description copied from interface: KeyBag
        Locates a redeem data (redeem script and keys) from the keychain given the hash of the script. This is needed when finding out which key and script we need to use to locally sign a P2SH transaction input. It is assumed that wallet should not have more than one private key for a single P2SH tx for security reasons. Returns RedeemData object or null if no such data was found.
        Specified by:
        findRedeemDataFromScriptHash in interface KeyBag
      • markP2SHAddressAsUsed

        public void markP2SHAddressAsUsed​(LegacyAddress address)
      • findKeyFromPubKeyHash

        @Nullable
        public ECKey findKeyFromPubKeyHash​(byte[] pubKeyHash,
                                           @Nullable
                                           Script.ScriptType scriptType)
        Description copied from interface: KeyBag
        Locates a keypair from the keychain given the hash of the public key, and (optionally) by usage for a specific script type. This is needed when finding out which key we need to use to redeem a transaction output.
        Specified by:
        findKeyFromPubKeyHash in interface KeyBag
        Parameters:
        pubKeyHash - hash of the keypair to look for
        scriptType - only look for given usage (currently Script.ScriptType.P2PKH or Script.ScriptType.P2WPKH) or null if we don't care
        Returns:
        found key or null if no such key was found.
      • hasKey

        public boolean hasKey​(ECKey key)
      • findKeyFromPubKey

        @Nullable
        public ECKey findKeyFromPubKey​(byte[] pubKey)
        Description copied from interface: KeyBag
        Locates a keypair from the keychain given the raw public key bytes.
        Specified by:
        findKeyFromPubKey in interface KeyBag
        Returns:
        ECKey or null if no such key was found.
      • numKeys

        public int numKeys()
        Returns the number of keys managed by this group, including the lookahead buffers.
      • removeImportedKey

        public boolean removeImportedKey​(ECKey key)
        Removes a key that was imported into the basic key chain. You cannot remove deterministic keys.
        Throws:
        java.lang.IllegalArgumentException - if the key is deterministic.
      • isMarried

        public final boolean isMarried()
        Whether the active keychain is married. A keychain is married when it vends P2SH addresses from multiple keychains in a multisig relationship.
        See Also:
        MarriedKeyChain
      • encrypt

        public void encrypt​(KeyCrypter keyCrypter,
                            org.bouncycastle.crypto.params.KeyParameter aesKey)
        Encrypt the keys in the group using the KeyCrypter and the AES key. A good default KeyCrypter to use is KeyCrypterScrypt.
        Throws:
        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.
      • decrypt

        public void decrypt​(org.bouncycastle.crypto.params.KeyParameter aesKey)
        Decrypt the keys in the group using the previously given key crypter and the AES key. A good default KeyCrypter to use is KeyCrypterScrypt.
        Throws:
        KeyCrypterException - Thrown if the wallet decryption fails for some reason, leaving the group unchanged.
      • isEncrypted

        public boolean isEncrypted()
        Returns true if the group is encrypted.
      • isWatching

        public boolean isWatching()
        Returns whether this chain has only watching keys (unencrypted keys with no private part). Mixed chains are forbidden.
        Throws:
        java.lang.IllegalStateException - if there are no keys, or if there is a mix between watching and non-watching keys.
      • getKeyCrypter

        @Nullable
        public KeyCrypter getKeyCrypter()
        Returns the key crypter or null if the group is not encrypted.
      • getImportedKeys

        public 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.
      • getEarliestKeyCreationTime

        public long getEarliestKeyCreationTime()
      • getBloomFilterElementCount

        public int getBloomFilterElementCount()
      • getBloomFilter

        public BloomFilter getBloomFilter​(int size,
                                          double falsePositiveRate,
                                          long nTweak)
      • isRequiringUpdateAllBloomFilter

        public boolean isRequiringUpdateAllBloomFilter()
      • addEventListener

        public void addEventListener​(KeyChainEventListener listener)
        Adds a listener for events that are run when keys are added, on the user thread.
      • addEventListener

        public 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.
      • removeEventListener

        public boolean removeEventListener​(KeyChainEventListener listener)
        Removes a listener for events that are run when keys are added.
      • addCurrentKeyChangeEventListener

        public void addCurrentKeyChangeEventListener​(CurrentKeyChangeEventListener listener)
        Removes a listener for events that are run when a current key and/or address changes.
      • addCurrentKeyChangeEventListener

        public void addCurrentKeyChangeEventListener​(CurrentKeyChangeEventListener listener,
                                                     java.util.concurrent.Executor executor)
        Adds a listener for events that are run when a current key and/or address changes, on the given executor.
      • removeCurrentKeyChangeEventListener

        public boolean removeCurrentKeyChangeEventListener​(CurrentKeyChangeEventListener listener)
        Removes a listener for events that are run when a current key and/or address changes.
      • serializeToProtobuf

        public java.util.List<Protos.Key> serializeToProtobuf()
        Returns a list of key protobufs obtained by merging the chains.
      • upgradeToDeterministic

        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.

        Parameters:
        preferredScriptType - desired script type for the active keychain
        structure - keychain group structure to derive an account path from
        keyRotationTimeSecs - 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.
        Throws:
        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
      • toString

        public java.lang.String toString​(boolean includeLookahead,
                                         boolean includePrivateKeys,
                                         @Nullable
                                         org.bouncycastle.crypto.params.KeyParameter aesKey)
      • getDeterministicKeyChains

        public java.util.List<DeterministicKeyChain> getDeterministicKeyChains()
        Returns a copy of the current list of chains.
      • getCombinedKeyLookaheadEpochs

        public 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.