/** * Check if given output (specified by output_index) * belongs is ours based * on our private view key and public spend key */ bool is_output_ours(const size_t& output_index, const transaction& tx, const secret_key& private_view_key, const public_key& public_spend_key) { // get transaction's public key public_key pub_tx_key = get_tx_pub_key_from_extra(tx); // check if transaction has valid public key // if no, then skip if (pub_tx_key == null_pkey) { return false; } // public transaction key is combined with our viewkey // to create, so called, derived key. key_derivation derivation; if (!generate_key_derivation(pub_tx_key, private_view_key, derivation)) { cerr << "Cant get dervied key for: " << "\n" << "pub_tx_key: " << pub_tx_key << " and " << "prv_view_key" << private_view_key << endl; return false; } // get the tx output public key // that normally would be generated for us, // if someone had sent us some xmr. public_key pubkey; derive_public_key(derivation, output_index, public_spend_key, pubkey); //cout << "\n" << tx.vout.size() << " " << output_index << endl; // get tx output public key const txout_to_key tx_out_to_key = boost::get<txout_to_key>(tx.vout[output_index].target); if (tx_out_to_key.key == pubkey) { return true; } return false; }
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; }
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 constructTransaction( const AccountKeys& sender_account_keys, const std::vector<TransactionSourceEntry>& sources, const std::vector<TransactionDestinationEntry>& destinations, std::vector<uint8_t> 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<input_generation_context_data> 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<TransactionDestinationEntry> 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<TransactionPrefix*>(&tx), tx_prefix_hash); size_t i = 0; for (const TransactionSourceEntry& src_entr : sources) { std::vector<const PublicKey*> keys_ptrs; for (const TransactionSourceEntry::OutputEntry& o : src_entr.outputs) { keys_ptrs.push_back(&o.second); } tx.signatures.push_back(std::vector<Signature>()); std::vector<Signature>& sigs = tx.signatures.back(); sigs.resize(src_entr.outputs.size()); generate_ring_signature(tx_prefix_hash, boost::get<KeyInput>(tx.inputs[i]).keyImage, keys_ptrs, in_contexts[i].in_ephemeral.secretKey, src_entr.realOutput, sigs.data()); i++; } return true; }
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; }
void OutputInputIdentification::identify_outputs() { // <public_key , amount , out idx> vector<tuple<txout_to_key, uint64_t, uint64_t>> outputs; outputs = get_ouputs_tuple(*tx); for (auto& out: outputs) { txout_to_key txout_k = std::get<0>(out); uint64_t amount = std::get<1>(out); uint64_t output_idx_in_tx = std::get<2>(out); // get the tx output public key // that normally would be generated for us, // if someone had sent us some xmr. public_key generated_tx_pubkey; derive_public_key(derivation, output_idx_in_tx, address_info->address.m_spend_public_key, generated_tx_pubkey); // check if generated public key matches the current output's key bool mine_output = (txout_k.key == generated_tx_pubkey); //cout << "Chekcing output: " << pod_to_hex(txout_k.key) << " " // << "mine_output: " << mine_output << endl; // placeholder variable for ringct outputs info // that we need to save in database string rtc_outpk; string rtc_mask; string rtc_amount; // if mine output has RingCT, i.e., tx version is 2 // need to decode its amount. otherwise its zero. if (mine_output && tx->version == 2) { // initialize with regular amount value // for ringct, except coinbase, it will be 0 uint64_t rct_amount_val = amount; // cointbase txs have amounts in plain sight. // so use amount from ringct, only for non-coinbase txs if (!tx_is_coinbase) { bool r; // for ringct non-coinbase txs, these values are provided with txs. // coinbase ringctx dont have this information. we will provide // them only when needed, in get_unspent_outputs. So go there // to see how we deal with ringct coinbase txs when we spent them // go to CurrentBlockchainStatus::construct_output_rct_field // to see how we deal with coinbase ringct that are used as mixins rtc_outpk = pod_to_hex(tx->rct_signatures.outPk[output_idx_in_tx].mask); rtc_mask = pod_to_hex(tx->rct_signatures.ecdhInfo[output_idx_in_tx].mask); rtc_amount = pod_to_hex(tx->rct_signatures.ecdhInfo[output_idx_in_tx].amount); rct::key mask = tx->rct_signatures.ecdhInfo[output_idx_in_tx].mask; r = decode_ringct(tx->rct_signatures, tx_pub_key, *viewkey, output_idx_in_tx, mask, rct_amount_val); if (!r) { cerr << "Cant decode ringCT!" << endl; throw OutputInputIdentificationException("Cant decode ringCT!"); } amount = rct_amount_val; } } // if (mine_output && tx.version == 2) if (mine_output) { string out_key_str = pod_to_hex(txout_k.key); // found an output associated with the given address and viewkey string msg = fmt::format("tx_hash: {:s}, output_pub_key: {:s}\n", get_tx_hash_str(), out_key_str); cout << msg << endl; total_received += amount; identified_outputs.emplace_back( output_info{ txout_k.key, amount, output_idx_in_tx, rtc_outpk, rtc_mask, rtc_amount }); } // if (mine_output) } // for (const auto& out: outputs) }
bool WalletManagerImpl::checkPayment(const std::string &address_text, const std::string &txid_text, const std::string &txkey_text, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const { error = ""; cryptonote::blobdata txid_data; if(!epee::string_tools::parse_hexstr_to_binbuff(txid_text, txid_data)) { error = tr("failed to parse txid"); return false; } crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); if (txkey_text.size() < 64 || txkey_text.size() % 64) { error = tr("failed to parse tx key"); return false; } crypto::secret_key tx_key; cryptonote::blobdata tx_key_data; if(!epee::string_tools::parse_hexstr_to_binbuff(txkey_text, tx_key_data)) { error = tr("failed to parse tx key"); return false; } tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); bool testnet = address_text[0] != '4'; cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 payment_id; if(!cryptonote::get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_text)) { error = tr("failed to parse address"); return false; } cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); epee::net_utils::http::http_simple_client http_client; if (!epee::net_utils::invoke_http_json_remote_command2(daemon_address + "/gettransactions", req, res, http_client) || (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) { error = tr("failed to get transaction from daemon"); return false; } cryptonote::blobdata tx_data; bool ok; if (res.txs.size() == 1) ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); else ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); if (!ok) { error = tr("failed to parse transaction from daemon"); return false; } crypto::hash tx_hash, tx_prefix_hash; cryptonote::transaction tx; if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) { error = tr("failed to validate transaction from daemon"); return false; } if (tx_hash != txid) { error = tr("failed to get the right transaction from daemon"); return false; } crypto::key_derivation derivation; if (!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation)) { error = tr("failed to generate key derivation from supplied parameters"); return false; } received = 0; try { for (size_t n = 0; n < tx.vout.size(); ++n) { if (typeid(cryptonote::txout_to_key) != tx.vout[n].target.type()) continue; const cryptonote::txout_to_key tx_out_to_key = boost::get<cryptonote::txout_to_key>(tx.vout[n].target); crypto::public_key pubkey; derive_public_key(derivation, n, address.m_spend_public_key, pubkey); if (pubkey == tx_out_to_key.key) { uint64_t amount; if (tx.version == 1) { amount = tx.vout[n].amount; } else { try { rct::key Ctmp; //rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); crypto::key_derivation derivation; bool r = crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation); if (!r) { LOG_ERROR("Failed to generate key derivation to decode rct output " << n); amount = 0; } else { crypto::secret_key scalar1; crypto::derivation_to_scalar(derivation, n, scalar1); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); rct::key C = tx.rct_signatures.outPk[n].mask; rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); if (rct::equalKeys(C, Ctmp)) amount = rct::h2d(ecdh_info.amount); else amount = 0; } } catch (...) { amount = 0; } } received += amount; } } } catch(const std::exception &e) { LOG_ERROR("error: " << e.what()); error = std::string(tr("error: ")) + e.what(); return false; } if (received > 0) { LOG_PRINT_L1(get_account_address_as_str(testnet, address) << " " << tr("received") << " " << cryptonote::print_money(received) << " " << tr("in txid") << " " << txid); } else { LOG_PRINT_L1(get_account_address_as_str(testnet, address) << " " << tr("received nothing in txid") << " " << txid); } if (res.txs.front().in_pool) { height = 0; } else { height = res.txs.front().block_height; } return true; }
/** * Get tx outputs associated with the given private view and public spend keys * * */ vector<xmreg::transfer_details> get_belonging_outputs(const block& blk, const transaction& tx, const secret_key& private_view_key, const public_key& public_spend_key, uint64_t block_height) { // vector to be returned vector<xmreg::transfer_details> our_outputs; // get transaction's public key public_key pub_tx_key = get_tx_pub_key_from_extra(tx); // check if transaction has valid public key // if no, then skip if (pub_tx_key == null_pkey) { return our_outputs; } // get the total number of outputs in a transaction. size_t output_no = tx.vout.size(); // check if the given transaction has any outputs // if no, then finish if (output_no == 0) { return our_outputs; } // public transaction key is combined with our viewkey // to create, so called, derived key. key_derivation derivation; if (!generate_key_derivation(pub_tx_key, private_view_key, derivation)) { cerr << "Cant get dervied key for: " << "\n" << "pub_tx_key: " << private_view_key << " and " << "prv_view_key" << private_view_key << endl; return our_outputs; } // each tx that we (or the address we are checking) received // contains a number of outputs. // some of them are ours, some not. so we need to go through // all of them in a given tx block, to check which outputs are ours. // sum amount of xmr sent to us // in the given transaction uint64_t money_transfered {0}; // loop through outputs in the given tx // to check which outputs our ours. we compare outputs' // public keys with the public key that would had been // generated for us if we had gotten the outputs. // not sure this is the case though, but that's my understanding. for (size_t i = 0; i < output_no; ++i) { // get the tx output public key // that normally would be generated for us, // if someone had sent us some xmr. public_key pubkey; derive_public_key(derivation, i, public_spend_key, pubkey); // get tx output public key const txout_to_key tx_out_to_key = boost::get<txout_to_key>(tx.vout[i].target); //cout << "Output no: " << i << ", " << tx_out_to_key.key; // check if the output's public key is ours if (tx_out_to_key.key == pubkey) { // if so, then add this output to the // returned vector //our_outputs.push_back(tx.vout[i]); our_outputs.push_back( xmreg::transfer_details {block_height, blk.timestamp, tx, i, false} ); } } return our_outputs; }
bool is_out_to_acc(const account_keys& acc, const TransactionOutputToKey& out_key, const crypto::key_derivation& derivation, size_t keyIndex) { crypto::public_key pk; derive_public_key(derivation, keyIndex, acc.m_account_address.m_spendPublicKey, pk); return pk == out_key.key; }
bool CurrentBlockchainStatus::search_if_payment_made( const string& payment_id_str, const uint64_t& desired_amount, string& tx_hash_with_payment) { vector<pair<uint64_t, transaction>> mempool_transactions = get_mempool_txs(); uint64_t current_blockchain_height = current_height; vector<transaction> txs_to_check; for (auto& mtx: mempool_transactions) { txs_to_check.push_back(mtx.second); } // apend txs in last to blocks into the txs_to_check vector for (uint64_t blk_i = current_blockchain_height - 10; blk_i <= current_blockchain_height; ++blk_i) { // get block cointaining this tx block blk; if (!get_block(blk_i, blk)) { cerr << "Cant get block of height: " + to_string(blk_i) << endl; return false; } list <cryptonote::transaction> blk_txs; if (!get_block_txs(blk, blk_txs)) { cerr << "Cant get transactions in block: " << to_string(blk_i) << endl; return false; } // combine mempool txs and txs from given number of // last blocks txs_to_check.insert(txs_to_check.end(), blk_txs.begin(), blk_txs.end()); } for (transaction& tx: txs_to_check) { if (is_coinbase(tx)) { // not interested in coinbase txs continue; } string tx_payment_id_str = get_payment_id_as_string(tx); // we are interested only in txs with encrypted payments id8 // they have length of 16 characters. if (tx_payment_id_str.length() != 16) { continue; } // we have some tx with encrypted payment_id8 // need to decode it using tx public key, and our // private view key, before we can comapre it is // what we are after. crypto::hash8 encrypted_payment_id8; if (!hex_to_pod(tx_payment_id_str, encrypted_payment_id8)) { cerr << "failed parsing hex to pod for encrypted_payment_id8" << '\n'; } // decrypt the encrypted_payment_id8 public_key tx_pub_key = xmreg::get_tx_pub_key_from_received_outs(tx); // public transaction key is combined with our viewkey // to create, so called, derived key. key_derivation derivation; if (!generate_key_derivation(tx_pub_key, import_payment_viewkey, derivation)) { cerr << "Cant get derived key for: " << "\n" << "pub_tx_key: " << tx_pub_key << " and " << "prv_view_key" << import_payment_viewkey << endl; return false; } // decrypt encrypted payment id, as used in integreated addresses crypto::hash8 decrypted_payment_id8 = encrypted_payment_id8; if (decrypted_payment_id8 != null_hash8) { if (!decrypt_payment_id(decrypted_payment_id8, tx_pub_key, import_payment_viewkey)) { cerr << "Cant decrypt decrypted_payment_id8: " << pod_to_hex(decrypted_payment_id8) << "\n"; } } string decrypted_tx_payment_id_str = pod_to_hex(decrypted_payment_id8); // check if decrypted payment id matches what we have stored // in mysql. if (payment_id_str != decrypted_tx_payment_id_str) { // check tx having specific payment id only continue; } // if everything ok with payment id, we proceed with // checking if the amount transfered is correct. // for each output, in a tx, check if it belongs // to the given account of specific address and viewkey // <public_key , amount , out idx> vector<tuple<txout_to_key, uint64_t, uint64_t>> outputs; outputs = get_ouputs_tuple(tx); string tx_hash_str = pod_to_hex(get_transaction_hash(tx)); uint64_t total_received {0}; for (auto& out: outputs) { txout_to_key txout_k = std::get<0>(out); uint64_t amount = std::get<1>(out); uint64_t output_idx_in_tx = std::get<2>(out); // get the tx output public key // that normally would be generated for us, // if someone had sent us some xmr. public_key generated_tx_pubkey; derive_public_key(derivation, output_idx_in_tx, import_payment_address.m_spend_public_key, generated_tx_pubkey); // check if generated public key matches the current output's key bool mine_output = (txout_k.key == generated_tx_pubkey); // if mine output has RingCT, i.e., tx version is 2 // need to decode its amount. otherwise its zero. if (mine_output && tx.version == 2) { // initialize with regular amount uint64_t rct_amount = amount; // cointbase txs have amounts in plain sight. // so use amount from ringct, only for non-coinbase txs if (!is_coinbase(tx)) { bool r; r = decode_ringct(tx.rct_signatures, tx_pub_key, import_payment_viewkey, output_idx_in_tx, tx.rct_signatures.ecdhInfo[output_idx_in_tx].mask, rct_amount); if (!r) { cerr << "Cant decode ringCT!" << endl; throw TxSearchException("Cant decode ringCT!"); } amount = rct_amount; } } // if (mine_output && tx.version == 2) if (mine_output) { total_received += amount; } } cout << " - payment id check in tx: " << tx_hash_str << " found: " << total_received << endl; if (total_received >= desired_amount) { // the payment has been made. tx_hash_with_payment = tx_hash_str; return true; } } return false; }