public static KeyChainGroup fromProtobufEncrypted(NetworkParameters params, List<Protos.Key> keys, KeyCrypter crypter, KeyChainFactory factory, boolean useSegwit) throws UnreadableWalletException { checkNotNull(crypter); BasicKeyChain basicKeyChain = BasicKeyChain.fromProtobufEncrypted(keys, crypter, useSegwit); List<DeterministicKeyChain> chains = DeterministicKeyChain.fromProtobuf(keys, crypter, factory, useSegwit); EnumMap<KeyChain.KeyPurpose, DeterministicKey> currentKeys = null; if (!chains.isEmpty()) currentKeys = createCurrentKeysMap(chains); extractFollowingKeychains(chains); return new KeyChainGroup(params, basicKeyChain, chains, currentKeys, crypter, useSegwit); }
/** If the given key is "current", advance the current key to a new one. */ private void maybeMarkCurrentKeyAsUsed(DeterministicKey key) { // It's OK for currentKeys to be empty here: it means we're a married wallet and the key may be a part of a // rotating chain. for (Map.Entry<KeyChain.KeyPurpose, DeterministicKey> entry : currentKeys.entrySet()) { if (entry.getValue() != null && entry.getValue().equals(key)) { log.info("Marking key as used: {}", key); currentKeys.put(entry.getKey(), freshKey(entry.getKey())); return; } } }
/** * Returns address for a {@link #currentKey(KeyChain.KeyPurpose)} */ public Address currentAddress(KeyChain.KeyPurpose purpose) { DeterministicKeyChain chain = getActiveKeyChain(); if (chain.isMarried()) { Address current = currentAddresses.get(purpose); if (current == null) { current = freshAddress(purpose); currentAddresses.put(purpose, current); } return current; } else { return currentKey(purpose).toAddress(params); } }
/** * Creates a wallet containing a given set of keys. All further keys will be derived from the oldest key. */ public static Wallet fromKeys(NetworkParameters params, List<ECKey> keys, boolean useSegwit) { for (ECKey key : keys) checkArgument(!(key instanceof DeterministicKey)); KeyChainGroup group = new KeyChainGroup(params, useSegwit); group.importKeys(keys); return new Wallet(params, group); }
@Test public void serialization() throws Exception { int initialKeys = INITIAL_KEYS + group.getActiveKeyChain().getAccountPath().size() - 1; assertEquals(initialKeys + 1 /* for the seed */, group.serializeToProtobuf().size()); group = KeyChainGroup.fromProtobufUnencrypted(PARAMS, group.serializeToProtobuf()); 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(initialKeys + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 1, protoKeys1.size()); group.importKeys(new ECKey()); List<Protos.Key> protoKeys2 = group.serializeToProtobuf(); assertEquals(initialKeys + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 2, protoKeys2.size()); group = KeyChainGroup.fromProtobufUnencrypted(PARAMS, protoKeys1); assertEquals(initialKeys + ((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); assertEquals(initialKeys + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 2, protoKeys2.size()); assertTrue(group.hasKey(key1)); assertTrue(group.hasKey(key2)); group.encrypt(scrypt, aesKey); List<Protos.Key> protoKeys3 = group.serializeToProtobuf(); group = KeyChainGroup.fromProtobufEncrypted(PARAMS, protoKeys3, scrypt); assertTrue(group.isEncrypted());
@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); assertEquals(3 + (group.getLookaheadSize() + group.getLookaheadThreshold() + 1) * 2, group.serializeToProtobuf().size()); }
@Test public void freshCurrentKeys() throws Exception { int numKeys = ((group.getLookaheadSize() + group.getLookaheadThreshold()) * 2) // * 2 because of internal/external + 1 // keys issued + group.getActiveKeyChain().getAccountPath().size() + 2 /* 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()); 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); ECKey r4 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); assertEquals(r2, r4); ECKey c3 = group.currentKey(KeyChain.KeyPurpose.CHANGE); assertEquals(c1, c3);
@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()); }
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) { assertEquals(now, group.getEarliestKeyCreationTime()); group.importKeys(b); assertEquals(yesterday, group.getEarliestKeyCreationTime()); 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());
@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.isP2SHAddress()); assertNotEquals(a1, a2); group.getBloomFilterElementCount(); assertEquals(((group.getLookaheadSize() + group.getLookaheadThreshold()) * 2) // * 2 because of internal/external + (2 - group.getLookaheadThreshold()) // keys issued + group.getActiveKeyChain().getAccountPath().size() + 3 /* master, account, int, ext */, group.numKeys()); Address a3 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS); assertEquals(a2, a3); }
@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); }
/** * Returns address for a {@link #freshKey(KeyChain.KeyPurpose)} */ public Address freshAddress(KeyChain.KeyPurpose purpose) { DeterministicKeyChain chain = getActiveKeyChain(); if (chain.isMarried()) { Script outputScript = chain.freshOutputScript(purpose); checkState(outputScript.isPayToScriptHash()); // Only handle P2SH for now Address freshAddress = Address.fromP2SHScript(params, outputScript); maybeLookaheadScripts(); currentAddresses.put(purpose, freshAddress); return freshAddress; } else { return useSegwit ? freshKey(purpose).toSegwitAddress(params) : freshKey(purpose).toAddress(params); } }
private KeyChainGroup createMarriedKeyChainGroup() { KeyChainGroup group = new KeyChainGroup(PARAMS); DeterministicKeyChain chain = createMarriedKeyChain(); group.addAndActivateHDChain(chain); group.setLookaheadSize(LOOKAHEAD_SIZE); group.getActiveKeyChain(); return group; }
@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 public void serializeMarried() throws Exception { group = createMarriedKeyChainGroup(); Address address1 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS); assertTrue(group.isMarried()); assertEquals(2, group.getActiveKeyChain().getSigsRequiredToSpend()); List<Protos.Key> protoKeys = group.serializeToProtobuf(); KeyChainGroup group2 = KeyChainGroup.fromProtobufUnencrypted(PARAMS, protoKeys); assertTrue(group2.isMarried()); assertEquals(2, group.getActiveKeyChain().getSigsRequiredToSpend()); Address address2 = group2.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS); assertEquals(address1, address2); }
@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 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<>(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()); }
/** * Gets the active keychain via {@link KeyChainGroup#getActiveKeyChain()} */ public DeterministicKeyChain getActiveKeyChain() { return keyChainGroup.getActiveKeyChain(); }
@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, PARAMS); }
/** * Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given watching key. A * watching key corresponds to account zero in the recommended BIP32 key hierarchy. */ public static Wallet fromWatchingKey(NetworkParameters params, DeterministicKey watchKey) { return new Wallet(params, new KeyChainGroup(params, watchKey)); }