// Copyright (c) 2011-2016 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "CryptoNoteFormatUtils.h" #include #include #include #include "Serialization/BinaryOutputStreamSerializer.h" #include "Serialization/BinaryInputStreamSerializer.h" #include "Account.h" #include "CryptoNoteBasicImpl.h" #include "CryptoNoteSerialization.h" #include "TransactionExtra.h" #include "CryptoNoteTools.h" #include "CryptoNoteConfig.h" using namespace Logging; using namespace Crypto; using namespace Common; namespace CryptoNote { bool parseAndValidateTransactionFromBinaryArray(const BinaryArray& tx_blob, Transaction& tx, Hash& tx_hash, Hash& tx_prefix_hash) { if (!fromBinaryArray(tx, tx_blob)) { return false; } //TODO: validate tx cn_fast_hash(tx_blob.data(), tx_blob.size(), tx_hash); getObjectHash(*static_cast(&tx), tx_prefix_hash); return true; } bool generate_key_image_helper(const AccountKeys& ack, const PublicKey& tx_public_key, size_t real_output_index, KeyPair& in_ephemeral, KeyImage& ki) { KeyDerivation recv_derivation; bool r = generate_key_derivation(tx_public_key, ack.viewSecretKey, recv_derivation); assert(r && "key image helper: failed to generate_key_derivation"); if (!r) { return false; } r = derive_public_key(recv_derivation, real_output_index, ack.address.spendPublicKey, in_ephemeral.publicKey); assert(r && "key image helper: failed to derive_public_key"); if (!r) { return false; } derive_secret_key(recv_derivation, real_output_index, ack.spendSecretKey, in_ephemeral.secretKey); generate_key_image(in_ephemeral.publicKey, in_ephemeral.secretKey, ki); return true; } uint64_t power_integral(uint64_t a, uint64_t b) { if (b == 0) return 1; uint64_t total = a; for (uint64_t i = 1; i != b; i++) total *= a; return total; } bool get_tx_fee(const Transaction& tx, uint64_t & fee) { uint64_t amount_in = 0; uint64_t amount_out = 0; for (const auto& in : tx.inputs) { if (in.type() == typeid(KeyInput)) { amount_in += boost::get(in).amount; } else if (in.type() == typeid(MultisignatureInput)) { amount_in += boost::get(in).amount; } } for (const auto& o : tx.outputs) { amount_out += o.amount; } if (!(amount_in >= amount_out)) { return false; } fee = amount_in - amount_out; return true; } uint64_t get_tx_fee(const Transaction& tx) { uint64_t r = 0; if (!get_tx_fee(tx, r)) return 0; return r; } bool constructTransaction( const AccountKeys& sender_account_keys, const std::vector& sources, const std::vector& destinations, std::vector extra, Transaction& tx, uint64_t unlock_time, Logging::ILogger& log) { LoggerRef logger(log, "construct_tx"); tx.inputs.clear(); tx.outputs.clear(); tx.signatures.clear(); tx.version = CURRENT_TRANSACTION_VERSION; tx.unlockTime = unlock_time; tx.extra = extra; KeyPair txkey = generateKeyPair(); addTransactionPublicKeyToExtra(tx.extra, txkey.publicKey); struct input_generation_context_data { KeyPair in_ephemeral; }; std::vector in_contexts; uint64_t summary_inputs_money = 0; //fill inputs for (const TransactionSourceEntry& src_entr : sources) { if (src_entr.realOutput >= src_entr.outputs.size()) { logger(ERROR) << "real_output index (" << src_entr.realOutput << ")bigger than output_keys.size()=" << src_entr.outputs.size(); return false; } summary_inputs_money += src_entr.amount; //KeyDerivation recv_derivation; in_contexts.push_back(input_generation_context_data()); KeyPair& in_ephemeral = in_contexts.back().in_ephemeral; KeyImage img; if (!generate_key_image_helper(sender_account_keys, src_entr.realTransactionPublicKey, src_entr.realOutputIndexInTransaction, in_ephemeral, img)) return false; //check that derived key is equal with real output key if (!(in_ephemeral.publicKey == src_entr.outputs[src_entr.realOutput].second)) { logger(ERROR) << "derived public key mismatch with output public key! " << ENDL << "derived_key:" << Common::podToHex(in_ephemeral.publicKey) << ENDL << "real output_public_key:" << Common::podToHex(src_entr.outputs[src_entr.realOutput].second); return false; } //put key image into tx input KeyInput input_to_key; input_to_key.amount = src_entr.amount; input_to_key.keyImage = img; //fill outputs array and use relative offsets for (const TransactionSourceEntry::OutputEntry& out_entry : src_entr.outputs) { input_to_key.outputIndexes.push_back(out_entry.first); } input_to_key.outputIndexes = absolute_output_offsets_to_relative(input_to_key.outputIndexes); tx.inputs.push_back(input_to_key); } // "Shuffle" outs std::vector shuffled_dsts(destinations); std::sort(shuffled_dsts.begin(), shuffled_dsts.end(), [](const TransactionDestinationEntry& de1, const TransactionDestinationEntry& de2) { return de1.amount < de2.amount; }); uint64_t summary_outs_money = 0; //fill outputs size_t output_index = 0; for (const TransactionDestinationEntry& dst_entr : shuffled_dsts) { if (!(dst_entr.amount > 0)) { logger(ERROR, BRIGHT_RED) << "Destination with wrong amount: " << dst_entr.amount; return false; } KeyDerivation derivation; PublicKey out_eph_public_key; bool r = generate_key_derivation(dst_entr.addr.viewPublicKey, txkey.secretKey, derivation); if (!(r)) { logger(ERROR, BRIGHT_RED) << "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.viewPublicKey << ", " << txkey.secretKey << ")"; return false; } r = derive_public_key(derivation, output_index, dst_entr.addr.spendPublicKey, out_eph_public_key); if (!(r)) { logger(ERROR, BRIGHT_RED) << "at creation outs: failed to derive_public_key(" << derivation << ", " << output_index << ", " << dst_entr.addr.spendPublicKey << ")"; return false; } TransactionOutput out; out.amount = dst_entr.amount; KeyOutput tk; tk.key = out_eph_public_key; out.target = tk; tx.outputs.push_back(out); output_index++; summary_outs_money += dst_entr.amount; } //check money if (summary_outs_money > summary_inputs_money) { logger(ERROR) << "Transaction inputs money (" << summary_inputs_money << ") less than outputs money (" << summary_outs_money << ")"; return false; } //generate ring signatures Hash tx_prefix_hash; getObjectHash(*static_cast(&tx), tx_prefix_hash); size_t i = 0; for (const TransactionSourceEntry& src_entr : sources) { std::vector keys_ptrs; for (const TransactionSourceEntry::OutputEntry& o : src_entr.outputs) { keys_ptrs.push_back(&o.second); } tx.signatures.push_back(std::vector()); std::vector& sigs = tx.signatures.back(); sigs.resize(src_entr.outputs.size()); generate_ring_signature(tx_prefix_hash, boost::get(tx.inputs[i]).keyImage, keys_ptrs, in_contexts[i].in_ephemeral.secretKey, src_entr.realOutput, sigs.data()); i++; } return true; } bool get_inputs_money_amount(const Transaction& tx, uint64_t& money) { money = 0; for (const auto& in : tx.inputs) { uint64_t amount = 0; if (in.type() == typeid(KeyInput)) { amount = boost::get(in).amount; } else if (in.type() == typeid(MultisignatureInput)) { amount = boost::get(in).amount; } money += amount; } return true; } uint32_t get_block_height(const Block& b) { if (b.baseTransaction.inputs.size() != 1) { return 0; } const auto& in = b.baseTransaction.inputs[0]; if (in.type() != typeid(BaseInput)) { return 0; } return boost::get(in).blockIndex; } bool check_inputs_types_supported(const TransactionPrefix& tx) { for (const auto& in : tx.inputs) { if (in.type() != typeid(KeyInput) && in.type() != typeid(MultisignatureInput)) { return false; } } return true; } bool check_outs_valid(const TransactionPrefix& tx, std::string* error) { for (const TransactionOutput& out : tx.outputs) { if (out.target.type() == typeid(KeyOutput)) { if (out.amount == 0) { if (error) { *error = "Zero amount ouput"; } return false; } if (!check_key(boost::get(out.target).key)) { if (error) { *error = "Output with invalid key"; } return false; } } else if (out.target.type() == typeid(MultisignatureOutput)) { const MultisignatureOutput& multisignatureOutput = ::boost::get(out.target); if (multisignatureOutput.requiredSignatureCount > multisignatureOutput.keys.size()) { if (error) { *error = "Multisignature output with invalid required signature count"; } return false; } for (const PublicKey& key : multisignatureOutput.keys) { if (!check_key(key)) { if (error) { *error = "Multisignature output with invalid public key"; } return false; } } } else { if (error) { *error = "Output with invalid type"; } return false; } } return true; } bool checkMultisignatureInputsDiff(const TransactionPrefix& tx) { std::set> inputsUsage; for (const auto& inv : tx.inputs) { if (inv.type() == typeid(MultisignatureInput)) { const MultisignatureInput& in = ::boost::get(inv); if (!inputsUsage.insert(std::make_pair(in.amount, in.outputIndex)).second) { return false; } } } return true; } bool check_money_overflow(const TransactionPrefix &tx) { return check_inputs_overflow(tx) && check_outs_overflow(tx); } bool check_inputs_overflow(const TransactionPrefix &tx) { uint64_t money = 0; for (const auto &in : tx.inputs) { uint64_t amount = 0; if (in.type() == typeid(KeyInput)) { amount = boost::get(in).amount; } else if (in.type() == typeid(MultisignatureInput)) { amount = boost::get(in).amount; } if (money > amount + money) return false; money += amount; } return true; } bool check_outs_overflow(const TransactionPrefix& tx) { uint64_t money = 0; for (const auto& o : tx.outputs) { if (money > o.amount + money) return false; money += o.amount; } return true; } uint64_t get_outs_money_amount(const Transaction& tx) { uint64_t outputs_amount = 0; for (const auto& o : tx.outputs) { outputs_amount += o.amount; } return outputs_amount; } std::string short_hash_str(const Hash& h) { std::string res = Common::podToHex(h); if (res.size() == 64) { auto erased_pos = res.erase(8, 48); res.insert(8, "...."); } return res; } bool is_out_to_acc(const AccountKeys& acc, const KeyOutput& out_key, const KeyDerivation& derivation, size_t keyIndex) { PublicKey pk; derive_public_key(derivation, keyIndex, acc.address.spendPublicKey, pk); return pk == out_key.key; } bool is_out_to_acc(const AccountKeys& acc, const KeyOutput& out_key, const PublicKey& tx_pub_key, size_t keyIndex) { KeyDerivation derivation; generate_key_derivation(tx_pub_key, acc.viewSecretKey, derivation); return is_out_to_acc(acc, out_key, derivation, keyIndex); } bool lookup_acc_outs(const AccountKeys& acc, const Transaction& tx, std::vector& outs, uint64_t& money_transfered) { PublicKey transactionPublicKey = getTransactionPublicKeyFromExtra(tx.extra); if (transactionPublicKey == NULL_PUBLIC_KEY) return false; return lookup_acc_outs(acc, tx, transactionPublicKey, outs, money_transfered); } bool lookup_acc_outs(const AccountKeys& acc, const Transaction& tx, const PublicKey& tx_pub_key, std::vector& outs, uint64_t& money_transfered) { money_transfered = 0; size_t keyIndex = 0; size_t outputIndex = 0; KeyDerivation derivation; generate_key_derivation(tx_pub_key, acc.viewSecretKey, derivation); for (const TransactionOutput& o : tx.outputs) { assert(o.target.type() == typeid(KeyOutput) || o.target.type() == typeid(MultisignatureOutput)); if (o.target.type() == typeid(KeyOutput)) { if (is_out_to_acc(acc, boost::get(o.target), derivation, keyIndex)) { outs.push_back(outputIndex); money_transfered += o.amount; } ++keyIndex; } else if (o.target.type() == typeid(MultisignatureOutput)) { keyIndex += boost::get(o.target).keys.size(); } ++outputIndex; } return true; } bool get_block_hashing_blob(const Block& b, BinaryArray& ba) { if (!toBinaryArray(static_cast(b), ba)) { return false; } Hash treeRootHash = get_tx_tree_hash(b); ba.insert(ba.end(), treeRootHash.data, treeRootHash.data + 32); auto transactionCount = asBinaryArray(Tools::get_varint_data(b.transactionHashes.size() + 1)); ba.insert(ba.end(), transactionCount.begin(), transactionCount.end()); return true; } bool get_block_hash(const Block& b, Hash& res) { BinaryArray ba; if (!get_block_hashing_blob(b, ba)) { return false; } return getObjectHash(ba, res); } Hash get_block_hash(const Block& b) { Hash p = NULL_HASH; get_block_hash(b, p); return p; } bool get_aux_block_header_hash(const Block& b, Hash& res) { BinaryArray blob; if (!get_block_hashing_blob(b, blob)) { return false; } return getObjectHash(blob, res); } bool get_block_longhash(cn_context &context, const Block& b, Hash& res) { BinaryArray bd; if (!get_block_hashing_blob(b, bd)) { return false; } cn_slow_hash(context, bd.data(), bd.size(), res); return true; } std::vector relative_output_offsets_to_absolute(const std::vector& off) { std::vector res = off; for (size_t i = 1; i < res.size(); i++) res[i] += res[i - 1]; return res; } std::vector absolute_output_offsets_to_relative(const std::vector& off) { std::vector res = off; if (!off.size()) return res; std::sort(res.begin(), res.end());//just to be sure, actually it is already should be sorted for (size_t i = res.size() - 1; i != 0; i--) res[i] -= res[i - 1]; return res; } void get_tx_tree_hash(const std::vector& tx_hashes, Hash& h) { tree_hash(tx_hashes.data(), tx_hashes.size(), h); } Hash get_tx_tree_hash(const std::vector& tx_hashes) { Hash h = NULL_HASH; get_tx_tree_hash(tx_hashes, h); return h; } Hash get_tx_tree_hash(const Block& b) { std::vector txs_ids; Hash h = NULL_HASH; getObjectHash(b.baseTransaction, h); txs_ids.push_back(h); for (auto& th : b.transactionHashes) { txs_ids.push_back(th); } return get_tx_tree_hash(txs_ids); } }