diff --git a/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java b/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java
index 07ba0717..ba53ebbd 100644
--- a/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java
+++ b/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java
@@ -22,6 +22,7 @@ import com.google.common.io.Closeables;
import com.google.common.util.concurrent.*;
import org.bitcoinj.core.listeners.*;
import org.bitcoinj.core.*;
+import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.net.discovery.*;
import org.bitcoinj.protocols.channels.*;
import org.bitcoinj.store.*;
@@ -81,6 +82,7 @@ public class WalletAppKit extends AbstractIdleService {
protected String userAgent, version;
protected WalletProtobufSerializer.WalletFactory walletFactory;
@Nullable protected DeterministicSeed restoreFromSeed;
+ @Nullable protected DeterministicKey restoreFromKey;
@Nullable protected PeerDiscovery discovery;
protected volatile Context context;
@@ -198,6 +200,19 @@ public class WalletAppKit extends AbstractIdleService {
return this;
}
+ /**
+ * If an account key is set here then any existing wallet that matches the file name will be renamed to a backup name,
+ * the chain file will be deleted, and the wallet object will be instantiated with the given key instead of
+ * a fresh seed being created. This is intended for restoring a wallet from an account key. To implement restore
+ * you would shut down the existing appkit, if any, then recreate it with the key given by the user, then start
+ * up the new kit. The next time your app starts it should work as normal (that is, don't keep calling this each
+ * time).
+ */
+ public WalletAppKit restoreWalletFromKey(DeterministicKey accountKey) {
+ this.restoreFromKey = accountKey;
+ return this;
+ }
+
/**
* Sets the peer discovery class to use. If none is provided then DNS is used, which is a reasonable default.
*/
@@ -268,12 +283,12 @@ public class WalletAppKit extends AbstractIdleService {
File chainFile = new File(directory, filePrefix + ".spvchain");
boolean chainFileExists = chainFile.exists();
vWalletFile = new File(directory, filePrefix + ".wallet");
- boolean shouldReplayWallet = (vWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
+ boolean shouldReplayWallet = (vWalletFile.exists() && !chainFileExists) || restoreFromSeed != null || restoreFromKey != null;
vWallet = createOrLoadWallet(shouldReplayWallet);
// Initiate Bitcoin network objects (block store, blockchain and peer group)
vStore = provideBlockStore(chainFile);
- if (!chainFileExists || restoreFromSeed != null) {
+ if (!chainFileExists || restoreFromSeed != null || restoreFromKey != null) {
if (checkpoints == null && !Utils.isAndroidRuntime()) {
checkpoints = CheckpointManager.openStream(params);
}
@@ -290,7 +305,18 @@ public class WalletAppKit extends AbstractIdleService {
throw new IOException("Failed to delete chain file in preparation for restore.");
vStore = new SPVBlockStore(params, chainFile);
}
- } else {
+ } else if (restoreFromKey != null) {
+ time = restoreFromKey.getCreationTimeSeconds();
+ if (chainFileExists) {
+ log.info("Deleting the chain file in preparation from restore.");
+ vStore.close();
+ if (!chainFile.delete())
+ throw new IOException("Failed to delete chain file in preparation for restore.");
+ vStore = new SPVBlockStore(params, chainFile);
+ }
+ }
+ else
+ {
time = vWallet.getEarliestKeyCreationTime();
}
if (time > 0)
@@ -411,6 +437,8 @@ public class WalletAppKit extends AbstractIdleService {
KeyChainGroup kcg;
if (restoreFromSeed != null)
kcg = new KeyChainGroup(params, restoreFromSeed);
+ else if (restoreFromKey != null)
+ kcg = new KeyChainGroup(params, restoreFromKey, false);
else
kcg = new KeyChainGroup(params);
if (walletFactory != null) {
@@ -422,6 +450,7 @@ public class WalletAppKit extends AbstractIdleService {
private void maybeMoveOldWalletOutOfTheWay() {
if (restoreFromSeed == null) return;
+ if (restoreFromKey == null) return;
if (!vWalletFile.exists()) return;
int counter = 1;
File newName;
diff --git a/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java b/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java
index d592e566..9a2cce4d 100644
--- a/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java
+++ b/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java
@@ -55,4 +55,15 @@ public class DefaultKeyChainFactory implements KeyChainFactory {
chain = new DeterministicKeyChain(accountKey, isFollowingKey);
return chain;
}
+
+ @Override
+ public DeterministicKeyChain makeSpendingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey,
+ boolean isMarried) throws UnreadableWalletException {
+ DeterministicKeyChain chain;
+ if (isMarried)
+ chain = new MarriedKeyChain(accountKey);
+ else
+ chain = DeterministicKeyChain.spend(accountKey);
+ 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 bb7a2c94..01d6f09c 100644
--- a/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java
+++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java
@@ -332,6 +332,31 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
initializeHierarchyUnencrypted(watchingKey);
}
+ /**
+ * Creates a deterministic key chain from a watched or spendable account key. If isWatching flag is set,
+ * then 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. If it is not set, then this
+ * creates a deterministic key chain that allows spending. If isFollowing flag is set(only allowed
+ * if isWatching is set) then this keychain follows some other keychain. In a married wallet following
+ * keychain represents "spouse's" keychain.
+ */
+ public DeterministicKeyChain(DeterministicKey key, boolean isFollowing, boolean isWatching) {
+ if (isWatching)
+ checkArgument(key.isPubKeyOnly(), "Private subtrees not currently supported for watching keys: if you got this key from DKC.getWatchingKey() then use .dropPrivate().dropParent() on it first.");
+ else
+ checkArgument(key.hasPrivKey(), "Private subtrees are required.");
+ checkArgument(isWatching || !isFollowing, "Can only follow a key that is watched");
+
+ basicKeyChain = new BasicKeyChain();
+ this.seed = null;
+ this.rootKey = null;
+ basicKeyChain.importKey(key);
+ hierarchy = new DeterministicHierarchy(key);
+ setAccountPath(key.getPath());
+ initializeHierarchyUnencrypted(key);
+ this.isFollowing = isFollowing;
+ }
+
/**
*
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.
@@ -358,6 +383,13 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
return new DeterministicKeyChain(accountKey);
}
+ /**
+ * Creates a key chain that can spend from the given account key.
+ */
+ public static DeterministicKeyChain spend(DeterministicKey accountKey) {
+ return new DeterministicKeyChain(accountKey, false, false);
+ }
+
/**
* For use in {@link KeyChainFactory} during deserialization.
*/
@@ -865,6 +897,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
// Possibly create the chain, if we didn't already do so yet.
boolean isWatchingAccountKey = false;
boolean isFollowingKey = false;
+ boolean isSpendingKey = false;
// save previous chain if any if the key is marked as following. Current key and the next ones are to be
// placed in new following key chain
if (key.getDeterministicKey().getIsFollowing()) {
@@ -882,7 +915,14 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
if (chain == null) {
// If this is not a following chain and previous was, this must be married
boolean isMarried = !isFollowingKey && !chains.isEmpty() && chains.get(chains.size() - 1).isFollowing();
- if (seed == null) {
+ // If this has a private key but no seed, then all we know is the spending key H
+ if (seed == null & key.hasSecretBytes()) {
+ DeterministicKey accountKey = new DeterministicKey(immutablePath, chainCode, pubkey, new BigInteger(1, key.getSecretBytes().toByteArray()), null);
+ accountKey.setCreationTimeSeconds(key.getCreationTimestamp() / 1000);
+ chain = factory.makeSpendingKeyChain(key, iter.peek(), accountKey, isMarried);
+ isSpendingKey = true;
+ }
+ else if (seed == null) {
DeterministicKey accountKey = new DeterministicKey(immutablePath, chainCode, pubkey, null, null);
accountKey.setCreationTimeSeconds(key.getCreationTimestamp() / 1000);
chain = factory.makeWatchingKeyChain(key, iter.peek(), accountKey, isFollowingKey, isMarried);
@@ -898,7 +938,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
}
// Find the parent key assuming this is not the root key, and not an account key for a watching chain.
DeterministicKey parent = null;
- if (!path.isEmpty() && !isWatchingAccountKey) {
+ if (!path.isEmpty() && !isWatchingAccountKey && !isSpendingKey) {
ChildNumber index = path.removeLast();
parent = chain.hierarchy.get(path, false, false);
path.add(index);
@@ -936,7 +976,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
chain.rootKey = detkey;
chain.hierarchy = new DeterministicHierarchy(detkey);
}
- } else if (path.size() == chain.getAccountPath().size() + 1) {
+ } else if ((path.size() == chain.getAccountPath().size() + 1) || isSpendingKey) {
// Constant 0 is used for external chain and constant 1 for internal chain
// (also known as change addresses). https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
if (detkey.getChildNumber().num() == 0) {
diff --git a/core/src/main/java/org/bitcoinj/wallet/KeyChainFactory.java b/core/src/main/java/org/bitcoinj/wallet/KeyChainFactory.java
index 3b44ed5a..8a803b9c 100644
--- a/core/src/main/java/org/bitcoinj/wallet/KeyChainFactory.java
+++ b/core/src/main/java/org/bitcoinj/wallet/KeyChainFactory.java
@@ -61,5 +61,19 @@ public interface KeyChainFactory {
* @param isFollowingKey whether the keychain is following in a marriage
* @param isMarried whether the keychain is leading in a marriage
*/
- DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey, boolean isFollowingKey, boolean isMarried) throws UnreadableWalletException;
+ DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey,
+ boolean isFollowingKey, boolean isMarried) throws UnreadableWalletException;
+
+ /**
+ * Make a spending keychain.
+ *
+ * isMarried and isFollowingKey must not be true at the same time.
+ *
+ * @param key the protobuf for the account key
+ * @param firstSubKey the protobuf for the first child key (normally the parent of the external subchain)
+ * @param accountKey the account extended public key
+ * @param isMarried whether the keychain is leading in a marriage
+ */
+ DeterministicKeyChain makeSpendingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey,
+ boolean isMarried) throws UnreadableWalletException;
}
diff --git a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java
index cc718865..62c38f8c 100644
--- a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java
+++ b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java
@@ -101,6 +101,14 @@ 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 or spending the given key.
+ * This HAS to be an account key as returned by {@link DeterministicKeyChain#getWatchingKey()}.
+ */
+ public KeyChainGroup(NetworkParameters params, DeterministicKey accountKey, boolean watch) {
+ this(params, null, ImmutableList.of(watch ? DeterministicKeyChain.watch(accountKey) : DeterministicKeyChain.spend(accountKey)), 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 a4f284dc..c0904e71 100644
--- a/core/src/main/java/org/bitcoinj/wallet/Wallet.java
+++ b/core/src/main/java/org/bitcoinj/wallet/Wallet.java
@@ -302,6 +302,35 @@ public class Wallet extends BaseTaggableObject
return fromWatchingKey(params, watchKey);
}
+ /**
+ * Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given spending key.
+ * This wallet can also spend.
+ */
+ public static Wallet fromSpendingKey(NetworkParameters params, DeterministicKey spendKey) {
+ return new Wallet(params, new KeyChainGroup(params, spendKey, false));
+ }
+
+ /**
+ * Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given spending key.
+ * 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 fromSpendingKeyB58(NetworkParameters params, String spendingKeyB58, long creationTimeSeconds) {
+ final DeterministicKey spendKey = DeterministicKey.deserializeB58(null, spendingKeyB58, params);
+ spendKey.setCreationTimeSeconds(creationTimeSeconds);
+ return fromSpendingKey(params, spendKey);
+ }
+
+ /**
+ * Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given spending key.
+ */
+ public static Wallet fromMasterKey(NetworkParameters params, DeterministicKey masterKey, ChildNumber accountNumber) {
+ DeterministicKey accountKey = HDKeyDerivation.deriveChildKey(masterKey, accountNumber);
+ accountKey = accountKey.dropParent();
+ accountKey.setCreationTimeSeconds(masterKey.getCreationTimeSeconds());
+ return new Wallet(params, new KeyChainGroup(params, accountKey, false));
+ }
+
/**
* Creates a wallet containing a given set of keys. All further keys will be derived from the oldest key.
*/
diff --git a/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java b/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java
index 62f07168..d0648769 100644
--- a/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java
+++ b/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java
@@ -159,6 +159,11 @@ public class DeterministicKeyChainTest {
public DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey, boolean isFollowingKey, boolean isMarried) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public DeterministicKeyChain makeSpendingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey, boolean isMarried) {
+ throw new UnsupportedOperationException();
+ }
};
chain1 = DeterministicKeyChain.fromProtobuf(keys, null, factory).get(0);
@@ -474,6 +479,162 @@ public class DeterministicKeyChainTest {
assertEquals(key4.getPubKeyPoint(), rekey4.getPubKeyPoint());
}
+ @Test
+ public void spendingChain() throws UnreadableWalletException {
+ Utils.setMockClock();
+ DeterministicKey key1 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
+ DeterministicKey key2 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
+ DeterministicKey key3 = chain.getKey(KeyChain.KeyPurpose.CHANGE);
+ DeterministicKey key4 = chain.getKey(KeyChain.KeyPurpose.CHANGE);
+
+ NetworkParameters params = MainNetParams.get();
+ DeterministicKey watchingKey = chain.getWatchingKey();
+ final String prv58 = watchingKey.serializePrivB58(params);
+ assertEquals("xprv9vL4k9HYXonmvqGSUrRM6wGEmx3ruGTXi4JxHRiwEvwDwYmTocPbQNpjN89gpqPrFofmfvALwgnNFBCH2grse1YDf8ERAwgdvbjRtoMfsbV", prv58);
+ watchingKey = DeterministicKey.deserializeB58(null, prv58, params);
+ watchingKey.setCreationTimeSeconds(100000);
+ chain = DeterministicKeyChain.spend(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 {
+ // We can sign with a key from a spending chain.
+ key.sign(Sha256Hash.ZERO_HASH);
+ } catch (ECKey.MissingPrivateKeyException e) {
+ fail();
+ }
+ // Test we can serialize and deserialize a watching chain OK.
+ List serialization = chain.serializeToProtobuf();
+ checkSerialization(serialization, "spending-wallet-serialization.txt");
+ chain = DeterministicKeyChain.fromProtobuf(serialization, null).get(0);
+ 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);
+ DeterministicKey firstReceiveKey = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
+ DeterministicKey secondReceiveKey = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
+ DeterministicKey firstChangeKey = chain.getKey(KeyChain.KeyPurpose.CHANGE);
+ DeterministicKey secondChangeKey = chain.getKey(KeyChain.KeyPurpose.CHANGE);
+
+ NetworkParameters params = MainNetParams.get();
+ DeterministicKey watchingKey = chain.getWatchingKey();
+
+ final String prv58 = watchingKey.serializePrivB58(params);
+ assertEquals("xprv9vL4k9HYXonmzR7UC1ngJ3hTjxkmjLLUo3RexSfUGSWcACHzghWBLJAwW6xzs59XeFizQxFQWtscoTfrF9PSXrUgAtBgr13Nuojax8xTBRz", prv58);
+ watchingKey = DeterministicKey.deserializeB58(null, prv58, params);
+ watchingKey.setCreationTimeSeconds(secs);
+ chain = DeterministicKeyChain.spend(watchingKey);
+ assertEquals(secs, chain.getEarliestKeyCreationTime());
+ chain.setLookaheadSize(10);
+ chain.maybeLookAhead();
+
+ verifySpendableKeyChain(firstReceiveKey, secondReceiveKey, firstChangeKey, secondChangeKey, chain, "spending-wallet-account-two-serialization.txt");
+ }
+
+ @Test
+ public void masterKeyAccount() throws UnreadableWalletException {
+ Utils.setMockClock();
+ long secs = 1389353062L;
+ DeterministicKey firstReceiveKey = bip44chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
+ DeterministicKey secondReceiveKey = bip44chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
+ DeterministicKey firstChangeKey = bip44chain.getKey(KeyChain.KeyPurpose.CHANGE);
+ DeterministicKey secondChangeKey = bip44chain.getKey(KeyChain.KeyPurpose.CHANGE);
+
+ NetworkParameters params = MainNetParams.get();
+ DeterministicKey watchingKey = bip44chain.getWatchingKey(); //m/44'/1'/0'
+ DeterministicKey coinLevelKey = bip44chain.getWatchingKey().getParent(); //m/44'/1'
+
+ //Simulate Wallet.fromSpendingKeyB58(PARAMS, prv58, secs)
+ final String prv58 = watchingKey.serializePrivB58(params);
+ assertEquals("xprv9yYQhynAmWWuz62PScx5Q2frBET2F1raaXna5A2E9Lj8XWgmKBL7S98Yand8F736j9UCTNWQeiB4yL5pLZP7JDY2tY8eszGQkiKDwBkezeS", prv58);
+ watchingKey = DeterministicKey.deserializeB58(null, prv58, params);
+ watchingKey.setCreationTimeSeconds(secs);
+ DeterministicKeyChain fromPrivBase58Chain = DeterministicKeyChain.spend(watchingKey);
+ assertEquals(secs, fromPrivBase58Chain.getEarliestKeyCreationTime());
+ fromPrivBase58Chain.setLookaheadSize(10);
+ fromPrivBase58Chain.maybeLookAhead();
+
+ verifySpendableKeyChain(firstReceiveKey, secondReceiveKey, firstChangeKey, secondChangeKey, fromPrivBase58Chain, "spending-wallet-from-bip44-serialization.txt");
+
+ //Simulate Wallet.fromMasterKey(params, coinLevelKey, 0)
+ DeterministicKey accountKey = HDKeyDerivation.deriveChildKey(coinLevelKey, new ChildNumber(0, true));
+ accountKey = accountKey.dropParent();
+ accountKey.setCreationTimeSeconds(watchingKey.getCreationTimeSeconds());
+ KeyChainGroup group = new KeyChainGroup(params, accountKey, false);
+ DeterministicKeyChain fromMasterKeyChain = group.getActiveKeyChain();
+ assertEquals(secs, fromMasterKeyChain.getEarliestKeyCreationTime());
+ fromMasterKeyChain.setLookaheadSize(10);
+ fromMasterKeyChain.maybeLookAhead();
+
+ verifySpendableKeyChain(firstReceiveKey, secondReceiveKey, firstChangeKey, secondChangeKey, fromMasterKeyChain, "spending-wallet-from-bip44-serialization-two.txt");
+ }
+
+ /**
+ * verifySpendableKeyChain
+ *
+ * firstReceiveKey and secondReceiveKey are the first two keys of the external chain of a known key chain
+ * firstChangeKey and secondChangeKey are the first two keys of the internal chain of a known key chain
+ * keyChain is a DeterministicKeyChain loaded from a serialized format or derived in some other way from
+ * the known key chain
+ *
+ * This method verifies that known keys match a newly created keyChain and that keyChain's protobuf
+ * matches the serializationFile.
+ */
+ private void verifySpendableKeyChain(DeterministicKey firstReceiveKey, DeterministicKey secondReceiveKey,
+ DeterministicKey firstChangeKey, DeterministicKey secondChangeKey,
+ DeterministicKeyChain keyChain, String serializationFile) throws UnreadableWalletException {
+
+ //verify that the keys are the same as the keyChain
+ assertEquals(firstReceiveKey.getPubKeyPoint(), keyChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).getPubKeyPoint());
+ assertEquals(secondReceiveKey.getPubKeyPoint(), keyChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).getPubKeyPoint());
+ final DeterministicKey key = keyChain.getKey(KeyChain.KeyPurpose.CHANGE);
+ assertEquals(firstChangeKey.getPubKeyPoint(), key.getPubKeyPoint());
+
+ try {
+ key.sign(Sha256Hash.ZERO_HASH);
+ } catch (ECKey.MissingPrivateKeyException e) {
+ // We can sign with a key from a spending chain.
+ fail();
+ }
+
+ // Test we can serialize and deserialize the chain OK
+ List serialization = keyChain.serializeToProtobuf();
+ checkSerialization(serialization, serializationFile);
+
+ // Check that the second change key matches after loading from the serialization, serializing and deserializing
+ long secs = keyChain.getEarliestKeyCreationTime();
+ keyChain = DeterministicKeyChain.fromProtobuf(serialization, null).get(0);
+ serialization = keyChain.serializeToProtobuf();
+ checkSerialization(serialization, serializationFile);
+ assertEquals(secs, keyChain.getEarliestKeyCreationTime());
+ final DeterministicKey nextChangeKey = keyChain.getKey(KeyChain.KeyPurpose.CHANGE);
+ assertEquals(secondChangeKey.getPubKeyPoint(), nextChangeKey.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/spending-wallet-account-two-serialization.txt b/core/src/test/resources/org/bitcoinj/wallet/spending-wallet-account-two-serialization.txt
new file mode 100644
index 00000000..d8f1be15
--- /dev/null
+++ b/core/src/test/resources/org/bitcoinj/wallet/spending-wallet-account-two-serialization.txt
@@ -0,0 +1,266 @@
+type: DETERMINISTIC_KEY
+secret_bytes: "1\026\363o1\250\031\314\340Wdh\273\272Q\"\253\205\3170\254\022\330\265G\0238hb\037\230\260"
+public_key: "\003Zx!\n\a:\205\374K\327\361R\372P%c\247\000\030\300m\237\'\224e\300"
+ path: 2147483650
+ path: 0
+ path: 1
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002(\335\203\303\0160\032\006\002\215\333\034\r\260\031V\035\317)\223`\001\203\333jdu\245\002\2136M"
+deterministic_key {
+ chain_code: "j\234\034\360\311J\223\265\232\264\252=,\353\246J\332\332\337\306\204\371^FH\206\335\357\222\004a\032"
+ path: 2147483650
+ path: 0
+ path: 2
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002M\240_\234\315\312yR]\274}#.\356\263\006\365\247RC\'k<*\313s0\322\314\334\226|"
+deterministic_key {
+ chain_code: "M\333\206\366\2154\234yn@J\021h\244\016\224\034\210\365\214\221\313\332\017\016\030)F6\275}\340"
+ path: 2147483650
+ path: 0
+ path: 3
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\222\3609iC:O\2772T\371\3433\330Y\366n\247\v\366\37542t\231{\263$\036~]="
+deterministic_key {
+ chain_code: "1\327G\352\347M\'\246y\304XA\2302\362xp\335\327\306\334\226%\203/\v6\325r\020\215\304"
+ path: 2147483650
+ path: 0
+ path: 4
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002+r\001\212\325\256]\2571\222Or\356{\323\355\370\361\347\311\217:\262\326\003$\374\f\266\343\277\036"
+deterministic_key {
+ chain_code: "\246\341(N\216^\023\262\033\230\243v\345\266x\202\r=\003\312d\377\212k\231\262\303Z^\212[\357"
+ path: 2147483650
+ path: 0
+ path: 5
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\211\212;\0309T\310\240:j\031\244Y2\326w\036,Ul\351\331fFkv\246\2322\304\351B"
+deterministic_key {
+ chain_code: "_\324E7\v\035e\321\232 \325\272\305\352\245\340\036\375\033\331\236\233\0326\021n/\031}\220\2262"
+ path: 2147483650
+ path: 0
+ path: 6
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\022z\v\251\244 3\224\334\207o\307\334:\212\316\026fV/\261\353\034\3371\357\354\036 VsL"
+deterministic_key {
+ chain_code: "\224-e\373A\252\377\3105\322\341\322\330\366JY}_\202,\267\303\252\220$\362\235\344\337\265\273?"
+ path: 2147483650
+ path: 0
+ path: 7
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\327\366\0233\245*\036\020\313$\026\030\037z\221\033\202\243Kg\232\377K\202z\232N>\342\030\005^"
+deterministic_key {
+ chain_code: "\027\200\234\377q\263\221H\a\303\274y\rw\327\310Fb\326G\255f\231\213O\270 %c\371\251"
+ path: 2147483650
+ path: 0
+ path: 8
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\302\310T\314\340p\n\223\332=7R&\217#>\331x\031\225\205S\360\371b\025\200V\000\243\335"
+deterministic_key {
+ chain_code: "\256\370\271\335\305mh\240\337\"\352\227\203\232\263\304A\237\331\032\201k\203rH\345B@\366\230\354Z"
+ path: 2147483650
+ path: 0
+ path: 9
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\241\215I\215h\206&\372\235Wp{\004\032\344Q\r\364\263B\365{\216\343,\205#K\004E\027\240"
+deterministic_key {
+ chain_code: "/\tvR\\\221\362\334\322\214\220,\215\233\304\023\315\025\220\274\255\325\3513G\367\375\235x,\253o"
+ path: 2147483650
+ path: 0
+ path: 10
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003h\372k\206\376\fx6\025\364\002\221\275Ad\324\230{\325b6\330\025\377?Lt\342\027\302\376\316"
+deterministic_key {
+ chain_code: "\207YX\355S\345\251)\265C{\3245\320\314\255\354\354\261\351\335\224\365Q^\202\304\227\016u)\032"
+ path: 2147483650
+ path: 0
+ path: 11
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002S{/\211.E{\312}bC2\025\334@\333$\333\217$\252x\241\376@u\354\004\336\203\3711"
+deterministic_key {
+ chain_code: "K\364\263\253O\207\237\304|\021\331\271\203\240\313 q\377\212\256\312\230\032R(`J\355\\<\366\277"
+ path: 2147483650
+ path: 0
+ path: 12
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\001\303\003\230e/\217\303\360Q3v,q\244\340\003\314.\030D\354Y\002\240\361\274q\274H\353,"
+deterministic_key {
+ chain_code: ",\326\234\336\206Mf-y\353wFZ\300\'\303\303\256\355P\251{]\220z)\317\004V\214\376\251"
+ path: 2147483650
+ path: 1
+ path: 0
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\230\244\202\327\226\252Y\221R\227zx\264}i\330u\314\345L\215*\rp2\317\255O\v\240\205\362"
+deterministic_key {
+ chain_code: "\t\317\020\246\355\322\023\271\236\"\332\360v\002,\026\203\345\377D\353\213x[\022\376\3758Y[\020J"
+ path: 2147483650
+ path: 1
+ path: 1
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\001e\312\334\330\016\367nL\224\021\033\n\300\203\f\247\332\323W\302%\307G\367f\016\325\226S\237{"
+deterministic_key {
+ chain_code: "\264\033\003I\021?\320\037\314,\230\330\006j\212\251\\\210]v\205\357\375b$-A\341\2224\256\017"
+ path: 2147483650
+ path: 1
+ path: 2
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002d+>kd\263\314Ld\261\026\027\276\221j{ \026\216\346\235t|\204\307\314\251\004\333\304\2320"
+deterministic_key {
+ chain_code: "TS\322\aU\266!\220\317M\317\353\265\337\212\314\254\000\3005\241\243\266\220\320\322\351\264l2\207\252"
+ path: 2147483650
+ path: 1
+ path: 3
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\212\2020\200\021\241\332\325\233\223\206\" &\303]\023\276A-B\232\256U\225\231\331\234\274o\344\f"
+deterministic_key {
+ chain_code: "\231\030s_\230qm$\246C\263m\344Wz\300\204\362\232\350\335\225\v\334,+\2065\247\fwg"
+ path: 2147483650
+ path: 1
+ path: 4
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003]R\374_ =\300y\344\343\300\276\037\005\316F\362d|m\277\312B\263\022v\313\266\266mvF"
+deterministic_key {
+ chain_code: "\220\322\2459V\327\003\225\361\251\365\354P\252\233Q\364\367"
+ path: 2147483650
+ path: 1
+ path: 8
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002I\000\325\334\376I\346+h\312\326\373\331\375z\310\342(\221\bY\232\345\004\361vy\235S\313(\266"
+deterministic_key {
+ chain_code: "\2445\361\252\300\232O\350yI\276W\'}\243:-\261p-\216\f\2243\276_\027{S \344w"
+ path: 2147483650
+ path: 1
+ path: 9
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\330\005Bd\263\230\311\225\263b\331\354|\260$\257\207Q\305\364[\261\365z\016H\371\242\317\301\273>"
+deterministic_key {
+ chain_code: "#M\366zV\006\3751\b\311\233\n8/z\214\354\035\220%\364\204\352M\023-\226\326\226\346\262\225"
+ path: 2147483650
+ path: 1
+ path: 10
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\337\027\036\342\333\032c\273X\202T\032?\243\256\313\311\3411\326J\017\2111@*\001\223xkp\352"
+deterministic_key {
+ chain_code: "8\336\262Z\314JCK\323\r\267\214\"\375U\215\247\217\211_\237\376\323.\247:\372\216\331\t\003\255"
+ path: 2147483650
+ path: 1
+ path: 11
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002l\255\003\316\3273l\034\241\r\003b\250y\201H\261\024\325$\302\351\226\224\345a\245\270\232%v\241"
+deterministic_key {
+ chain_code: "~\020\352Z\325\2674\245\272\003\221$\r\"\362\\\344\320\227\267\316\330\272.\"\272\031o\241\027\334"
+ path: 2147483650
+ path: 1
+ path: 12
+}
\ No newline at end of file
diff --git a/core/src/test/resources/org/bitcoinj/wallet/spending-wallet-from-bip44-serialization-two.txt b/core/src/test/resources/org/bitcoinj/wallet/spending-wallet-from-bip44-serialization-two.txt
new file mode 100644
index 00000000..fcc0d612
--- /dev/null
+++ b/core/src/test/resources/org/bitcoinj/wallet/spending-wallet-from-bip44-serialization-two.txt
@@ -0,0 +1,324 @@
+type: DETERMINISTIC_KEY
+secret_bytes: "\023\354\032\244\374\220\017\302\241u\017DvD\2662\342\316"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 0
+ path: 2
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003 1\316\277\355_\343\214}\a\205\233\325\232\241n\256\325\300\2369\020\nh\335\0243\355\362$?7"
+deterministic_key {
+ chain_code: "\334(\\u\022\245\370\t*\372\315\330\365\256Ms\254J_{B\035[f\333\351\272\261\363\373_\023"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 0
+ path: 3
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\354\fhI\2731\026\222\v\274\027\357\327\033X\324\270\323\252}\314}\221\213\272\\\362k\352\334#"
+deterministic_key {
+ chain_code: "#\351\"(\t\245\006\351\354f\334\216(\272\252\200\226\337\370\260XO\375\016/\377\306\263yE\222\311"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 0
+ path: 4
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002-7\rx2zP\r(B\247\350\026\205\210w\251G\b\254\213\000\227\271Q\272\342\357\304>G"
+deterministic_key {
+ chain_code: "US\242J\307\2672<\373l\217\200[\316\352\361*~\324\f\304\267oD\273\300_\340K\247V\370"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 0
+ path: 5
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002Y\260\332\377;T\263\335\331\004\020kv\207E=\311|\270*hP>)\340\272\203LD\036\313\271"
+deterministic_key {
+ chain_code: "\r34\'I\027\266\272\300\003\366f\274\333\260\006\311\3556!\227\216\301\361\247\354\025\305\321\376\274\214"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 0
+ path: 6
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002I\367x\235p9\334\234\034\366\247&\321\237\217\241V\252\017`w\212\301\000\305-\312\003\352`\302V"
+deterministic_key {
+ chain_code: "\257\001\375\203E\315\221W\316&\035\244\306\037\351\361\027\020\346\305^Z\274O\212\363P\036\273n\367\326"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 0
+ path: 7
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\0029\373\030u\305\214S\345/\373y%\t\252~\267\f\016t|\354\020\356\306\313\317\027\325\376\232kh"
+deterministic_key {
+ chain_code: "\260\203\277\231\352\265y\020\356r_bO\374l\347\002\032i\216Ct\260\221-\207\200\243\364;\247I"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 0
+ path: 8
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002[\304\301.\342\253\256\364\025\'\017\356-t\340R\250Z\327\374\250\r\331\221`\334\362a[q\260\271"
+deterministic_key {
+ chain_code: "\376\277#\275\035S\362`\323\246C73n\326P]{\260@\327\242\'\263$H\271\371\371YIJ"
+deterministic_key {
+ chain_code: "9\214\315\275\300\206\253U;\235\002fA\016\215\222\235K\253\311\3648w20\2005\343\310\\:"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 1
+ path: 3
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002!\373+L\341\025\265\232\a\247J?v\274\273|v\035g\033\211\026\332\233\37378c\226\020\304\360"
+deterministic_key {
+ chain_code: "i\003w\037b!Y]\214l\373]`x\355Je\\\v\205\n\310\254s\301\272\246\315\024}\366\037"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 1
+ path: 4
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002p\261|\230%\350\a\347?-}\317\274W\210\032\331\006\350\320\016\331\300\024\302\321[O\210E\231\342"
+deterministic_key {
+ chain_code: "\367\263\236B#W\223\3206\3644!?Im\250\277\vY\322\302\254\212A\227\352\244\003\031)\374b"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 1
+ path: 5
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\355\213b\333\3157\262iu\361\274\252\271\223\346\276^\350\260q\272m\025.\256\353\006\005\020\255&\017"
+deterministic_key {
+ chain_code: "\003\265\376U\001\240P\'X\364\326\326\275\375s\306\225\373\264\306H5[\356\b\301>\227\325\323\315\344"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 1
+ path: 6
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\317\215\311\246\322\335\226,\355\243\274E\270\027\307I\264\344\260\350_\230\372\034\340\363\3113T\222\274c"
+deterministic_key {
+ chain_code: "r\203\252\217hI\312S\323\377](\331C\2711\214T.\031\277\333\267,U|\323x\006\003\263\233"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 1
+ path: 7
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\277\306\215\221N\224\b=\v\001\216Ui\033v\250\326\361\221\332\215\343$\344A\306\357f\236\330\241\337"
+deterministic_key {
+ chain_code: "1n@\331(\246#4\262\017\006\360a\206i\270\211n\344\363\343Y\0000\372,\231\352\252f\272!"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 1
+ path: 8
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002!\032\210\034\267\234\t\311T\'(~c-dKt;\366\030fI5[\026\242\372\310\342\'\205"
+deterministic_key {
+ chain_code: "\004AX{\301\030\035K\353\353S\223m\271\352\323\272\'\202=_5\322\240J.\227\370[gZ?"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 1
+ path: 9
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002KZ\215?\f\365\"o\364\035\n\240\276_\335\\\256\277\212J\247\201A\325\220\361\356\213/!\301\224"
+deterministic_key {
+ chain_code: "\247\233H)F\252\276\242\370\350\263\270\b:^\247d]\232\316QUA2\n\262\321U\003\r]"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 1
+ path: 10
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\204U\310%\024\"\363\267\340\220\031\341koQ\210\037\022\224Y\354\016\370\360\374\346\216\354@B\247\233"
+deterministic_key {
+ chain_code: "w\265\277h\352\025\351\274\233\310;rk\264`*-H-r\026\326\237%\230\034\005\236_6#a"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 1
+ path: 11
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\213\241A\022c\322\031\367O\273\375\3354\'Vh\371\362\202\220\253\366\206:\033\347\300\227<6\252\034"
+deterministic_key {
+ chain_code: "R\222\341\341.\352\306O\340+\276\266#K\211\022\264\203\225\240\246\263\023l\327\356 \350\342\242]F"
+ path: 2147483692
+ path: 2147483649
+ path: 2147483648
+ path: 1
+ path: 12
+}
\ No newline at end of file
diff --git a/core/src/test/resources/org/bitcoinj/wallet/spending-wallet-from-bip44-serialization.txt b/core/src/test/resources/org/bitcoinj/wallet/spending-wallet-from-bip44-serialization.txt
new file mode 100644
index 00000000..37f67ab7
--- /dev/null
+++ b/core/src/test/resources/org/bitcoinj/wallet/spending-wallet-from-bip44-serialization.txt
@@ -0,0 +1,266 @@
+type: DETERMINISTIC_KEY
+secret_bytes: "\023\354\032\244\374\220\017\302\241u\017DvD\2662\342\316"
+ path: 2147483648
+ path: 0
+ path: 2
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003 1\316\277\355_\343\214}\a\205\233\325\232\241n\256\325\300\2369\020\nh\335\0243\355\362$?7"
+deterministic_key {
+ chain_code: "\334(\\u\022\245\370\t*\372\315\330\365\256Ms\254J_{B\035[f\333\351\272\261\363\373_\023"
+ path: 2147483648
+ path: 0
+ path: 3
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\354\fhI\2731\026\222\v\274\027\357\327\033X\324\270\323\252}\314}\221\213\272\\\362k\352\334#"
+deterministic_key {
+ chain_code: "#\351\"(\t\245\006\351\354f\334\216(\272\252\200\226\337\370\260XO\375\016/\377\306\263yE\222\311"
+ path: 2147483648
+ path: 0
+ path: 4
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002-7\rx2zP\r(B\247\350\026\205\210w\251G\b\254\213\000\227\271Q\272\342\357\304>G"
+deterministic_key {
+ chain_code: "US\242J\307\2672<\373l\217\200[\316\352\361*~\324\f\304\267oD\273\300_\340K\247V\370"
+ path: 2147483648
+ path: 0
+ path: 5
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002Y\260\332\377;T\263\335\331\004\020kv\207E=\311|\270*hP>)\340\272\203LD\036\313\271"
+deterministic_key {
+ chain_code: "\r34\'I\027\266\272\300\003\366f\274\333\260\006\311\3556!\227\216\301\361\247\354\025\305\321\376\274\214"
+ path: 2147483648
+ path: 0
+ path: 6
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002I\367x\235p9\334\234\034\366\247&\321\237\217\241V\252\017`w\212\301\000\305-\312\003\352`\302V"
+deterministic_key {
+ chain_code: "\257\001\375\203E\315\221W\316&\035\244\306\037\351\361\027\020\346\305^Z\274O\212\363P\036\273n\367\326"
+ path: 2147483648
+ path: 0
+ path: 7
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\0029\373\030u\305\214S\345/\373y%\t\252~\267\f\016t|\354\020\356\306\313\317\027\325\376\232kh"
+deterministic_key {
+ chain_code: "\260\203\277\231\352\265y\020\356r_bO\374l\347\002\032i\216Ct\260\221-\207\200\243\364;\247I"
+ path: 2147483648
+ path: 0
+ path: 8
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002[\304\301.\342\253\256\364\025\'\017\356-t\340R\250Z\327\374\250\r\331\221`\334\362a[q\260\271"
+deterministic_key {
+ chain_code: "\376\277#\275\035S\362`\323\246C73n\326P]{\260@\327\242\'\263$H\271\371\371YIJ"
+deterministic_key {
+ chain_code: "9\214\315\275\300\206\253U;\235\002fA\016\215\222\235K\253\311\3648w20\2005\343\310\\:"
+ path: 2147483648
+ path: 1
+ path: 3
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002!\373+L\341\025\265\232\a\247J?v\274\273|v\035g\033\211\026\332\233\37378c\226\020\304\360"
+deterministic_key {
+ chain_code: "i\003w\037b!Y]\214l\373]`x\355Je\\\v\205\n\310\254s\301\272\246\315\024}\366\037"
+ path: 2147483648
+ path: 1
+ path: 4
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002p\261|\230%\350\a\347?-}\317\274W\210\032\331\006\350\320\016\331\300\024\302\321[O\210E\231\342"
+deterministic_key {
+ chain_code: "\367\263\236B#W\223\3206\3644!?Im\250\277\vY\322\302\254\212A\227\352\244\003\031)\374b"
+ path: 2147483648
+ path: 1
+ path: 5
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\355\213b\333\3157\262iu\361\274\252\271\223\346\276^\350\260q\272m\025.\256\353\006\005\020\255&\017"
+deterministic_key {
+ chain_code: "\003\265\376U\001\240P\'X\364\326\326\275\375s\306\225\373\264\306H5[\356\b\301>\227\325\323\315\344"
+ path: 2147483648
+ path: 1
+ path: 6
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\317\215\311\246\322\335\226,\355\243\274E\270\027\307I\264\344\260\350_\230\372\034\340\363\3113T\222\274c"
+deterministic_key {
+ chain_code: "r\203\252\217hI\312S\323\377](\331C\2711\214T.\031\277\333\267,U|\323x\006\003\263\233"
+ path: 2147483648
+ path: 1
+ path: 7
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\277\306\215\221N\224\b=\v\001\216Ui\033v\250\326\361\221\332\215\343$\344A\306\357f\236\330\241\337"
+deterministic_key {
+ chain_code: "1n@\331(\246#4\262\017\006\360a\206i\270\211n\344\363\343Y\0000\372,\231\352\252f\272!"
+ path: 2147483648
+ path: 1
+ path: 8
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002!\032\210\034\267\234\t\311T\'(~c-dKt;\366\030fI5[\026\242\372\310\342\'\205"
+deterministic_key {
+ chain_code: "\004AX{\301\030\035K\353\353S\223m\271\352\323\272\'\202=_5\322\240J.\227\370[gZ?"
+ path: 2147483648
+ path: 1
+ path: 9
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002KZ\215?\f\365\"o\364\035\n\240\276_\335\\\256\277\212J\247\201A\325\220\361\356\213/!\301\224"
+deterministic_key {
+ chain_code: "\247\233H)F\252\276\242\370\350\263\270\b:^\247d]\232\316QUA2\n\262\321U\003\r]"
+ path: 2147483648
+ path: 1
+ path: 10
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\204U\310%\024\"\363\267\340\220\031\341koQ\210\037\022\224Y\354\016\370\360\374\346\216\354@B\247\233"
+deterministic_key {
+ chain_code: "w\265\277h\352\025\351\274\233\310;rk\264`*-H-r\026\326\237%\230\034\005\236_6#a"
+ path: 2147483648
+ path: 1
+ path: 11
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\213\241A\022c\322\031\367O\273\375\3354\'Vh\371\362\202\220\253\366\206:\033\347\300\227<6\252\034"
+deterministic_key {
+ chain_code: "R\222\341\341.\352\306O\340+\276\266#K\211\022\264\203\225\240\246\263\023l\327\356 \350\342\242]F"
+ path: 2147483648
+ path: 1
+ path: 12
+}
\ No newline at end of file
diff --git a/core/src/test/resources/org/bitcoinj/wallet/spending-wallet-serialization.txt b/core/src/test/resources/org/bitcoinj/wallet/spending-wallet-serialization.txt
new file mode 100644
index 00000000..034a6e5c
--- /dev/null
+++ b/core/src/test/resources/org/bitcoinj/wallet/spending-wallet-serialization.txt
@@ -0,0 +1,266 @@
+type: DETERMINISTIC_KEY
+secret_bytes: "\354B\331\275;\000\254?\3428\006\220G\365\243\333s\260s\213R\313\307\377f\331B\351\327=\001\333"
+public_key: "\002\357\\\252\376]\023\315\'\316`\317\362\032@\232\"\360\331\335\221] `\016,\351<\b\300\225\032m"
+creation_timestamp: 100000000
+deterministic_key {
+ chain_code: "\370\017\223\021O?.@gZ|\233j\3437\317q-\241!J \323\'\264s\203\314\321\v\346"
+ path: 2147483648
+}
+
+type: DETERMINISTIC_KEY
+secret_bytes: "a\305j\001P\217Q\242\261.\353\367\315"
+deterministic_key {
+ chain_code: "\231B\211S[\216\237\277q{a\365\216\325\250\223s\v\n(\364\257@3c\312rix\260c\217"
+ path: 2147483648
+ path: 0
+ issued_subkeys: 2
+ lookahead_size: 10
+ sigsRequiredToSpend: 1
+}
+
+type: DETERMINISTIC_KEY
+secret_bytes: "\f0\266\235\272\205\212:\265\372\214P\226\344\a{S0\354\250\210\316L\256;W\036\200t\347\343\246"
+public_key: "\0022\n\252\267NDr.7i7\332\232x\367\204G-|\204\301\333G\033g\300O\241\006\217\366\370"
+deterministic_key {
+ chain_code: "\213\237\327\245a\273\274\310\377\360\351\352<\211k\033g\0251>y\236\345Jb\244[\b\fO\0311"
+ path: 2147483648
+ path: 1
+ issued_subkeys: 1
+ lookahead_size: 10
+ sigsRequiredToSpend: 1
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002O_Q\223\337\360\245\234\322b_op\b?\030\364\255l\206\344`w\274\204\"\257\235U<}\377"
+deterministic_key {
+ chain_code: "\331\233\342\227\336r\212>\021\022p\347* +\220\021{\206\310Z\314\335\322\230\331\365\221}\321\036\035"
+ path: 2147483648
+ path: 0
+ path: 0
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\270\311\006\363\375\002{\310\254n\301\366\303\315\255\3462\004/\251\'\205+\341~d\275\350\"\313\204\313"
+deterministic_key {
+ chain_code: "5\037!\360\335\017\276\231\273\3531\020\253\223 \312\240M+\250\2520e\006\034\214{\331\376\201\004\306"
+ path: 2147483648
+ path: 0
+ path: 1
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\000\n\256n\324$.\324\365\231\f\224\001\376\266\341\036Q\212\374>\245\324\\8*\342\370\251x\b-"
+deterministic_key {
+ chain_code: "5\202n|A\251$y+t\005\365\231\357\323\264E\266l\220\367\211dA\306\370\247<\'\034\323\324"
+ path: 2147483648
+ path: 0
+ path: 2
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\313/\026\020\254\240\3455\216\342E\300\316\353m.\270\204\264\327\220H\326E9\310\227 \023~\204\215"
+deterministic_key {
+ chain_code: "\342\263a\033~\374\234UN\034\302\300\370\232\347B#L\251\267\035\255\210\356\vE\264\210\317\030]t"
+ path: 2147483648
+ path: 0
+ path: 3
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\217\n\021GL\354\214\354WhX\254\351\337w.\211&q1o\003\033\330\352**\351\356\210\264m"
+deterministic_key {
+ chain_code: "\036\216\345\320e\267p\241\000\204\254\370\251d\000\253\354\316RH\275RS\221\016\343=T\236\335\222P"
+ path: 2147483648
+ path: 0
+ path: 4
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\325\n\347\346\3273\312J\211e\335?\227\236\304i\227\377J\222;\253\017\213\371\235d\220\231\026aV"
+deterministic_key {
+ chain_code: "YSn>5\364i(j\b\326\212,\f,\322\3200\230\210)\366g\201\274\232\356\027\212O\345\215"
+ path: 2147483648
+ path: 0
+ path: 5
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\264\331\220\207*\342T\277\323\363\210\266\335\300\245?\024d\002\021\263|\253\035\253\244D\023\004\200\212X"
+deterministic_key {
+ chain_code: "yP\342|\327\364\034\f\302}\236\032\031\t\345h(q7\346?wR\221\325\370\021\225\334\317Bg"
+ path: 2147483648
+ path: 0
+ path: 6
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002HX\261\035\270!\263\2232-F\334\226n=<\0178\270^\202\225\264PF\v#\bdP/\355"
+deterministic_key {
+ chain_code: "Z#\227\222\225\303\203\006q\206\321\v\355\353\220#Oh\360]\001IQD\333\025\356\276\342\270\021\313"
+ path: 2147483648
+ path: 0
+ path: 7
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\020C\2310\227\302\342\274u\217\021h\270\235\356\326_\365\321\261\272\340\267\n\335~\360\343\"Ow\b"
+deterministic_key {
+ chain_code: "\232\000\3117\235\003`)\021g}/\203tk\201\021\364\247\245;\253\321\202\207\342\265\267_<\206\224"
+ path: 2147483648
+ path: 0
+ path: 8
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\276\211n\305\3339[D\337+\034\263\267U0\263\3039}/\376\207\030\364K\335\301\245\311\241\3419"
+deterministic_key {
+ chain_code: "B\232\f\')\277\034\316HOdn\213\b\204\361\030\357YS \365zY\2749e\260)\233.-"
+ path: 2147483648
+ path: 0
+ path: 9
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002h\356\202\222P\204x\345\226\017\256/E\304\273{)\272\034\375\2451\321\257\233\372?\320\352\356\271K"
+deterministic_key {
+ chain_code: "\035\222\325\fY\221(\006\tf#7\370\355el\377\276\245\354\002\327\320\247\315V\004\016v\367\351\225"
+ path: 2147483648
+ path: 0
+ path: 10
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\325\313@\"\320\035\277(\245\222\342{\374g\235\203\347\217\035\204j\027\034\244\021bY0\247P`\323"
+deterministic_key {
+ chain_code: "\226~!\327\210.\033\214\251\2367\205<\226`UF\354\234/\365\267E\317\202\354\211P\244\221\336\200"
+ path: 2147483648
+ path: 0
+ path: 11
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\000\334\035\2400n\26636x\316\327\3666\271\375K\031\366\307\221J@\331@dL\232Bv\324\262"
+deterministic_key {
+ chain_code: "\207^n\317\370\t\207\341*\\\360\026iBRTQ#\252Z\237\373{\315\333\004\340nA9\252\352"
+ path: 2147483648
+ path: 0
+ path: 12
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\225b\3515\202\233\335\320.7\265\274uh\230N\242\254\317J\364\331\2345\220)\362\334\216\202\\"
+deterministic_key {
+ chain_code: "\202:\344\3109?\350\345\001\314(\244q\370\233Rk\261}\302(\275\326\305R\342:\246\036\nV\330"
+ path: 2147483648
+ path: 1
+ path: 0
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003>K!8\222VEL\371\305 z\aD8\020\233\330S\251T\330\201V\026-k2\227\266;"
+deterministic_key {
+ chain_code: "\223\265.\200\316\361\241{\223\342c\212\0213ym+\032=#\360\333X\003\2770Z\311\335\267\342\313"
+ path: 2147483648
+ path: 1
+ path: 1
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\331t\251d\023\355w\221\266\301\264\306T\252\350\200\260A\220\363\212\345\021\222\236\003\210\215\342\r\251\000"
+deterministic_key {
+ chain_code: "\276\262\033\030\227\271&e\254\377\346\031\2112\344[\234Z\221-\033\306P,Mi\021\313r\031\317\341"
+ path: 2147483648
+ path: 1
+ path: 2
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002D\374\231\027\306\310\251\261\200\350@\ro\314\216\037>rp\017\276Q\203\027\016\213\320\206VqO\237"
+deterministic_key {
+ chain_code: "_K4\n\356\235\036\243O\261\200\004\367\324\305;1\247I\350*\353`\204\004d\202\302\335\200/#"
+ path: 2147483648
+ path: 1
+ path: 3
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\370\352\3530]|\262\270]5\361\263\255)\027f\342\262\272a-\275\006\302\266\236\344\332\364\r\260\321"
+deterministic_key {
+ chain_code: "o!GH\357\030\264\003_S\305\204\234wO\344.\215\377\232\025\206\351\030\227,\303%U2x\225"
+ path: 2147483648
+ path: 1
+ path: 4
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002\221\021\370a[\205\267\036\021\366`\036\371\253Yk\r\303\025\f\255\2768\310\212\234\221\333\344\340t"
+deterministic_key {
+ chain_code: "\370~\245F\n\307\377Q:\v\207\245\336F\376\2443R\034\346\b\372\b\\o\303\204D#}\266"
+ path: 2147483648
+ path: 1
+ path: 5
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\002c\034w@c\225\257n~G\330\002\241^\264\231\030\025\220gr\202`u\b\262\361\312\246\202J\341"
+deterministic_key {
+ chain_code: "\\\2542\003\022\254\361*\a/4\307\3430\322\303\v\205\351\027\260 l\332\326\235<\363v\020\232"
+ path: 2147483648
+ path: 1
+ path: 6
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003\266\304\006g\244l\271>\364\357G8B\374\026w\316\022\205\313\220\274\273>$\350\212o!\rt\230"
+deterministic_key {
+ chain_code: "6]\325WN\017o\255\314\213\344\201f\204\361\235\'\343\217\341m\327\326=T\2018g\324\261`\335"
+ path: 2147483648
+ path: 1
+ path: 7
+}
+
+type: DETERMINISTIC_KEY
+public_key: "\003X\331\344\227G\366//