/**
* Copyright 2014 Mike Hearn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.matthewmitchell.peercoinj.wallet;
import com.matthewmitchell.peercoinj.core.*;
import com.matthewmitchell.peercoinj.crypto.*;
import com.matthewmitchell.peercoinj.params.MainNetParams;
import com.matthewmitchell.peercoinj.utils.BriefLogFormatter;
import com.matthewmitchell.peercoinj.utils.Threading;
import com.google.common.collect.ImmutableList;
import com.matthewmitchell.peercoinj.wallet.*;
import org.junit.Before;
import org.junit.Test;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.*;
import static org.junit.Assert.assertArrayEquals;
public class KeyChainGroupTest {
// Number of initial keys in this tests HD wallet, including interior keys.
private static final int INITIAL_KEYS = 4;
private static final int LOOKAHEAD_SIZE = 5;
private static final NetworkParameters params = MainNetParams.get();
private static final String XPUB = "xpub68KFnj3bqUx1s7mHejLDBPywCAKdJEu1b49uniEEn2WSbHmZ7xbLqFTjJbtx1LUcAt1DwhoqWHmo2s5WMJp6wi38CiF2hYD49qVViKVvAoi";
private KeyChainGroup group;
private DeterministicKey watchingAccountKey;
@Before
public void setup() {
BriefLogFormatter.init();
Utils.setMockClock();
group = new KeyChainGroup(params);
group.setLookaheadSize(LOOKAHEAD_SIZE); // Don't want slow tests.
group.getActiveKeyChain(); // Force create a chain.
watchingAccountKey = DeterministicKey.deserializeB58(null, XPUB);
}
private KeyChainGroup createMarriedKeyChainGroup() {
byte[] entropy = Sha256Hash.create("don't use a seed like this in real life".getBytes()).getBytes();
DeterministicSeed seed = new DeterministicSeed(entropy, "", MnemonicCode.BIP39_STANDARDISATION_TIME_SECS);
KeyChainGroup group = new KeyChainGroup(params, seed, ImmutableList.of(watchingAccountKey), 2);
group.setLookaheadSize(LOOKAHEAD_SIZE);
group.getActiveKeyChain();
return group;
}
@Test
public void freshCurrentKeys() throws Exception {
int numKeys = ((group.getLookaheadSize() + group.getLookaheadThreshold()) * 2) // * 2 because of internal/external
+ 1 // keys issued
+ 3 /* account key + int/ext parent keys */;
assertEquals(numKeys, group.numKeys());
assertEquals(2 * numKeys, group.getBloomFilterElementCount());
ECKey r1 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(numKeys, group.numKeys());
assertEquals(2 * numKeys, group.getBloomFilterElementCount());
ECKey i1 = new ECKey();
group.importKeys(i1);
numKeys++;
assertEquals(numKeys, group.numKeys());
assertEquals(2 * numKeys, group.getBloomFilterElementCount());
ECKey r2 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(r1, r2);
ECKey c1 = group.currentKey(KeyChain.KeyPurpose.CHANGE);
assertNotEquals(r1, c1);
ECKey r3 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertNotEquals(r1, r3);
ECKey c2 = group.freshKey(KeyChain.KeyPurpose.CHANGE);
assertNotEquals(r3, c2);
// Current key has not moved and will not under marked as used.
ECKey r4 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(r2, r4);
ECKey c3 = group.currentKey(KeyChain.KeyPurpose.CHANGE);
assertEquals(c1, c3);
// Mark as used. Current key is now different.
group.markPubKeyAsUsed(r4.getPubKey());
ECKey r5 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertNotEquals(r4, r5);
}
@Test
public void freshCurrentKeysForMarriedKeychain() throws Exception {
group = createMarriedKeyChainGroup();
try {
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
fail();
} catch (UnsupportedOperationException e) {
}
try {
group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
fail();
} catch (UnsupportedOperationException e) {
}
}
@Test
public void imports() throws Exception {
ECKey key1 = new ECKey();
int numKeys = group.numKeys();
assertFalse(group.removeImportedKey(key1));
assertEquals(1, group.importKeys(ImmutableList.of(key1)));
assertEquals(numKeys + 1, group.numKeys()); // Lookahead is triggered by requesting a key, so none yet.
group.removeImportedKey(key1);
assertEquals(numKeys, group.numKeys());
}
@Test
public void findKey() throws Exception {
ECKey a = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
ECKey b = group.freshKey(KeyChain.KeyPurpose.CHANGE);
ECKey c = new ECKey();
ECKey d = new ECKey(); // Not imported.
group.importKeys(c);
assertTrue(group.hasKey(a));
assertTrue(group.hasKey(b));
assertTrue(group.hasKey(c));
assertFalse(group.hasKey(d));
ECKey result = group.findKeyFromPubKey(a.getPubKey());
assertEquals(a, result);
result = group.findKeyFromPubKey(b.getPubKey());
assertEquals(b, result);
result = group.findKeyFromPubHash(a.getPubKeyHash());
assertEquals(a, result);
result = group.findKeyFromPubHash(b.getPubKeyHash());
assertEquals(b, result);
result = group.findKeyFromPubKey(c.getPubKey());
assertEquals(c, result);
result = group.findKeyFromPubHash(c.getPubKeyHash());
assertEquals(c, result);
assertNull(group.findKeyFromPubKey(d.getPubKey()));
assertNull(group.findKeyFromPubHash(d.getPubKeyHash()));
}
@Test
public void currentP2SHAddress() throws Exception {
group = createMarriedKeyChainGroup();
Address a1 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(a1.isSelectedP2SHAddress());
Address a2 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(a1, a2);
Address a3 = group.currentAddress(KeyChain.KeyPurpose.CHANGE);
assertNotEquals(a2, a3);
}
@Test
public void freshAddress() throws Exception {
group = createMarriedKeyChainGroup();
Address a1 = group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
Address a2 = group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(a1.isSelectedP2SHAddress());
assertNotEquals(a1, a2);
group.getBloomFilterElementCount();
assertEquals(((group.getLookaheadSize() + group.getLookaheadThreshold()) * 2) // * 2 because of internal/external
+ (2 - group.getLookaheadThreshold()) // keys issued
+ 4 /* master, account, int, ext */, group.numKeys());
Address a3 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(a2, a3);
}
@Test
public void findRedeemData() throws Exception {
group = createMarriedKeyChainGroup();
// test script hash that we don't have
assertNull(group.findRedeemDataFromScriptHash(new ECKey().getPubKey()));
// test our script hash
Address address = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
RedeemData redeemData = group.findRedeemDataFromScriptHash(address.getHash160());
assertNotNull(redeemData);
assertNotNull(redeemData.redeemScript);
assertEquals(2, redeemData.keys.size());
}
// Check encryption with and without a basic keychain.
@Test
public void encryptionWithoutImported() throws Exception {
encryption(false);
}
@Test
public void encryptionWithImported() throws Exception {
encryption(true);
}
public void encryption(boolean withImported) throws Exception {
Utils.rollMockClock(0);
long now = Utils.currentTimeSeconds();
ECKey a = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(now, group.getEarliestKeyCreationTime());
Utils.rollMockClock(-86400);
long yesterday = Utils.currentTimeSeconds();
ECKey b = new ECKey();
assertFalse(group.isEncrypted());
try {
group.checkPassword("foo"); // Cannot check password of an unencrypted group.
fail();
} catch (IllegalStateException e) {
}
if (withImported) {
assertEquals(now, group.getEarliestKeyCreationTime());
group.importKeys(b);
assertEquals(yesterday, group.getEarliestKeyCreationTime());
}
KeyCrypterScrypt scrypt = new KeyCrypterScrypt(2);
final KeyParameter aesKey = scrypt.deriveKey("password");
group.encrypt(scrypt, aesKey);
assertTrue(group.isEncrypted());
assertTrue(group.checkPassword("password"));
assertFalse(group.checkPassword("wrong password"));
final ECKey ea = group.findKeyFromPubKey(a.getPubKey());
assertTrue(checkNotNull(ea).isEncrypted());
if (withImported) {
assertTrue(checkNotNull(group.findKeyFromPubKey(b.getPubKey())).isEncrypted());
assertEquals(yesterday, group.getEarliestKeyCreationTime());
} else {
assertEquals(now, group.getEarliestKeyCreationTime());
}
try {
ea.sign(Sha256Hash.ZERO_HASH);
fail();
} catch (ECKey.KeyIsEncryptedException e) {
// Ignored.
}
if (withImported) {
ECKey c = new ECKey();
try {
group.importKeys(c);
fail();
} catch (KeyCrypterException e) {
}
group.importKeysAndEncrypt(ImmutableList.of(c), aesKey);
ECKey ec = group.findKeyFromPubKey(c.getPubKey());
try {
group.importKeysAndEncrypt(ImmutableList.of(ec), aesKey);
fail();
} catch (IllegalArgumentException e) {
}
}
try {
group.decrypt(scrypt.deriveKey("WRONG PASSWORD"));
fail();
} catch (KeyCrypterException e) {
}
group.decrypt(aesKey);
assertFalse(group.isEncrypted());
assertFalse(checkNotNull(group.findKeyFromPubKey(a.getPubKey())).isEncrypted());
if (withImported) {
assertFalse(checkNotNull(group.findKeyFromPubKey(b.getPubKey())).isEncrypted());
assertEquals(yesterday, group.getEarliestKeyCreationTime());
} else {
assertEquals(now, group.getEarliestKeyCreationTime());
}
}
@Test
public void encryptionWhilstEmpty() throws Exception {
group = new KeyChainGroup(params);
group.setLookaheadSize(5);
KeyCrypterScrypt scrypt = new KeyCrypterScrypt(2);
final KeyParameter aesKey = scrypt.deriveKey("password");
group.encrypt(scrypt, aesKey);
assertTrue(group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).isEncrypted());
final ECKey key = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
group.decrypt(aesKey);
assertFalse(checkNotNull(group.findKeyFromPubKey(key.getPubKey())).isEncrypted());
}
@Test
public void bloom() throws Exception {
ECKey key1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
ECKey key2 = new ECKey();
BloomFilter filter = group.getBloomFilter(group.getBloomFilterElementCount(), 0.001, (long)(Math.random() * Long.MAX_VALUE));
assertTrue(filter.contains(key1.getPubKeyHash()));
assertTrue(filter.contains(key1.getPubKey()));
assertFalse(filter.contains(key2.getPubKey()));
// Check that the filter contains the lookahead buffer and threshold zone.
for (int i = 0; i < LOOKAHEAD_SIZE + group.getLookaheadThreshold(); i++) {
ECKey k = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(filter.contains(k.getPubKeyHash()));
}
// We ran ahead of the lookahead buffer.
assertFalse(filter.contains(group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).getPubKey()));
group.importKeys(key2);
filter = group.getBloomFilter(group.getBloomFilterElementCount(), 0.001, (long) (Math.random() * Long.MAX_VALUE));
assertTrue(filter.contains(key1.getPubKeyHash()));
assertTrue(filter.contains(key1.getPubKey()));
assertTrue(filter.contains(key2.getPubKey()));
}
@Test
public void findRedeemScriptFromPubHash() throws Exception {
group = createMarriedKeyChainGroup();
Address address = group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(group.findRedeemDataFromScriptHash(address.getHash160()) != null);
group.getBloomFilterElementCount();
KeyChainGroup group2 = createMarriedKeyChainGroup();
group2.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
group2.getBloomFilterElementCount(); // Force lookahead.
// test address from lookahead zone and lookahead threshold zone
for (int i = 0; i < group.getLookaheadSize() + group.getLookaheadThreshold(); i++) {
address = group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(group2.findRedeemDataFromScriptHash(address.getHash160()) != null);
}
assertFalse(group2.findRedeemDataFromScriptHash(group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS).getHash160()) != null);
}
@Test
public void bloomFilterForMarriedChains() throws Exception {
group = createMarriedKeyChainGroup();
int bufferSize = group.getLookaheadSize() + group.getLookaheadThreshold();
int expected = bufferSize * 2 /* chains */ * 2 /* elements */;
assertEquals(expected, group.getBloomFilterElementCount());
Address address1 = group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(expected, group.getBloomFilterElementCount());
BloomFilter filter = group.getBloomFilter(expected + 2, 0.001, (long)(Math.random() * Long.MAX_VALUE));
assertTrue(filter.contains(address1.getHash160()));
Address address2 = group.freshAddress(KeyChain.KeyPurpose.CHANGE);
assertTrue(filter.contains(address2.getHash160()));
// Check that the filter contains the lookahead buffer.
for (int i = 0; i < bufferSize - 1 /* issued address */; i++) {
Address address = group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue("key " + i, filter.contains(address.getHash160()));
}
// We ran ahead of the lookahead buffer.
assertFalse(filter.contains(group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS).getHash160()));
}
@Test
public void earliestKeyTime() throws Exception {
long now = Utils.currentTimeSeconds(); // mock
long yesterday = now - 86400;
assertEquals(now, group.getEarliestKeyCreationTime());
Utils.rollMockClock(10000);
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
Utils.rollMockClock(10000);
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
// Check that all keys are assumed to be created at the same instant the seed is.
assertEquals(now, group.getEarliestKeyCreationTime());
ECKey key = new ECKey();
key.setCreationTimeSeconds(yesterday);
group.importKeys(key);
assertEquals(yesterday, group.getEarliestKeyCreationTime());
}
@Test
public void events() throws Exception {
// Check that events are registered with the right chains and that if a chain is added, it gets the event
// listeners attached properly even post-hoc.
final AtomicReference<ECKey> ran = new AtomicReference<ECKey>(null);
final KeyChainEventListener listener = new KeyChainEventListener() {
@Override
public void onKeysAdded(List<ECKey> keys) {
ran.set(keys.get(0));
}
};
group.addEventListener(listener, Threading.SAME_THREAD);
ECKey key = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(key, ran.getAndSet(null));
ECKey key2 = new ECKey();
group.importKeys(key2);
assertEquals(key2, ran.getAndSet(null));
group.removeEventListener(listener);
ECKey key3 = new ECKey();
group.importKeys(key3);
assertNull(ran.get());
}
@Test
public void serialization() throws Exception {
assertEquals(INITIAL_KEYS + 1 /* for the seed */, group.serializeToProtobuf().size());
group = KeyChainGroup.fromProtobufUnencrypted(params, group.serializeToProtobuf(), 1);
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key2 = group.freshKey(KeyChain.KeyPurpose.CHANGE);
group.getBloomFilterElementCount();
List<Protos.Key> protoKeys1 = group.serializeToProtobuf();
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 1, protoKeys1.size());
group.importKeys(new ECKey());
List<Protos.Key> protoKeys2 = group.serializeToProtobuf();
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 2, protoKeys2.size());
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys1, 1);
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 1, protoKeys1.size());
assertTrue(group.hasKey(key1));
assertTrue(group.hasKey(key2));
assertEquals(key2, group.currentKey(KeyChain.KeyPurpose.CHANGE));
assertEquals(key1, group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS));
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys2, 1);
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 2, protoKeys2.size());
assertTrue(group.hasKey(key1));
assertTrue(group.hasKey(key2));
KeyCrypterScrypt scrypt = new KeyCrypterScrypt(2);
final KeyParameter aesKey = scrypt.deriveKey("password");
group.encrypt(scrypt, aesKey);
List<Protos.Key> protoKeys3 = group.serializeToProtobuf();
group = KeyChainGroup.fromProtobufEncrypted(params, protoKeys3, 1, scrypt);
assertTrue(group.isEncrypted());
assertTrue(group.checkPassword("password"));
group.decrypt(aesKey);
// No need for extensive contents testing here, as that's done in the keychain class tests.
}
@Test
public void serializeWatching() throws Exception {
group = new KeyChainGroup(params, watchingAccountKey);
group.setLookaheadSize(LOOKAHEAD_SIZE);
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
group.freshKey(KeyChain.KeyPurpose.CHANGE);
group.getBloomFilterElementCount(); // Force lookahead.
List<Protos.Key> protoKeys1 = group.serializeToProtobuf();
assertEquals(3 + (group.getLookaheadSize() + group.getLookaheadThreshold() + 1) * 2, protoKeys1.size());
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys1, 1);
assertEquals(3 + (group.getLookaheadSize() + group.getLookaheadThreshold() + 1) * 2, group.serializeToProtobuf().size());
}
@Test
public void serializeMarried() throws Exception {
group = createMarriedKeyChainGroup();
Address address1 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(group.isMarried());
assertEquals(2, group.getSigsRequiredToSpend());
List<Protos.Key> protoKeys = group.serializeToProtobuf();
KeyChainGroup group2 = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys, 2);
assertTrue(group2.isMarried());
assertEquals(2, group.getSigsRequiredToSpend());
Address address2 = group2.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(address1, address2);
}
@Test
public void addFollowingAccounts() throws Exception {
assertFalse(group.isMarried());
group.addFollowingAccountKeys(ImmutableList.of(watchingAccountKey));
assertTrue(group.isMarried());
}
@Test (expected = IllegalStateException.class)
public void addFollowingAccountsTwiceShouldFail() {
ImmutableList<DeterministicKey> followingKeys = ImmutableList.of(watchingAccountKey);
group.addFollowingAccountKeys(followingKeys);
group.addFollowingAccountKeys(followingKeys);
}
@Test (expected = IllegalStateException.class)
public void addFollowingAccountsForUsedKeychainShouldFail() {
group.freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
group.addFollowingAccountKeys(ImmutableList.of(watchingAccountKey));
}
@Test
public void constructFromSeed() throws Exception {
ECKey key1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
final DeterministicSeed seed = checkNotNull(group.getActiveKeyChain().getSeed());
KeyChainGroup group2 = new KeyChainGroup(params, seed);
group2.setLookaheadSize(5);
ECKey key2 = group2.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(key1, key2);
}
@Test(expected = DeterministicUpgradeRequiredException.class)
public void deterministicUpgradeRequired() throws Exception {
// Check that if we try to use HD features in a KCG that only has random keys, we get an exception.
group = new KeyChainGroup(params);
group.importKeys(new ECKey(), new ECKey());
assertTrue(group.isDeterministicUpgradeRequired());
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); // throws
}
@Test
public void deterministicUpgradeUnencrypted() throws Exception {
// Check that a group that contains only random keys has its HD chain created using the private key bytes of
// the oldest random key, so upgrading the same wallet twice gives the same outcome.
group = new KeyChainGroup(params);
group.setLookaheadSize(LOOKAHEAD_SIZE); // Don't want slow tests.
ECKey key1 = new ECKey();
Utils.rollMockClock(86400);
ECKey key2 = new ECKey();
group.importKeys(key2, key1);
List<Protos.Key> protobufs = group.serializeToProtobuf();
group.upgradeToDeterministic(0, null);
assertFalse(group.isDeterministicUpgradeRequired());
DeterministicKey dkey1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicSeed seed1 = group.getActiveKeyChain().getSeed();
assertNotNull(seed1);
group = KeyChainGroup.fromProtobufUnencrypted(params, protobufs, 1);
group.upgradeToDeterministic(0, null); // Should give same result as last time.
DeterministicKey dkey2 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicSeed seed2 = group.getActiveKeyChain().getSeed();
assertEquals(seed1, seed2);
assertEquals(dkey1, dkey2);
// Check we used the right (oldest) key despite backwards import order.
byte[] truncatedBytes = Arrays.copyOfRange(key1.getSecretBytes(), 0, 16);
assertArrayEquals(seed1.getEntropyBytes(), truncatedBytes);
}
@Test
public void deterministicUpgradeRotating() throws Exception {
group = new KeyChainGroup(params);
group.setLookaheadSize(LOOKAHEAD_SIZE); // Don't want slow tests.
long now = Utils.currentTimeSeconds();
ECKey key1 = new ECKey();
Utils.rollMockClock(86400);
ECKey key2 = new ECKey();
Utils.rollMockClock(86400);
ECKey key3 = new ECKey();
group.importKeys(key2, key1, key3);
group.upgradeToDeterministic(now + 10, null);
DeterministicSeed seed = group.getActiveKeyChain().getSeed();
assertNotNull(seed);
// Check we used the right key: oldest non rotating.
byte[] truncatedBytes = Arrays.copyOfRange(key2.getSecretBytes(), 0, 16);
assertArrayEquals(seed.getEntropyBytes(), truncatedBytes);
}
@Test
public void deterministicUpgradeEncrypted() throws Exception {
group = new KeyChainGroup(params);
final ECKey key = new ECKey();
group.importKeys(key);
final KeyCrypterScrypt crypter = new KeyCrypterScrypt();
final KeyParameter aesKey = crypter.deriveKey("abc");
assertTrue(group.isDeterministicUpgradeRequired());
group.encrypt(crypter, aesKey);
assertTrue(group.isDeterministicUpgradeRequired());
try {
group.upgradeToDeterministic(0, null);
fail();
} catch (DeterministicUpgradeRequiresPassword e) {
// Expected.
}
group.upgradeToDeterministic(0, aesKey);
assertFalse(group.isDeterministicUpgradeRequired());
final DeterministicSeed deterministicSeed = group.getActiveKeyChain().getSeed();
assertNotNull(deterministicSeed);
assertTrue(deterministicSeed.isEncrypted());
byte[] entropy = checkNotNull(group.getActiveKeyChain().toDecrypted(aesKey).getSeed()).getEntropyBytes();
// Check we used the right key: oldest non rotating.
byte[] truncatedBytes = Arrays.copyOfRange(key.getSecretBytes(), 0, 16);
assertArrayEquals(entropy, truncatedBytes);
}
@Test
public void markAsUsed() throws Exception {
Address addr1 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
Address addr2 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(addr1, addr2);
group.markPubKeyHashAsUsed(addr1.getHash160());
Address addr3 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertNotEquals(addr2, addr3);
}
}