implemented monero_transfer_utils::create_transaction

pull/29/head
Paul Shapiro 6 years ago
parent b72ebdcb04
commit e599578273

@ -33,14 +33,19 @@
//
#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 epee;
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 tools; // for error::
using namespace monero_transfer_utils;
using namespace monero_fork_rules;
//
// Protocol / Defaults
uint32_t monero_transfer_utils::fixed_ringsize()
@ -246,3 +251,297 @@ std::string monero_transfer_utils::new_dummy_address_string_for_rct_tx(network_t
//
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 vector<tx_destination_entry> &dsts,
vector<SpendableOutput> &outputs,
vector<RandomAmountOutputs> &mix_outs,
uint64_t fee_amount,
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; // (does this need to be initialized?)
//
// TODO: 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 (dsts.size() == 0) {
retVals.errCode = noDestinations;
return;
}
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 = 0;
for (size_t i = 0; i < dsts.size(); i++) {
needed_money += dsts[i].amount;
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 = dsts;
cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts);
change_dts.amount = found_money - needed_money;
//
/* This is commented because it's presently supplied by whoever is calling this function.... But there's a good argument for bringing it in, here, especially after MyMonero clients integrate with this code and soon, share an implementation of SendFunds() (the analog of create_transactions_2 + transfer_selected*)
if (change_dts.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_dts.addr = dummy.get_keys().m_account_address;
LOG_PRINT_L2("generated dummy address for 0 change");
splitted_dsts.push_back(change_dts);
}
} else {
change_dts.addr = sender_account_keys.m_account_address;
splitted_dsts.push_back(change_dts);
}
*/
//
// TODO: log: "sources: " << sources
if (found_money > needed_money) {
if (change_dts.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_dts.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;
}

@ -32,11 +32,16 @@
#ifndef monero_transfer_utils_hpp
#define monero_transfer_utils_hpp
//
#include <boost/optional.hpp>
//
#include "string_tools.h"
//
#include "crypto.h"
#include "cryptonote_basic.h"
#include "cryptonote_format_utils.h"
#include "cryptonote_tx_utils.h"
#include "ringct/rctSigs.h"
//
#include "monero_fork_rules.hpp"
//
using namespace tools;
@ -47,32 +52,104 @@ using namespace tools;
//
namespace monero_transfer_utils
{
using namespace std;
using namespace boost;
using namespace cryptonote;
using namespace monero_fork_rules;
using namespace crypto;
//
uint64_t get_upper_transaction_size_limit(uint64_t upper_transaction_size_limit__or_0_for_default, monero_fork_rules::use_fork_rules_fn_type use_fork_rules_fn);
uint64_t get_fee_multiplier(uint32_t priority, uint32_t default_priority, int fee_algorithm, monero_fork_rules::use_fork_rules_fn_type use_fork_rules_fn);
int get_fee_algorithm(monero_fork_rules::use_fork_rules_fn_type use_fork_rules_fn);
uint64_t get_upper_transaction_size_limit(uint64_t upper_transaction_size_limit__or_0_for_default, use_fork_rules_fn_type use_fork_rules_fn);
uint64_t get_fee_multiplier(uint32_t priority, uint32_t default_priority, int fee_algorithm, use_fork_rules_fn_type use_fork_rules_fn);
int get_fee_algorithm(use_fork_rules_fn_type use_fork_rules_fn);
//
uint64_t calculate_fee(uint64_t fee_per_kb, size_t bytes, uint64_t fee_multiplier);
uint64_t calculate_fee(uint64_t fee_per_kb, const cryptonote::blobdata &blob, uint64_t fee_multiplier);
uint64_t calculate_fee(uint64_t fee_per_kb, const blobdata &blob, uint64_t fee_multiplier);
//
size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof);
size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof);
uint64_t estimated_tx_network_fee( // convenience function for size + calc
uint64_t fee_per_kb,
uint32_t priority, // when priority=0, falls back to monero_transfer_utils::default_priority()
cryptonote::network_type nettype,
monero_fork_rules::use_fork_rules_fn_type use_fork_rules_fn // this is extracted to a function so that implementations can optionally query the daemon (although this presently implies that such a call remains blocking)
network_type nettype,
use_fork_rules_fn_type use_fork_rules_fn // this is extracted to a function so that implementations can optionally query the daemon (although this presently implies that such a call remains blocking)
);
//
bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height, uint64_t blockchain_size, cryptonote::network_type nettype = cryptonote::MAINNET);
bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_height, uint64_t blockchain_size, cryptonote::network_type nettype = cryptonote::MAINNET);
bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height, uint64_t blockchain_size, network_type nettype = MAINNET);
bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_height, uint64_t blockchain_size, network_type nettype = MAINNET);
//
uint32_t fixed_ringsize(); // not mixinsize, which would be ringsize-1
uint32_t fixed_mixinsize(); // not ringsize, which would be mixinsize+1
uint32_t default_priority();
//
std::string new_dummy_address_string_for_rct_tx(cryptonote::network_type nettype = cryptonote::MAINNET);
string new_dummy_address_string_for_rct_tx(network_type nettype = MAINNET);
//
uint32_t default_priority();
// Types - Arguments
struct SpendableOutput
{
uint64_t amount;
string public_key;
optional<string> rct;
uint64_t global_index;
uint64_t index;
string tx_pub_key;
};
struct RandomAmountOutput
{
uint64_t global_index; // this is, I believe, presently supplied as a string by the API, probably to avoid overflow
string public_key;
optional<string> rct;
};
struct RandomAmountOutputs
{
uint64_t amount;
vector<RandomAmountOutput> outputs;
};
//
// Types - Return value
enum CreateTransactionErrorCode // TODO: switch to enum class to fix namespacing
{ // These codes have values for serialization
noError = 0,
//
noDestinations = 1,
wrongNumberOfMixOutsProvided = 2,
notEnoughOutputsForMixing = 3,
invalidSecretKeys = 4,
outputAmountOverflow = 5,
inputAmountOverflow = 6,
mixRCTOutsMissingCommit = 7,
resultFeeNotEqualToGiven = 8,
needMoreMoneyThanFound = 9,
invalidDestinationAddress = 10,
nonZeroPIDWithIntAddress = 11,
cantUsePIDWithSubAddress = 12,
couldntSetPIDToTXExtra = 13,
givenAnInvalidPubKey = 14,
invalidCommitOrMaskOnOutputRCT = 15,
transactionNotConstructed = 16,
transactionTooBig = 17
};
struct TransactionConstruction_RetVals
{
CreateTransactionErrorCode errCode;
//
cryptonote::transaction tx;
};
// TODO: add priority
void create_transaction(
TransactionConstruction_RetVals &retVals,
const account_keys& sender_account_keys, // this will reference a particular hw::device
const uint32_t subaddr_account_idx, // pass 0 for no subaddrs
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses,
const vector<tx_destination_entry> &dsts, // presently, this must include change as well as, if necessary, dummy output
vector<SpendableOutput> &outputs,
vector<RandomAmountOutputs> &mix_outs,
uint64_t fee_amount,
std::vector<uint8_t> &extra, // this is not declared const b/c it may have the output tx pub key appended to it
use_fork_rules_fn_type use_fork_rules_fn,
uint64_t unlock_time = 0, // or 0
bool rct = true,
network_type nettype = MAINNET
);
}
#endif /* monero_transfer_utils_hpp */

@ -42,7 +42,9 @@
#include <sstream>
using namespace std;
#include "string_tools.h"
// using namespace boost;
using namespace epee;
using namespace boost;
#include "cryptonote_format_utils.h"
//
// Shared code
//
@ -106,3 +108,213 @@ BOOST_AUTO_TEST_CASE(transfers__fee)
std::cout << "est_fee with fee_per_kb " << fee_per_kb << ": " << est_fee << std::endl;
BOOST_REQUIRE(est_fee > 0);
}
BOOST_AUTO_TEST_CASE(transfers__create)
{
using namespace monero_transfer_utils;
using namespace monero_fork_rules;
TransactionConstruction_RetVals retVals;
monero_fork_rules::use_fork_rules_fn_type use_fork_rules_fn = [] (uint8_t version, int64_t early_blocks) -> bool
{
return monero_fork_rules::lightwallet_hardcoded__use_fork_rules(version, early_blocks);
};
cryptonote::network_type nettype = cryptonote::MAINNET;
string from_addressString = "43zxvpcj5Xv9SEkNXbMCG7LPQStHMpFCQCmkmR4u5nzjWwq5Xkv5VmGgYEsHXg4ja2FGRD5wMWbBVMijDTqmmVqm93wHGkg";
string sec_viewKey_string = "......";
string sec_spendKey_string = "......";
cryptonote::address_parse_info from_addr_info;
BOOST_REQUIRE(cryptonote::get_account_address_from_str(from_addr_info, nettype, from_addressString));
cryptonote::account_keys account_keys;
{
account_keys.m_account_address = from_addr_info.address;
//
crypto::secret_key sec_viewKey;
BOOST_REQUIRE(string_tools::hex_to_pod(sec_viewKey_string, sec_viewKey));
account_keys.m_view_secret_key = sec_viewKey;
//
crypto::secret_key sec_spendKey;
BOOST_REQUIRE(string_tools::hex_to_pod(sec_spendKey_string, sec_spendKey));
account_keys.m_spend_secret_key = sec_spendKey;
}
cout << "spend sec key: " << string_tools::pod_to_hex(account_keys.m_spend_secret_key) << endl;
cout << "spend pub key: " << string_tools::pod_to_hex(from_addr_info.address.m_spend_public_key) << endl;
//
optional<string> payment_id_string = string("b79f8efc81f58f67");
bool pid_encrypt = true;
uint64_t amount = 10000000000;
uint64_t fee_amount = 2167750000;
string to_address_string("43zxvpcj5Xv9SEkNXbMCG7LPQStHMpFCQCmkmR4u5nzjWwq5Xkv5VmGgYEsHXg4ja2FGRD5wMWbBVMijDTqmmVqm93wHGkg");
cryptonote::address_parse_info to_addr_info;
BOOST_REQUIRE(cryptonote::get_account_address_from_str(to_addr_info, nettype, to_address_string));
//
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;
}
}
//
std::vector<cryptonote::tx_destination_entry> dsts; // without change this would normally require a dummy addr with 0 amount pushed as a fake 'change' output ... that should probably be moved into the function
{ // 0. actual destination address
cryptonote::tx_destination_entry de;
de.addr = to_addr_info.address;
de.amount = amount;
de.is_subaddress = to_addr_info.is_subaddress;
if (to_addr_info.is_subaddress && payment_id_seen) {
BOOST_REQUIRE_MESSAGE(false, "Illegal: Never supply a pid with a subaddress."); // TODO: is this true?
return;
}
if (to_addr_info.has_payment_id) {
if (payment_id_seen) {
BOOST_REQUIRE(false); // can't use int addr at same time as supplying manual pid
return;
}
if (to_addr_info.is_subaddress) {
BOOST_REQUIRE(false); // should never happen .. logic err?
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) {
BOOST_REQUIRE(false);
return;
}
payment_id_seen = true;
}
dsts.push_back(de);
}
{ // 1. change
cryptonote::tx_destination_entry de;
de.addr = from_addr_info.address;
de.amount = 112832250000;
de.is_subaddress = from_addr_info.is_subaddress; // not
dsts.push_back(de);
}
//
vector<SpendableOutput> outputs;
{ // required info from output
auto out = SpendableOutput{};
out.amount = 125000000000;
out.public_key = "596fa47b6b3905269503435099a05e3ede54564026c93cbe5285e2df074c7118";
out.rct = "920ee8d99299f304d17fdb104720d1f62be0b03383c7bb466ff39c6a264d80d616ce1eccd6c4de1cc0fba87e463f2e0c373146c475e8a1517f36e7a37351d50034688cc8cb528c14188cae45d89b313d444e583c9d68a32cb80938a5e2aa200b";
out.global_index = 6451664;
out.index = 0;
out.tx_pub_key = "0a86e588dc67ca11993737e003a9e60c57174a663a47495e3b1d764f486fc88f";
outputs.push_back(out);
}
//
vector<RandomAmountOutputs> mix_outs;
{
auto amountAndOuts = RandomAmountOutputs{};
amountAndOuts.amount = 0;
{
auto amountOutput = RandomAmountOutput{};
amountOutput.global_index = 5260585; // this is, I believe, presently supplied as a string by the API, probably to avoid overflow
amountOutput.public_key = "da77082624fce921891c4fb80a1e7076a6714ca8c9fc547311737926a0b85a46";
amountOutput.rct = "bb227b27e36b7f3e695dffb641c29bb60bfd991accdb5ef4b580c9acd48c16b6";
amountAndOuts.outputs.push_back(amountOutput);
}
{
auto amountOutput = RandomAmountOutput{};
amountOutput.global_index = 1929918;
amountOutput.public_key = "8c983e7053d7a1dc9de8ac00468bcf11836a787d712dc0c02bd54a3ee00a55e8";
amountOutput.rct = "8dec45867644d1a76aafe4487292d7cf401302e6bbbb99a61c2f3b6cef4f4f34";
amountAndOuts.outputs.push_back(amountOutput);
}
{
auto amountOutput = RandomAmountOutput{};
amountOutput.global_index = 3921094;
amountOutput.public_key = "0133219bd5e247eef51003921ec792784c41fc34289c703e9326d46f78d9b10a";
amountOutput.rct = "75082f4ce31904acba4af37699c28d8d4f0f74fdf63b1e4a8069ebed50df3220";
amountAndOuts.outputs.push_back(amountOutput);
}
{
auto amountOutput = RandomAmountOutput{};
amountOutput.global_index = 6627106;
amountOutput.public_key = "daef1663dd1084bd7fe585c3d493480ee1c4cefb93254eac5855afdf38f662b1";
amountOutput.rct = "1d96763c5bc3300090c286705b7d544f02c185d9be8c32baac6bbfb8e0d0d283";
amountAndOuts.outputs.push_back(amountOutput);
}
{
auto amountOutput = RandomAmountOutput{};
amountOutput.global_index = 3308654;
amountOutput.public_key = "ae135f58762b1133667002538f8c353a1869db815aa686e2544b5243c2d2212f";
amountOutput.rct = "15046b93bb181189f2917eed38173202fbbb9cdbfcf3d1bc3e432df999ae1b1c";
amountAndOuts.outputs.push_back(amountOutput);
}
{
auto amountOutput = RandomAmountOutput{};
amountOutput.global_index = 1972531;
amountOutput.public_key = "39e44fa88d684d71762c40eb64ac80ddc694b74a99ac445667bf433536c09c8f";
amountOutput.rct = "66a42d0e8123768b392ad4a230759258d9156fab1aea00a19b041832326aca0a";
amountAndOuts.outputs.push_back(amountOutput);
}
{
auto amountOutput = RandomAmountOutput{};
amountOutput.global_index = 3274424;
amountOutput.public_key = "a89b91648645ba6f32e214ba5720f5387376e5a144e698d5d5d1ebac971de349";
amountOutput.rct = "815a6b1da6fc6a3bd791c4342782381cf948ee822ac9da7149f1b3717e0266d2";
amountAndOuts.outputs.push_back(amountOutput);
}
mix_outs.push_back(amountAndOuts);
}
//
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};
cout << "account_keys.m_account_address.m_spend_public_key: " << string_tools::pod_to_hex(account_keys.m_account_address.m_spend_public_key) << endl;
//
monero_transfer_utils::create_transaction(
retVals,
account_keys,
subaddr_account_idx,
subaddresses,
dsts,
outputs,
mix_outs,
fee_amount,
extra,
use_fork_rules_fn,
0, // unlock_time
true, // rct
nettype
);
if (retVals.errCode != noError) {
BOOST_REQUIRE_MESSAGE(false, "create_transaction failed");
return;
}
auto txBlob = t_serializable_object_to_blob(retVals.tx);
size_t txBlob_byteLength = txBlob.size();
// cout << "txBlob: " << txBlob << endl;
cout << "txBlob_byteLength: " << txBlob_byteLength << endl;
BOOST_REQUIRE(txBlob_byteLength > 0);
// tx hash
auto tx_hash_string = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(retVals.tx));
auto signed_serialized_tx_string = epee::string_tools::buff_to_hex_nodelimer(cryptonote::tx_to_blob(retVals.tx));
cout << "tx_hash_string: " << tx_hash_string << endl;
cout << "signed_serialized_tx_string: " << signed_serialized_tx_string << endl;
}

Loading…
Cancel
Save