You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mymonero-core-cpp/src/monero_transfer_utils.cpp

679 lines
24 KiB

//
// monero_transfer_utils.cpp
// Copyright © 2018 MyMonero. All rights reserved.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//
//
//
#include "monero_transfer_utils.hpp"
#include "wallet_errors.h"
#include "string_tools.h"
#include "monero_paymentID_utils.hpp"
//
using namespace std;
using namespace crypto;
using namespace std;
using namespace boost;
using namespace epee;
using namespace cryptonote;
using namespace tools; // for error::
using namespace monero_transfer_utils;
using namespace monero_fork_rules;
//
// Protocol / Defaults
uint32_t monero_transfer_utils::fixed_ringsize()
{
return 7; // best practice is to conform to fixed default ring size
}
uint32_t monero_transfer_utils::fixed_mixinsize()
{
return monero_transfer_utils::fixed_ringsize() - 1;
}
uint32_t monero_transfer_utils::default_priority()
{
return 1; // lowest (TODO: declare centrally)
}
//
// Fee estimation
uint64_t monero_transfer_utils::estimated_tx_network_fee(
uint64_t fee_per_kb,
uint32_t priority,
use_fork_rules_fn_type use_fork_rules_fn
) {
bool bulletproof = use_fork_rules_fn(get_bulletproof_fork(), 0);
uint64_t fee_multiplier = get_fee_multiplier(priority, default_priority(), get_fee_algorithm(use_fork_rules_fn), use_fork_rules_fn);
std::vector<uint8_t> extra; // blank extra
size_t est_tx_size = estimate_rct_tx_size(2, fixed_mixinsize(), 2, extra.size(), bulletproof); // typically ~14kb post-rct, pre-bulletproofs
uint64_t estimated_fee = calculate_fee(fee_per_kb, est_tx_size, fee_multiplier);
//
return estimated_fee;
}
uint64_t monero_transfer_utils::get_upper_transaction_size_limit(
uint64_t upper_transaction_size_limit__or_0_for_default,
use_fork_rules_fn_type use_fork_rules_fn
) {
if (upper_transaction_size_limit__or_0_for_default > 0)
return upper_transaction_size_limit__or_0_for_default;
uint64_t full_reward_zone = use_fork_rules_fn(5, 10) ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : use_fork_rules_fn(2, 10) ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 : CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1;
return full_reward_zone - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
}
uint64_t monero_transfer_utils::get_fee_multiplier(
uint32_t priority,
uint32_t default_priority,
int fee_algorithm,
use_fork_rules_fn_type use_fork_rules_fn
) {
static const uint64_t old_multipliers[3] = {1, 2, 3};
static const uint64_t new_multipliers[3] = {1, 20, 166};
static const uint64_t newer_multipliers[4] = {1, 4, 20, 166};
if (fee_algorithm == -1)
fee_algorithm = get_fee_algorithm(use_fork_rules_fn);
// 0 -> default (here, x1 till fee algorithm 2, x4 from it)
if (priority == 0)
priority = default_priority;
if (priority == 0)
{
if (fee_algorithm >= 2)
priority = 2;
else
priority = 1;
}
// 1 to 3/4 are allowed as priorities
uint32_t max_priority = (fee_algorithm >= 2) ? 4 : 3;
if (priority >= 1 && priority <= max_priority)
{
switch (fee_algorithm)
{
case 0: return old_multipliers[priority-1];
case 1: return new_multipliers[priority-1];
case 2: return newer_multipliers[priority-1];
default: THROW_WALLET_EXCEPTION_IF (true, error::invalid_priority);
}
}
THROW_WALLET_EXCEPTION_IF (false, error::invalid_priority);
return 1;
}
int monero_transfer_utils::get_fee_algorithm(use_fork_rules_fn_type use_fork_rules_fn)
{
// changes at v3 and v5
if (use_fork_rules_fn(5, 0))
return 2;
if (use_fork_rules_fn(3, -720 * 14))
return 1;
return 0;
}
size_t monero_transfer_utils::estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof)
{
size_t size = 0;
// tx prefix
// first few bytes
size += 1 + 6;
// vin
size += n_inputs * (1+6+(mixin+1)*2+32);
// vout
size += n_outputs * (6+32);
// extra
size += extra_size;
// rct signatures
// type
size += 1;
// rangeSigs
if (bulletproof)
size += ((2*6 + 4 + 5)*32 + 3) * n_outputs;
else
size += (2*64*32+32+64*32) * n_outputs;
// MGs
size += n_inputs * (64 * (mixin+1) + 32);
// mixRing - not serialized, can be reconstructed
/* size += 2 * 32 * (mixin+1) * n_inputs; */
// pseudoOuts
size += 32 * n_inputs;
// ecdhInfo
size += 2 * 32 * n_outputs;
// outPk - only commitment is saved
size += 32 * n_outputs;
// txnFee
size += 4;
LOG_PRINT_L2("estimated rct tx size for " << n_inputs << " with ring size " << (mixin+1) << " and " << n_outputs << ": " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)");
return size;
}
size_t monero_transfer_utils::estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof)
{
if (use_rct)
return estimate_rct_tx_size(n_inputs, mixin, n_outputs + 1, extra_size, bulletproof);
else
return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size;
}
//
uint64_t monero_transfer_utils::calculate_fee(uint64_t fee_per_kb, size_t bytes, uint64_t fee_multiplier)
{
uint64_t kB = (bytes + 1023) / 1024;
//
return kB * fee_per_kb * fee_multiplier;
}
uint64_t monero_transfer_utils::calculate_fee(uint64_t fee_per_kb, const cryptonote::blobdata &blob, uint64_t fee_multiplier)
{
return calculate_fee(fee_per_kb, blob.size(), fee_multiplier);
}
//
// Transfer parsing/derived properties
bool monero_transfer_utils::is_transfer_unlocked(
uint64_t unlock_time,
uint64_t block_height,
uint64_t blockchain_size, /* extracting wallet2->m_blockchain.size() / m_local_bc_height */
network_type nettype
) {
if(!is_tx_spendtime_unlocked(unlock_time, block_height, blockchain_size, nettype))
return false;
if(block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > blockchain_size)
return false;
return true;
}
bool monero_transfer_utils::is_tx_spendtime_unlocked(
uint64_t unlock_time,
uint64_t block_height,
uint64_t blockchain_size,
network_type nettype
) {
if(unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER)
{
//interpret as block index
if(blockchain_size-1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time)
return true;
else
return false;
}else
{
//interpret as time
uint64_t current_time = static_cast<uint64_t>(time(NULL));
// XXX: this needs to be fast, so we'd need to get the starting heights
// from the daemon to be correct once voting kicks in
uint64_t v2height = nettype == TESTNET ? 624634 : nettype == STAGENET ? (uint64_t)-1/*TODO*/ : 1009827;
uint64_t leeway = block_height < v2height ? CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1 : CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2;
if(current_time + leeway >= unlock_time)
return true;
else
return false;
}
return false;
}
//
// Constructing transactions
std::string monero_transfer_utils::new_dummy_address_string_for_rct_tx(network_type nettype)
{
cryptonote::account_base account;
account.generate();
//
return account.get_public_address_str(nettype);
}
bool _rct_hex_to_rct_commit(
const std::string &rct_string,
rct::key &rct_commit
) {
// rct string is empty if output is non RCT
if (rct_string.empty()) {
return false;
}
// rct_string is a string with length 64+64+64 (<rct commit> + <encrypted mask> + <rct amount>)
std::string rct_commit_str = rct_string.substr(0,64);
THROW_WALLET_EXCEPTION_IF(!string_tools::validate_hex(64, rct_commit_str), error::wallet_internal_error, "Invalid rct commit hash: " + rct_commit_str);
string_tools::hex_to_pod(rct_commit_str, rct_commit);
return true;
}
bool _rct_hex_to_decrypted_mask(
const std::string &rct_string,
const crypto::secret_key &view_secret_key,
const crypto::public_key& tx_pub_key,
uint64_t internal_output_index,
rct::key &decrypted_mask
) {
// rct string is empty if output is non RCT
if (rct_string.empty()) {
return false;
}
// rct_string is a string with length 64+64+64 (<rct commit> + <encrypted mask> + <rct amount>)
rct::key encrypted_mask;
std::string encrypted_mask_str = rct_string.substr(64,64);
THROW_WALLET_EXCEPTION_IF(!string_tools::validate_hex(64, encrypted_mask_str), error::wallet_internal_error, "Invalid rct mask: " + encrypted_mask_str);
string_tools::hex_to_pod(encrypted_mask_str, encrypted_mask);
//
// Decrypt the mask
crypto::key_derivation derivation;
bool r = generate_key_derivation(tx_pub_key, view_secret_key, derivation);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation");
crypto::secret_key scalar;
crypto::derivation_to_scalar(derivation, internal_output_index, scalar);
sc_sub(decrypted_mask.bytes,encrypted_mask.bytes,rct::hash_to_scalar(rct::sk2rct(scalar)).bytes);
return true;
}
bool _verify_sec_key(const crypto::secret_key &secret_key, const crypto::public_key &public_key)
{ // borrowed from device_default.cpp
crypto::public_key calculated_pub;
bool r = crypto::secret_key_to_public_key(secret_key, calculated_pub);
return r && public_key == calculated_pub;
}
//
void monero_transfer_utils::create_transaction(
TransactionConstruction_RetVals &retVals,
const account_keys& sender_account_keys, // this will reference a particular hw::device
const uint32_t subaddr_account_idx,
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses,
const account_public_address &to_addr,
uint64_t sending_amount,
uint64_t change_amount,
uint64_t fee_amount,
vector<SpendableOutput> &outputs,
vector<RandomAmountOutputs> &mix_outs,
std::vector<uint8_t> &extra,
use_fork_rules_fn_type use_fork_rules_fn,
uint64_t unlock_time, // or 0
bool rct,
cryptonote::network_type nettype
) {
retVals.errCode = noError;
//
// TODO: do we need to sort destinations by amount, here, according to 'decompose_destinations'?
//
uint32_t fake_outputs_count = fixed_mixinsize();
bool bulletproof = use_fork_rules_fn(get_bulletproof_fork(), 0);
//
if (mix_outs.size() != outputs.size() && fake_outputs_count != 0) {
retVals.errCode = wrongNumberOfMixOutsProvided;
return;
}
for (size_t i = 0; i < mix_outs.size(); i++) {
if (mix_outs[i].outputs.size() < fake_outputs_count) {
retVals.errCode = notEnoughOutputsForMixing;
return;
}
}
if (!sender_account_keys.get_device().verify_keys(sender_account_keys.m_spend_secret_key, sender_account_keys.m_account_address.m_spend_public_key)
|| !sender_account_keys.get_device().verify_keys(sender_account_keys.m_view_secret_key, sender_account_keys.m_account_address.m_view_public_key)) {
retVals.errCode = invalidSecretKeys;
return;
}
uint64_t needed_money = sending_amount + change_amount + fee_amount; // TODO: is this correct?
if (needed_money > UINT64_MAX) {
retVals.errCode = outputAmountOverflow;
return;
}
//
uint64_t found_money = 0;
std::vector<tx_source_entry> sources;
// TODO: log: "Selected transfers: " << outputs
for (size_t out_index = 0; out_index < outputs.size(); out_index++) {
found_money += outputs[out_index].amount;
if (found_money > UINT64_MAX) {
retVals.errCode = inputAmountOverflow;
}
auto src = tx_source_entry{};
src.amount = outputs[out_index].amount;
src.rct = outputs[out_index].rct != none;
//
typedef cryptonote::tx_source_entry::output_entry tx_output_entry;
if (mix_outs.size() != 0) {
// Sort fake outputs by global index
std::sort(mix_outs[out_index].outputs.begin(), mix_outs[out_index].outputs.end(), [] (
RandomAmountOutput const& a,
RandomAmountOutput const& b
) {
return a.global_index < b.global_index;
});
for (
size_t j = 0;
src.outputs.size() < fake_outputs_count && j < mix_outs[out_index].outputs.size();
j++
) {
auto mix_out__output = mix_outs[out_index].outputs[j];
if (mix_out__output.global_index == outputs[out_index].global_index) {
LOG_PRINT_L2("got mixin the same as output, skipping");
continue;
}
auto oe = tx_output_entry{};
oe.first = mix_out__output.global_index;
//
crypto::public_key public_key = AUTO_VAL_INIT(public_key);
if(!string_tools::validate_hex(64, mix_out__output.public_key)) {
retVals.errCode = givenAnInvalidPubKey;
return;
}
string_tools::hex_to_pod(mix_out__output.public_key, public_key);
oe.second.dest = rct::pk2rct(public_key);
//
if (mix_out__output.rct != boost::none) {
rct::key commit;
_rct_hex_to_rct_commit(*mix_out__output.rct, commit);
oe.second.mask = commit;
} else {
if (outputs[out_index].rct != boost::none) {
retVals.errCode = mixRCTOutsMissingCommit;
return;
}
oe.second.mask = rct::zeroCommit(src.amount); //create identity-masked commitment for non-rct mix input
}
src.outputs.push_back(oe);
}
}
auto real_oe = tx_output_entry{};
real_oe.first = outputs[out_index].global_index;
//
crypto::public_key public_key = AUTO_VAL_INIT(public_key);
if(!string_tools::validate_hex(64, outputs[out_index].public_key)) {
retVals.errCode = givenAnInvalidPubKey;
return;
}
if (!string_tools::hex_to_pod(outputs[out_index].public_key, public_key)) {
retVals.errCode = givenAnInvalidPubKey;
return;
}
real_oe.second.dest = rct::pk2rct(public_key);
//
if (outputs[out_index].rct != none) {
rct::key commit;
_rct_hex_to_rct_commit(*(outputs[out_index].rct), commit);
real_oe.second.mask = commit; //add commitment for real input
} else {
real_oe.second.mask = rct::zeroCommit(src.amount/*aka outputs[out_index].amount*/); //create identity-masked commitment for non-rct input
}
//
// Add real_oe to outputs
uint64_t real_output_index = src.outputs.size();
for (size_t j = 0; j < src.outputs.size(); j++) {
if (real_oe.first < src.outputs[j].first) {
real_output_index = j;
break;
}
}
src.outputs.insert(src.outputs.begin() + real_output_index, real_oe);
//
crypto::public_key tx_pub_key = AUTO_VAL_INIT(tx_pub_key);
if(!string_tools::validate_hex(64, outputs[out_index].tx_pub_key)) {
retVals.errCode = givenAnInvalidPubKey;
return;
}
string_tools::hex_to_pod(outputs[out_index].tx_pub_key, tx_pub_key);
src.real_out_tx_key = tx_pub_key;
//
add_tx_pub_key_to_extra(extra, tx_pub_key); // TODO: is this necessary? it doesn't seem to affect is_out_to_acc checks..
//
src.real_out_additional_tx_keys = get_additional_tx_pub_keys_from_extra(extra);
//
src.real_output = real_output_index;
uint64_t internal_output_index = outputs[out_index].index;
src.real_output_in_tx_index = internal_output_index;
//
src.rct = outputs[out_index].rct != boost::none && (*(outputs[out_index].rct)).empty() == false;
if (src.rct) {
rct::key decrypted_mask;
_rct_hex_to_decrypted_mask(
*(outputs[out_index].rct),
sender_account_keys.m_view_secret_key,
tx_pub_key,
internal_output_index,
decrypted_mask
);
src.mask = decrypted_mask;
// rct::key calculated_commit = rct::commit(outputs[out_index].amount, decrypted_mask);
// rct::key parsed_commit;
// _rct_hex_to_rct_commit(*(outputs[out_index].rct), parsed_commit);
// if (!(real_oe.second.mask == calculated_commit)) { // real_oe.second.mask==parsed_commit(outputs[out_index].rct)
// retVals.errCode = invalidCommitOrMaskOnOutputRCT;
// return;
// }
} else {
rct::key I;
rct::identity(I);
src.mask = I; // in JS MM code this was left as null for generate_key_image_helper_rct to, I think, fill in with identity I
}
// not doing multisig here yet
src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
sources.push_back(src);
}
//
// TODO: if this is a multisig wallet, create a list of multisig signers we can use
std::vector<cryptonote::tx_destination_entry> splitted_dsts;
tx_destination_entry to_dst = AUTO_VAL_INIT(to_dst);
to_dst.addr = to_addr;
to_dst.amount = sending_amount;
splitted_dsts.push_back(to_dst);
//
cryptonote::tx_destination_entry change_dst = AUTO_VAL_INIT(change_dst);
change_dst.amount = change_amount;
//
if (change_dst.amount == 0) {
if (splitted_dsts.size() == 1) {
// If the change is 0, send it to a random address, to avoid confusing
// the sender with a 0 amount output. We send a 0 amount in order to avoid
// letting the destination be able to work out which of the inputs is the
// real one in our rings
LOG_PRINT_L2("generating dummy address for 0 change");
cryptonote::account_base dummy;
dummy.generate();
change_dst.addr = dummy.get_keys().m_account_address;
LOG_PRINT_L2("generated dummy address for 0 change");
splitted_dsts.push_back(change_dst);
}
} else {
change_dst.addr = sender_account_keys.m_account_address;
splitted_dsts.push_back(change_dst);
}
//
// TODO: log: "sources: " << sources
if (found_money > needed_money) {
if (change_dst.amount != fee_amount) {
retVals.errCode = resultFeeNotEqualToGiven; // aka "early fee calculation != later"
return; // early
}
} else if (found_money < needed_money) {
retVals.errCode = needMoreMoneyThanFound; // TODO: return actual found_money and needed_money in generalized err params in return val
return;
}
cryptonote::transaction tx;
// TODO: need to initialize tx here?
//
auto sources_copy = sources;
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
bool r = cryptonote::construct_tx_and_get_tx_key(
sender_account_keys, subaddresses,
sources, splitted_dsts, change_dst.addr, extra,
tx, unlock_time, tx_key, additional_tx_keys,
true, bulletproof,
/*m_multisig ? &msout : */NULL
);
LOG_PRINT_L2("constructed tx, r="<<r);
if (!r) {
// TODO: return error::tx_not_constructed, sources, dsts, unlock_time, nettype
retVals.errCode = transactionNotConstructed;
return;
}
if (get_object_blobsize(tx) >= get_upper_transaction_size_limit(0, use_fork_rules_fn)) {
// TODO: return error::tx_too_big, tx, upper_transaction_size_limit
retVals.errCode = transactionTooBig;
return;
}
bool use_bulletproofs = !tx.rct_signatures.p.bulletproofs.empty();
THROW_WALLET_EXCEPTION_IF(use_bulletproofs != bulletproof, error::wallet_internal_error, "Expected tx use_bulletproofs to equal bulletproof flag");
//
retVals.tx = tx;
retVals.tx_key = tx_key;
retVals.additional_tx_keys = additional_tx_keys;
}
//
//
void monero_transfer_utils::convenience__create_transaction(
Convenience_TransactionConstruction_RetVals &retVals,
const string &from_address_string,
const string &sec_viewKey_string,
const string &sec_spendKey_string,
const string &to_address_string,
optional<string> payment_id_string,
uint64_t sending_amount,
uint64_t change_amount,
uint64_t fee_amount,
vector<SpendableOutput> &outputs,
vector<RandomAmountOutputs> &mix_outs,
use_fork_rules_fn_type use_fork_rules_fn,
uint64_t unlock_time,
network_type nettype
) {
retVals.errCode = noError;
//
cryptonote::address_parse_info from_addr_info;
THROW_WALLET_EXCEPTION_IF(!cryptonote::get_account_address_from_str(from_addr_info, nettype, from_address_string), error::wallet_internal_error, "Couldn't parse from-address");
cryptonote::account_keys account_keys;
{
account_keys.m_account_address = from_addr_info.address;
//
crypto::secret_key sec_viewKey;
THROW_WALLET_EXCEPTION_IF(!string_tools::hex_to_pod(sec_viewKey_string, sec_viewKey), error::wallet_internal_error, "Couldn't parse view key");
account_keys.m_view_secret_key = sec_viewKey;
//
crypto::secret_key sec_spendKey;
THROW_WALLET_EXCEPTION_IF(!string_tools::hex_to_pod(sec_spendKey_string, sec_spendKey), error::wallet_internal_error, "Couldn't parse spend key");
account_keys.m_spend_secret_key = sec_spendKey;
}
//
cryptonote::address_parse_info to_addr_info;
THROW_WALLET_EXCEPTION_IF(!cryptonote::get_account_address_from_str(to_addr_info, nettype, to_address_string), error::wallet_internal_error, "Couldn't parse to-address");
//
std::vector<uint8_t> extra;
bool payment_id_seen = false;
{ // Detect hash8 or hash32 char hex string as pid and configure 'extra' accordingly
bool r = false;
if (payment_id_string != none) {
crypto::hash payment_id;
r = monero_paymentID_utils::parse_long_payment_id(*payment_id_string, payment_id);
if (r) {
std::string extra_nonce;
cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id);
r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce);
} else {
crypto::hash8 payment_id8;
r = monero_paymentID_utils::parse_short_payment_id(*payment_id_string, payment_id8);
if (r) {
std::string extra_nonce;
cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id8);
r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce);
}
}
payment_id_seen = true;
} else {
if (to_addr_info.is_subaddress && payment_id_seen) {
retVals.errCode = cantUsePIDWithSubAddress; // Never use a subaddress with a payment ID
return;
}
if (to_addr_info.has_payment_id) {
if (payment_id_seen) {
retVals.errCode = nonZeroPIDWithIntAddress; // can't use int addr at same time as supplying manual pid
return;
}
if (to_addr_info.is_subaddress) {
THROW_WALLET_EXCEPTION_IF(false, error::wallet_internal_error, "Unexpected is_subaddress && has_payment_id"); // should never happen
return;
}
std::string extra_nonce;
cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, to_addr_info.payment_id);
bool r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce);
if (!r) {
retVals.errCode = couldntSetPIDToTXExtra;
return;
}
payment_id_seen = true;
}
}
}
//
uint32_t subaddr_account_idx = 0;
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
subaddresses[account_keys.m_account_address.m_spend_public_key] = {0,0};
//
TransactionConstruction_RetVals actualCall_retVals;
create_transaction(
actualCall_retVals,
account_keys,
subaddr_account_idx,
subaddresses,
to_addr_info.address,
sending_amount,
change_amount,
fee_amount,
outputs,
mix_outs,
extra,
use_fork_rules_fn,
unlock_time,
true, // rct
nettype
);
if (actualCall_retVals.errCode != noError) {
retVals.errCode = actualCall_retVals.errCode; // pass-through
return; // already set the error
}
auto txBlob = t_serializable_object_to_blob(*actualCall_retVals.tx);
size_t txBlob_byteLength = txBlob.size();
// cout << "txBlob: " << txBlob << endl;
cout << "txBlob_byteLength: " << txBlob_byteLength << endl;
THROW_WALLET_EXCEPTION_IF(txBlob_byteLength <= 0, error::wallet_internal_error, "Expected tx blob byte length > 0");
//
// tx hash
retVals.tx_hash_string = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(*actualCall_retVals.tx));
// signed serialized tx
retVals.signed_serialized_tx_string = epee::string_tools::buff_to_hex_nodelimer(cryptonote::tx_to_blob(*actualCall_retVals.tx));
// (concatenated) tx key
ostringstream oss;
{
oss << epee::string_tools::pod_to_hex(*actualCall_retVals.tx_key);
for (size_t i = 0; i < (*actualCall_retVals.additional_tx_keys).size(); ++i) {
oss << epee::string_tools::pod_to_hex((*actualCall_retVals.additional_tx_keys)[i]);
}
}
retVals.tx_key_string = oss.str();
}