예제 #1
0
  /**
   * 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;
  }
예제 #2
0
  protected void checkArguments(List<FieldValue> arguments, int size, boolean allowNulls) {

    if (arguments.size() != size) {
      throw new FunctionException(
          getName(), "Expected " + size + " arguments, but got " + arguments.size() + " arguments");
    } // End if

    if (!allowNulls && arguments.contains(null)) {
      throw new FunctionException(getName(), "Missing arguments");
    }
  }
예제 #3
0
 /**
  * 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();
   }
 }
예제 #4
0
 // 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;
 }
  // expensiveChecks enables checks that require looking at blocks further back in the chain
  // than the previous one when connecting (eg median timestamp check)
  // It could be exposed, but for now we just set it to shouldVerifyTransactions()
  private void connectBlock(
      final Block block,
      StoredBlock storedPrev,
      boolean expensiveChecks,
      @Nullable final List<Sha256Hash> filteredTxHashList,
      @Nullable final Map<Sha256Hash, Transaction> filteredTxn)
      throws BlockStoreException, VerificationException, PrunedException {
    checkState(lock.isHeldByCurrentThread());
    boolean filtered = filteredTxHashList != null && filteredTxn != null;
    // Check that we aren't connecting a block that fails a checkpoint check
    if (!params.passesCheckpoint(storedPrev.getHeight() + 1, block.getHash()))
      throw new VerificationException(
          "Block failed checkpoint lockin at " + (storedPrev.getHeight() + 1));
    if (shouldVerifyTransactions()) {
      checkNotNull(block.transactions);
      for (Transaction tx : block.transactions)
        if (!tx.isFinal(storedPrev.getHeight() + 1, block.getTimeSeconds()))
          throw new VerificationException("Block contains non-final transaction");
    }

    StoredBlock head = getChainHead();
    if (storedPrev.equals(head)) {
      if (filtered && filteredTxn.size() > 0) {
        log.debug(
            "Block {} connects to top of best chain with {} transaction(s) of which we were sent {}",
            block.getHashAsString(),
            filteredTxHashList.size(),
            filteredTxn.size());
        for (Sha256Hash hash : filteredTxHashList) log.debug("  matched tx {}", hash);
      }
      if (expensiveChecks
          && block.getTimeSeconds() <= getMedianTimestampOfRecentBlocks(head, blockStore))
        throw new VerificationException("Block's timestamp is too early");

      // This block connects to the best known block, it is a normal continuation of the system.
      TransactionOutputChanges txOutChanges = null;
      if (shouldVerifyTransactions())
        txOutChanges = connectTransactions(storedPrev.getHeight() + 1, block);
      StoredBlock newStoredBlock =
          addToBlockStore(
              storedPrev, block.transactions == null ? block : block.cloneAsHeader(), txOutChanges);
      setChainHead(newStoredBlock);
      log.debug("Chain is now {} blocks high, running listeners", newStoredBlock.getHeight());
      informListenersForNewBlock(
          block, NewBlockType.BEST_CHAIN, filteredTxHashList, filteredTxn, newStoredBlock);
    } else {
      // This block connects to somewhere other than the top of the best known chain. We treat these
      // differently.
      //
      // Note that we send the transactions to the wallet FIRST, even if we're about to re-organize
      // this block
      // to become the new best chain head. This simplifies handling of the re-org in the Wallet
      // class.
      StoredBlock newBlock = storedPrev.build(block);
      boolean haveNewBestChain = newBlock.moreWorkThan(head);
      if (haveNewBestChain) {
        log.info("Block is causing a re-organize");
      } else {
        StoredBlock splitPoint = findSplit(newBlock, head, blockStore);
        if (splitPoint != null && splitPoint.equals(newBlock)) {
          // newStoredBlock is a part of the same chain, there's no fork. This happens when we
          // receive a block
          // that we already saw and linked into the chain previously, which isn't the chain head.
          // Re-processing it is confusing for the wallet so just skip.
          log.warn(
              "Saw duplicated block in main chain at height {}: {}",
              newBlock.getHeight(),
              newBlock.getHeader().getHash());
          return;
        }
        if (splitPoint == null) {
          // This should absolutely never happen
          // (lets not write the full block to disk to keep any bugs which allow this to happen
          //  from writing unreasonable amounts of data to disk)
          throw new VerificationException("Block forks the chain but splitPoint is null");
        } else {
          // We aren't actually spending any transactions (yet) because we are on a fork
          addToBlockStore(storedPrev, block);
          int splitPointHeight = splitPoint.getHeight();
          String splitPointHash = splitPoint.getHeader().getHashAsString();
          log.info(
              "Block forks the chain at height {}/block {}, but it did not cause a reorganize:\n{}",
              splitPointHeight,
              splitPointHash,
              newBlock.getHeader().getHashAsString());
        }
      }

      // We may not have any transactions if we received only a header, which can happen during fast
      // catchup.
      // If we do, send them to the wallet but state that they are on a side chain so it knows not
      // to try and
      // spend them until they become activated.
      if (block.transactions != null || filtered) {
        informListenersForNewBlock(
            block, NewBlockType.SIDE_CHAIN, filteredTxHashList, filteredTxn, newBlock);
      }

      if (haveNewBestChain) handleNewBestChain(storedPrev, newBlock, block, expensiveChecks);
    }
  }
예제 #6
0
 /**
  * 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;
 }
예제 #7
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();
   }
 }