/** * Pre-generate enough keys to reach the lookahead size, but only if there are more than the * lookaheadThreshold to be generated, so that the Bloom filter does not have to be regenerated * that often. * * <p>The returned mutable list of keys must be inserted into the basic key chain. */ private List<DeterministicKey> maybeLookAhead( DeterministicKey parent, int issued, int lookaheadSize, int lookaheadThreshold) { checkState(lock.isHeldByCurrentThread()); final int numChildren = hierarchy.getNumChildren(parent.getPath()); final int needed = issued + lookaheadSize + lookaheadThreshold - numChildren; if (needed <= lookaheadThreshold) return new ArrayList<DeterministicKey>(); log.info( "{} keys needed for {} = {} issued + {} lookahead size + {} lookahead threshold - {} num children", needed, parent.getPathAsString(), issued, lookaheadSize, lookaheadThreshold, numChildren); List<DeterministicKey> result = new ArrayList<DeterministicKey>(needed); long now = System.currentTimeMillis(); int nextChild = numChildren; for (int i = 0; i < needed; i++) { DeterministicKey key = HDKeyDerivation.deriveThisOrNextChildKey(parent, nextChild); key = key.getPubOnly(); hierarchy.putKey(key); result.add(key); nextChild = key.getChildNumber().num() + 1; } log.info("Took {} msec", System.currentTimeMillis() - now); return result; }
// Derives the account path keys and inserts them into the basic key chain. This is important to // preserve their // order for serialization, amongst other things. private void initializeHierarchyUnencrypted(DeterministicKey baseKey) { if (baseKey.getPath().isEmpty()) { // baseKey is a master/root key derived directly from a seed. addToBasicChain(rootKey); hierarchy = new DeterministicHierarchy(rootKey); addToBasicChain(hierarchy.get(ACCOUNT_ZERO_PATH, false, true)); } else if (baseKey.getPath().size() == 1) { // baseKey is a "watching key" that we were given so we could follow along with this account. rootKey = null; addToBasicChain(baseKey); hierarchy = new DeterministicHierarchy(baseKey); } else { throw new IllegalArgumentException(); } externalKey = hierarchy.deriveChild(ACCOUNT_ZERO_PATH, false, false, ChildNumber.ZERO); internalKey = hierarchy.deriveChild(ACCOUNT_ZERO_PATH, false, false, ChildNumber.ONE); addToBasicChain(externalKey); addToBasicChain(internalKey); }
private DeterministicKey encryptNonLeaf( KeyParameter aesKey, DeterministicKeyChain chain, DeterministicKey parent, ImmutableList<ChildNumber> path) { DeterministicKey key = chain.hierarchy.get(path, false, false); key = key.encrypt(checkNotNull(basicKeyChain.getKeyCrypter()), aesKey, parent); hierarchy.putKey(key); basicKeyChain.importKey(key); return key; }
// For use in encryption. private DeterministicKeyChain( KeyCrypter crypter, KeyParameter aesKey, DeterministicKeyChain chain) { // Can't encrypt a watching chain. checkNotNull(chain.rootKey); checkNotNull(chain.seed); checkArgument(!chain.rootKey.isEncrypted(), "Chain already encrypted"); this.issuedExternalKeys = chain.issuedExternalKeys; this.issuedInternalKeys = chain.issuedInternalKeys; this.lookaheadSize = chain.lookaheadSize; this.lookaheadThreshold = chain.lookaheadThreshold; this.seed = chain.seed.encrypt(crypter, aesKey); basicKeyChain = new BasicKeyChain(crypter); // The first number is the "account number" but we don't use that feature. rootKey = chain.rootKey.encrypt(crypter, aesKey, null); hierarchy = new DeterministicHierarchy(rootKey); basicKeyChain.importKey(rootKey); DeterministicKey account = encryptNonLeaf(aesKey, chain, rootKey, ACCOUNT_ZERO_PATH); externalKey = encryptNonLeaf(aesKey, chain, account, EXTERNAL_PATH); internalKey = encryptNonLeaf(aesKey, chain, account, INTERNAL_PATH); // Now copy the (pubkey only) leaf keys across to avoid rederiving them. The private key bytes // are missing // anyway so there's nothing to encrypt. for (ECKey eckey : chain.basicKeyChain.getKeys()) { DeterministicKey key = (DeterministicKey) eckey; if (key.getPath().size() != 3) continue; // Not a leaf key. DeterministicKey parent = hierarchy.get(checkNotNull(key.getParent()).getPath(), false, false); // Clone the key to the new encrypted hierarchy. key = new DeterministicKey(key.getPubOnly(), parent); hierarchy.putKey(key); basicKeyChain.importKey(key); } }
/** * Returns the deterministic key for the given absolute path in the hierarchy, optionally creating * it */ public DeterministicKey getKeyByPath(List<ChildNumber> path, boolean create) { return hierarchy.get(path, false, create); }
/** Returns freshly derived key/s that have not been returned by this method before. */ @Override public List<DeterministicKey> getKeys(KeyPurpose purpose, int numberOfKeys) { checkArgument(numberOfKeys > 0); lock.lock(); try { DeterministicKey parentKey; int index; switch (purpose) { // Map both REFUND and RECEIVE_KEYS to the same branch for now. Refunds are a feature of // the BIP 70 // payment protocol. Later we may wish to map it to a different branch (in a new wallet // version?). // This would allow a watching wallet to only be able to see inbound payments, but not // change // (i.e. spends) or refunds. Might be useful for auditing ... case RECEIVE_FUNDS: case REFUND: issuedExternalKeys += numberOfKeys; index = issuedExternalKeys; parentKey = externalKey; break; case AUTHENTICATION: case CHANGE: issuedInternalKeys += numberOfKeys; index = issuedInternalKeys; parentKey = internalKey; break; default: throw new UnsupportedOperationException(); } // Optimization: potentially do a very quick key generation for just the number of keys we // need if we // didn't already create them, ignoring the configured lookahead size. This ensures we'll be // able to // retrieve the keys in the following loop, but if we're totally fresh and didn't get a chance // to // calculate the lookahead keys yet, this will not block waiting to calculate 100+ EC point // multiplies. // On slow/crappy Android phones looking ahead 100 keys can take ~5 seconds but the OS will // kill us // if we block for just one second on the UI thread. Because UI threads may need an address in // order // to render the screen, we need getKeys to be fast even if the wallet is totally brand new // and lookahead // didn't happen yet. // // It's safe to do this because when a network thread tries to calculate a Bloom filter, we'll // go ahead // and calculate the full lookahead zone there, so network requests will always use the right // amount. List<DeterministicKey> lookahead = maybeLookAhead(parentKey, index, 0, 0); basicKeyChain.importKeys(lookahead); List<DeterministicKey> keys = new ArrayList<DeterministicKey>(numberOfKeys); for (int i = 0; i < numberOfKeys; i++) { ImmutableList<ChildNumber> path = HDUtils.append(parentKey.getPath(), new ChildNumber(index - numberOfKeys + i, false)); DeterministicKey k = hierarchy.get(path, false, false); // Just a last minute sanity check before we hand the key out to the app for usage. This // isn't inspired // by any real problem reports from bitcoinj users, but I've heard of cases via the // grapevine of // places that lost money due to bitflips causing addresses to not match keys. Of course in // an // environment with flaky RAM there's no real way to always win: bitflips could be // introduced at any // other layer. But as we're potentially retrieving from long term storage here, check // anyway. checkForBitFlip(k); keys.add(k); } return keys; } finally { lock.unlock(); } }