Beispiel #1
0
 public void timeLockedTransaction(boolean useNotFound) throws Exception {
   connectWithVersion(useNotFound ? 70001 : 60001);
   // Test that if we receive a relevant transaction that has a lock time, it doesn't result in a
   // notification
   // until we explicitly opt in to seeing those.
   ECKey key = new ECKey();
   Wallet wallet = new Wallet(unitTestParams);
   wallet.addKey(key);
   peer.addWallet(wallet);
   final Transaction[] vtx = new Transaction[1];
   wallet.addEventListener(
       new AbstractWalletEventListener() {
         @Override
         public void onCoinsReceived(
             Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
           vtx[0] = tx;
         }
       });
   // Send a normal relevant transaction, it's received correctly.
   Transaction t1 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), key);
   inbound(writeTarget, t1);
   GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
   if (useNotFound) {
     inbound(writeTarget, new NotFoundMessage(unitTestParams, getdata.getItems()));
   } else {
     bouncePing();
   }
   pingAndWait(writeTarget);
   Threading.waitForUserCode();
   assertNotNull(vtx[0]);
   vtx[0] = null;
   // Send a timelocked transaction, nothing happens.
   Transaction t2 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(2, 0), key);
   t2.setLockTime(999999);
   inbound(writeTarget, t2);
   Threading.waitForUserCode();
   assertNull(vtx[0]);
   // Now we want to hear about them. Send another, we are told about it.
   wallet.setAcceptRiskyTransactions(true);
   inbound(writeTarget, t2);
   getdata = (GetDataMessage) outbound(writeTarget);
   if (useNotFound) {
     inbound(writeTarget, new NotFoundMessage(unitTestParams, getdata.getItems()));
   } else {
     bouncePing();
   }
   pingAndWait(writeTarget);
   Threading.waitForUserCode();
   assertEquals(t2, vtx[0]);
 }
Beispiel #2
0
  // Check that inventory message containing blocks we want is processed correctly.
  @Test
  public void newBlock() throws Exception {
    Block b1 = createFakeBlock(blockStore).block;
    blockChain.add(b1);
    final Block b2 = makeSolvedTestBlock(b1);
    // Receive notification of a new block.
    final InventoryMessage inv = new InventoryMessage(unitTestParams);
    InventoryItem item = new InventoryItem(InventoryItem.Type.Block, b2.getHash());
    inv.addItem(item);

    final AtomicInteger newBlockMessagesReceived = new AtomicInteger(0);

    connect();
    // Round-trip a ping so that we never see the response verack if we attach too quick
    pingAndWait(writeTarget);
    peer.addEventListener(
        new AbstractPeerEventListener() {
          @Override
          public synchronized Message onPreMessageReceived(Peer p, Message m) {
            if (p != peer) fail.set(true);
            if (m instanceof Pong) return m;
            int newValue = newBlockMessagesReceived.incrementAndGet();
            if (newValue == 1 && !inv.equals(m)) fail.set(true);
            else if (newValue == 2 && !b2.equals(m)) fail.set(true);
            else if (newValue > 3) fail.set(true);
            return m;
          }

          @Override
          public synchronized void onBlocksDownloaded(Peer p, Block block, int blocksLeft) {
            int newValue = newBlockMessagesReceived.incrementAndGet();
            if (newValue != 3
                || p != peer
                || !block.equals(b2)
                || blocksLeft != OTHER_PEER_CHAIN_HEIGHT - 2) fail.set(true);
          }
        },
        Threading.SAME_THREAD);
    long height = peer.getBestHeight();

    inbound(writeTarget, inv);
    pingAndWait(writeTarget);
    assertEquals(height + 1, peer.getBestHeight());
    // Response to the getdata message.
    inbound(writeTarget, b2);

    pingAndWait(writeTarget);
    Threading.waitForUserCode();
    pingAndWait(writeTarget);
    assertEquals(3, newBlockMessagesReceived.get());

    GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
    List<InventoryItem> items = getdata.getItems();
    assertEquals(1, items.size());
    assertEquals(b2.getHash(), items.get(0).hash);
    assertEquals(InventoryItem.Type.Block, items.get(0).type);
  }
Beispiel #3
0
 @Test
 public void exceptionListener() throws Exception {
   wallet.addEventListener(
       new AbstractWalletEventListener() {
         @Override
         public void onCoinsReceived(
             Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
           throw new NullPointerException("boo!");
         }
       });
   final Throwable[] throwables = new Throwable[1];
   Threading.uncaughtExceptionHandler =
       new Thread.UncaughtExceptionHandler() {
         @Override
         public void uncaughtException(Thread thread, Throwable throwable) {
           throwables[0] = throwable;
         }
       };
   // In real usage we're not really meant to adjust the uncaught exception handler after stuff
   // started happening
   // but in the unit test environment other tests have just run so the thread is probably still
   // kicking around.
   // Force it to crash so it'll be recreated with our new handler.
   Threading.USER_THREAD.execute(
       new Runnable() {
         @Override
         public void run() {
           throw new RuntimeException();
         }
       });
   connect();
   Transaction t1 = new Transaction(unitTestParams);
   t1.addInput(new TransactionInput(unitTestParams, t1, new byte[] {}));
   t1.addOutput(Utils.toNanoCoins(1, 0), new ECKey().toAddress(unitTestParams));
   Transaction t2 = new Transaction(unitTestParams);
   t2.addInput(t1.getOutput(0));
   t2.addOutput(Utils.toNanoCoins(1, 0), wallet.getChangeAddress());
   inbound(writeTarget, t2);
   final InventoryItem inventoryItem =
       new InventoryItem(InventoryItem.Type.Transaction, t2.getInput(0).getOutpoint().getHash());
   final NotFoundMessage nfm =
       new NotFoundMessage(unitTestParams, Lists.newArrayList(inventoryItem));
   inbound(writeTarget, nfm);
   pingAndWait(writeTarget);
   Threading.waitForUserCode();
   assertTrue(throwables[0] instanceof NullPointerException);
   Threading.uncaughtExceptionHandler = null;
 }
Beispiel #4
0
  @Override
  public void onCreate() {
    new LinuxSecureRandom(); // init proper random number generator

    initLogging();

    StrictMode.setThreadPolicy(
        new StrictMode.ThreadPolicy.Builder()
            .detectAll()
            .permitDiskReads()
            .permitDiskWrites()
            .penaltyLog()
            .build());

    Threading.throwOnLockCycles();

    log.info(
        "configuration: "
            + (Constants.TEST ? "test" : "prod")
            + ", "
            + Constants.NETWORK_PARAMETERS.getId());

    super.onCreate();

    try {
      packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
    } catch (final NameNotFoundException x) {
      throw new RuntimeException(x);
    }

    CrashReporter.init(getCacheDir());

    Threading.uncaughtExceptionHandler =
        new Thread.UncaughtExceptionHandler() {
          @Override
          public void uncaughtException(final Thread thread, final Throwable throwable) {
            log.info("bitcoinj uncaught exception", throwable);
            CrashReporter.saveBackgroundTrace(throwable, packageInfo);
          }
        };

    prefs = PreferenceManager.getDefaultSharedPreferences(this);
    activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

    blockchainServiceIntent = new Intent(this, BlockchainServiceImpl.class);
    blockchainServiceCancelCoinsReceivedIntent =
        new Intent(
            BlockchainService.ACTION_CANCEL_COINS_RECEIVED,
            null,
            this,
            BlockchainServiceImpl.class);
    blockchainServiceResetBlockchainIntent =
        new Intent(
            BlockchainService.ACTION_RESET_BLOCKCHAIN, null, this, BlockchainServiceImpl.class);

    walletFile = getFileStreamPath(Constants.WALLET_FILENAME_PROTOBUF);

    migrateWalletToProtobuf();

    loadWalletFromProtobuf();
    wallet.autosaveToFile(walletFile, 1, TimeUnit.SECONDS, new WalletAutosaveEventListener());

    final int lastVersionCode = prefs.getInt(Constants.PREFS_KEY_LAST_VERSION, 0);
    prefs.edit().putInt(Constants.PREFS_KEY_LAST_VERSION, packageInfo.versionCode).commit();

    if (packageInfo.versionCode > lastVersionCode)
      log.info("detected app upgrade: " + lastVersionCode + " -> " + packageInfo.versionCode);
    else if (packageInfo.versionCode < lastVersionCode)
      log.warn("detected app downgrade: " + lastVersionCode + " -> " + packageInfo.versionCode);

    if (lastVersionCode > 0
        && lastVersionCode < KEY_ROTATION_VERSION_CODE
        && packageInfo.versionCode >= KEY_ROTATION_VERSION_CODE) {
      log.info("detected version jump crossing key rotation");
      wallet.setKeyRotationTime(System.currentTimeMillis() / 1000);
    }

    ensureKey();
  }
Beispiel #5
0
 private void checkTimeLockedDependency(boolean shouldAccept, boolean useNotFound)
     throws Exception {
   // Initial setup.
   connectWithVersion(useNotFound ? 70001 : 60001);
   ECKey key = new ECKey();
   Wallet wallet = new Wallet(unitTestParams);
   wallet.addKey(key);
   wallet.setAcceptRiskyTransactions(shouldAccept);
   peer.addWallet(wallet);
   final Transaction[] vtx = new Transaction[1];
   wallet.addEventListener(
       new AbstractWalletEventListener() {
         @Override
         public void onCoinsReceived(
             Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
           vtx[0] = tx;
         }
       });
   // t1 -> t2 [locked] -> t3 (not available)
   Transaction t2 = new Transaction(unitTestParams);
   t2.setLockTime(999999);
   // Add a fake input to t3 that goes nowhere.
   Sha256Hash t3 = Sha256Hash.create("abc".getBytes(Charset.forName("UTF-8")));
   t2.addInput(
       new TransactionInput(
           unitTestParams, t2, new byte[] {}, new TransactionOutPoint(unitTestParams, 0, t3)));
   t2.getInput(0).setSequenceNumber(0xDEADBEEF);
   t2.addOutput(Utils.toNanoCoins(1, 0), new ECKey());
   Transaction t1 = new Transaction(unitTestParams);
   t1.addInput(t2.getOutput(0));
   t1.addOutput(Utils.toNanoCoins(1, 0), key); // Make it relevant.
   // Announce t1.
   InventoryMessage inv = new InventoryMessage(unitTestParams);
   inv.addTransaction(t1);
   inbound(writeTarget, inv);
   // Send it.
   GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
   assertEquals(t1.getHash(), getdata.getItems().get(0).hash);
   inbound(writeTarget, t1);
   // Nothing arrived at our event listener yet.
   assertNull(vtx[0]);
   // We request t2.
   getdata = (GetDataMessage) outbound(writeTarget);
   assertEquals(t2.getHash(), getdata.getItems().get(0).hash);
   inbound(writeTarget, t2);
   if (!useNotFound) bouncePing();
   // We request t3.
   getdata = (GetDataMessage) outbound(writeTarget);
   assertEquals(t3, getdata.getItems().get(0).hash);
   // Can't find it: bottom of tree.
   if (useNotFound) {
     NotFoundMessage notFound = new NotFoundMessage(unitTestParams);
     notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t3));
     inbound(writeTarget, notFound);
   } else {
     bouncePing();
   }
   pingAndWait(writeTarget);
   Threading.waitForUserCode();
   // We're done but still not notified because it was timelocked.
   if (shouldAccept) assertNotNull(vtx[0]);
   else assertNull(vtx[0]);
 }
Beispiel #6
0
  public void recursiveDownload(boolean useNotFound) throws Exception {
    // Using ping or notfound?
    connectWithVersion(useNotFound ? 70001 : 60001);
    // Check that we can download all dependencies of an unconfirmed relevant transaction from the
    // mempool.
    ECKey to = new ECKey();

    final Transaction[] onTx = new Transaction[1];
    peer.addEventListener(
        new AbstractPeerEventListener() {
          @Override
          public void onTransaction(Peer peer1, Transaction t) {
            onTx[0] = t;
          }
        },
        Threading.SAME_THREAD);

    // Make the some fake transactions in the following graph:
    //   t1 -> t2 -> [t5]
    //      -> t3 -> t4 -> [t6]
    //      -> [t7]
    //      -> [t8]
    // The ones in brackets are assumed to be in the chain and are represented only by hashes.
    Transaction t2 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), to);
    Sha256Hash t5 = t2.getInput(0).getOutpoint().getHash();
    Transaction t4 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), new ECKey());
    Sha256Hash t6 = t4.getInput(0).getOutpoint().getHash();
    t4.addOutput(Utils.toNanoCoins(1, 0), new ECKey());
    Transaction t3 = new Transaction(unitTestParams);
    t3.addInput(t4.getOutput(0));
    t3.addOutput(Utils.toNanoCoins(1, 0), new ECKey());
    Transaction t1 = new Transaction(unitTestParams);
    t1.addInput(t2.getOutput(0));
    t1.addInput(t3.getOutput(0));
    Sha256Hash someHash =
        new Sha256Hash("2b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6");
    t1.addInput(
        new TransactionInput(
            unitTestParams,
            t1,
            new byte[] {},
            new TransactionOutPoint(unitTestParams, 0, someHash)));
    Sha256Hash anotherHash =
        new Sha256Hash("3b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6");
    t1.addInput(
        new TransactionInput(
            unitTestParams,
            t1,
            new byte[] {},
            new TransactionOutPoint(unitTestParams, 1, anotherHash)));
    t1.addOutput(Utils.toNanoCoins(1, 0), to);
    t1 = TestUtils.roundTripTransaction(unitTestParams, t1);
    t2 = TestUtils.roundTripTransaction(unitTestParams, t2);
    t3 = TestUtils.roundTripTransaction(unitTestParams, t3);
    t4 = TestUtils.roundTripTransaction(unitTestParams, t4);

    // Announce the first one. Wait for it to be downloaded.
    InventoryMessage inv = new InventoryMessage(unitTestParams);
    inv.addTransaction(t1);
    inbound(writeTarget, inv);
    GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
    Threading.waitForUserCode();
    assertEquals(t1.getHash(), getdata.getItems().get(0).hash);
    inbound(writeTarget, t1);
    pingAndWait(writeTarget);
    assertEquals(t1, onTx[0]);
    // We want its dependencies so ask for them.
    ListenableFuture<List<Transaction>> futures = peer.downloadDependencies(t1);
    assertFalse(futures.isDone());
    // It will recursively ask for the dependencies of t1: t2, t3, someHash and anotherHash.
    getdata = (GetDataMessage) outbound(writeTarget);
    assertEquals(4, getdata.getItems().size());
    assertEquals(t2.getHash(), getdata.getItems().get(0).hash);
    assertEquals(t3.getHash(), getdata.getItems().get(1).hash);
    assertEquals(someHash, getdata.getItems().get(2).hash);
    assertEquals(anotherHash, getdata.getItems().get(3).hash);
    long nonce = -1;
    if (!useNotFound) nonce = ((Ping) outbound(writeTarget)).getNonce();
    // For some random reason, t4 is delivered at this point before it's needed - perhaps it was a
    // Bloom filter
    // false positive. We do this to check that the mempool is being checked for seen transactions
    // before
    // requesting them.
    inbound(writeTarget, t4);
    // Deliver the requested transactions.
    inbound(writeTarget, t2);
    inbound(writeTarget, t3);
    if (useNotFound) {
      NotFoundMessage notFound = new NotFoundMessage(unitTestParams);
      notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, someHash));
      notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, anotherHash));
      inbound(writeTarget, notFound);
    } else {
      inbound(writeTarget, new Pong(nonce));
    }
    assertFalse(futures.isDone());
    // It will recursively ask for the dependencies of t2: t5 and t4, but not t3 because it already
    // found t4.
    getdata = (GetDataMessage) outbound(writeTarget);
    assertEquals(getdata.getItems().get(0).hash, t2.getInput(0).getOutpoint().getHash());
    // t5 isn't found and t4 is.
    if (useNotFound) {
      NotFoundMessage notFound = new NotFoundMessage(unitTestParams);
      notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t5));
      inbound(writeTarget, notFound);
    } else {
      bouncePing();
    }
    assertFalse(futures.isDone());
    // Continue to explore the t4 branch and ask for t6, which is in the chain.
    getdata = (GetDataMessage) outbound(writeTarget);
    assertEquals(t6, getdata.getItems().get(0).hash);
    if (useNotFound) {
      NotFoundMessage notFound = new NotFoundMessage(unitTestParams);
      notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t6));
      inbound(writeTarget, notFound);
    } else {
      bouncePing();
    }
    pingAndWait(writeTarget);
    // That's it, we explored the entire tree.
    assertTrue(futures.isDone());
    List<Transaction> results = futures.get();
    assertTrue(results.contains(t2));
    assertTrue(results.contains(t3));
    assertTrue(results.contains(t4));
  }
/**
 * A deterministic key chain is a {@link KeyChain} that uses the <a
 * href="https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki">BIP 32 standard</a>, as
 * implemented by {@link com.google.bitcoin.crypto.DeterministicHierarchy}, to derive all the keys
 * in the keychain from a master seed. This type of wallet is extremely convenient and flexible.
 * Although backing up full wallet files is always a good idea, to recover money only the root seed
 * needs to be preserved and that is a number small enough that it can be written down on paper or,
 * when represented using a BIP 39 {@link com.google.bitcoin.crypto.MnemonicCode}, dictated over the
 * phone (possibly even memorized).
 *
 * <p>Deterministic key chains have other advantages: parts of the key tree can be selectively
 * revealed to allow for auditing, and new public keys can be generated without access to the
 * private keys, yielding a highly secure configuration for web servers which can accept payments
 * into a wallet but not spend from them. This does not work quite how you would expect due to a
 * quirk of elliptic curve mathematics and the techniques used to deal with it. A watching wallet is
 * not instantiated using the public part of the master key as you may imagine. Instead, you need to
 * take the account key (first child of the master key) and provide the public part of that to the
 * watching wallet instead. You can do this by calling {@link #getWatchingKey()} and then
 * serializing it with {@link com.google.bitcoin.crypto.DeterministicKey#serializePubB58()}. The
 * resulting "xpub..." string encodes sufficient information about the account key to create a
 * watching chain via {@link
 * com.google.bitcoin.crypto.DeterministicKey#deserializeB58(com.google.bitcoin.crypto.DeterministicKey,
 * String)} (with null as the first parameter) and then {@link
 * DeterministicKeyChain#DeterministicKeyChain(com.google.bitcoin.crypto.DeterministicKey)}.
 *
 * <p>This class builds on {@link com.google.bitcoin.crypto.DeterministicHierarchy} and {@link
 * com.google.bitcoin.crypto.DeterministicKey} by adding support for serialization to and from
 * protobufs, and encryption of parts of the key tree. Internally it arranges itself as per the BIP
 * 32 spec, with the seed being used to derive a master key, which is then used to derive an account
 * key, the account key is used to derive two child keys called the <i>internal</i> and
 * <i>external</i> keys (for change and handing out addresses respectively) and finally the actual
 * leaf keys that users use hanging off the end. The leaf keys are special in that they don't
 * internally store the private part at all, instead choosing to rederive the private key from the
 * parent when needed for signing. This simplifies the design for encrypted key chains.
 *
 * <p>The key chain manages a <i>lookahead zone</i>. This zone is required because when scanning the
 * chain, you don't know exactly which keys might receive payments. The user may have handed out
 * several addresses and received payments on them, but for latency reasons the block chain is
 * requested from remote peers in bulk, meaning you must "look ahead" when calculating keys to put
 * in the Bloom filter. The default lookahead zone is 100 keys, meaning if the user hands out more
 * than 100 addresses and receives payment on them before the chain is next scanned, some
 * transactions might be missed. 100 is a reasonable choice for consumer wallets running on CPU
 * constrained devices. For industrial wallets that are receiving keys all the time, a higher value
 * is more appropriate. Ideally DKC and the wallet would know how to adjust this value
 * automatically, but that's not implemented at the moment.
 *
 * <p>In fact the real size of the lookahead zone is larger than requested, by default, it's one
 * third larger. This is because the act of deriving new keys means recalculating the Bloom filters
 * and this is an expensive operation. Thus, to ensure we don't have to recalculate on every single
 * new key/address requested or seen we add more buffer space and only extend the lookahead zone
 * when that buffer is exhausted. For example with a lookahead zone of 100 keys, you can request 33
 * keys before more keys will be calculated and the Bloom filter rebuilt and rebroadcast. But even
 * when you are requesting the 33rd key, you will still be looking 100 keys ahead.
 */
public class DeterministicKeyChain implements EncryptableKeyChain {
  private static final Logger log = LoggerFactory.getLogger(DeterministicKeyChain.class);
  public static final String DEFAULT_PASSPHRASE_FOR_MNEMONIC = "";

  private final ReentrantLock lock = Threading.lock("DeterministicKeyChain");

  private DeterministicHierarchy hierarchy;
  @Nullable private DeterministicKey rootKey;
  @Nullable private DeterministicSeed seed;

  // Ignored if seed != null. Useful for watching hierarchies.
  private long creationTimeSeconds = MnemonicCode.BIP39_STANDARDISATION_TIME_SECS;

  // Paths through the key tree. External keys are ones that are communicated to other parties.
  // Internal keys are
  // keys created for change addresses, coinbases, mixing, etc - anything that isn't communicated.
  // The distinction
  // is somewhat arbitrary but can be useful for audits. The first number is the "account number"
  // but we don't use
  // that feature yet. In future we might hand out different accounts for cases where we wish to
  // hand payers
  // a payment request that can generate lots of addresses independently.
  public static final ImmutableList<ChildNumber> ACCOUNT_ZERO_PATH =
      ImmutableList.of(ChildNumber.ZERO_HARDENED);
  public static final ImmutableList<ChildNumber> EXTERNAL_PATH =
      ImmutableList.of(ChildNumber.ZERO_HARDENED, ChildNumber.ZERO);
  public static final ImmutableList<ChildNumber> INTERNAL_PATH =
      ImmutableList.of(ChildNumber.ZERO_HARDENED, ChildNumber.ONE);

  // We try to ensure we have at least this many keys ready and waiting to be handed out via
  // getKey().
  // See docs for getLookaheadSize() for more info on what this is for. The -1 value means it hasn't
  // been calculated
  // yet. For new chains it's set to whatever the default is, unless overridden by setLookaheadSize.
  // For deserialized
  // chains, it will be calculated on demand from the number of loaded keys.
  private static final int LAZY_CALCULATE_LOOKAHEAD = -1;
  private int lookaheadSize = 100;
  // The lookahead threshold causes us to batch up creation of new keys to minimize the frequency of
  // Bloom filter
  // regenerations, which are expensive and will (in future) trigger chain download stalls/retries.
  // One third
  // is an efficiency tradeoff.
  private int lookaheadThreshold = calcDefaultLookaheadThreshold();

  private int calcDefaultLookaheadThreshold() {
    return lookaheadSize / 3;
  }

  // The parent keys for external keys (handed out to other people) and internal keys (used for
  // change addresses).
  private DeterministicKey externalKey, internalKey;
  // How many keys on each path have actually been used. This may be fewer than the number that have
  // been deserialized
  // or held in memory, because of the lookahead zone.
  private int issuedExternalKeys, issuedInternalKeys;

  // We simplify by wrapping a basic key chain and that way we get some functionality like key
  // lookup and event
  // listeners "for free". All keys in the key tree appear here, even if they aren't meant to be
  // used for receiving
  // money.
  private final BasicKeyChain basicKeyChain;

  // If set this chain is following another chain in a married KeyChainGroup
  private boolean isFollowing;

  /**
   * Generates a new key chain with entropy selected randomly from the given {@link
   * java.security.SecureRandom} object and the default entropy size.
   */
  public DeterministicKeyChain(SecureRandom random) {
    this(
        random,
        DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS,
        DEFAULT_PASSPHRASE_FOR_MNEMONIC,
        Utils.currentTimeSeconds());
  }

  /**
   * Generates a new key chain with entropy selected randomly from the given {@link
   * java.security.SecureRandom} object and of the requested size in bits.
   */
  public DeterministicKeyChain(SecureRandom random, int bits) {
    this(random, bits, DEFAULT_PASSPHRASE_FOR_MNEMONIC, Utils.currentTimeSeconds());
  }

  /**
   * Generates a new key chain with entropy selected randomly from the given {@link
   * java.security.SecureRandom} object and of the requested size in bits. The derived seed is
   * further protected with a user selected passphrase (see BIP 39).
   */
  public DeterministicKeyChain(
      SecureRandom random, int bits, String passphrase, long seedCreationTimeSecs) {
    this(new DeterministicSeed(random, bits, passphrase, seedCreationTimeSecs));
  }

  /**
   * Creates a deterministic key chain starting from the given seed. All keys yielded by this chain
   * will be the same if the starting seed is the same. You should provide the creation time in
   * seconds since the UNIX epoch for the seed: this lets us know from what part of the chain we can
   * expect to see derived keys appear.
   */
  public DeterministicKeyChain(byte[] entropy, String passphrase, long seedCreationTimeSecs) {
    this(new DeterministicSeed(entropy, passphrase, seedCreationTimeSecs));
  }

  /**
   * Creates a deterministic key chain starting from the given seed. All keys yielded by this chain
   * will be the same if the starting seed is the same.
   */
  protected DeterministicKeyChain(DeterministicSeed seed) {
    this(seed, null);
  }

  /**
   * Creates a deterministic key chain that watches the given (public only) root key. You can use
   * this to calculate balances and generally follow along, but spending is not possible with such a
   * chain. Currently you can't use this method to watch an arbitrary fragment of some other tree,
   * this limitation may be removed in future.
   */
  public DeterministicKeyChain(DeterministicKey watchingKey, long creationTimeSeconds) {
    checkArgument(watchingKey.isPubKeyOnly(), "Private subtrees not currently supported");
    checkArgument(watchingKey.getPath().size() == 1, "You can only watch an account key currently");
    basicKeyChain = new BasicKeyChain();
    this.creationTimeSeconds = creationTimeSeconds;
    this.seed = null;
    initializeHierarchyUnencrypted(watchingKey);
  }

  public DeterministicKeyChain(DeterministicKey watchingKey) {
    this(watchingKey, Utils.currentTimeSeconds());
  }

  /**
   * Creates a deterministic key chain with the given watch key. If <code>isFollowing</code> flag is
   * set then this keychain follows some other keychain. In a married wallet following keychain
   * represents "spouse's" keychain.
   *
   * <p>Watch key has to be an account key.
   */
  private DeterministicKeyChain(DeterministicKey watchKey, boolean isFollowing) {
    this(watchKey, Utils.currentTimeSeconds());
    this.isFollowing = isFollowing;
  }

  /**
   * Creates a deterministic key chain with the given watch key and that follows some other
   * keychain. In a married wallet following keychain represents "spouse" Watch key has to be an
   * account key.
   */
  public static DeterministicKeyChain watchAndFollow(DeterministicKey watchKey) {
    return new DeterministicKeyChain(watchKey, true);
  }

  /**
   * Creates a key chain that watches the given account key. The creation time is taken to be the
   * time that BIP 32 was standardised: most likely, you can optimise by selecting a more accurate
   * creation time for your key and using the other watch method.
   */
  public static DeterministicKeyChain watch(DeterministicKey accountKey) {
    return watch(accountKey, DeterministicHierarchy.BIP32_STANDARDISATION_TIME_SECS);
  }

  /**
   * Creates a key chain that watches the given account key, and assumes there are no transactions
   * involving it until the given time (this is an optimisation for chain scanning purposes).
   */
  public static DeterministicKeyChain watch(
      DeterministicKey accountKey, long seedCreationTimeSecs) {
    return new DeterministicKeyChain(accountKey, seedCreationTimeSecs);
  }

  DeterministicKeyChain(DeterministicSeed seed, @Nullable KeyCrypter crypter) {
    this.seed = seed;
    basicKeyChain = new BasicKeyChain(crypter);
    if (!seed.isEncrypted()) {
      rootKey = HDKeyDerivation.createMasterPrivateKey(checkNotNull(seed.getSeedBytes()));
      rootKey.setCreationTimeSeconds(seed.getCreationTimeSeconds());
      initializeHierarchyUnencrypted(rootKey);
    }
    // Else...
    // We can't initialize ourselves with just an encrypted seed, so we expected deserialization
    // code to do the
    // rest of the setup (loading the root 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);
    }
  }

  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;
  }

  // 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);
  }

  /** Returns a freshly derived key that has not been returned by this method before. */
  @Override
  public DeterministicKey getKey(KeyPurpose purpose) {
    return getKeys(purpose, 1).get(0);
  }

  /** 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();
    }
  }

  private void checkForBitFlip(DeterministicKey k) {
    DeterministicKey parent = checkNotNull(k.getParent());
    byte[] rederived =
        HDKeyDerivation.deriveChildKeyBytesFromPublic(
                parent, k.getChildNumber(), HDKeyDerivation.PublicDeriveMode.WITH_INVERSION)
            .keyBytes;
    byte[] actual = k.getPubKey();
    if (!Arrays.equals(rederived, actual))
      throw new IllegalStateException(
          String.format(
              "Bit-flip check failed: %s vs %s",
              Arrays.toString(rederived), Arrays.toString(actual)));
  }

  private void addToBasicChain(DeterministicKey key) {
    basicKeyChain.importKeys(ImmutableList.of(key));
  }

  /**
   * Mark the DeterministicKey as used. Also correct the issued{Internal|External}Keys counter,
   * because all lower children seem to be requested already. If the counter was updated, we also
   * might trigger lookahead.
   */
  public DeterministicKey markKeyAsUsed(DeterministicKey k) {
    int numChildren = k.getChildNumber().i() + 1;

    if (k.getParent() == internalKey) {
      if (issuedInternalKeys < numChildren) {
        issuedInternalKeys = numChildren;
        maybeLookAhead();
      }
    } else if (k.getParent() == externalKey) {
      if (issuedExternalKeys < numChildren) {
        issuedExternalKeys = numChildren;
        maybeLookAhead();
      }
    }
    return k;
  }

  public DeterministicKey findKeyFromPubHash(byte[] pubkeyHash) {
    lock.lock();
    try {
      return (DeterministicKey) basicKeyChain.findKeyFromPubHash(pubkeyHash);
    } finally {
      lock.unlock();
    }
  }

  public DeterministicKey findKeyFromPubKey(byte[] pubkey) {
    lock.lock();
    try {
      return (DeterministicKey) basicKeyChain.findKeyFromPubKey(pubkey);
    } finally {
      lock.unlock();
    }
  }

  /**
   * Mark the DeterministicKeys as used, if they match the pubkeyHash See {@link
   * com.google.bitcoin.wallet.DeterministicKeyChain#markKeyAsUsed(DeterministicKey)} for more info
   * on this.
   */
  @Nullable
  public DeterministicKey markPubHashAsUsed(byte[] pubkeyHash) {
    lock.lock();
    try {
      DeterministicKey k = (DeterministicKey) basicKeyChain.findKeyFromPubHash(pubkeyHash);
      if (k != null) markKeyAsUsed(k);
      return k;
    } finally {
      lock.unlock();
    }
  }

  /**
   * Mark the DeterministicKeys as used, if they match the pubkey See {@link
   * com.google.bitcoin.wallet.DeterministicKeyChain#markKeyAsUsed(DeterministicKey)} for more info
   * on this.
   */
  @Nullable
  public DeterministicKey markPubKeyAsUsed(byte[] pubkey) {
    lock.lock();
    try {
      DeterministicKey k = (DeterministicKey) basicKeyChain.findKeyFromPubKey(pubkey);
      if (k != null) markKeyAsUsed(k);
      return k;
    } finally {
      lock.unlock();
    }
  }

  @Override
  public boolean hasKey(ECKey key) {
    lock.lock();
    try {
      return basicKeyChain.hasKey(key);
    } finally {
      lock.unlock();
    }
  }

  /** Returns the deterministic key for the given absolute path in the hierarchy. */
  protected DeterministicKey getKeyByPath(ChildNumber... path) {
    return getKeyByPath(ImmutableList.<ChildNumber>copyOf(path));
  }

  /** Returns the deterministic key for the given absolute path in the hierarchy. */
  protected DeterministicKey getKeyByPath(List<ChildNumber> path) {
    return getKeyByPath(path, false);
  }

  /**
   * 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);
  }

  /**
   * An alias for <code>getKeyByPath(DeterministicKeyChain.ACCOUNT_ZERO_PATH).getPubOnly()</code>.
   * Use this when you would like to create a watching key chain that follows this one, but can't
   * spend money from it. The returned key can be serialized and then passed into {@link
   * #watch(com.google.bitcoin.crypto.DeterministicKey)} on another system to watch the hierarchy.
   */
  public DeterministicKey getWatchingKey() {
    return getKeyByPath(ACCOUNT_ZERO_PATH).getPubOnly();
  }

  @Override
  public int numKeys() {
    // We need to return here the total number of keys including the lookahead zone, not the number
    // of keys we
    // have issued via getKey/freshReceiveKey.
    lock.lock();
    try {
      maybeLookAhead();
      return basicKeyChain.numKeys();
    } finally {
      lock.unlock();
    }
  }

  /**
   * Returns number of leaf keys used including both internal and external paths. This may be fewer
   * than the number that have been deserialized or held in memory, because of the lookahead zone.
   */
  public int numLeafKeysIssued() {
    lock.lock();
    try {
      return issuedExternalKeys + issuedInternalKeys;
    } finally {
      lock.unlock();
    }
  }

  @Override
  public long getEarliestKeyCreationTime() {
    return seed != null ? seed.getCreationTimeSeconds() : creationTimeSeconds;
  }

  @Override
  public void addEventListener(KeyChainEventListener listener) {
    basicKeyChain.addEventListener(listener);
  }

  @Override
  public void addEventListener(KeyChainEventListener listener, Executor executor) {
    basicKeyChain.addEventListener(listener, executor);
  }

  @Override
  public boolean removeEventListener(KeyChainEventListener listener) {
    return basicKeyChain.removeEventListener(listener);
  }

  /** Returns a list of words that represent the seed or null if this chain is a watching chain. */
  @Nullable
  public List<String> getMnemonicCode() {
    if (seed == null) return null;

    lock.lock();
    try {
      return seed.getMnemonicCode();
    } finally {
      lock.unlock();
    }
  }

  /** Return true if this keychain is following another keychain */
  public boolean isFollowing() {
    return isFollowing;
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //
  // Serialization support
  //
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  @Override
  public List<Protos.Key> serializeToProtobuf() {
    lock.lock();
    try {
      // Most of the serialization work is delegated to the basic key chain, which will serialize
      // the bulk of the
      // data (handling encryption along the way), and letting us patch it up with the extra data we
      // care about.
      LinkedList<Protos.Key> entries = newLinkedList();
      if (seed != null) {
        Protos.Key.Builder mnemonicEntry = BasicKeyChain.serializeEncryptableItem(seed);
        mnemonicEntry.setType(Protos.Key.Type.DETERMINISTIC_MNEMONIC);
        entries.add(mnemonicEntry.build());
      }
      Map<ECKey, Protos.Key.Builder> keys = basicKeyChain.serializeToEditableProtobufs();
      for (Map.Entry<ECKey, Protos.Key.Builder> entry : keys.entrySet()) {
        DeterministicKey key = (DeterministicKey) entry.getKey();
        Protos.Key.Builder proto = entry.getValue();
        proto.setType(Protos.Key.Type.DETERMINISTIC_KEY);
        final Protos.DeterministicKey.Builder detKey = proto.getDeterministicKeyBuilder();
        detKey.setChainCode(ByteString.copyFrom(key.getChainCode()));
        for (ChildNumber num : key.getPath()) detKey.addPath(num.i());
        if (key.equals(externalKey)) {
          detKey.setIssuedSubkeys(issuedExternalKeys);
          detKey.setLookaheadSize(lookaheadSize);
        } else if (key.equals(internalKey)) {
          detKey.setIssuedSubkeys(issuedInternalKeys);
          detKey.setLookaheadSize(lookaheadSize);
        }
        // Flag the very first key of following keychain.
        if (entries.isEmpty() && isFollowing()) {
          detKey.setIsFollowing(true);
        }
        if (key.getParent() != null) {
          // HD keys inherit the timestamp of their parent if they have one, so no need to serialize
          // it.
          proto.clearCreationTimestamp();
        }
        entries.add(proto.build());
      }
      return entries;
    } finally {
      lock.unlock();
    }
  }

  /**
   * Returns all the key chains found in the given list of keys. Typically there will only be one,
   * but in the case of key rotation it can happen that there are multiple chains found.
   */
  public static List<DeterministicKeyChain> fromProtobuf(
      List<Protos.Key> keys, @Nullable KeyCrypter crypter) throws UnreadableWalletException {
    List<DeterministicKeyChain> chains = newLinkedList();
    DeterministicSeed seed = null;
    DeterministicKeyChain chain = null;
    int lookaheadSize = -1;
    for (Protos.Key key : keys) {
      final Protos.Key.Type t = key.getType();
      if (t == Protos.Key.Type.DETERMINISTIC_MNEMONIC) {
        if (chain != null) {
          checkState(lookaheadSize >= 0);
          chain.setLookaheadSize(lookaheadSize);
          chain.maybeLookAhead();
          chains.add(chain);
          chain = null;
        }
        long timestamp = key.getCreationTimestamp() / 1000;
        String passphrase = DEFAULT_PASSPHRASE_FOR_MNEMONIC; // FIXME allow non-empty passphrase
        if (key.hasSecretBytes()) {
          seed = new DeterministicSeed(key.getSecretBytes().toStringUtf8(), passphrase, timestamp);
        } else if (key.hasEncryptedData()) {
          EncryptedData data =
              new EncryptedData(
                  key.getEncryptedData().getInitialisationVector().toByteArray(),
                  key.getEncryptedData().getEncryptedPrivateKey().toByteArray());
          seed = new DeterministicSeed(data, timestamp);
        } else {
          throw new UnreadableWalletException("Malformed key proto: " + key.toString());
        }
        if (log.isDebugEnabled()) log.debug("Deserializing: DETERMINISTIC_MNEMONIC: {}", seed);
      } else if (t == Protos.Key.Type.DETERMINISTIC_KEY) {
        if (!key.hasDeterministicKey())
          throw new UnreadableWalletException(
              "Deterministic key missing extra data: " + key.toString());
        byte[] chainCode = key.getDeterministicKey().getChainCode().toByteArray();
        // Deserialize the path through the tree.
        LinkedList<ChildNumber> path = newLinkedList();
        for (int i : key.getDeterministicKey().getPathList()) path.add(new ChildNumber(i));
        // Deserialize the public key and path.
        ECPoint pubkey = ECKey.CURVE.getCurve().decodePoint(key.getPublicKey().toByteArray());
        final ImmutableList<ChildNumber> immutablePath = ImmutableList.copyOf(path);
        // Possibly create the chain, if we didn't already do so yet.
        boolean isWatchingAccountKey = false;
        boolean isFollowingKey = false;
        // save previous chain if any if the key is marked as following. Current key and the next
        // ones are to be
        // placed in new following key chain
        if (key.getDeterministicKey().getIsFollowing()) {
          if (chain != null) {
            checkState(lookaheadSize >= 0);
            chain.setLookaheadSize(lookaheadSize);
            chain.maybeLookAhead();
            chains.add(chain);
            chain = null;
            seed = null;
          }
          isFollowingKey = true;
        }
        if (chain == null) {
          if (seed == null) {
            DeterministicKey accountKey =
                new DeterministicKey(immutablePath, chainCode, pubkey, null, null);
            if (!accountKey.getPath().equals(ACCOUNT_ZERO_PATH))
              throw new UnreadableWalletException(
                  "Expecting account key but found key with path: "
                      + HDUtils.formatPath(accountKey.getPath()));
            chain = new DeterministicKeyChain(accountKey, isFollowingKey);
            isWatchingAccountKey = true;
          } else {
            chain = new DeterministicKeyChain(seed, crypter);
            chain.lookaheadSize = LAZY_CALCULATE_LOOKAHEAD;
            // If the seed is encrypted, then the chain is incomplete at this point. However, we
            // will load
            // it up below as we parse in the keys. We just need to check at the end that we've
            // loaded
            // everything afterwards.
          }
        }
        // Find the parent key assuming this is not the root key, and not an account key for a
        // watching chain.
        DeterministicKey parent = null;
        if (!path.isEmpty() && !isWatchingAccountKey) {
          ChildNumber index = path.removeLast();
          parent = chain.hierarchy.get(path, false, false);
          path.add(index);
        }
        DeterministicKey detkey;
        if (key.hasSecretBytes()) {
          // Not encrypted: private key is available.
          final BigInteger priv = new BigInteger(1, key.getSecretBytes().toByteArray());
          detkey = new DeterministicKey(immutablePath, chainCode, pubkey, priv, parent);
        } else {
          if (key.hasEncryptedData()) {
            Protos.EncryptedData proto = key.getEncryptedData();
            EncryptedData data =
                new EncryptedData(
                    proto.getInitialisationVector().toByteArray(),
                    proto.getEncryptedPrivateKey().toByteArray());
            checkNotNull(crypter, "Encountered an encrypted key but no key crypter provided");
            detkey = new DeterministicKey(immutablePath, chainCode, crypter, pubkey, data, parent);
          } else {
            // No secret key bytes and key is not encrypted: either a watching key or private key
            // bytes
            // will be rederived on the fly from the parent.
            detkey = new DeterministicKey(immutablePath, chainCode, pubkey, null, parent);
          }
        }
        if (key.hasCreationTimestamp())
          detkey.setCreationTimeSeconds(key.getCreationTimestamp() / 1000);
        if (log.isDebugEnabled()) log.debug("Deserializing: DETERMINISTIC_KEY: {}", detkey);
        if (!isWatchingAccountKey) {
          // If the non-encrypted case, the non-leaf keys (account, internal, external) have already
          // been
          // rederived and inserted at this point and the two lines below are just a no-op. In the
          // encrypted
          // case though, we can't rederive and we must reinsert, potentially building the heirarchy
          // object
          // if need be.
          if (path.size() == 0) {
            // Master key.
            chain.rootKey = detkey;
            chain.hierarchy = new DeterministicHierarchy(detkey);
          } else if (path.size() == 2) {
            if (detkey.getChildNumber().num() == 0) {
              chain.externalKey = detkey;
              chain.issuedExternalKeys = key.getDeterministicKey().getIssuedSubkeys();
              lookaheadSize = Math.max(lookaheadSize, key.getDeterministicKey().getLookaheadSize());
            } else if (detkey.getChildNumber().num() == 1) {
              chain.internalKey = detkey;
              chain.issuedInternalKeys = key.getDeterministicKey().getIssuedSubkeys();
            }
          }
        }
        chain.hierarchy.putKey(detkey);
        chain.basicKeyChain.importKey(detkey);
      }
    }
    if (chain != null) {
      checkState(lookaheadSize >= 0);
      chain.setLookaheadSize(lookaheadSize);
      chain.maybeLookAhead();
      chains.add(chain);
    }
    return chains;
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //
  // Encryption support
  //
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  @Override
  public DeterministicKeyChain toEncrypted(CharSequence password) {
    checkNotNull(password);
    checkArgument(password.length() > 0);
    checkState(seed != null, "Attempt to encrypt a watching chain.");
    checkState(!seed.isEncrypted());
    KeyCrypter scrypt = new KeyCrypterScrypt();
    KeyParameter derivedKey = scrypt.deriveKey(password);
    return toEncrypted(scrypt, derivedKey);
  }

  @Override
  public DeterministicKeyChain toEncrypted(KeyCrypter keyCrypter, KeyParameter aesKey) {
    return new DeterministicKeyChain(keyCrypter, aesKey, this);
  }

  @Override
  public DeterministicKeyChain toDecrypted(CharSequence password) {
    checkNotNull(password);
    checkArgument(password.length() > 0);
    KeyCrypter crypter = getKeyCrypter();
    checkState(crypter != null, "Chain not encrypted");
    KeyParameter derivedKey = crypter.deriveKey(password);
    return toDecrypted(derivedKey);
  }

  @Override
  public DeterministicKeyChain toDecrypted(KeyParameter aesKey) {
    checkState(getKeyCrypter() != null, "Key chain not encrypted");
    checkState(seed != null, "Can't decrypt a watching chain");
    checkState(seed.isEncrypted());
    String passphrase = DEFAULT_PASSPHRASE_FOR_MNEMONIC; // FIXME allow non-empty passphrase
    DeterministicSeed decSeed = seed.decrypt(getKeyCrypter(), passphrase, aesKey);
    DeterministicKeyChain chain = new DeterministicKeyChain(decSeed);
    // Now double check that the keys match to catch the case where the key is wrong but padding
    // didn't catch it.
    if (!chain.getWatchingKey().getPubKeyPoint().equals(getWatchingKey().getPubKeyPoint()))
      throw new KeyCrypterException("Provided AES key is wrong");
    chain.lookaheadSize = lookaheadSize;
    // Now copy the (pubkey only) leaf keys across to avoid rederiving them. The private key bytes
    // are missing
    // anyway so there's nothing to decrypt.
    for (ECKey eckey : basicKeyChain.getKeys()) {
      DeterministicKey key = (DeterministicKey) eckey;
      if (key.getPath().size() != 3) continue; // Not a leaf key.
      checkState(key.isEncrypted());
      DeterministicKey parent =
          chain.hierarchy.get(checkNotNull(key.getParent()).getPath(), false, false);
      // Clone the key to the new decrypted hierarchy.
      key = new DeterministicKey(key.getPubOnly(), parent);
      chain.hierarchy.putKey(key);
      chain.basicKeyChain.importKey(key);
    }
    chain.issuedExternalKeys = issuedExternalKeys;
    chain.issuedInternalKeys = issuedInternalKeys;
    return chain;
  }

  @Override
  public boolean checkPassword(CharSequence password) {
    checkNotNull(password);
    checkState(getKeyCrypter() != null, "Key chain not encrypted");
    return checkAESKey(getKeyCrypter().deriveKey(password));
  }

  @Override
  public boolean checkAESKey(KeyParameter aesKey) {
    checkState(rootKey != null, "Can't check password for a watching chain");
    checkNotNull(aesKey);
    checkState(getKeyCrypter() != null, "Key chain not encrypted");
    try {
      return rootKey.decrypt(aesKey).getPubKeyPoint().equals(rootKey.getPubKeyPoint());
    } catch (KeyCrypterException e) {
      return false;
    }
  }

  @Nullable
  @Override
  public KeyCrypter getKeyCrypter() {
    return basicKeyChain.getKeyCrypter();
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //
  // Bloom filtering support
  //
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  @Override
  public int numBloomFilterEntries() {
    return numKeys() * 2;
  }

  @Override
  public BloomFilter getFilter(int size, double falsePositiveRate, long tweak) {
    lock.lock();
    try {
      checkArgument(size >= numBloomFilterEntries());
      maybeLookAhead();
      return basicKeyChain.getFilter(size, falsePositiveRate, tweak);
    } finally {
      lock.unlock();
    }
  }

  /**
   * The number of public keys we should pre-generate on each path before they are requested by the
   * app. This is required so that when scanning through the chain given only a seed, we can give
   * enough keys to the remote node via the Bloom filter such that we see transactions that are
   * "from the future", for example transactions created by a different app that's sharing the same
   * seed, or transactions we made before but we're replaying the chain given just the seed. The
   * default is 100.
   */
  public int getLookaheadSize() {
    lock.lock();
    try {
      return lookaheadSize;
    } finally {
      lock.unlock();
    }
  }

  /**
   * Sets a new lookahead size. See {@link #getLookaheadSize()} for details on what this is. Setting
   * a new size that's larger than the current size will return immediately and the new size will
   * only take effect next time a fresh filter is requested (e.g. due to a new peer being
   * connected). So you should set this before starting to sync the chain, if you want to modify it.
   * If you haven't modified the lookahead threshold manually then it will be automatically set to
   * be a third of the new size.
   */
  public void setLookaheadSize(int lookaheadSize) {
    lock.lock();
    try {
      boolean readjustThreshold = this.lookaheadThreshold == calcDefaultLookaheadThreshold();
      this.lookaheadSize = lookaheadSize;
      if (readjustThreshold) this.lookaheadThreshold = calcDefaultLookaheadThreshold();
    } finally {
      lock.unlock();
    }
  }

  /**
   * Sets the threshold for the key pre-generation. This is used to avoid adding new keys and thus
   * re-calculating Bloom filters every time a new key is calculated. Without a lookahead threshold,
   * every time we received a relevant transaction we'd extend the lookahead zone and generate a new
   * filter, which is inefficient.
   */
  public void setLookaheadThreshold(int num) {
    lock.lock();
    try {
      if (num >= lookaheadSize)
        throw new IllegalArgumentException("Threshold larger or equal to the lookaheadSize");
      this.lookaheadThreshold = num;
    } finally {
      lock.unlock();
    }
  }

  /**
   * Gets the threshold for the key pre-generation. See {@link #setLookaheadThreshold(int)} for
   * details on what this is.
   */
  public int getLookaheadThreshold() {
    lock.lock();
    try {
      if (lookaheadThreshold >= lookaheadSize) return 0;
      return lookaheadThreshold;
    } finally {
      lock.unlock();
    }
  }

  /**
   * Pre-generate enough keys to reach the lookahead size. You can call this if you need to
   * explicitly invoke the lookahead procedure, but it's normally unnecessary as it will be done
   * automatically when needed.
   */
  public void maybeLookAhead() {
    lock.lock();
    try {
      List<DeterministicKey> keys = maybeLookAhead(externalKey, issuedExternalKeys);
      keys.addAll(maybeLookAhead(internalKey, issuedInternalKeys));
      // Batch add all keys at once so there's only one event listener invocation, as this will be
      // listened to
      // by the wallet and used to rebuild/broadcast the Bloom filter. That's expensive so we don't
      // want to do
      // it more often than necessary.
      basicKeyChain.importKeys(keys);
    } finally {
      lock.unlock();
    }
  }

  private List<DeterministicKey> maybeLookAhead(DeterministicKey parent, int issued) {
    checkState(lock.isHeldByCurrentThread());
    return maybeLookAhead(parent, issued, getLookaheadSize(), getLookaheadThreshold());
  }

  /**
   * 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;
  }

  /**
   * Returns number of keys used on external path. This may be fewer than the number that have been
   * deserialized or held in memory, because of the lookahead zone.
   */
  public int getIssuedExternalKeys() {
    lock.lock();
    try {
      return issuedExternalKeys;
    } finally {
      lock.unlock();
    }
  }

  /**
   * Returns number of keys used on internal path. This may be fewer than the number that have been
   * deserialized or held in memory, because of the lookahead zone.
   */
  public int getIssuedInternalKeys() {
    lock.lock();
    try {
      return issuedInternalKeys;
    } finally {
      lock.unlock();
    }
  }

  /** Returns the seed or null if this chain is a watching chain. */
  @Nullable
  public DeterministicSeed getSeed() {
    lock.lock();
    try {
      return seed;
    } finally {
      lock.unlock();
    }
  }

  // For internal usage only
  /* package */ List<ECKey> getKeys(boolean includeLookahead) {
    List<ECKey> keys = basicKeyChain.getKeys();
    if (!includeLookahead) {
      int treeSize = internalKey.getPath().size();
      List<ECKey> issuedKeys = new LinkedList<ECKey>();
      for (ECKey key : keys) {
        DeterministicKey detkey = (DeterministicKey) key;
        DeterministicKey parent = detkey.getParent();
        if (parent == null) continue;
        if (detkey.getPath().size() <= treeSize) continue;
        if (parent.equals(internalKey) && detkey.getChildNumber().i() > issuedInternalKeys)
          continue;
        if (parent.equals(externalKey) && detkey.getChildNumber().i() > issuedExternalKeys)
          continue;
        issuedKeys.add(detkey);
      }
      return issuedKeys;
    }
    return keys;
  }

  /** Returns leaf keys issued by this chain (including lookahead zone) */
  public List<DeterministicKey> getLeafKeys() {
    ImmutableList.Builder<DeterministicKey> keys = ImmutableList.builder();
    for (ECKey key : getKeys(true)) {
      DeterministicKey dKey = (DeterministicKey) key;
      if (dKey.getPath().size() > 2) {
        keys.add(dKey);
      }
    }
    return keys.build();
  }
}
/**
 * This class maintains a set of {@link StoredClientChannel}s, automatically (re)broadcasting the
 * contract transaction and broadcasting the refund transaction over the given {@link
 * TransactionBroadcaster}.
 */
public class StoredPaymentChannelClientStates implements WalletExtension {
  private static final Logger log = LoggerFactory.getLogger(StoredPaymentChannelClientStates.class);
  static final String EXTENSION_ID = StoredPaymentChannelClientStates.class.getName();

  @GuardedBy("lock")
  @VisibleForTesting
  final HashMultimap<Sha256Hash, StoredClientChannel> mapChannels = HashMultimap.create();

  @VisibleForTesting final Timer channelTimeoutHandler = new Timer(true);

  private Wallet containingWallet;
  private final TransactionBroadcaster announcePeerGroup;

  protected final ReentrantLock lock = Threading.lock("StoredPaymentChannelClientStates");

  /**
   * Creates a new StoredPaymentChannelClientStates and associates it with the given {@link Wallet}
   * and {@link TransactionBroadcaster} which are used to complete and announce contract and refund
   * transactions.
   */
  public StoredPaymentChannelClientStates(
      Wallet containingWallet, TransactionBroadcaster announcePeerGroup) {
    this.announcePeerGroup = checkNotNull(announcePeerGroup);
    this.containingWallet = checkNotNull(containingWallet);
  }

  /** Returns this extension from the given wallet, or null if no such extension was added. */
  @Nullable
  public static StoredPaymentChannelClientStates getFromWallet(Wallet wallet) {
    return (StoredPaymentChannelClientStates) wallet.getExtensions().get(EXTENSION_ID);
  }

  /**
   * Returns the outstanding amount of money sent back to us for all channels to this server added
   * together.
   */
  public BigInteger getBalanceForServer(Sha256Hash id) {
    BigInteger balance = BigInteger.ZERO;
    lock.lock();
    try {
      Set<StoredClientChannel> setChannels = mapChannels.get(id);
      for (StoredClientChannel channel : setChannels) {
        synchronized (channel) {
          if (channel.close != null) continue;
          balance = balance.add(channel.valueToMe);
        }
      }
      return balance;
    } finally {
      lock.unlock();
    }
  }

  /**
   * Returns the number of seconds from now until this servers next channel will expire, or zero if
   * no unexpired channels found.
   */
  public long getSecondsUntilExpiry(Sha256Hash id) {
    lock.lock();
    try {
      final Set<StoredClientChannel> setChannels = mapChannels.get(id);
      final long nowSeconds = Utils.currentTimeSeconds();
      int earliestTime = Integer.MAX_VALUE;
      for (StoredClientChannel channel : setChannels) {
        synchronized (channel) {
          if (channel.expiryTimeSeconds() > nowSeconds)
            earliestTime = Math.min(earliestTime, (int) channel.expiryTimeSeconds());
        }
      }
      return earliestTime == Integer.MAX_VALUE ? 0 : earliestTime - nowSeconds;
    } finally {
      lock.unlock();
    }
  }

  /** Finds an inactive channel with the given id and returns it, or returns null. */
  @Nullable
  StoredClientChannel getUsableChannelForServerID(Sha256Hash id) {
    lock.lock();
    try {
      Set<StoredClientChannel> setChannels = mapChannels.get(id);
      for (StoredClientChannel channel : setChannels) {
        synchronized (channel) {
          // Check if the channel is usable (has money, inactive) and if so, activate it.
          log.info(
              "Considering channel {} contract {}", channel.hashCode(), channel.contract.getHash());
          if (channel.close != null || channel.valueToMe.equals(BigInteger.ZERO)) {
            log.info("  ... but is closed or empty");
            continue;
          }
          if (!channel.active) {
            log.info("  ... activating");
            channel.active = true;
            return channel;
          }
          log.info("  ... but is already active");
        }
      }
    } finally {
      lock.unlock();
    }
    return null;
  }

  /** Finds a channel with the given id and contract hash and returns it, or returns null. */
  @Nullable
  StoredClientChannel getChannel(Sha256Hash id, Sha256Hash contractHash) {
    lock.lock();
    try {
      Set<StoredClientChannel> setChannels = mapChannels.get(id);
      for (StoredClientChannel channel : setChannels) {
        if (channel.contract.getHash().equals(contractHash)) return channel;
      }
      return null;
    } finally {
      lock.unlock();
    }
  }

  /**
   * Adds the given channel to this set of stored states, broadcasting the contract and refund
   * transactions when the channel expires and notifies the wallet of an update to this wallet
   * extension
   */
  void putChannel(final StoredClientChannel channel) {
    putChannel(channel, true);
  }

  // Adds this channel and optionally notifies the wallet of an update to this extension (used
  // during deserialize)
  private void putChannel(final StoredClientChannel channel, boolean updateWallet) {
    lock.lock();
    try {
      mapChannels.put(channel.id, channel);
      channelTimeoutHandler.schedule(
          new TimerTask() {
            @Override
            public void run() {
              removeChannel(channel);
              announcePeerGroup.broadcastTransaction(channel.contract);
              announcePeerGroup.broadcastTransaction(channel.refund);
            }
            // Add the difference between real time and Utils.now() so that test-cases can use a
            // mock clock.
          },
          new Date(
              channel.expiryTimeSeconds() * 1000
                  + (System.currentTimeMillis() - Utils.currentTimeMillis())));
    } finally {
      lock.unlock();
    }
    if (updateWallet) containingWallet.addOrUpdateExtension(this);
  }

  /**
   * Removes the channel with the given id from this set of stored states and notifies the wallet of
   * an update to this wallet extension.
   *
   * <p>Note that the channel will still have its contract and refund transactions broadcast via the
   * connected {@link TransactionBroadcaster} as long as this {@link
   * StoredPaymentChannelClientStates} continues to exist in memory.
   */
  void removeChannel(StoredClientChannel channel) {
    lock.lock();
    try {
      mapChannels.remove(channel.id, channel);
    } finally {
      lock.unlock();
    }
    containingWallet.addOrUpdateExtension(this);
  }

  @Override
  public String getWalletExtensionID() {
    return EXTENSION_ID;
  }

  @Override
  public boolean isWalletExtensionMandatory() {
    return false;
  }

  @Override
  public byte[] serializeWalletExtension() {
    lock.lock();
    try {
      ClientState.StoredClientPaymentChannels.Builder builder =
          ClientState.StoredClientPaymentChannels.newBuilder();
      for (StoredClientChannel channel : mapChannels.values()) {
        // First a few asserts to make sure things won't break
        checkState(
            channel.valueToMe.signum() >= 0
                && channel.valueToMe.compareTo(NetworkParameters.MAX_MONEY) < 0);
        checkState(
            channel.refundFees.signum() >= 0
                && channel.refundFees.compareTo(NetworkParameters.MAX_MONEY) < 0);
        checkNotNull(channel.myKey.getPrivKeyBytes());
        checkState(channel.refund.getConfidence().getSource() == TransactionConfidence.Source.SELF);
        final ClientState.StoredClientPaymentChannel.Builder value =
            ClientState.StoredClientPaymentChannel.newBuilder()
                .setId(ByteString.copyFrom(channel.id.getBytes()))
                .setContractTransaction(ByteString.copyFrom(channel.contract.bitcoinSerialize()))
                .setRefundTransaction(ByteString.copyFrom(channel.refund.bitcoinSerialize()))
                .setMyKey(ByteString.copyFrom(channel.myKey.getPrivKeyBytes()))
                .setValueToMe(channel.valueToMe.longValue())
                .setRefundFees(channel.refundFees.longValue());
        if (channel.close != null)
          value.setCloseTransactionHash(ByteString.copyFrom(channel.close.getHash().getBytes()));
        builder.addChannels(value);
      }
      return builder.build().toByteArray();
    } finally {
      lock.unlock();
    }
  }

  @Override
  public void deserializeWalletExtension(Wallet containingWallet, byte[] data) throws Exception {
    lock.lock();
    try {
      checkState(this.containingWallet == null || this.containingWallet == containingWallet);
      this.containingWallet = containingWallet;
      NetworkParameters params = containingWallet.getParams();
      ClientState.StoredClientPaymentChannels states =
          ClientState.StoredClientPaymentChannels.parseFrom(data);
      for (ClientState.StoredClientPaymentChannel storedState : states.getChannelsList()) {
        Transaction refundTransaction =
            new Transaction(params, storedState.getRefundTransaction().toByteArray());
        refundTransaction.getConfidence().setSource(TransactionConfidence.Source.SELF);
        StoredClientChannel channel =
            new StoredClientChannel(
                new Sha256Hash(storedState.getId().toByteArray()),
                new Transaction(params, storedState.getContractTransaction().toByteArray()),
                refundTransaction,
                new ECKey(new BigInteger(1, storedState.getMyKey().toByteArray()), null, true),
                BigInteger.valueOf(storedState.getValueToMe()),
                BigInteger.valueOf(storedState.getRefundFees()),
                false);
        if (storedState.hasCloseTransactionHash())
          channel.close =
              containingWallet.getTransaction(new Sha256Hash(storedState.toByteArray()));
        putChannel(channel, false);
      }
    } finally {
      lock.unlock();
    }
  }

  @Override
  public String toString() {
    lock.lock();
    try {
      StringBuilder buf = new StringBuilder("Client payment channel states:\n");
      for (StoredClientChannel channel : mapChannels.values())
        buf.append("  ").append(channel).append("\n");
      return buf.toString();
    } finally {
      lock.unlock();
    }
  }
}