Move HDPath methods from HDUtils to HDPath

All HDPath-related methods in HDUtils are now deprecated and delegate
to HDPath. HDPath now contains parsePath code.
This commit is contained in:
Sean Gilligan 2019-06-30 15:19:12 -07:00 committed by Andreas Schildbach
parent b59add47a7
commit 823a03db77
8 changed files with 70 additions and 46 deletions

View file

@ -79,13 +79,14 @@ public class DeterministicHierarchy {
* @throws IllegalArgumentException if create is false and the path was not found.
*/
public DeterministicKey get(List<ChildNumber> path, boolean relativePath, boolean create) {
HDPath inputPath = HDPath.of(path);
HDPath absolutePath = relativePath
? rootPath.extend(path)
: HDPath.of(path);
: inputPath;
if (!keys.containsKey(absolutePath)) {
if (!create)
throw new IllegalArgumentException(String.format(Locale.US, "No key found for %s path %s.",
relativePath ? "relative" : "absolute", HDUtils.formatPath(path)));
relativePath ? "relative" : "absolute", inputPath.toString()));
checkArgument(absolutePath.size() > 0, "Can't derive the master key: nothing to derive from.");
DeterministicKey parent = get(absolutePath.subList(0, absolutePath.size() - 1), false, true);
putKey(HDKeyDerivation.deriveChildKey(parent, absolutePath.get(absolutePath.size() - 1)));
@ -101,7 +102,7 @@ public class DeterministicHierarchy {
* @param relative whether the path is relative to the root path
* @param createParent whether the parent corresponding to path should be created (with any necessary ancestors) if it doesn't exist already
* @param privateDerivation whether to use private or public derivation
* @return next newly created key using the child derivation funtcion
* @return next newly created key using the child derivation function
* @throws IllegalArgumentException if the parent doesn't exist and createParent is false.
*/
public DeterministicKey deriveNextChild(List<ChildNumber> parentPath, boolean relative, boolean createParent, boolean privateDerivation) {
@ -116,14 +117,14 @@ public class DeterministicHierarchy {
throw new HDDerivationException("Maximum number of child derivation attempts reached, this is probably an indication of a bug.");
}
private ChildNumber getNextChildNumberToDerive(List<ChildNumber> path, boolean privateDerivation) {
private ChildNumber getNextChildNumberToDerive(HDPath path, boolean privateDerivation) {
ChildNumber lastChildNumber = lastChildNumbers.get(path);
ChildNumber nextChildNumber = new ChildNumber(lastChildNumber != null ? lastChildNumber.num() + 1 : 0, privateDerivation);
lastChildNumbers.put(HDPath.of(path), nextChildNumber);
lastChildNumbers.put(path, nextChildNumber);
return nextChildNumber;
}
public int getNumChildren(List<ChildNumber> path) {
public int getNumChildren(HDPath path) {
final ChildNumber cn = lastChildNumbers.get(path);
if (cn == null)
return 0;

View file

@ -188,10 +188,10 @@ public class DeterministicKey extends ECKey {
}
/**
* Returns the path of this key as a human readable string starting with M to indicate the master key.
* Returns the path of this key as a human readable string starting with M or m to indicate the master key.
*/
public String getPathAsString() {
return HDUtils.formatPath(getPath());
return getPath().toString();
}
/**
@ -558,7 +558,7 @@ public class DeterministicKey extends ECKey {
throw new IllegalArgumentException("Parent was provided but this key doesn't have one");
if (parent.getFingerprint() != parentFingerprint)
throw new IllegalArgumentException("Parent fingerprints don't match");
path = HDUtils.append(parent.getPath(), childNumber);
path = parent.getPath().extend(childNumber);
if (path.size() != depth)
throw new IllegalArgumentException("Depth does not match");
} else {

View file

@ -146,7 +146,7 @@ public final class HDKeyDerivation {
public static DeterministicKey deriveChildKeyFromPrivate(DeterministicKey parent, ChildNumber childNumber)
throws HDDerivationException {
RawKeyBytes rawKey = deriveChildKeyBytesFromPrivate(parent, childNumber);
return new DeterministicKey(HDUtils.append(parent.getPath(), childNumber), rawKey.chainCode,
return new DeterministicKey(parent.getPath().extend(childNumber), rawKey.chainCode,
new BigInteger(1, rawKey.keyBytes), parent);
}
@ -182,7 +182,7 @@ public final class HDKeyDerivation {
public static DeterministicKey deriveChildKeyFromPublic(DeterministicKey parent, ChildNumber childNumber,
PublicDeriveMode mode) throws HDDerivationException {
RawKeyBytes rawKey = deriveChildKeyBytesFromPublic(parent, childNumber, PublicDeriveMode.NORMAL);
return new DeterministicKey(HDUtils.append(parent.getPath(), childNumber), rawKey.chainCode,
return new DeterministicKey(parent.getPath().extend(childNumber), rawKey.chainCode,
new LazyECPoint(ECKey.CURVE.getCurve(), rawKey.keyBytes), null, parent);
}

View file

@ -16,6 +16,7 @@
package org.bitcoinj.crypto;
import javax.annotation.Nonnull;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
@ -66,6 +67,29 @@ public class HDPath extends AbstractList<ChildNumber> {
return HDPath.of(Collections.<ChildNumber>emptyList());
}
/**
* Create an HDPath from a path string. The path string is a human-friendly representation of the deterministic path. For example:
*
* "44H / 0H / 0H / 1 / 1"
*
* Where a letter "H" means hardened key. Spaces are ignored.
*/
public static HDPath parsePath(@Nonnull String path) {
String[] parsedNodes = path.replace("M", "").split("/");
List<ChildNumber> nodes = new ArrayList<>();
for (String n : parsedNodes) {
n = n.replaceAll(" ", "");
if (n.length() == 0) continue;
boolean isHard = n.endsWith("H");
if (isHard) n = n.substring(0, n.length() - 1);
int nodeNumber = Integer.parseInt(n);
nodes.add(new ChildNumber(nodeNumber, isHard));
}
return new HDPath(nodes);
}
/**
* Is this a path to a private key?
*

View file

@ -18,24 +18,19 @@
package org.bitcoinj.crypto;
import org.bitcoinj.core.ECKey;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Static utilities used in BIP 32 Hierarchical Deterministic Wallets (HDW).
*/
public final class HDUtils {
private static final Joiner PATH_JOINER = Joiner.on("/");
static HMac createHmacSha512Digest(byte[] key) {
SHA512Digest digest = new SHA512Digest();
@ -66,41 +61,43 @@ public final class HDUtils {
return bytes;
}
/** Append a derivation level to an existing path */
/**
* Append a derivation level to an existing path
*
* @deprecated Use {@code HDPath#extend}
*/
@Deprecated
public static HDPath append(List<ChildNumber> path, ChildNumber childNumber) {
return new HDPath(path).extend(childNumber);
}
/** Concatenate two derivation paths */
/**
* Concatenate two derivation paths
*
* @deprecated Use {@code HDPath#extend}
*/
@Deprecated
public static HDPath concat(List<ChildNumber> path, List<ChildNumber> path2) {
return new HDPath(path).extend(path2);
}
/** Convert to a string path, starting with "M/" */
/**
* Convert to a string path, starting with "M/"
*
* @deprecated Use {@code HDPath#toString}
*/
@Deprecated
public static String formatPath(List<ChildNumber> path) {
return PATH_JOINER.join(Iterables.concat(Collections.singleton("M"), path));
return HDPath.of(path).toString();
}
/**
* The path is a human-friendly representation of the deterministic path. For example:
* Create an HDPath from a path string.
*
* "44H / 0H / 0H / 1 / 1"
*
* Where a letter "H" means hardened key. Spaces are ignored.
* @deprecated Use {@code HDPath.parsePath}
*/
@Deprecated
public static HDPath parsePath(@Nonnull String path) {
String[] parsedNodes = path.replace("M", "").split("/");
List<ChildNumber> nodes = new ArrayList<>();
for (String n : parsedNodes) {
n = n.replaceAll(" ", "");
if (n.length() == 0) continue;
boolean isHard = n.endsWith("H");
if (isHard) n = n.substring(0, n.length() - 1);
int nodeNumber = Integer.parseInt(n);
nodes.add(new ChildNumber(nodeNumber, isHard));
}
return new HDPath(nodes);
return HDPath.parsePath(path);
}
}

View file

@ -412,8 +412,8 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
encryptNonLeaf(aesKey, chain, rootKey, getAccountPath().subList(0, i));
}
DeterministicKey account = encryptNonLeaf(aesKey, chain, rootKey, getAccountPath());
externalParentKey = encryptNonLeaf(aesKey, chain, account, HDUtils.concat(getAccountPath(), EXTERNAL_SUBPATH));
internalParentKey = encryptNonLeaf(aesKey, chain, account, HDUtils.concat(getAccountPath(), INTERNAL_SUBPATH));
externalParentKey = encryptNonLeaf(aesKey, chain, account, getAccountPath().extend(EXTERNAL_SUBPATH));
internalParentKey = encryptNonLeaf(aesKey, chain, account, getAccountPath().extend(INTERNAL_SUBPATH));
// Now copy the (pubkey only) leaf keys across to avoid rederiving them. The private key bytes are missing
// anyway so there's nothing to encrypt.
@ -503,7 +503,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
basicKeyChain.importKeys(lookahead);
List<DeterministicKey> keys = new ArrayList<>(numberOfKeys);
for (int i = 0; i < numberOfKeys; i++) {
HDPath path = HDUtils.append(parentKey.getPath(), new ChildNumber(index - numberOfKeys + i, false));
HDPath path = parentKey.getPath().extend(new ChildNumber(index - numberOfKeys + i, false));
DeterministicKey k = hierarchy.get(path, false, false);
// Just a last minute sanity check before we hand the key out to the app for usage. This isn't inspired
// by any real problem reports from bitcoinj users, but I've heard of cases via the grapevine of

View file

@ -1019,17 +1019,17 @@ public class KeyChainGroup implements KeyBag {
// kinds of KeyPurpose are introduced.
if (activeChain.getIssuedExternalKeys() > 0) {
DeterministicKey currentExternalKey = activeChain.getKeyByPath(
HDUtils.append(
HDUtils.concat(activeChain.getAccountPath(), DeterministicKeyChain.EXTERNAL_SUBPATH),
new ChildNumber(activeChain.getIssuedExternalKeys() - 1)));
activeChain.getAccountPath()
.extend(DeterministicKeyChain.EXTERNAL_SUBPATH)
.extend(new ChildNumber(activeChain.getIssuedExternalKeys() - 1)));
currentKeys.put(KeyChain.KeyPurpose.RECEIVE_FUNDS, currentExternalKey);
}
if (activeChain.getIssuedInternalKeys() > 0) {
DeterministicKey currentInternalKey = activeChain.getKeyByPath(
HDUtils.append(
HDUtils.concat(activeChain.getAccountPath(), DeterministicKeyChain.INTERNAL_SUBPATH),
new ChildNumber(activeChain.getIssuedInternalKeys() - 1)));
activeChain.getAccountPath()
.extend(DeterministicKeyChain.INTERNAL_SUBPATH)
.extend(new ChildNumber(activeChain.getIssuedInternalKeys() - 1)));
currentKeys.put(KeyChain.KeyPurpose.CHANGE, currentInternalKey);
}
return currentKeys;

View file

@ -125,6 +125,7 @@ public class HDUtilsTest {
}
// TODO: Move this test to HDPath test class and call HDPath directly
@Test
public void testFormatPath() {
Object[] tv = {
@ -154,6 +155,7 @@ public class HDUtilsTest {
}
// TODO: Move this test to HDPath test class and call HDPath directly
@Test
public void testParsePath() {
Object[] tv = {