Made some methods public in BasicKeyChain. Fixes in deterministic key generation

This commit is contained in:
Giannis Dzegoutanis 2014-07-14 18:16:30 +02:00 committed by Mike Hearn
parent 72b7929523
commit 5e1942f3f0
4 changed files with 56 additions and 24 deletions

View file

@ -41,12 +41,6 @@ import static com.google.common.base.Preconditions.checkArgument;
* is a list of {@link ChildNumber}s.</p>
*/
public class DeterministicHierarchy implements Serializable {
/**
* Child derivation may fail (although with extremely low probability); in such case it is re-attempted.
* This is the maximum number of re-attempts (to avoid an infinite loop in case of bugs etc.).
*/
private static final int MAX_CHILD_DERIVATION_ATTEMPTS = 100;
private final Map<ImmutableList<ChildNumber>, DeterministicKey> keys = Maps.newHashMap();
private final ImmutableList<ChildNumber> rootPath;
// Keep track of how many child keys each node has. This is kind of weak.
@ -115,7 +109,7 @@ public class DeterministicHierarchy implements Serializable {
public DeterministicKey deriveNextChild(ImmutableList<ChildNumber> parentPath, boolean relative, boolean createParent, boolean privateDerivation) {
DeterministicKey parent = get(parentPath, relative, createParent);
int nAttempts = 0;
while (nAttempts++ < MAX_CHILD_DERIVATION_ATTEMPTS) {
while (nAttempts++ < HDKeyDerivation.MAX_CHILD_DERIVATION_ATTEMPTS) {
try {
ChildNumber createChildNumber = getNextChildNumberToDerive(parent.getPath(), privateDerivation);
return deriveChild(parent, createChildNumber);

View file

@ -37,6 +37,12 @@ public final class HDKeyDerivation {
private HDKeyDerivation() { }
/**
* Child derivation may fail (although with extremely low probability); in such case it is re-attempted.
* This is the maximum number of re-attempts (to avoid an infinite loop in case of bugs etc.).
*/
public static final int MAX_CHILD_DERIVATION_ATTEMPTS = 100;
private static final HMac MASTER_HMAC_SHA512 = HDUtils.createHmacSha512Digest("Bitcoin seed".getBytes());
/**
@ -88,6 +94,25 @@ public final class HDKeyDerivation {
return deriveChildKey(parent, new ChildNumber(childNumber));
}
/**
* Derives a key of the "extended" child number, ie. with the 0x80000000 bit specifying whether to use
* hardened derivation or not. If derivation fails, tries a next child.
*/
public static DeterministicKey deriveThisOrNextChildKey(DeterministicKey parent, int childNumber) {
int nAttempts = 0;
ChildNumber child = new ChildNumber(childNumber);
boolean isHardened = child.isHardened();
while (nAttempts < MAX_CHILD_DERIVATION_ATTEMPTS) {
try {
child = new ChildNumber(child.num() + nAttempts, isHardened);
return deriveChildKey(parent, child);
} catch (HDDerivationException ignore) { }
nAttempts++;
}
throw new HDDerivationException("Maximum number of child derivation attempts reached, this is probably an indication of a bug.");
}
/**
* @throws HDDerivationException if private derivation is attempted for a public-only parent key, or
* if the resulting derived key is invalid (eg. private key == 0).
@ -147,20 +172,25 @@ public final class HDKeyDerivation {
byte[] il = Arrays.copyOfRange(i, 0, 32);
byte[] chainCode = Arrays.copyOfRange(i, 32, 64);
BigInteger ilInt = new BigInteger(1, il);
// TODO: Throw a specific exception here to make sure the caller iterates.
assertLessThanN(ilInt, "Illegal derived key: I_L >= n");
ECPoint Ki = ECKey.CURVE.getG().multiply(ilInt).add(parent.getPubKeyPoint());
checkArgument(!Ki.equals(ECKey.CURVE.getCurve().getInfinity()),
"Illegal derived key: derived public key equals infinity.");
assertNonInfinity(Ki, "Illegal derived key: derived public key equals infinity.");
return new RawKeyBytes(Ki.getEncoded(true), chainCode);
}
private static void assertNonZero(BigInteger integer, String errorMessage) {
checkArgument(!integer.equals(BigInteger.ZERO), errorMessage);
if (integer.equals(BigInteger.ZERO))
throw new HDDerivationException(errorMessage);
}
private static void assertNonInfinity(ECPoint point, String errorMessage) {
if (point.equals(ECKey.CURVE.getCurve().getInfinity()))
throw new HDDerivationException(errorMessage);
}
private static void assertLessThanN(BigInteger integer, String errorMessage) {
checkArgument(integer.compareTo(ECKey.CURVE.getN()) < 0, errorMessage);
if (integer.compareTo(ECKey.CURVE.getN()) > 0)
throw new HDDerivationException(errorMessage);
}
private static class RawKeyBytes {

View file

@ -55,7 +55,7 @@ public class BasicKeyChain implements EncryptableKeyChain {
this(null);
}
BasicKeyChain(@Nullable KeyCrypter crypter) {
public BasicKeyChain(@Nullable KeyCrypter crypter) {
this.keyCrypter = crypter;
hashToKeys = new LinkedHashMap<ByteString, ECKey>();
pubkeyToKeys = new LinkedHashMap<ByteString, ECKey>();
@ -178,9 +178,14 @@ public class BasicKeyChain implements EncryptableKeyChain {
}
}
/* package */ void importKey(ECKey key) {
/**
* Imports a key to the key chain. If key is present in the key chain, ignore it.
*/
public void importKey(ECKey key) {
lock.lock();
try {
checkKeyEncryptionStateMatches(key);
if (hasKey(key)) return;
importKeyLocked(key);
queueOnKeysAdded(ImmutableList.of(key));
} finally {

View file

@ -98,8 +98,8 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
private final ReentrantLock lock = Threading.lock("DeterministicKeyChain");
private DeterministicHierarchy hierarchy;
private DeterministicKey rootKey;
private DeterministicSeed seed;
@Nullable private DeterministicKey rootKey;
@Nullable private DeterministicSeed seed;
// Ignored if seed != null. Useful for watching hierarchies.
private long creationTimeSeconds = MnemonicCode.BIP39_STANDARDISATION_TIME_SECS;
@ -111,7 +111,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
// a payment request that can generate lots of addresses independently.
public static final ImmutableList<ChildNumber> ACCOUNT_ZERO_PATH = ImmutableList.of(ChildNumber.ZERO_HARDENED);
public static final ImmutableList<ChildNumber> EXTERNAL_PATH = ImmutableList.of(ChildNumber.ZERO_HARDENED, ChildNumber.ZERO);
public static final ImmutableList<ChildNumber> INTERNAL_PATH = ImmutableList.of(ChildNumber.ZERO_HARDENED, new ChildNumber(1, false));
public static final ImmutableList<ChildNumber> INTERNAL_PATH = ImmutableList.of(ChildNumber.ZERO_HARDENED, ChildNumber.ONE);
// We try to ensure we have at least this many keys ready and waiting to be handed out via getKey().
// See docs for getLookaheadSize() for more info on what this is for. The -1 value means it hasn't been calculated
@ -245,10 +245,10 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
rootKey = HDKeyDerivation.createMasterPrivateKey(checkNotNull(seed.getSeedBytes()));
rootKey.setCreationTimeSeconds(seed.getCreationTimeSeconds());
initializeHierarchyUnencrypted(rootKey);
} else {
// We can't initialize ourselves with just an encrypted seed, so we expected deserialization code to do the
// rest of the setup (loading the root key).
}
// Else...
// We can't initialize ourselves with just an encrypted seed, so we expected deserialization code to do the
// rest of the setup (loading the root key).
}
// For use in encryption.
@ -349,7 +349,6 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
default:
throw new UnsupportedOperationException();
}
// TODO: Handle the case where the derived key is >= curve order.
List<DeterministicKey> lookahead = maybeLookAhead(parentKey, index);
basicKeyChain.importKeys(lookahead);
List<DeterministicKey> keys = new ArrayList<DeterministicKey>(numberOfKeys);
@ -513,8 +512,11 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
return basicKeyChain.removeEventListener(listener);
}
/** Returns a list of words that represent the seed. */
/** Returns a list of words that represent the seed or null if this chain is a watching chain. */
@Nullable
public List<String> getMnemonicCode() {
if (seed == null) return null;
lock.lock();
try {
return seed.getMnemonicCode();
@ -922,12 +924,13 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
List<DeterministicKey> result = new ArrayList<DeterministicKey>(needed);
long now = System.currentTimeMillis();
log.info("Pre-generating {} keys for {}", needed, parent.getPathAsString());
int nextChild = numChildren;
for (int i = 0; i < needed; i++) {
// TODO: Handle the case where the derived key is >= curve order.
DeterministicKey key = HDKeyDerivation.deriveChildKey(parent, numChildren + i);
DeterministicKey key = HDKeyDerivation.deriveThisOrNextChildKey(parent, nextChild);
key = key.getPubOnly();
hierarchy.putKey(key);
result.add(key);
nextChild = key.getChildNumber().num() + 1;
}
log.info("Took {} msec", System.currentTimeMillis() - now);
return result;