Arbitrary number of key creation (batch)

Functionality added to create keys in batch (any arbitary number),
previously keys  were created/returned one at a time.
This commit is contained in:
Kalpesh Parmar 2014-05-30 10:10:13 +01:00 committed by Mike Hearn
parent 638e921e53
commit 4e568354be
5 changed files with 86 additions and 10 deletions

View file

@ -329,13 +329,25 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* to someone who wishes to send money. * to someone who wishes to send money.
*/ */
public DeterministicKey freshKey(KeyChain.KeyPurpose purpose) { public DeterministicKey freshKey(KeyChain.KeyPurpose purpose) {
return freshKeys(purpose, 1).get(0);
}
/**
* Returns a key/s that has not been returned by this method before (fresh). You can think of this as being
* a newly created key/s, although the notion of "create" is not really valid for a
* {@link com.google.bitcoin.wallet.DeterministicKeyChain}. When the parameter is
* {@link com.google.bitcoin.wallet.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/s out
* to someone who wishes to send money.
*/
public List<DeterministicKey> freshKeys(KeyChain.KeyPurpose purpose, int numberOfKeys) {
lock.lock(); lock.lock();
try { try {
DeterministicKey key = keychain.freshKey(purpose); List<DeterministicKey> keys = keychain.freshKeys(purpose, numberOfKeys);
// Do we really need an immediate hard save? Arguably all this is doing is saving the 'current' key // Do we really need an immediate hard save? Arguably all this is doing is saving the 'current' key
// and that's not quite so important, so we could coalesce for more performance. // and that's not quite so important, so we could coalesce for more performance.
saveNow(); saveNow();
return key; return keys;
} finally { } finally {
lock.unlock(); lock.unlock();
} }

View file

@ -89,6 +89,36 @@ public class BasicKeyChain implements EncryptableKeyChain {
} }
} }
@Override
public List<ECKey> getKeys(@Nullable KeyPurpose purpose, int numberOfKeys) {
checkArgument(numberOfKeys > 0);
lock.lock();
try {
if (hashToKeys.size() < numberOfKeys) {
checkState(keyCrypter == null);
List<ECKey> keys = new ArrayList<ECKey>();
for (int i = 0; i < numberOfKeys - hashToKeys.size(); i++) {
keys.add(new ECKey());
}
ImmutableList<ECKey> immutableKeys = ImmutableList.copyOf(keys);
importKeysLocked(immutableKeys);
queueOnKeysAdded(immutableKeys);
}
List<ECKey> keysToReturn = new ArrayList<ECKey>();
int count = 0;
while (hashToKeys.values().iterator().hasNext() && numberOfKeys != count) {
keysToReturn.add(hashToKeys.values().iterator().next());
count++;
}
return keysToReturn;
} finally {
lock.unlock();
}
}
/** Returns a copy of the list of keys that this chain is managing. */ /** Returns a copy of the list of keys that this chain is managing. */
public List<ECKey> getKeys() { public List<ECKey> getKeys() {
lock.lock(); lock.lock();
@ -141,6 +171,12 @@ public class BasicKeyChain implements EncryptableKeyChain {
hashToKeys.put(ByteString.copyFrom(key.getPubKeyHash()), key); hashToKeys.put(ByteString.copyFrom(key.getPubKeyHash()), key);
} }
private void importKeysLocked(List<ECKey> keys) {
for (ECKey key : keys) {
importKeyLocked(key);
}
}
/* package */ void importKey(ECKey key) { /* package */ void importKey(ECKey key) {
lock.lock(); lock.lock();
try { try {

View file

@ -262,9 +262,16 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
/** Returns a freshly derived key that has not been returned by this method before. */ /** Returns a freshly derived key that has not been returned by this method before. */
@Override @Override
public DeterministicKey getKey(KeyPurpose purpose) { public DeterministicKey getKey(KeyPurpose purpose) {
return getKeys(purpose,1).get(0);
}
/** Returns freshly derived key/s that have not been returned by this method before. */
@Override
public List<DeterministicKey> getKeys(KeyPurpose purpose,int numberOfKeys) {
checkArgument(numberOfKeys > 0);
lock.lock(); lock.lock();
try { try {
DeterministicKey key, parentKey; DeterministicKey parentKey;
int index; int index;
switch (purpose) { switch (purpose) {
// Map both REFUND and RECEIVE_KEYS to the same branch for now. Refunds are a feature of the BIP 70 // Map both REFUND and RECEIVE_KEYS to the same branch for now. Refunds are a feature of the BIP 70
@ -273,11 +280,13 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
// (i.e. spends) or refunds. Might be useful for auditing ... // (i.e. spends) or refunds. Might be useful for auditing ...
case RECEIVE_FUNDS: case RECEIVE_FUNDS:
case REFUND: case REFUND:
index = ++issuedExternalKeys; issuedExternalKeys += numberOfKeys;
index = issuedExternalKeys;
parentKey = externalKey; parentKey = externalKey;
break; break;
case CHANGE: case CHANGE:
index = ++issuedInternalKeys; issuedInternalKeys += numberOfKeys;
index = issuedInternalKeys;
parentKey = internalKey; parentKey = internalKey;
break; break;
default: default:
@ -286,8 +295,12 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
// TODO: Handle the case where the derived key is >= curve order. // TODO: Handle the case where the derived key is >= curve order.
List<DeterministicKey> lookahead = maybeLookAhead(parentKey, index); List<DeterministicKey> lookahead = maybeLookAhead(parentKey, index);
basicKeyChain.importKeys(lookahead); basicKeyChain.importKeys(lookahead);
key = hierarchy.get(HDUtils.append(parentKey.getPath(), new ChildNumber(index - 1, false)), false, false); List<DeterministicKey> keys = new ArrayList<DeterministicKey>(numberOfKeys);
return key;
for (int i = 1; i <= numberOfKeys; i++) {
keys.add(hierarchy.get(HDUtils.append(parentKey.getPath(), new ChildNumber((index-numberOfKeys+i) - 1, false)), false, false));
}
return keys;
} finally { } finally {
lock.unlock(); lock.unlock();
} }

View file

@ -44,6 +44,9 @@ public interface KeyChain extends KeyBag {
REFUND REFUND
} }
/** Obtains a number of key/s intended for the given purpose. The chain may create new key/s, derive, or re-use an old one. */
public List<? extends ECKey> getKeys(KeyPurpose purpose, int numberOfKeys);
/** Obtains a key intended for the given purpose. The chain may create a new key, derive one, or re-use an old one. */ /** Obtains a key intended for the given purpose. The chain may create a new key, derive one, or re-use an old one. */
public ECKey getKey(KeyPurpose purpose); public ECKey getKey(KeyPurpose purpose);

View file

@ -134,10 +134,22 @@ public class KeyChainGroup {
* to someone who wishes to send money. * to someone who wishes to send money.
*/ */
public DeterministicKey freshKey(KeyChain.KeyPurpose purpose) { public DeterministicKey freshKey(KeyChain.KeyPurpose purpose) {
return freshKeys(purpose,1).get(0);
}
/**
* 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
* {@link com.google.bitcoin.wallet.DeterministicKeyChain}. When the parameter is
* {@link com.google.bitcoin.wallet.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.
*/
public List<DeterministicKey> freshKeys(KeyChain.KeyPurpose purpose, int numberOfKeys) {
DeterministicKeyChain chain = getActiveKeyChain(); DeterministicKeyChain chain = getActiveKeyChain();
DeterministicKey key = chain.getKey(purpose); // Always returns the next key along the key chain. List<DeterministicKey> keys = chain.getKeys(purpose, numberOfKeys); // Always returns the next key along the key chain.
currentKeys.put(purpose, key); currentKeys.put(purpose, keys.get(keys.size() - 1));
return key; return keys;
} }
/** /**