text: // Copyright (c) 2011-2017 The Cryptonote developers
  // Copyright (c) 2017-2018 The Circle Foundation & Conceal Devs
  // Copyright (c) 2018-2019 Conceal Network & Conceal Devs
  // Distributed under the MIT/X11 software license, see the accompanying
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  
  #include "TransactionUtils.h"
  
  #include <unordered_set>
  
  #include "crypto/crypto.h"
  #include "CryptoNoteCore/Account.h"
  #include "CryptoNoteFormatUtils.h"
  #include "TransactionExtra.h"
  
  using namespace Crypto;
  
  namespace CryptoNote {
  
  bool checkInputsKeyimagesDiff(const CryptoNote::TransactionPrefix& tx) {
    std::unordered_set<Crypto::KeyImage> ki;
    for (const auto& in : tx.inputs) {
      if (in.type() == typeid(KeyInput)) {
        if (!ki.insert(boost::get<KeyInput>(in).keyImage).second)
          return false;
      }
    }
    return true;
  }
  
  // TransactionInput helper functions
  
  size_t getRequiredSignaturesCount(const TransactionInput& in) {
    if (in.type() == typeid(KeyInput)) {
      return boost::get<KeyInput>(in).outputIndexes.size();
    }
    if (in.type() == typeid(MultisignatureInput)) {
      return boost::get<MultisignatureInput>(in).signatureCount;
    }
    return 0;
  }
  
  uint64_t getTransactionInputAmount(const TransactionInput& in) {
    if (in.type() == typeid(KeyInput)) {
      return boost::get<KeyInput>(in).amount;
    }
    if (in.type() == typeid(MultisignatureInput)) {
      // TODO calculate interest
      return boost::get<MultisignatureInput>(in).amount;
    }
    return 0;
  }
  
  TransactionTypes::InputType getTransactionInputType(const TransactionInput& in) {
    if (in.type() == typeid(KeyInput)) {
      return TransactionTypes::InputType::Key;
    }
    if (in.type() == typeid(MultisignatureInput)) {
      return TransactionTypes::InputType::Multisignature;
    }
    if (in.type() == typeid(BaseInput)) {
      return TransactionTypes::InputType::Generating;
    }
    return TransactionTypes::InputType::Invalid;
  }
  
  const TransactionInput& getInputChecked(const CryptoNote::TransactionPrefix& transaction, size_t index) {
    if (transaction.inputs.size() <= index) {
      throw std::runtime_error("Transaction input index out of range");
    }
    return transaction.inputs[index];
  }
  
  const TransactionInput& getInputChecked(const CryptoNote::TransactionPrefix& transaction, size_t index, TransactionTypes::InputType type) {
    const auto& input = getInputChecked(transaction, index);
    if (getTransactionInputType(input) != type) {
      throw std::runtime_error("Unexpected transaction input type");
    }
    return input;
  }
  
  // TransactionOutput helper functions
  
  TransactionTypes::OutputType getTransactionOutputType(const TransactionOutputTarget& out) {
    if (out.type() == typeid(KeyOutput)) {
      return TransactionTypes::OutputType::Key;
    }
    if (out.type() == typeid(MultisignatureOutput)) {
      return TransactionTypes::OutputType::Multisignature;
    }
    return TransactionTypes::OutputType::Invalid;
  }
  
  const TransactionOutput& getOutputChecked(const CryptoNote::TransactionPrefix& transaction, size_t index) {
    if (transaction.outputs.size() <= index) {
      throw std::runtime_error("Transaction output index out of range");
    }
    return transaction.outputs[index];
  }
  
  const TransactionOutput& getOutputChecked(const CryptoNote::TransactionPrefix& transaction, size_t index, TransactionTypes::OutputType type) {
    const auto& output = getOutputChecked(transaction, index);
    if (getTransactionOutputType(output.target) != type) {
      throw std::runtime_error("Unexpected transaction output target type");
    }
    return output;
  }
  
  bool isOutToKey(const Crypto::PublicKey& spendPublicKey, const Crypto::PublicKey& outKey, const Crypto::KeyDerivation& derivation, size_t keyIndex) {
    Crypto::PublicKey pk;
    derive_public_key(derivation, keyIndex, spendPublicKey, pk);
    return pk == outKey;
  }
  
  bool findOutputsToAccount(const CryptoNote::TransactionPrefix& transaction, const AccountPublicAddress& addr,
                            const SecretKey& viewSecretKey, std::vector<uint32_t>& out, uint64_t& amount) {
    AccountKeys keys;
    keys.address = addr;
    // only view secret key is used, spend key is not needed
    keys.viewSecretKey = viewSecretKey;
  
    Crypto::PublicKey txPubKey = getTransactionPublicKeyFromExtra(transaction.extra);
  
    amount = 0;
    size_t keyIndex = 0;
    uint32_t outputIndex = 0;
  
    Crypto::KeyDerivation derivation;
    generate_key_derivation(txPubKey, keys.viewSecretKey, derivation);
  
    for (const TransactionOutput& o : transaction.outputs) {
      assert(o.target.type() == typeid(KeyOutput) || o.target.type() == typeid(MultisignatureOutput));
      if (o.target.type() == typeid(KeyOutput)) {
        if (is_out_to_acc(keys, boost::get<KeyOutput>(o.target), derivation, keyIndex)) {
          out.push_back(outputIndex);
          amount += o.amount;
        }
        ++keyIndex;
      } else if (o.target.type() == typeid(MultisignatureOutput)) {
        const auto& target = boost::get<MultisignatureOutput>(o.target);
        for (const auto& key : target.keys) {
          if (isOutToKey(keys.address.spendPublicKey, key, derivation, static_cast<size_t>(outputIndex))) {
            out.push_back(outputIndex);
          }
          ++keyIndex;
        }
      }
      ++outputIndex;
    }
  
    return true;
  }
  
  }
  