diff --git a/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java b/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java index 47278b87..799261e2 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java +++ b/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java @@ -48,11 +48,14 @@ public class DefaultKeyChainFactory implements KeyChainFactory { @Override public DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey, boolean isFollowingKey, boolean isMarried) throws UnreadableWalletException { + if (accountKey.getPath().size() != DeterministicKeyChain.ACCOUNT_ZERO_PATH.size()) + throw new UnreadableWalletException("Expecting account key but found key with path: " + + HDUtils.formatPath(accountKey.getPath())); DeterministicKeyChain chain; if (isMarried) chain = new MarriedKeyChain(accountKey); else - chain = new DeterministicKeyChain(accountKey, isFollowingKey, accountKey.getPath()); + chain = new DeterministicKeyChain(accountKey, isFollowingKey); 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 8ce63d09..7f49c57f 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java @@ -322,17 +322,9 @@ public class DeterministicKeyChain implements EncryptableKeyChain { * balances and generally follow along, but spending is not possible with such a chain. */ public DeterministicKeyChain(DeterministicKey watchingKey) { - this(watchingKey, ACCOUNT_ZERO_PATH); - } - - /** - * Creates a deterministic key chain that watches the given (public only) root key. You can use this to calculate - * balances and generally follow along, but spending is not possible with such a chain. - */ - public DeterministicKeyChain(DeterministicKey watchingKey, ImmutableList accountPath) { checkArgument(watchingKey.isPubKeyOnly(), "Private subtrees not currently supported: if you got this key from DKC.getWatchingKey() then use .dropPrivate().dropParent() on it first."); - checkArgument(watchingKey.getPath().size() == accountPath.size(), "You can only watch an account key currently"); - setAccountPath(accountPath); + checkArgument(watchingKey.getPath().size() == getAccountPath().size(), "You can only watch an account key currently"); + setAccountPath(watchingKey.getPath()); basicKeyChain = new BasicKeyChain(); this.seed = null; this.rootKey = null; @@ -347,17 +339,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain { *

Watch key has to be an account key.

*/ protected DeterministicKeyChain(DeterministicKey watchKey, boolean isFollowing) { - this(watchKey, isFollowing, ACCOUNT_ZERO_PATH); - } - - /** - *

Creates a deterministic key chain with the given watch key. If isFollowing flag is set then this keychain follows - * some other keychain. In a married wallet following keychain represents "spouse's" keychain.

- *

Watch key has to be an account key.

- */ - protected DeterministicKeyChain(DeterministicKey watchKey, boolean isFollowing, - ImmutableList accountPath) { - this(watchKey, accountPath); + this(watchKey); this.isFollowing = isFollowing; } @@ -374,14 +356,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain { * Creates a key chain that watches the given account key. */ public static DeterministicKeyChain watch(DeterministicKey accountKey) { - return watch(accountKey, ACCOUNT_ZERO_PATH); - } - - /** - * Creates a key chain that watches the given account key. - */ - public static DeterministicKeyChain watch(DeterministicKey accountKey, ImmutableList accountPath) { - return new DeterministicKeyChain(accountKey, accountPath); + return new DeterministicKeyChain(accountKey); } /** diff --git a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java index d9e4b480..6a4cd9f2 100644 --- a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java +++ b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java @@ -100,14 +100,6 @@ public class KeyChainGroup implements KeyBag { this(params, null, ImmutableList.of(DeterministicKeyChain.watch(watchKey)), null, null); } - /** - * Creates a keychain group with no basic chain, and an HD chain that is watching the given watching key. - * This HAS to be an account key as returned by {@link DeterministicKeyChain#getWatchingKey()}. - */ - public KeyChainGroup(NetworkParameters params, DeterministicKey watchKey, ImmutableList accountPath) { - this(params, null, ImmutableList.of(DeterministicKeyChain.watch(watchKey, accountPath)), null, null); - } - // Used for deserialization. private KeyChainGroup(NetworkParameters params, @Nullable BasicKeyChain basicKeyChain, List chains, @Nullable EnumMap currentKeys, @Nullable KeyCrypter crypter) { diff --git a/core/src/main/java/org/bitcoinj/wallet/Wallet.java b/core/src/main/java/org/bitcoinj/wallet/Wallet.java index 2b349dae..b5d01be4 100644 --- a/core/src/main/java/org/bitcoinj/wallet/Wallet.java +++ b/core/src/main/java/org/bitcoinj/wallet/Wallet.java @@ -287,42 +287,21 @@ public class Wallet extends BaseTaggableObject } /** - * Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given watching key. A - * watching key corresponds to account zero in the recommended BIP32 key hierarchy. + * Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given watching key. */ public static Wallet fromWatchingKey(NetworkParameters params, DeterministicKey watchKey) { return new Wallet(params, new KeyChainGroup(params, watchKey)); } - /** - * Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given watching key. The account path is specified. - */ - public static Wallet fromWatchingKey(NetworkParameters params, DeterministicKey watchKey, ImmutableList accountPath) { - return new Wallet(params, new KeyChainGroup(params, watchKey, accountPath)); - } - - /** - * Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given watching key. A - * watching key corresponds to account zero in the recommended BIP32 key hierarchy. The key is specified in base58 - * notation and the creation time of the key. If you don't know the creation time, you can pass - * {@link DeterministicHierarchy#BIP32_STANDARDISATION_TIME_SECS}. - */ - public static Wallet fromWatchingKeyB58(NetworkParameters params, String watchKeyB58, long creationTimeSeconds) { - final DeterministicKey watchKey = DeterministicKey.deserializeB58(null, watchKeyB58, params); - watchKey.setCreationTimeSeconds(creationTimeSeconds); - return fromWatchingKey(params, watchKey); - } - /** * Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given watching key. The * account path is specified. The key is specified in base58 notation and the creation time of the key. If you don't * know the creation time, you can pass {@link DeterministicHierarchy#BIP32_STANDARDISATION_TIME_SECS}. */ - public static Wallet fromWatchingKeyB58(NetworkParameters params, String watchKeyB58, long creationTimeSeconds, - ImmutableList accountPath) { + public static Wallet fromWatchingKeyB58(NetworkParameters params, String watchKeyB58, long creationTimeSeconds) { final DeterministicKey watchKey = DeterministicKey.deserializeB58(null, watchKeyB58, params); watchKey.setCreationTimeSeconds(creationTimeSeconds); - return fromWatchingKey(params, watchKey, accountPath); + return fromWatchingKey(params, watchKey); } /** diff --git a/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java b/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java index c0a74628..8161fde2 100644 --- a/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java @@ -395,6 +395,45 @@ public class DeterministicKeyChainTest { assertEquals(key4.getPubKeyPoint(), rekey4.getPubKeyPoint()); } + @Test + public void watchingChainAccountOne() throws UnreadableWalletException { + Utils.setMockClock(); + DeterministicKeyChain chain1 = new AccountOneChain(chain.getKeyCrypter(), chain.getSeed()); + DeterministicKey key1 = chain1.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); + DeterministicKey key2 = chain1.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); + DeterministicKey key3 = chain1.getKey(KeyChain.KeyPurpose.CHANGE); + DeterministicKey key4 = chain1.getKey(KeyChain.KeyPurpose.CHANGE); + + NetworkParameters params = MainNetParams.get(); + DeterministicKey watchingKey = chain1.getWatchingKey(); + final String pub58 = watchingKey.serializePubB58(params); + assertEquals("xpub69KR9epJ2Wp6ywiv4Xu5WfBUpX4GLu6D5NUMd4oUkCFoZoRNyk3ZCxfKPDkkGvCPa16dPgEdY63qoyLqEa5TQQy1nmfSmgWcagRzimyV7uA", pub58); + watchingKey = DeterministicKey.deserializeB58(null, pub58, params); + watchingKey.setCreationTimeSeconds(100000); + chain = DeterministicKeyChain.watch(watchingKey); + assertEquals(100000, chain.getEarliestKeyCreationTime()); + chain.setLookaheadSize(10); + chain.maybeLookAhead(); + + assertEquals(key1.getPubKeyPoint(), chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).getPubKeyPoint()); + assertEquals(key2.getPubKeyPoint(), chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).getPubKeyPoint()); + final DeterministicKey key = chain.getKey(KeyChain.KeyPurpose.CHANGE); + assertEquals(key3.getPubKeyPoint(), key.getPubKeyPoint()); + try { + // Can't sign with a key from a watching chain. + key.sign(Sha256Hash.ZERO_HASH); + fail(); + } catch (ECKey.MissingPrivateKeyException e) { + // Ignored. + } + // Test we can serialize and deserialize a watching chain OK. + List serialization = chain.serializeToProtobuf(); + checkSerialization(serialization, "watching-wallet-serialization-account-one.txt"); + chain = DeterministicKeyChain.fromProtobuf(serialization, null).get(0); + final DeterministicKey rekey4 = chain.getKey(KeyChain.KeyPurpose.CHANGE); + assertEquals(key4.getPubKeyPoint(), rekey4.getPubKeyPoint()); + } + @Test(expected = IllegalStateException.class) public void watchingCannotEncrypt() throws Exception { final DeterministicKey accountKey = chain.getKeyByPath(DeterministicKeyChain.ACCOUNT_ZERO_PATH); diff --git a/core/src/test/resources/org/bitcoinj/wallet/watching-wallet-serialization-account-one.txt b/core/src/test/resources/org/bitcoinj/wallet/watching-wallet-serialization-account-one.txt new file mode 100644 index 00000000..a1ee276c --- /dev/null +++ b/core/src/test/resources/org/bitcoinj/wallet/watching-wallet-serialization-account-one.txt @@ -0,0 +1,263 @@ +type: DETERMINISTIC_KEY +public_key: "\002\221 {7k\345UH\324x)\006.)\201\363$\376\vN\215J\336\315\021\245\351w!\336\004\034" +creation_timestamp: 100000000 +deterministic_key { + chain_code: "SE\031\000\277\023 \256W\237\036\034c\340\aB-I\246\304\025\325\262\314\235\240]\020\374T\315\027" + path: 1 +} + +type: DETERMINISTIC_KEY +public_key: "\003QO{n\351\326\357\343T\203u\345\025{]\231\234\273A\376U`\307\212\336h\255\215\335(\364\001" +deterministic_key { + chain_code: "\375y\345\372\243\305\021\000p\325\320\232\277\022?\245\024\027\262T\364z\206\204C\216E\376C\275\\\b" + path: 1 + path: 0 + issued_subkeys: 2 + lookahead_size: 10 + sigsRequiredToSpend: 1 +} + +type: DETERMINISTIC_KEY +public_key: "\003\006\371Ph\\*\342)*\232]\272\322\261\256\240\020\025\005\270\026\3636\246\342\200\240f\005\234\244\v\221\"" + path: 1 + path: 0 + path: 6 +} + +type: DETERMINISTIC_KEY +public_key: "\003\v\370\324\n\332V\311\260\375U\261`\271/{\216\246\230\267\243\2664\001{\226\bP\265\"\273\356\'" +deterministic_key { + chain_code: "\033\227\342\017\027O\205\2462\322\321\021\314vX\255+\3465\as\246T6\231\227G\273\n\221\355\002" + path: 1 + path: 0 + path: 7 +} + +type: DETERMINISTIC_KEY +public_key: "\003o\244\334\344\3278f_\251\r\f\035d\326\251\300\231\206\032oL\323\320\254\'\336y\201N\364\265\017" +deterministic_key { + chain_code: "ET\204]\017A\360_iHv\364\332\tO\354c\031\002\0301eI\236`\304\366\332i0\003\375" + path: 1 + path: 0 + path: 8 +} + +type: DETERMINISTIC_KEY +public_key: "\003;\234\200F\370\217\277\2652\265Y\276%\020\226\304x\321\350\313v\316J,\373.\224\213\317\367\326\353" +deterministic_key { + chain_code: "\260\3533#\203\322\376\322\301\233\260B\334\304\235_\342=\246\300\266\0234\345r\203\213\2576|\311" + path: 1 + path: 0 + path: 9 +} + +type: DETERMINISTIC_KEY +public_key: "\002\274Q\024Q\312\364\244\363~J\346\2250\nl\334\274\200\247\016\345;4u&\022tAY#MN" +deterministic_key { + chain_code: "\357\025!\243\025Z\3614\327\310\211\3624\026\n\032\233\256\362\250\376\370>\tb)\340\357\3621\362" + path: 1 + path: 0 + path: 10 +} + +type: DETERMINISTIC_KEY +public_key: "\002\375\231\206\364\364m\v\232\230\233\355\001\357\237\237\214\343b\235\212\320E\261\tF \227\250y\a\3026" +deterministic_key { + chain_code: "\3525>2\r\261\t\217H\f6 L\262\304$N\250JZ\0019\037\033w\201f,\371,s\342" + path: 1 + path: 0 + path: 11 +} + +type: DETERMINISTIC_KEY +public_key: "\003\362\021\026v`\310\333e\375\331\nE\r\242Z\337\321w(\316z\n\017#1F\323\302\317\370f\313" +deterministic_key { + chain_code: "\205\302e\221\250\f\3705\233\303\261H\324\366\376\v\354g\333\031*J\267\226\303\347\037\354\275k\t\276" + path: 1 + path: 0 + path: 12 +} + +type: DETERMINISTIC_KEY +public_key: "\002B\236[KR\272\273\315\030_\362V\272\252\245(\352m\276\020\260\027\365\362mM\b\265gq.M" +deterministic_key { + chain_code: ",?\220\362\246\20356\321\250\276\3739<\334u\320\221\033qC\214\024id\252!6\375k\036]" + path: 1 + path: 1 + path: 0 +} + +type: DETERMINISTIC_KEY +public_key: "\002&\2323!\301\254,\210e\"\222\"\357b\300u\341\000\275VDV\026\210\222U\277\016y\202@\240" +deterministic_key { + chain_code: "\fj\300\'\227\fo\036N\r\370\004,\201\270\023m:E\335-\331I\032\032\203\310o\236\261\213\213" + path: 1 + path: 1 + path: 1 +} + +type: DETERMINISTIC_KEY +public_key: "\003\341\331\037\216I\026O\2271\316\301+IZ\\\004\225V\331\231\312\022Df\232_t}B\v\365\232" +deterministic_key { + chain_code: "4YZ\277<\247\003\304\2672TS\362\b&\201\306\024\220\253\260m\267\001Y\201\342X\310R{x" + path: 1 + path: 1 + path: 2 +} + +type: DETERMINISTIC_KEY +public_key: "\003-/ \26795Z\000\341\363\346\377:#\026A\001\335X:\r!K\000\275\005\251\203\260\024\016\377" +deterministic_key { + chain_code: "\271\320\211\260W\fq\365\t\305\026!\n\364\362\0228\230\212\350\252\\\0048\250\n\017\231\244\334\344\341" + path: 1 + path: 1 + path: 3 +} + +type: DETERMINISTIC_KEY +public_key: "\002\\g\216\026\342\204\226z4K\330\a@yST\341c=\233r\224\0379\211I\347\341\363\333\264\263" +deterministic_key { + chain_code: ">\341z\r\212\206\351{\221\353\337d\266\364y=\t\230\264.\302\321/\002\2140\017\244\271\361\362\226" + path: 1 + path: 1 + path: 4 +} + +type: DETERMINISTIC_KEY +public_key: "\003GT\304l\350R\352\300\220\f\275_\223\201\374oU\327mx}\232\252-l\t\270\303\027O>\366" +deterministic_key { + chain_code: "\2423\212H\335-\232\v:\330\307\227L\313\272.x\344\242\363\331\034y[\003>\323\276\334\033@\341" + path: 1 + path: 1 + path: 5 +} + +type: DETERMINISTIC_KEY +public_key: "\003\254\200U0\2106sb\305\3456\'\027\204\02716q\251\215*$\232S\'\303\375\272\325V\342Q" +deterministic_key { + chain_code: "{i\034\237\236\233SE,\247\301\343\255J%\232\201\303\031\203=\303,D\313Pp6\f\r/\333" + path: 1 + path: 1 + path: 6 +} + +type: DETERMINISTIC_KEY +public_key: "\002\366\265^\241\263<_\316\036S]C\225S;\233\004\002E\a\261\324\004\336\003\276\345a\002>\311\233" +deterministic_key { + chain_code: "\244\327\315\353\231\371 \302/\227\320\303D\207\033@^\334\024\223;\220{\355G\236k\220\303\320zL" + path: 1 + path: 1 + path: 7 +} + +type: DETERMINISTIC_KEY +public_key: "\002\243I\215\246\2358PN\3238A\262\370i\031\206\266\202!`h\221\274\r\344\220\\,\350;)\300" +deterministic_key { + chain_code: "]/6\360\n\t\356\\B:Zs\317\274\363]\034`N\237`\337\340\277\347\001\231v:,\340\224" + path: 1 + path: 1 + path: 8 +} + +type: DETERMINISTIC_KEY +public_key: "\002\302rf\260E\306\303\205\271\222\222\315\364\317br\237\345w&\254}\314\363H\317fv\247\203\250\372" +deterministic_key { + chain_code: ",\270\243\347Q\340U\tx\270\214S\306\205\312\242Y\302\0168\002\366\343\316\"&\034W\352\330\245\357" + path: 1 + path: 1 + path: 9 +} + +type: DETERMINISTIC_KEY +public_key: "\002\\\207\256\270oNU\203M\f/z\334\003\353\310A\226m#\321!\313E\237\361\310\211\255\376\316\262" +deterministic_key { + chain_code: "\245\336+\332\307\342q\356)0\350\004mz\215\321\001U\2059g\200\247\240\252\fQ\341\276J\303" + path: 1 + path: 1 + path: 10 +} + +type: DETERMINISTIC_KEY +public_key: "\002G\327\343 \210A\356\354\233\276\277\341\311\352F}D}F\233\000bu(\333\343\002\277\033\272\243\232" +deterministic_key { + chain_code: "\301\261\333\361\275\255FE4k\224\301\302\325\223\272\216]D*f~\233\305\350\212\3678\242\262\257H" + path: 1 + path: 1 + path: 11 +} + +type: DETERMINISTIC_KEY +public_key: "\002\366\2432<_\361\026W\372\f\004\224\027\367\360\346\347M\377\271\207\264\233\234\001I\340\322`Y\216k" +deterministic_key { + chain_code: "c\324M\225h\242\374\021o~[\203\212\343wxVs\226\343\025\316\243|}\t\350\330\242*\247\\" + path: 1 + path: 1 + path: 12 +} \ No newline at end of file