From d92dfdfd14756b6837aa0065da7c5014e066e9ca Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Sat, 18 Nov 2017 23:50:13 -0800 Subject: [PATCH] Add ability to import an account key that allows for a wallet to spend from it. Previous functionality only allowed watching. Contributor: Nelson MELINA --- .../java/org/bitcoinj/kits/WalletAppKit.java | 35 +- .../wallet/DefaultKeyChainFactory.java | 11 + .../wallet/DeterministicKeyChain.java | 46 ++- .../org/bitcoinj/wallet/KeyChainFactory.java | 16 +- .../org/bitcoinj/wallet/KeyChainGroup.java | 8 + .../main/java/org/bitcoinj/wallet/Wallet.java | 29 ++ .../wallet/DeterministicKeyChainTest.java | 161 +++++++++ ...nding-wallet-account-two-serialization.txt | 266 ++++++++++++++ ...ng-wallet-from-bip44-serialization-two.txt | 324 ++++++++++++++++++ ...ending-wallet-from-bip44-serialization.txt | 266 ++++++++++++++ .../wallet/spending-wallet-serialization.txt | 266 ++++++++++++++ 11 files changed, 1421 insertions(+), 7 deletions(-) create mode 100644 core/src/test/resources/org/bitcoinj/wallet/spending-wallet-account-two-serialization.txt create mode 100644 core/src/test/resources/org/bitcoinj/wallet/spending-wallet-from-bip44-serialization-two.txt create mode 100644 core/src/test/resources/org/bitcoinj/wallet/spending-wallet-from-bip44-serialization.txt create mode 100644 core/src/test/resources/org/bitcoinj/wallet/spending-wallet-serialization.txt 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//