From 50dd5af0c8661f6d6035c49ff42b9484b9da9d41 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 11 Jul 2013 13:10:07 +0200 Subject: [PATCH] Add more javadocs. Delete the wallet integration code - it's good, but we won't ship it in 0.10 as it's not finished. --- .../google/bitcoin/crypto/hd/ChildNumber.java | 7 +- .../crypto/hd/DeterministicHierarchy.java | 57 ++++++++++--- .../crypto/hd/ExtendedHierarchicKey.java | 27 +++++- .../bitcoin/crypto/hd/HDKeyDerivation.java | 19 ++--- .../crypto/hd/wallet/DefaultKeyGenerator.java | 38 --------- .../hd/wallet/DeterministicKeyGenerator.java | 85 ------------------- .../crypto/hd/wallet/WalletKeyGenerator.java | 16 ---- 7 files changed, 81 insertions(+), 168 deletions(-) delete mode 100644 core/src/main/java/com/google/bitcoin/crypto/hd/wallet/DefaultKeyGenerator.java delete mode 100644 core/src/main/java/com/google/bitcoin/crypto/hd/wallet/DeterministicKeyGenerator.java delete mode 100644 core/src/main/java/com/google/bitcoin/crypto/hd/wallet/WalletKeyGenerator.java diff --git a/core/src/main/java/com/google/bitcoin/crypto/hd/ChildNumber.java b/core/src/main/java/com/google/bitcoin/crypto/hd/ChildNumber.java index b88190a9..51bd27f7 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/hd/ChildNumber.java +++ b/core/src/main/java/com/google/bitcoin/crypto/hd/ChildNumber.java @@ -17,10 +17,9 @@ package com.google.bitcoin.crypto.hd; /** - * This is just a wrapper for the i (child number) as per BIP 32 with a boolean getter for the first bit and a getter - * for the actual 0-based child number. - * - * This class is immutable. + *

This is just a wrapper for the i (child number) as per BIP 32 with a boolean getter for the first bit and a getter + * for the actual 0-based child number. A {@link List} of these forms a path through a + * {@link DeterministicHierarchy}. This class is immutable. */ public class ChildNumber { public static final int PRIV_BIT = 0x80000000; diff --git a/core/src/main/java/com/google/bitcoin/crypto/hd/DeterministicHierarchy.java b/core/src/main/java/com/google/bitcoin/crypto/hd/DeterministicHierarchy.java index 1773df16..c9f27ba1 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/hd/DeterministicHierarchy.java +++ b/core/src/main/java/com/google/bitcoin/crypto/hd/DeterministicHierarchy.java @@ -16,7 +16,6 @@ package com.google.bitcoin.crypto.hd; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; @@ -24,9 +23,19 @@ import java.io.Serializable; import java.util.List; import java.util.Map; +import static com.google.common.base.Preconditions.checkArgument; + /** - * A DeterministicHierarchy calculates and keeps a whole tree (hierarchy) of keys originating from a single - * root key. + *

A DeterministicHierarchy calculates and keeps a whole tree (hierarchy) of keys originating from a single + * root key. This implements part of the BIP 32 specification. A deterministic key tree is useful because + * Bitcoin's privacy system require new keys to be created for each transaction, but managing all these + * keys quickly becomes unwieldy. In particular it becomes hard to back up and distribute them. By having + * a way to derive random-looking but deterministic keys we can make wallet backup simpler and gain the + * ability to hand out {@link ExtendedHierarchicKey}s to other people who can then create new addresses + * on the fly, without having to contact us.

+ * + *

The hierarchy is started from a single root key, and a location in the tree is given by a path which + * is a list of {@link ChildNumber}s.

*/ public class DeterministicHierarchy implements Serializable { /** @@ -40,6 +49,10 @@ public class DeterministicHierarchy implements Serializable { private final Map, ChildNumber> lastPrivDerivedNumbers = Maps.newHashMap(); private final Map, ChildNumber> lastPubDerivedNumbers = Maps.newHashMap(); + /** + * Constructs a new hierarchy rooted at the given key. Note that this does not have to be the top of the tree. + * You can construct a DeterministicHierarchy for a subtree of a larger tree that you may not own. + */ public DeterministicHierarchy(ExtendedHierarchicKey rootKey) { putKey(rootKey); rootPath = rootKey.getChildNumberPath(); @@ -50,33 +63,40 @@ public class DeterministicHierarchy implements Serializable { } /** + * Returns a key for the given path, optionally creating it. + * * @param path the path to the key * @param relativePath whether the path is relative to the root path * @param create whether the key corresponding to path should be created (with any necessary ancestors) if it doesn't exist already - * @return next newly created key using the child derivation funtcion + * @return next newly created key using the child derivation function + * @throws IllegalArgumentException if create is false and the path was not found. */ public ExtendedHierarchicKey get(List path, boolean relativePath, boolean create) { ImmutableList absolutePath = relativePath ? ImmutableList.builder().addAll(rootPath).addAll(path).build() : ImmutableList.copyOf(path); if (!keys.containsKey(absolutePath)) { - Preconditions.checkArgument(create, "No key found for {} path {}.", relativePath ? "relative" : "absolute", path); - Preconditions.checkArgument(absolutePath.size() > 0, "Can't derive the master key: nothing to derive from."); - ExtendedHierarchicKey parent = get(absolutePath.subList(0, absolutePath.size() - 1), relativePath, create); + checkArgument(create, "No key found for {} path {}.", relativePath ? "relative" : "absolute", path); + checkArgument(absolutePath.size() > 0, "Can't derive the master key: nothing to derive from."); + ExtendedHierarchicKey parent = get(absolutePath.subList(0, absolutePath.size() - 1), relativePath, true); putKey(HDKeyDerivation.deriveChildKey(parent, absolutePath.get(absolutePath.size() - 1))); } return keys.get(absolutePath); } /** + * Extends the tree by calculating the next key that hangs off the given parent path. For example, if you pass a + * path of 1/2 here and there are already keys 1/2/1 and 1/2/2 then it will derive 1/2/3. + * * @param parentPath the path to the parent - * @param relativePath whether the path is relative to the root path + * @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 + * @throws IllegalArgumentException if the parent doesn't exist and createParent is false. */ - public ExtendedHierarchicKey deriveNextChild(ImmutableList parentPath, boolean relativePath, boolean createParent, boolean privateDerivation) { - ExtendedHierarchicKey parent = get(parentPath, relativePath, createParent); + public ExtendedHierarchicKey deriveNextChild(ImmutableList parentPath, boolean relative, boolean createParent, boolean privateDerivation) { + ExtendedHierarchicKey parent = get(parentPath, relative, createParent); int nAttempts = 0; while (nAttempts++ < MAX_CHILD_DERIVATION_ATTEMPTS) { try { @@ -95,8 +115,18 @@ public class DeterministicHierarchy implements Serializable { return nextChildNumber; } - public ExtendedHierarchicKey deriveChild(List parentPath, boolean relativePath, boolean createParent, ChildNumber createChildNumber) { - return deriveChild(get(parentPath, relativePath, createParent), createChildNumber); + /** + * Extends the tree by calculating the requested child for the given path. For example, to get the key at position + * 1/2/3 you would pass 1/2 as the parent path and 3 as the child number. + * + * @param parentPath the path to the parent + * @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 + * @return the requested key. + * @throws IllegalArgumentException if the parent doesn't exist and createParent is false. + */ + public ExtendedHierarchicKey deriveChild(List parentPath, boolean relative, boolean createParent, ChildNumber createChildNumber) { + return deriveChild(get(parentPath, relative, createParent), createChildNumber); } private ExtendedHierarchicKey deriveChild(ExtendedHierarchicKey parent, ChildNumber createChildNumber) { @@ -105,6 +135,9 @@ public class DeterministicHierarchy implements Serializable { return childKey; } + /** + * Returns the root key that the {@link DeterministicHierarchy} was created with. + */ public ExtendedHierarchicKey getRootKey() { return get(rootPath, false, false); } diff --git a/core/src/main/java/com/google/bitcoin/crypto/hd/ExtendedHierarchicKey.java b/core/src/main/java/com/google/bitcoin/crypto/hd/ExtendedHierarchicKey.java index e534d707..8b6f0290 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/hd/ExtendedHierarchicKey.java +++ b/core/src/main/java/com/google/bitcoin/crypto/hd/ExtendedHierarchicKey.java @@ -32,7 +32,9 @@ import java.util.Arrays; import java.util.Collections; /** - * Extended key as per BIP 32 is a pair (key, chaincode). + * An extended key is a node in a {@link DeterministicHierarchy}. As per + * the BIP 32 specification it is a pair (key, chaincode). If you + * know its path in the tree you can derive more keys from this. */ public class ExtendedHierarchicKey implements Serializable { public static final ChildNumber MASTER_CHILD_NUMBER = new ChildNumber(0); @@ -57,6 +59,11 @@ public class ExtendedHierarchicKey implements Serializable { this.privateAsFieldElement = privateKeyFieldElt; } + /** + * Returns the path through some {@link DeterministicHierarchy} which reaches this keys position in the tree. + * A path can be written as 1/2/1 which means the first child of the root, the second child of that node, then + * the first child of that node. + */ public ImmutableList getChildNumberPath() { return childNumberPath; } @@ -65,18 +72,30 @@ public class ExtendedHierarchicKey implements Serializable { return childNumberPath.size(); } + /** + * Returns the last element of the path returned by {@link com.google.bitcoin.crypto.hd.ExtendedHierarchicKey#getChildNumberPath()} + */ public ChildNumber getChildNumber() { return getDepth() == 0 ? MASTER_CHILD_NUMBER : childNumberPath.get(childNumberPath.size() - 1); } - byte[] getChainCode() { + /** + * Returns the chain code associated with this key. See the specification to learn more about chain codes. + */ + public byte[] getChainCode() { return chainCode; } + /** + * Returns the path of this key as a human readable string starting with M to indicate the master key. + */ public String getPath() { return PATH_JOINER.join(Iterables.concat(Collections.singleton("M"), getChildNumberPath())); } + /** + * Returns RIPE-MD160(SHA256(pub key bytes)). + */ public byte[] getIdentifier() { return Utils.sha256hash160(getPubKeyBytes()); } @@ -92,8 +111,10 @@ public class ExtendedHierarchicKey implements Serializable { return getPubPoint().getEncoded(); } + + /** Returns the first 32 bits of the result of {@link #getIdentifier()}. */ public byte[] getFingerprint() { - // todo: why is this different than armory's fingerprint? BIP 32: "The first 32 bits of the identifier are called the fingerprint." + // TODO: why is this different than armory's fingerprint? BIP 32: "The first 32 bits of the identifier are called the fingerprint." return Arrays.copyOfRange(getIdentifier(), 0, 4); } diff --git a/core/src/main/java/com/google/bitcoin/crypto/hd/HDKeyDerivation.java b/core/src/main/java/com/google/bitcoin/crypto/hd/HDKeyDerivation.java index c5364272..caca4189 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/hd/HDKeyDerivation.java +++ b/core/src/main/java/com/google/bitcoin/crypto/hd/HDKeyDerivation.java @@ -24,6 +24,7 @@ import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.Arrays; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; /** @@ -36,6 +37,10 @@ public final class HDKeyDerivation { private static final HMac MASTER_HMAC_SHA256 = HDUtils.createHmacSha256Digest("Bitcoin seed".getBytes()); /** + * Generates a new deterministic key from the given seed, which can be any arbitrary byte array. However resist + * the temptation to use a string as the seed - any key derived from a password is likely to be weak and easily + * broken by attackers (this is not theoretical, people have had money stolen that way). + * * @throws HDDerivationException if generated master key is invalid (private key 0 or >= n). */ public static ExtendedHierarchicKey createMasterPrivateKey(byte[] seed) throws HDDerivationException { @@ -114,9 +119,9 @@ public final class HDKeyDerivation { assertNonZero(ki, "Illegal derived key: derived private key equals 0."); keyBytes = ki.toByteArray(); } else { - assertArgument(!childNumber.isPrivateDerivation(), "Can't use private derivation with public keys only."); + checkArgument(!childNumber.isPrivateDerivation(), "Can't use private derivation with public keys only."); ECPoint Ki = HDUtils.getEcParams().getG().multiply(ilInt).add(parent.getPubPoint()); - assertArgument(!Ki.equals(HDUtils.getCurve().getInfinity()), + checkArgument(!Ki.equals(HDUtils.getCurve().getInfinity()), "Illegal derived key: derived public key equals infinity."); keyBytes = HDUtils.toCompressed(Ki.getEncoded()); } @@ -124,17 +129,11 @@ public final class HDKeyDerivation { } private static void assertNonZero(BigInteger integer, String errorMessage) { - assertArgument(!integer.equals(BigInteger.ZERO), errorMessage); + checkArgument(!integer.equals(BigInteger.ZERO), errorMessage); } private static void assertLessThanN(BigInteger integer, String errorMessage) { - assertArgument(integer.compareTo(HDUtils.getEcParams().getN()) < 0, errorMessage); - } - - private static void assertArgument(boolean assertion, String errorMessage) { - if (!assertion) { - throw new HDDerivationException(errorMessage); - } + checkArgument(integer.compareTo(HDUtils.getEcParams().getN()) < 0, errorMessage); } private static class RawKeyBytes { diff --git a/core/src/main/java/com/google/bitcoin/crypto/hd/wallet/DefaultKeyGenerator.java b/core/src/main/java/com/google/bitcoin/crypto/hd/wallet/DefaultKeyGenerator.java deleted file mode 100644 index 1aced627..00000000 --- a/core/src/main/java/com/google/bitcoin/crypto/hd/wallet/DefaultKeyGenerator.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.google.bitcoin.crypto.hd.wallet; - -import com.google.bitcoin.core.ECKey; - -import java.io.Serializable; -import java.util.ArrayList; - -import static com.google.common.base.Preconditions.checkState; - -/** - * @author Matija Mazi
- * - * The default WalletKeyGenerator implementation, creating random receiving keys and always returning the second - * existing keychain key (or first if only one). - */ -public class DefaultKeyGenerator implements WalletKeyGenerator, Serializable { - - @Override - public ECKey nextReceivingKey() { - return new ECKey(); - } - - @Override - public ECKey nextChangeKey(ArrayList keychain) { - // For now let's just pick the second key in our keychain. In future we might want to do something else to - // give the user better privacy here, eg in incognito mode. - // The second key is chosen rather than the first because, by default, a wallet is created with a - // single key. If the user imports say a blockchain.info backup they typically want change to go - // to one of the imported keys - checkState(keychain.size() > 0, "Can't send value without an address to use for receiving change"); - ECKey change = keychain.get(0); - - if (keychain.size() > 1) { - change = keychain.get(1); - } - return change; - } -} diff --git a/core/src/main/java/com/google/bitcoin/crypto/hd/wallet/DeterministicKeyGenerator.java b/core/src/main/java/com/google/bitcoin/crypto/hd/wallet/DeterministicKeyGenerator.java deleted file mode 100644 index 77304c2f..00000000 --- a/core/src/main/java/com/google/bitcoin/crypto/hd/wallet/DeterministicKeyGenerator.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.google.bitcoin.crypto.hd.wallet; - -import com.google.bitcoin.core.ECKey; -import com.google.bitcoin.crypto.hd.ChildNumber; -import com.google.bitcoin.crypto.hd.DeterministicHierarchy; -import com.google.bitcoin.crypto.hd.ExtendedHierarchicKey; -import com.google.common.collect.ImmutableList; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Serializable; -import java.util.ArrayList; - -/** - * @author Matija Mazi
- * - * A WalletKeyGenerator, used by Wallet, that uses BIP32 for key generation: internal deterministic chain for change - * keys and external chain for receiving keys. - */ -public class DeterministicKeyGenerator implements WalletKeyGenerator, Serializable { - private static final Logger log = LoggerFactory.getLogger(DeterministicKeyGenerator.class); - - public static final boolean PRIVATE_DERIVATION = true; - - public static final ChildNumber EXTERNAL_CHAIN = new ChildNumber(0, PRIVATE_DERIVATION); - public static final ChildNumber INTERNAL_CHAIN = new ChildNumber(1, PRIVATE_DERIVATION); - - // private final NetworkParameters params; - private final DeterministicHierarchy hierarchy; - private final ImmutableList externalChainRootPath; - private final ImmutableList internalChainRootPath; - - public DeterministicKeyGenerator(ExtendedHierarchicKey rootKey) { - log.debug("DeterministicKeyGenerator.DeterministicKeyGenerator"); - long start = System.currentTimeMillis(); -// this.params = params; - hierarchy = new DeterministicHierarchy(rootKey); - externalChainRootPath = hierarchy.deriveChild(rootKey.getChildNumberPath(), false, false, EXTERNAL_CHAIN).getChildNumberPath(); - internalChainRootPath = hierarchy.deriveChild(rootKey.getChildNumberPath(), false, false, INTERNAL_CHAIN).getChildNumberPath(); - log.debug("DKG constructor took {}", System.currentTimeMillis() - start); - } - - public ExtendedHierarchicKey nextExternal() { - log.debug("DeterministicKeyGenerator.nextExternal"); - return hierarchy.deriveNextChild(externalChainRootPath, false, false, PRIVATE_DERIVATION); - } - - public ExtendedHierarchicKey nextInternal() { - log.debug("DeterministicKeyGenerator.nextInternal"); - return hierarchy.deriveNextChild(internalChainRootPath, false, false, PRIVATE_DERIVATION); - } - - @Override - public ECKey nextReceivingKey() { - return nextExternal().toECKey(); - } - - @Override - public ECKey nextChangeKey(ArrayList keychain) { - return nextInternal().toECKey(); - } - - /* - private Address nextExternalAddress() { - return nextExternal().toECKey().toAddress(getParams()); - } - - private Address nextInternalAddress() { - return nextInternal().toECKey().toAddress(getParams()); - } - - synchronized Address getChangeAddress() { - return nextInternalAddress(); - } - - public ExtendedHierarchicKey getRootKey() { - return hierarchy.getRootKey(); - } - - public NetworkParameters getParams() { - return params; - } -*/ -} - diff --git a/core/src/main/java/com/google/bitcoin/crypto/hd/wallet/WalletKeyGenerator.java b/core/src/main/java/com/google/bitcoin/crypto/hd/wallet/WalletKeyGenerator.java deleted file mode 100644 index de58486f..00000000 --- a/core/src/main/java/com/google/bitcoin/crypto/hd/wallet/WalletKeyGenerator.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.google.bitcoin.crypto.hd.wallet; - -import com.google.bitcoin.core.ECKey; - -import java.util.ArrayList; - -/** - * @author Matija Mazi
- * - * Used by {@link com.google.bitcoin.core.Wallet} to generate receiving and change keys. - */ -public interface WalletKeyGenerator { - ECKey nextReceivingKey(); - - ECKey nextChangeKey(ArrayList keychain); -}