/**
  * Parses a wallet from the given stream, using the provided Wallet instance to load data into.
  * This is primarily used when you want to register extensions. Data in the proto will be added
  * into the wallet where applicable and overwrite where not.
  *
  * <p>A wallet can be unreadable for various reasons, such as inability to open the file, corrupt
  * data, internally inconsistent data, a wallet extension marked as mandatory that cannot be
  * handled and so on. You should always handle {@link UnreadableWalletException} and communicate
  * failure to the user in an appropriate manner.
  *
  * @throws UnreadableWalletException thrown in various error conditions (see description).
  */
 public Wallet readWallet(InputStream input) throws UnreadableWalletException {
   try {
     Protos.Wallet walletProto = parseToProto(input);
     final String paramsID = walletProto.getNetworkIdentifier();
     NetworkParameters params = NetworkParameters.fromID(paramsID);
     if (params == null)
       throw new UnreadableWalletException("Unknown network parameters ID " + paramsID);
     return readWallet(params, null, walletProto);
   } catch (IOException e) {
     throw new UnreadableWalletException("Could not parse input stream to protobuf", e);
   } catch (IllegalStateException e) {
     throw new UnreadableWalletException("Could not parse input stream to protobuf", e);
   }
 }
 private void loadExtensions(
     Wallet wallet, WalletExtension[] extensionsList, Protos.Wallet walletProto)
     throws UnreadableWalletException {
   final Map<String, WalletExtension> extensions = new HashMap<String, WalletExtension>();
   for (WalletExtension e : extensionsList) extensions.put(e.getWalletExtensionID(), e);
   // The Wallet object, if subclassed, might have added some extensions to itself already. In that
   // case, don't
   // expect them to be passed in, just fetch them here and don't re-add.
   extensions.putAll(wallet.getExtensions());
   for (Protos.Extension extProto : walletProto.getExtensionList()) {
     String id = extProto.getId();
     WalletExtension extension = extensions.get(id);
     if (extension == null) {
       if (extProto.getMandatory()) {
         if (requireMandatoryExtensions)
           throw new UnreadableWalletException("Unknown mandatory extension in wallet: " + id);
         else log.error("Unknown extension in wallet {}, ignoring", id);
       }
     } else {
       log.info("Loading wallet extension {}", id);
       try {
         extension.deserializeWalletExtension(wallet, extProto.getData().toByteArray());
         wallet.addOrGetExistingExtension(extension);
       } catch (Exception e) {
         if (extProto.getMandatory() && requireMandatoryExtensions)
           throw new UnreadableWalletException(
               "Could not parse mandatory extension in wallet: " + id);
         else log.error("Error whilst reading extension {}, ignoring", id, e);
       }
     }
   }
 }
 /**
  * Returns the loaded protocol buffer from the given byte stream. You normally want {@link
  * Wallet#loadFromFile(java.io.File)} instead - this method is designed for low level work
  * involving the wallet file format itself.
  */
 public static Protos.Wallet parseToProto(InputStream input) throws IOException {
   return Protos.Wallet.parseFrom(input);
 }
  /**
   * Loads wallet data from the given protocol buffer and inserts it into the given Wallet object.
   * This is primarily useful when you wish to pre-register extension objects. Note that if loading
   * fails the provided Wallet object may be in an indeterminate state and should be thrown away.
   *
   * <p>A wallet can be unreadable for various reasons, such as inability to open the file, corrupt
   * data, internally inconsistent data, a wallet extension marked as mandatory that cannot be
   * handled and so on. You should always handle {@link UnreadableWalletException} and communicate
   * failure to the user in an appropriate manner.
   *
   * @throws UnreadableWalletException thrown in various error conditions (see description).
   */
  public Wallet readWallet(
      NetworkParameters params, @Nullable WalletExtension[] extensions, Protos.Wallet walletProto)
      throws UnreadableWalletException {
    if (walletProto.getVersion() > 1) throw new UnreadableWalletException.FutureVersion();
    if (!walletProto.getNetworkIdentifier().equals(params.getId()))
      throw new UnreadableWalletException.WrongNetwork();

    int sigsRequiredToSpend = walletProto.getSigsRequiredToSpend();

    // Read the scrypt parameters that specify how encryption and decryption is performed.
    KeyChainGroup chain;
    if (walletProto.hasEncryptionParameters()) {
      Protos.ScryptParameters encryptionParameters = walletProto.getEncryptionParameters();
      final KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(encryptionParameters);
      chain =
          KeyChainGroup.fromProtobufEncrypted(
              params, walletProto.getKeyList(), sigsRequiredToSpend, keyCrypter);
    } else {
      chain =
          KeyChainGroup.fromProtobufUnencrypted(
              params, walletProto.getKeyList(), sigsRequiredToSpend);
    }
    Wallet wallet = factory.create(params, chain);

    List<Script> scripts = Lists.newArrayList();
    for (Protos.Script protoScript : walletProto.getWatchedScriptList()) {
      try {
        Script script =
            new Script(
                protoScript.getProgram().toByteArray(), protoScript.getCreationTimestamp() / 1000);
        scripts.add(script);
      } catch (ScriptException e) {
        throw new UnreadableWalletException("Unparseable script in wallet");
      }
    }

    wallet.addWatchedScripts(scripts);

    if (walletProto.hasDescription()) {
      wallet.setDescription(walletProto.getDescription());
    }

    // Read all transactions and insert into the txMap.
    for (Protos.Transaction txProto : walletProto.getTransactionList()) {
      readTransaction(txProto, wallet.getParams());
    }

    // Update transaction outputs to point to inputs that spend them
    for (Protos.Transaction txProto : walletProto.getTransactionList()) {
      WalletTransaction wtx = connectTransactionOutputs(txProto);
      wallet.addWalletTransaction(wtx);
    }

    // Update the lastBlockSeenHash.
    if (!walletProto.hasLastSeenBlockHash()) {
      wallet.setLastBlockSeenHash(null);
    } else {
      wallet.setLastBlockSeenHash(byteStringToHash(walletProto.getLastSeenBlockHash()));
    }
    if (!walletProto.hasLastSeenBlockHeight()) {
      wallet.setLastBlockSeenHeight(-1);
    } else {
      wallet.setLastBlockSeenHeight(walletProto.getLastSeenBlockHeight());
    }
    // Will default to zero if not present.
    wallet.setLastBlockSeenTimeSecs(walletProto.getLastSeenBlockTimeSecs());

    if (walletProto.hasKeyRotationTime()) {
      wallet.setKeyRotationTime(new Date(walletProto.getKeyRotationTime() * 1000));
    }

    loadExtensions(wallet, extensions != null ? extensions : new WalletExtension[0], walletProto);

    for (Protos.Tag tag : walletProto.getTagsList()) {
      wallet.setTag(tag.getTag(), tag.getData());
    }

    for (Protos.TransactionSigner signerProto : walletProto.getTransactionSignersList()) {
      try {
        Class signerClass = Class.forName(signerProto.getClassName());
        TransactionSigner signer = (TransactionSigner) signerClass.newInstance();
        signer.deserialize(signerProto.getData().toByteArray());
        wallet.addTransactionSigner(signer);
      } catch (Exception e) {
        throw new UnreadableWalletException(
            "Unable to deserialize TransactionSigner instance: " + signerProto.getClassName(), e);
      }
    }

    if (walletProto.hasVersion()) {
      wallet.setVersion(walletProto.getVersion());
    }

    // Make sure the object can be re-used to read another wallet without corruption.
    txMap.clear();

    return wallet;
  }
  /**
   * Converts the given wallet to the object representation of the protocol buffers. This can be
   * modified, or additional data fields set, before serialization takes place.
   */
  public Protos.Wallet walletToProto(Wallet wallet) {
    Protos.Wallet.Builder walletBuilder = Protos.Wallet.newBuilder();
    walletBuilder.setNetworkIdentifier(wallet.getNetworkParameters().getId());
    if (wallet.getDescription() != null) {
      walletBuilder.setDescription(wallet.getDescription());
    }

    for (WalletTransaction wtx : wallet.getWalletTransactions()) {
      Protos.Transaction txProto = makeTxProto(wtx);
      walletBuilder.addTransaction(txProto);
    }

    walletBuilder.addAllKey(wallet.serializeKeychainToProtobuf());

    for (Script script : wallet.getWatchedScripts()) {
      Protos.Script protoScript =
          Protos.Script.newBuilder()
              .setProgram(ByteString.copyFrom(script.getProgram()))
              .setCreationTimestamp(script.getCreationTimeSeconds() * 1000)
              .build();

      walletBuilder.addWatchedScript(protoScript);
    }

    // Populate the lastSeenBlockHash field.
    Sha256Hash lastSeenBlockHash = wallet.getLastBlockSeenHash();
    if (lastSeenBlockHash != null) {
      walletBuilder.setLastSeenBlockHash(hashToByteString(lastSeenBlockHash));
      walletBuilder.setLastSeenBlockHeight(wallet.getLastBlockSeenHeight());
    }
    if (wallet.getLastBlockSeenTimeSecs() > 0)
      walletBuilder.setLastSeenBlockTimeSecs(wallet.getLastBlockSeenTimeSecs());

    // Populate the scrypt parameters.
    KeyCrypter keyCrypter = wallet.getKeyCrypter();
    if (keyCrypter == null) {
      // The wallet is unencrypted.
      walletBuilder.setEncryptionType(EncryptionType.UNENCRYPTED);
    } else {
      // The wallet is encrypted.
      walletBuilder.setEncryptionType(keyCrypter.getUnderstoodEncryptionType());
      if (keyCrypter instanceof KeyCrypterScrypt) {
        KeyCrypterScrypt keyCrypterScrypt = (KeyCrypterScrypt) keyCrypter;
        walletBuilder.setEncryptionParameters(keyCrypterScrypt.getScryptParameters());
      } else {
        // Some other form of encryption has been specified that we do not know how to persist.
        throw new RuntimeException(
            "The wallet has encryption of type '"
                + keyCrypter.getUnderstoodEncryptionType()
                + "' but this WalletProtobufSerializer does not know how to persist this.");
      }
    }

    if (wallet.getKeyRotationTime() != null) {
      long timeSecs = wallet.getKeyRotationTime().getTime() / 1000;
      walletBuilder.setKeyRotationTime(timeSecs);
    }

    populateExtensions(wallet, walletBuilder);

    for (Map.Entry<String, ByteString> entry : wallet.getTags().entrySet()) {
      Protos.Tag.Builder tag =
          Protos.Tag.newBuilder().setTag(entry.getKey()).setData(entry.getValue());
      walletBuilder.addTags(tag);
    }

    for (TransactionSigner signer : wallet.getTransactionSigners()) {
      // do not serialize LocalTransactionSigner as it's being added implicitly
      if (signer instanceof LocalTransactionSigner) continue;
      Protos.TransactionSigner.Builder protoSigner = Protos.TransactionSigner.newBuilder();
      protoSigner.setClassName(signer.getClass().getName());
      protoSigner.setData(ByteString.copyFrom(signer.serialize()));
      walletBuilder.addTransactionSigners(protoSigner);
    }

    walletBuilder.setSigsRequiredToSpend(wallet.getSigsRequiredToSpend());

    // Populate the wallet version.
    walletBuilder.setVersion(wallet.getVersion());

    return walletBuilder.build();
  }
 /**
  * Formats the given wallet (transactions and keys) to the given output stream in protocol buffer
  * format.
  *
  * <p>Equivalent to <tt>walletToProto(wallet).writeTo(output);</tt>
  */
 public void writeWallet(Wallet wallet, OutputStream output) throws IOException {
   Protos.Wallet walletProto = walletToProto(wallet);
   walletProto.writeTo(output);
 }