From 42bfbb9b1c97a89a14f55a13348e809069b1131d Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 19 May 2014 16:40:22 +0200 Subject: [PATCH] Add some Javadocs and clean up the watching key API a tiny bit. Default creation time is now BIP32 standardisation time. --- HD Wallets TODO.txt | 3 ++- .../crypto/DeterministicHierarchy.java | 2 ++ .../bitcoin/wallet/DeterministicKeyChain.java | 19 +++++++++++++++++-- .../google/bitcoin/wallet/KeyChainGroup.java | 11 ++++++++--- .../wallet/DeterministicKeyChainTest.java | 7 ++++--- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/HD Wallets TODO.txt b/HD Wallets TODO.txt index 2eaf7060..65994872 100644 --- a/HD Wallets TODO.txt +++ b/HD Wallets TODO.txt @@ -1,4 +1,5 @@ -- Switch to the tree format agreed on with slush/stick +- Switch to the tree format agreed on with slush/stick. +- Store the account key creation time for an HD hierarchy. - Support seeds up to 512 bits in size. Test compatibility with greenaddress, at least for some keys. - Make Wallet notify key chains when a key has been observed in a transaction. - Make DeterministicKeyChain auto-extend when a key was observed, to keep a gap limit in place. diff --git a/core/src/main/java/com/google/bitcoin/crypto/DeterministicHierarchy.java b/core/src/main/java/com/google/bitcoin/crypto/DeterministicHierarchy.java index 540163a8..08db0ec1 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/DeterministicHierarchy.java +++ b/core/src/main/java/com/google/bitcoin/crypto/DeterministicHierarchy.java @@ -52,6 +52,8 @@ public class DeterministicHierarchy implements Serializable { // Keep track of how many child keys each node has. This is kind of weak. private final Map, ChildNumber> lastChildNumbers = Maps.newHashMap(); + public static final int BIP32_STANDARDISATION_TIME_SECS = 1369267200; + /** * 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. diff --git a/core/src/main/java/com/google/bitcoin/wallet/DeterministicKeyChain.java b/core/src/main/java/com/google/bitcoin/wallet/DeterministicKeyChain.java index 20b46c97..b5eefdd5 100644 --- a/core/src/main/java/com/google/bitcoin/wallet/DeterministicKeyChain.java +++ b/core/src/main/java/com/google/bitcoin/wallet/DeterministicKeyChain.java @@ -81,6 +81,8 @@ public class DeterministicKeyChain implements EncryptableKeyChain { private DeterministicHierarchy hierarchy; private DeterministicKey rootKey; private DeterministicSeed seed; + + // Ignored if seed != null. Useful for watching hierarchies. private long creationTimeSeconds = MnemonicCode.BIP39_STANDARDISATION_TIME_SECS; // Paths through the key tree. External keys are ones that are communicated to other parties. Internal keys are @@ -133,7 +135,11 @@ public class DeterministicKeyChain implements EncryptableKeyChain { this(new DeterministicSeed(seed, seedCreationTimeSecs)); } - public DeterministicKeyChain(DeterministicSeed seed) { + /** + * Creates a deterministic key chain starting from the given seed. All keys yielded by this chain will be the same + * if the starting seed is the same. + */ + protected DeterministicKeyChain(DeterministicSeed seed) { this(seed, null); } @@ -155,10 +161,19 @@ public class DeterministicKeyChain implements EncryptableKeyChain { this(watchingKey, Utils.currentTimeSeconds()); } + /** + * Creates a key chain that watches the given account key. The creation time is taken to be the time that BIP 32 + * was standardised: most likely, you can optimise by selecting a more accurate creation time for your key and + * using the other watch method. + */ public static DeterministicKeyChain watch(DeterministicKey accountKey) { - return new DeterministicKeyChain(accountKey); + return watch(accountKey, DeterministicHierarchy.BIP32_STANDARDISATION_TIME_SECS); } + /** + * Creates a key chain that watches the given account key, and assumes there are no transactions involving it until + * the given time (this is an optimisation for chain scanning purposes). + */ public static DeterministicKeyChain watch(DeterministicKey accountKey, long seedCreationTimeSecs) { return new DeterministicKeyChain(accountKey, seedCreationTimeSecs); } diff --git a/core/src/main/java/com/google/bitcoin/wallet/KeyChainGroup.java b/core/src/main/java/com/google/bitcoin/wallet/KeyChainGroup.java index 79e3111b..ecd686cd 100644 --- a/core/src/main/java/com/google/bitcoin/wallet/KeyChainGroup.java +++ b/core/src/main/java/com/google/bitcoin/wallet/KeyChainGroup.java @@ -73,11 +73,16 @@ public class KeyChainGroup { * This HAS to be an account key as returned by {@link DeterministicKeyChain#getWatchingKey()}. */ public KeyChainGroup(DeterministicKey watchKey) { - this(null, ImmutableList.of(new DeterministicKeyChain(watchKey)), null); + this(null, ImmutableList.of(DeterministicKeyChain.watch(watchKey)), null); } - public KeyChainGroup(DeterministicKey watchKey, long creationTimeSeconds) { - this(null, ImmutableList.of(new DeterministicKeyChain(watchKey, creationTimeSeconds)), null); + /** + * Creates a keychain group with no basic chain, and an HD chain that is watching the given watching key which + * was assumed to be first used at the given UNIX time. + * This HAS to be an account key as returned by {@link DeterministicKeyChain#getWatchingKey()}. + */ + public KeyChainGroup(DeterministicKey watchKey, long creationTimeSecondsSecs) { + this(null, ImmutableList.of(DeterministicKeyChain.watch(watchKey, creationTimeSecondsSecs)), null); } // Used for deserialization. diff --git a/core/src/test/java/com/google/bitcoin/wallet/DeterministicKeyChainTest.java b/core/src/test/java/com/google/bitcoin/wallet/DeterministicKeyChainTest.java index d9c9a6f8..06a2b073 100644 --- a/core/src/test/java/com/google/bitcoin/wallet/DeterministicKeyChainTest.java +++ b/core/src/test/java/com/google/bitcoin/wallet/DeterministicKeyChainTest.java @@ -17,6 +17,7 @@ package com.google.bitcoin.wallet; import com.google.bitcoin.core.*; +import com.google.bitcoin.crypto.DeterministicHierarchy; import com.google.bitcoin.crypto.DeterministicKey; import com.google.bitcoin.params.UnitTestParams; import com.google.bitcoin.store.UnreadableWalletException; @@ -228,8 +229,8 @@ public class DeterministicKeyChainTest { final String pub58 = watchingKey.serializePubB58(); assertEquals("xpub68KFnj3bqUx1s7mHejLDBPywCAKdJEu1b49uniEEn2WSbHmZ7xbLqFTjJbtx1LUcAt1DwhoqWHmo2s5WMJp6wi38CiF2hYD49qVViKVvAoi", pub58); watchingKey = DeterministicKey.deserializeB58(null, pub58); - chain = new DeterministicKeyChain(watchingKey); - assertEquals(Utils.currentTimeSeconds(), chain.getEarliestKeyCreationTime()); + chain = DeterministicKeyChain.watch(watchingKey); + assertEquals(DeterministicHierarchy.BIP32_STANDARDISATION_TIME_SECS, chain.getEarliestKeyCreationTime()); chain.setLookaheadSize(10); assertEquals(key1.getPubKeyPoint(), chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).getPubKeyPoint()); @@ -254,7 +255,7 @@ public class DeterministicKeyChainTest { @Test(expected = IllegalStateException.class) public void watchingCannotEncrypt() throws Exception { final DeterministicKey accountKey = chain.getKeyByPath(DeterministicKeyChain.ACCOUNT_ZERO_PATH); - chain = new DeterministicKeyChain(accountKey.getPubOnly()); + chain = DeterministicKeyChain.watch(accountKey.getPubOnly()); chain = chain.toEncrypted("this doesn't make any sense"); }