diff --git a/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java b/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java index 9a2cce4d..482a6794 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java +++ b/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java @@ -28,7 +28,7 @@ public class DefaultKeyChainFactory implements KeyChainFactory { public DeterministicKeyChain makeKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicSeed seed, KeyCrypter crypter, boolean isMarried) { DeterministicKeyChain chain; if (isMarried) - chain = new MarriedKeyChain(seed, crypter); + chain = new MarriedKeyChain(seed, crypter, null); else chain = new DeterministicKeyChain(seed, crypter); return chain; @@ -39,7 +39,7 @@ public class DefaultKeyChainFactory implements KeyChainFactory { KeyCrypter crypter, boolean isMarried, ImmutableList accountPath) { DeterministicKeyChain chain; if (isMarried) - chain = new MarriedKeyChain(seed, crypter); + chain = new MarriedKeyChain(seed, crypter, accountPath); else chain = new DeterministicKeyChain(seed, crypter, accountPath); return chain; diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java index 23300cf0..8421b5b1 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java @@ -167,7 +167,8 @@ public class DeterministicKeyChain implements EncryptableKeyChain { protected long seedCreationTimeSecs; protected byte[] entropy; protected DeterministicSeed seed; - protected DeterministicKey watchingKey; + protected DeterministicKey watchingKey = null; + protected ImmutableList accountPath = null; protected Builder() { } @@ -221,6 +222,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain { } public T watchingKey(DeterministicKey watchingKey) { + checkState(accountPath == null, "either watchingKey or accountPath"); this.watchingKey = watchingKey; return self(); } @@ -237,19 +239,31 @@ public class DeterministicKeyChain implements EncryptableKeyChain { return self(); } + /** + * Use an account path other than the default {@link DeterministicKeyChain#ACCOUNT_ZERO_PATH}. + */ + public T accountPath(ImmutableList accountPath) { + checkState(watchingKey == null, "either watchingKey or accountPath"); + this.accountPath = accountPath; + return self(); + } + public DeterministicKeyChain build() { checkState(random != null || entropy != null || seed != null || watchingKey!= null, "Must provide either entropy or random or seed or watchingKey"); checkState(passphrase == null || seed == null, "Passphrase must not be specified with seed"); - DeterministicKeyChain chain; + if (accountPath == null) + accountPath = ACCOUNT_ZERO_PATH; + + DeterministicKeyChain chain; if (random != null) { // Default passphrase to "" if not specified - chain = new DeterministicKeyChain(random, bits, getPassphrase(), seedCreationTimeSecs); + chain = new DeterministicKeyChain(new DeterministicSeed(random, bits, getPassphrase(), seedCreationTimeSecs), null, accountPath); } else if (entropy != null) { - chain = new DeterministicKeyChain(entropy, getPassphrase(), seedCreationTimeSecs); + chain = new DeterministicKeyChain(new DeterministicSeed(entropy, getPassphrase(), seedCreationTimeSecs), null, accountPath); } else if (seed != null) { seed.setCreationTimeSeconds(seedCreationTimeSecs); - chain = new DeterministicKeyChain(seed); + chain = new DeterministicKeyChain(seed, null, accountPath); } else { watchingKey.setCreationTimeSeconds(seedCreationTimeSecs); chain = new DeterministicKeyChain(watchingKey); diff --git a/core/src/main/java/org/bitcoinj/wallet/MarriedKeyChain.java b/core/src/main/java/org/bitcoinj/wallet/MarriedKeyChain.java index f2fb1f71..25c2da59 100644 --- a/core/src/main/java/org/bitcoinj/wallet/MarriedKeyChain.java +++ b/core/src/main/java/org/bitcoinj/wallet/MarriedKeyChain.java @@ -24,6 +24,7 @@ import org.bitcoinj.core.BloomFilter; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Utils; +import org.bitcoinj.crypto.ChildNumber; import org.bitcoinj.crypto.DeterministicKey; import org.bitcoinj.crypto.KeyCrypter; import org.bitcoinj.script.Script; @@ -95,16 +96,20 @@ public class MarriedKeyChain extends DeterministicKeyChain { public MarriedKeyChain build() { checkState(random != null || entropy != null || seed != null || watchingKey!= null, "Must provide either entropy or random or seed or watchingKey"); checkNotNull(followingKeys, "followingKeys must be provided"); - MarriedKeyChain chain; + if (threshold == 0) threshold = (followingKeys.size() + 1) / 2 + 1; + if (accountPath == null) + accountPath = ACCOUNT_ZERO_PATH; + + MarriedKeyChain chain; if (random != null) { - chain = new MarriedKeyChain(random, bits, getPassphrase(), seedCreationTimeSecs); + chain = new MarriedKeyChain(new DeterministicSeed(random, bits, getPassphrase(), seedCreationTimeSecs), null, accountPath); } else if (entropy != null) { - chain = new MarriedKeyChain(entropy, getPassphrase(), seedCreationTimeSecs); + chain = new MarriedKeyChain(new DeterministicSeed(entropy, getPassphrase(), seedCreationTimeSecs), null, accountPath); } else if (seed != null) { seed.setCreationTimeSeconds(seedCreationTimeSecs); - chain = new MarriedKeyChain(seed); + chain = new MarriedKeyChain(seed, null, accountPath); } else { watchingKey.setCreationTimeSeconds(seedCreationTimeSecs); chain = new MarriedKeyChain(watchingKey); @@ -123,8 +128,8 @@ public class MarriedKeyChain extends DeterministicKeyChain { super(accountKey, false); } - MarriedKeyChain(DeterministicSeed seed, KeyCrypter crypter) { - super(seed, crypter); + protected MarriedKeyChain(DeterministicSeed seed, KeyCrypter crypter, ImmutableList accountPath) { + super(seed, crypter, accountPath); } // Builder constructors diff --git a/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java b/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java index a7ec8fe6..8f6c3338 100644 --- a/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java @@ -105,7 +105,8 @@ public class DeterministicKeyChainTest { @Test public void deriveAccountOne() throws Exception { long secs = 1389353062L; - DeterministicKeyChain chain1 = new AccountOneChain(ENTROPY, "", secs); + DeterministicKeyChain chain1 = DeterministicKeyChain.builder().accountPath(ImmutableList.of(ChildNumber.ONE)) + .entropy(ENTROPY).seedCreationTimeSecs(secs).build(); ECKey key1 = chain1.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); ECKey key2 = chain1.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); @@ -122,25 +123,11 @@ public class DeterministicKeyChainTest { key3.sign(Sha256Hash.ZERO_HASH); } - static class AccountOneChain extends DeterministicKeyChain { - public AccountOneChain(byte[] entropy, String s, long secs) { - super(entropy, s, secs); - } - - public AccountOneChain(KeyCrypter crypter, DeterministicSeed seed) { - super(seed, crypter); - } - - @Override - protected ImmutableList getAccountPath() { - return ImmutableList.of(ChildNumber.ONE); - } - } - @Test public void serializeAccountOne() throws Exception { long secs = 1389353062L; - DeterministicKeyChain chain1 = new AccountOneChain(ENTROPY, "", secs); + DeterministicKeyChain chain1 = DeterministicKeyChain.builder().accountPath(ImmutableList.of(ChildNumber.ONE)) + .entropy(ENTROPY).seedCreationTimeSecs(secs).build(); ECKey key1 = chain1.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); final Address address = LegacyAddress.fromBase58(UNITTEST, "n2nHHRHs7TiZScTuVhZUkzZfTfVgGYwy6X"); @@ -153,12 +140,12 @@ public class DeterministicKeyChainTest { @Override public DeterministicKeyChain makeKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicSeed seed, KeyCrypter crypter, boolean isMarried, ImmutableList accountPath) { - return new AccountOneChain(crypter, seed); + return DeterministicKeyChain.builder().seed(seed).accountPath(accountPath).build(); } @Override public DeterministicKeyChain makeKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicSeed seed, KeyCrypter crypter, boolean isMarried) { - return new AccountOneChain(crypter, seed); + return DeterministicKeyChain.builder().seed(seed).build(); } @Override @@ -450,7 +437,8 @@ public class DeterministicKeyChainTest { @Test public void watchingChainAccountOne() throws UnreadableWalletException { Utils.setMockClock(); - DeterministicKeyChain chain1 = new AccountOneChain(chain.getKeyCrypter(), chain.getSeed()); + DeterministicKeyChain chain1 = DeterministicKeyChain.builder().accountPath(ImmutableList.of(ChildNumber.ONE)) + .seed(chain.getSeed()).build(); DeterministicKey key1 = chain1.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicKey key2 = chain1.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicKey key3 = chain1.getKey(KeyChain.KeyPurpose.CHANGE); @@ -521,26 +509,13 @@ public class DeterministicKeyChainTest { final DeterministicKey rekey4 = chain.getKey(KeyChain.KeyPurpose.CHANGE); assertEquals(key4.getPubKeyPoint(), rekey4.getPubKeyPoint()); } - static class AccountTwoChain extends DeterministicKeyChain { - public AccountTwoChain(byte[] entropy, String s, long secs) { - super(entropy, s, secs); - } - - public AccountTwoChain(KeyCrypter crypter, DeterministicSeed seed) { - super(seed, crypter); - } - - @Override - protected ImmutableList getAccountPath() { - return ImmutableList.of(new ChildNumber(2, true)); - } - } @Test public void spendingChainAccountTwo() throws UnreadableWalletException { Utils.setMockClock(); long secs = 1389353062L; - chain = new AccountTwoChain(ENTROPY, "", secs); + chain = DeterministicKeyChain.builder().accountPath(ImmutableList.of(new ChildNumber(2, true))).entropy(ENTROPY) + .seedCreationTimeSecs(secs).build(); DeterministicKey firstReceiveKey = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicKey secondReceiveKey = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicKey firstChangeKey = chain.getKey(KeyChain.KeyPurpose.CHANGE);