/** * Deserialize an HD Key. * * @param parent The parent node in the given key's deterministic hierarchy. */ public static DeterministicKey deserialize( NetworkParameters params, byte[] serializedKey, @Nullable DeterministicKey parent) { ByteBuffer buffer = ByteBuffer.wrap(serializedKey); int header = buffer.getInt(); if (header != params.getBip32HeaderPriv() && header != params.getBip32HeaderPub()) throw new IllegalArgumentException( "Unknown header bytes: " + toBase58(serializedKey).substring(0, 4)); boolean pub = header == params.getBip32HeaderPub(); int depth = buffer.get() & 0xFF; // convert signed byte to positive int since depth cannot be negative final int parentFingerprint = buffer.getInt(); final int i = buffer.getInt(); final ChildNumber childNumber = new ChildNumber(i); ImmutableList<ChildNumber> path; if (parent != null) { if (parentFingerprint == 0) throw new IllegalArgumentException("Parent was provided but this key doesn't have one"); if (parent.getFingerprint() != parentFingerprint) throw new IllegalArgumentException("Parent fingerprints don't match"); path = HDUtils.append(parent.getPath(), childNumber); if (path.size() != depth) throw new IllegalArgumentException("Depth does not match"); } else { if (depth >= 1) // We have been given a key that is not a root key, yet we lack the object representing the // parent. // This can happen when deserializing an account key for a watching wallet. In this case, // we assume that // the client wants to conceal the key's position in the hierarchy. The path is truncated // at the // parent's node. path = ImmutableList.of(childNumber); else path = ImmutableList.of(); } byte[] chainCode = new byte[32]; buffer.get(chainCode); byte[] data = new byte[33]; buffer.get(data); checkArgument(!buffer.hasRemaining(), "Found unexpected data in key"); if (pub) { return new DeterministicKey( path, chainCode, new LazyECPoint(ECKey.CURVE.getCurve(), data), parent, depth, parentFingerprint); } else { return new DeterministicKey( path, chainCode, new BigInteger(1, data), parent, depth, parentFingerprint); } }
/** 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(); } }