3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-31 23:32:16 +00:00

HD Wallets: add a threshold for the DeterministicKey lookahead

DeterministicKeyChain.maybeLookAhead() would pre-generate a new key, for
every issued key, even if it is only one. If we replay the blockchain
and update the issuedKeys counter, maybeLookAhead() would trigger the
regeneration and resending of the bloom filter for every used key.

This patch adds a threshold, where keys are only pre-generated after
more keys are needed than the value of the threshold.

Signed-off-by: Harald Hoyer <harald@harald-hoyer.de>
This commit is contained in:
Harald Hoyer 2014-04-11 11:49:54 +02:00 committed by Mike Hearn
parent 42bfbb9b1c
commit 534a1e3a5c
3 changed files with 100 additions and 6 deletions

View File

@ -468,6 +468,28 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
return keychain.getLookaheadSize(); return keychain.getLookaheadSize();
} }
/** See {@link com.google.bitcoin.wallet.DeterministicKeyChain#setLookaheadThreshold(int)} for more info on this. */
public void setLookaheadThreshold(int num) {
lock.lock();
try {
keychain.setLookaheadThreshold(num);
} finally {
lock.unlock();
}
}
/** See {@link com.google.bitcoin.wallet.DeterministicKeyChain#setLookaheadThreshold(int)} for more info on this. */
public int getKeychainLookaheadThreshold() {
int threshold = 0;
lock.lock();
try {
threshold = keychain.getLookaheadThreshold();
} finally {
lock.unlock();
}
return threshold;
}
/** /**
* Returns a public-only DeterministicKey that can be used to set up a watching wallet: that is, a wallet that * Returns a public-only DeterministicKey that can be used to set up a watching wallet: that is, a wallet that
* can import transactions from the block chain just as the normal wallet can, but which cannot spend. Watching * can import transactions from the block chain just as the normal wallet can, but which cannot spend. Watching

View File

@ -34,6 +34,7 @@ import javax.annotation.Nullable;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -100,6 +101,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
// chains, it will be calculated on demand from the number of loaded keys. // chains, it will be calculated on demand from the number of loaded keys.
private static final int LAZY_CALCULATE_LOOKAHEAD = -1; private static final int LAZY_CALCULATE_LOOKAHEAD = -1;
private int lookaheadSize = 100; private int lookaheadSize = 100;
private int lookaheadThreshold = 33;
// The parent keys for external keys (handed out to other people) and internal keys (used for change addresses). // The parent keys for external keys (handed out to other people) and internal keys (used for change addresses).
private DeterministicKey externalKey, internalKey; private DeterministicKey externalKey, internalKey;
@ -198,6 +200,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
this.issuedInternalKeys = chain.issuedInternalKeys; this.issuedInternalKeys = chain.issuedInternalKeys;
this.lookaheadSize = chain.lookaheadSize; this.lookaheadSize = chain.lookaheadSize;
this.lookaheadThreshold = chain.lookaheadThreshold;
this.seed = chain.seed.encrypt(crypter, aesKey); this.seed = chain.seed.encrypt(crypter, aesKey);
basicKeyChain = new BasicKeyChain(crypter); basicKeyChain = new BasicKeyChain(crypter);
@ -673,6 +676,40 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
} }
} }
/**
* Sets the threshold for the key pre-generation.
* If a key is used in a transaction, the keychain would pre-generate a new key, for every issued key,
* even if it is only one. If the blockchain is replayed, every key would trigger a regeneration
* of the bloom filter sent to the peers as a consequence.
* To prevent this, new keys are only generated, if more than the threshold value are needed.
*/
public void setLookaheadThreshold(int num) {
lock.lock();
if (num >= lookaheadSize)
throw new IllegalArgumentException("Threshold larger or equal to the lookaheadSize");
try {
this.lookaheadThreshold = num;
} finally {
lock.unlock();
}
}
/**
* Gets the threshold for the key pre-generation.
* See {@link #setLookaheadThreshold(int)} for details on what this is.
*/
public int getLookaheadThreshold() {
lock.lock();
try {
if (lookaheadThreshold >= lookaheadSize)
return 0;
return lookaheadThreshold;
} finally {
lock.unlock();
}
}
// Pre-generate enough keys to reach the lookahead size. // Pre-generate enough keys to reach the lookahead size.
private void maybeLookAhead() { private void maybeLookAhead() {
lock.lock(); lock.lock();
@ -688,16 +725,28 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
} }
} }
// Returned keys must be inserted into the basic key chain. /**
* Pre-generate enough keys to reach the lookahead size, but only if there are more than the lookaheadThreshold to
* be generated, so that the Bloom filter does not have to be regenerated that often.
*
* Returned keys must be inserted into the basic key chain.
*/
private List<DeterministicKey> maybeLookAhead(DeterministicKey parent, int issued) { private List<DeterministicKey> maybeLookAhead(DeterministicKey parent, int issued) {
checkState(lock.isHeldByCurrentThread()); checkState(lock.isHeldByCurrentThread());
final int numChildren = hierarchy.getNumChildren(parent.getPath()); final int numChildren = hierarchy.getNumChildren(parent.getPath());
int needed = issued + getLookaheadSize() - numChildren; final int needed = issued + getLookaheadSize() - numChildren;
checkState(needed >= 0, "needed = " + needed);
log.info("maybeLookAhead(): {} needed = lookaheadSize({}) - (numChildren({}) - issued({}) = {} < lookaheadThreshold({}))",
parent.getPathAsString(), getLookaheadSize(), numChildren,
issued, needed, getLookaheadThreshold());
/* Even if needed is negative, we have more than enough */
if (needed <= getLookaheadThreshold())
return Collections.emptyList();
List<DeterministicKey> result = new ArrayList<DeterministicKey>(needed); List<DeterministicKey> result = new ArrayList<DeterministicKey>(needed);
if (needed == 0) return result;
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
log.info("Pre-generating {} keys for {}", needed, parent.getPathAsString()); log.info("maybeLookAhead(): Pre-generating {} keys for {}", needed, parent.getPathAsString());
for (int i = 0; i < needed; i++) { for (int i = 0; i < needed; i++) {
// TODO: Handle the case where the derived key is >= curve order. // TODO: Handle the case where the derived key is >= curve order.
DeterministicKey key = HDKeyDerivation.deriveChildKey(parent, numChildren + i); DeterministicKey key = HDKeyDerivation.deriveChildKey(parent, numChildren + i);
@ -705,7 +754,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
hierarchy.putKey(key); hierarchy.putKey(key);
result.add(key); result.add(key);
} }
log.info("Took {} msec", System.currentTimeMillis() - now); log.info("maybeLookAhead(): Took {} msec", System.currentTimeMillis() - now);
return result; return result;
} }

View File

@ -57,6 +57,7 @@ public class KeyChainGroup {
private final EnumMap<KeyChain.KeyPurpose, DeterministicKey> currentKeys; private final EnumMap<KeyChain.KeyPurpose, DeterministicKey> currentKeys;
@Nullable private KeyCrypter keyCrypter; @Nullable private KeyCrypter keyCrypter;
private int lookaheadSize = -1; private int lookaheadSize = -1;
private int lookaheadThreshold = -1;
/** Creates a keychain group with no basic chain, and a single randomly initialized HD chain. */ /** Creates a keychain group with no basic chain, and a single randomly initialized HD chain. */
public KeyChainGroup() { public KeyChainGroup() {
@ -99,6 +100,8 @@ public class KeyChainGroup {
chain.addEventListener(registration.listener, registration.executor); chain.addEventListener(registration.listener, registration.executor);
if (lookaheadSize >= 0) if (lookaheadSize >= 0)
chain.setLookaheadSize(lookaheadSize); chain.setLookaheadSize(lookaheadSize);
if (lookaheadThreshold >= 0)
chain.setLookaheadThreshold(lookaheadThreshold);
chains.add(chain); chains.add(chain);
} }
@ -157,6 +160,26 @@ public class KeyChainGroup {
return lookaheadSize; return lookaheadSize;
} }
/**
* Sets the lookahead buffer threshold for ALL deterministic key chains, see
* {@link com.google.bitcoin.wallet.DeterministicKeyChain#setLookaheadThreshold(int)}
* for more information.
*/
public void setLookaheadThreshold(int num) {
for (DeterministicKeyChain chain : chains) {
chain.setLookaheadThreshold(num);
}
}
/**
* Gets the current lookahead threshold being used for ALL deterministic key chains. See
* {@link com.google.bitcoin.wallet.DeterministicKeyChain#setLookaheadThreshold(int)}
* for more information.
*/
public int getLookaheadThreshold() {
return lookaheadThreshold;
}
/** Imports the given keys into the basic chain, creating it if necessary. */ /** Imports the given keys into the basic chain, creating it if necessary. */
public int importKeys(List<ECKey> keys) { public int importKeys(List<ECKey> keys) {
return basic.importKeys(keys); return basic.importKeys(keys);