/** 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; } } }
/** 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; } } }
/** 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; } } }
/** 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; } } }
@Override public SigningKey makeSigningKey() { ECKey newSignKey = keyChainGroup.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); return new SigningKeyImpl(newSignKey, this.getParams()); }
/** * Returns a key that hasn't been seen in a transaction yet, and which is suitable for displaying in a wallet * user interface as "a convenient key to receive funds on" when the purpose parameter is * {@link KeyChain.KeyPurpose#RECEIVE_FUNDS}. The returned key is stable until * it's actually seen in a pending or confirmed transaction, at which point this method will start returning * a different key (for each purpose independently). * <p>This method is not supposed to be used for married keychains and will throw UnsupportedOperationException if * the active chain is married. * For married keychains use {@link #currentAddress(KeyChain.KeyPurpose)} * to get a proper P2SH address</p> */ public DeterministicKey currentKey(KeyChain.KeyPurpose purpose) { DeterministicKeyChain chain = getActiveKeyChain(); if (chain.isMarried()) { throw new UnsupportedOperationException("Key is not suitable to receive coins for married keychains." + " Use freshAddress to get P2SH address instead"); } DeterministicKey current = currentKeys.get(purpose); if (current == null) { current = freshKey(purpose); currentKeys.put(purpose, current); } return current; }
/** * Returns a key that hasn't been seen in a transaction yet, and which is suitable for displaying in a wallet * user interface as "a convenient key to receive funds on" when the purpose parameter is * {@link KeyChain.KeyPurpose#RECEIVE_FUNDS}. The returned key is stable until * it's actually seen in a pending or confirmed transaction, at which point this method will start returning * a different key (for each purpose independently). * <p>This method is not supposed to be used for married keychains and will throw UnsupportedOperationException if * the active chain is married. * For married keychains use {@link #currentAddress(KeyChain.KeyPurpose)} * to get a proper P2SH address</p> */ public DeterministicKey currentKey(KeyChain.KeyPurpose purpose) { DeterministicKeyChain chain = getActiveKeyChain(); if (chain.isMarried()) { throw new UnsupportedOperationException("Key is not suitable to receive coins for married keychains." + " Use freshAddress to get P2SH address instead"); } DeterministicKey current = currentKeys.get(purpose); if (current == null) { current = freshKey(purpose); currentKeys.put(purpose, current); } return current; }
/** * Returns a key that hasn't been seen in a transaction yet, and which is suitable for displaying in a wallet * user interface as "a convenient key to receive funds on" when the purpose parameter is * {@link KeyChain.KeyPurpose#RECEIVE_FUNDS}. The returned key is stable until * it's actually seen in a pending or confirmed transaction, at which point this method will start returning * a different key (for each purpose independently). * <p>This method is not supposed to be used for married keychains and will throw UnsupportedOperationException if * the active chain is married. * For married keychains use {@link #currentAddress(KeyChain.KeyPurpose)} * to get a proper P2SH address</p> */ public DeterministicKey currentKey(KeyChain.KeyPurpose purpose) { DeterministicKeyChain chain = getActiveKeyChain(); if (chain.isMarried()) { throw new UnsupportedOperationException("Key is not suitable to receive coins for married keychains." + " Use freshAddress to get P2SH address instead"); } DeterministicKey current = currentKeys.get(purpose); if (current == null) { current = freshKey(purpose); currentKeys.put(purpose, current); } return current; }
/** * 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); } }
/** * 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 freshKey(purpose).toAddress(params); } }
/** * 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 freshKey(purpose).toAddress(params); } }
/** * 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 freshKey(purpose).toAddress(params); } }
@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 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 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 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()); }
@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(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 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())); }