예제 #1
0
 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;
 }
예제 #2
0
 @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;
 }
예제 #3
0
 public DeterministicKey findKeyFromPubKey(byte[] pubkey) {
   lock.lock();
   try {
     return (DeterministicKey) basicKeyChain.findKeyFromPubKey(pubkey);
   } finally {
     lock.unlock();
   }
 }
예제 #4
0
 @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();
   }
 }
예제 #5
0
 @Override
 public boolean hasKey(ECKey key) {
   lock.lock();
   try {
     return basicKeyChain.hasKey(key);
   } finally {
     lock.unlock();
   }
 }
예제 #6
0
 @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();
   }
 }
예제 #7
0
 /**
  * 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();
   }
 }
예제 #8
0
  // 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);
    }
  }
예제 #9
0
 @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();
   }
 }
예제 #10
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();
   }
 }
예제 #11
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;
 }
예제 #12
0
 @Nullable
 @Override
 public KeyCrypter getKeyCrypter() {
   return basicKeyChain.getKeyCrypter();
 }
예제 #13
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();
   }
 }
예제 #14
0
 @Override
 public boolean removeEventListener(KeyChainEventListener listener) {
   return basicKeyChain.removeEventListener(listener);
 }
예제 #15
0
 @Override
 public void addEventListener(KeyChainEventListener listener, Executor executor) {
   basicKeyChain.addEventListener(listener, executor);
 }
예제 #16
0
 @Override
 public void addEventListener(KeyChainEventListener listener) {
   basicKeyChain.addEventListener(listener);
 }
예제 #17
0
 private void addToBasicChain(DeterministicKey key) {
   basicKeyChain.importKeys(ImmutableList.of(key));
 }