@ -1,21 +1,21 @@
// Copyright (c) 2014-2018, The Monero Project
// Copyright (c) 2014-2018, The Monero Project
//
//
// All rights reserved.
// All rights reserved.
//
//
// Redistribution and use in source and binary forms, with or without modification, are
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
// permitted provided that the following conditions are met:
//
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
// conditions and the following disclaimer.
//
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// 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
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
// materials provided with the distribution.
//
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// 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
// used to endorse or promote products derived from this software without specific
// prior written permission.
// prior written permission.
//
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// 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
// 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
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
@ -25,7 +25,7 @@
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// 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
// 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.
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
# include <unordered_set>
# include <unordered_set>
@ -195,445 +195,6 @@ namespace cryptonote
return addr . m_view_public_key ;
return addr . m_view_public_key ;
}
}
//---------------------------------------------------------------
//---------------------------------------------------------------
bool construct_tx_with_tx_key ( const account_keys & sender_account_keys , const std : : unordered_map < crypto : : public_key , subaddress_index > & subaddresses , std : : vector < tx_source_entry > & sources , std : : vector < tx_destination_entry > & destinations , const boost : : optional < cryptonote : : account_public_address > & change_addr , std : : vector < uint8_t > extra , transaction & tx , uint64_t unlock_time , const crypto : : secret_key & tx_key , const std : : vector < crypto : : secret_key > & additional_tx_keys , bool rct , const rct : : RCTConfig & rct_config , rct : : multisig_out * msout , bool shuffle_outs )
{
hw : : device & hwdev = sender_account_keys . get_device ( ) ;
if ( sources . empty ( ) )
{
LOG_ERROR ( " Empty sources " ) ;
return false ;
}
std : : vector < rct : : key > amount_keys ;
tx . set_null ( ) ;
amount_keys . clear ( ) ;
if ( msout )
{
msout - > c . clear ( ) ;
}
tx . version = rct ? 2 : 1 ;
tx . unlock_time = unlock_time ;
tx . extra = extra ;
crypto : : public_key txkey_pub ;
// if we have a stealth payment id, find it and encrypt it with the tx key now
std : : vector < tx_extra_field > tx_extra_fields ;
if ( parse_tx_extra ( tx . extra , tx_extra_fields ) )
{
bool add_dummy_payment_id = true ;
tx_extra_nonce extra_nonce ;
if ( find_tx_extra_field_by_type ( tx_extra_fields , extra_nonce ) )
{
crypto : : hash payment_id = null_hash ;
crypto : : hash8 payment_id8 = null_hash8 ;
if ( get_encrypted_payment_id_from_tx_extra_nonce ( extra_nonce . nonce , payment_id8 ) )
{
LOG_PRINT_L2 ( " Encrypting payment id " < < payment_id8 ) ;
crypto : : public_key view_key_pub = get_destination_view_key_pub ( destinations , change_addr ) ;
if ( view_key_pub = = null_pkey )
{
LOG_ERROR ( " Destinations have to have exactly one output to support encrypted payment ids " ) ;
return false ;
}
if ( ! hwdev . encrypt_payment_id ( payment_id8 , view_key_pub , tx_key ) )
{
LOG_ERROR ( " Failed to encrypt payment id " ) ;
return false ;
}
std : : string extra_nonce ;
set_encrypted_payment_id_to_tx_extra_nonce ( extra_nonce , payment_id8 ) ;
remove_field_from_tx_extra ( tx . extra , typeid ( tx_extra_nonce ) ) ;
if ( ! add_extra_nonce_to_tx_extra ( tx . extra , extra_nonce ) )
{
LOG_ERROR ( " Failed to add encrypted payment id to tx extra " ) ;
return false ;
}
LOG_PRINT_L1 ( " Encrypted payment ID: " < < payment_id8 ) ;
add_dummy_payment_id = false ;
}
else if ( get_payment_id_from_tx_extra_nonce ( extra_nonce . nonce , payment_id ) )
{
add_dummy_payment_id = false ;
}
}
// we don't add one if we've got more than the usual 1 destination plus change
if ( destinations . size ( ) > 2 )
add_dummy_payment_id = false ;
if ( add_dummy_payment_id )
{
// if we have neither long nor short payment id, add a dummy short one,
// this should end up being the vast majority of txes as time goes on
std : : string extra_nonce ;
crypto : : hash8 payment_id8 = null_hash8 ;
crypto : : public_key view_key_pub = get_destination_view_key_pub ( destinations , change_addr ) ;
if ( view_key_pub = = null_pkey )
{
LOG_ERROR ( " Failed to get key to encrypt dummy payment id with " ) ;
}
else
{
hwdev . encrypt_payment_id ( payment_id8 , view_key_pub , tx_key ) ;
set_encrypted_payment_id_to_tx_extra_nonce ( extra_nonce , payment_id8 ) ;
if ( ! add_extra_nonce_to_tx_extra ( tx . extra , extra_nonce ) )
{
LOG_ERROR ( " Failed to add dummy encrypted payment id to tx extra " ) ;
// continue anyway
}
}
}
}
else
{
LOG_ERROR ( " Failed to parse tx extra " ) ;
return false ;
}
struct input_generation_context_data
{
keypair in_ephemeral ;
} ;
std : : vector < input_generation_context_data > in_contexts ;
uint64_t summary_inputs_money = 0 ;
//fill inputs
int idx = - 1 ;
for ( const tx_source_entry & src_entr : sources )
{
+ + idx ;
if ( src_entr . real_output > = src_entr . outputs . size ( ) )
{
LOG_ERROR ( " real_output index ( " < < src_entr . real_output < < " )bigger than output_keys.size()= " < < src_entr . outputs . size ( ) ) ;
return false ;
}
summary_inputs_money + = src_entr . amount ;
//key_derivation recv_derivation;
in_contexts . push_back ( input_generation_context_data ( ) ) ;
keypair & in_ephemeral = in_contexts . back ( ) . in_ephemeral ;
crypto : : key_image img ;
const auto & out_key = reinterpret_cast < const crypto : : public_key & > ( src_entr . outputs [ src_entr . real_output ] . second . dest ) ;
if ( ! generate_key_image_helper ( sender_account_keys , subaddresses , out_key , src_entr . real_out_tx_key , src_entr . real_out_additional_tx_keys , src_entr . real_output_in_tx_index , in_ephemeral , img , hwdev ) )
{
LOG_ERROR ( " Key image generation failed! " ) ;
return false ;
}
//check that derivated key is equal with real output key (if non multisig)
if ( ! msout & & ! ( in_ephemeral . pub = = src_entr . outputs [ src_entr . real_output ] . second . dest ) )
{
LOG_ERROR ( " derived public key mismatch with output public key at index " < < idx < < " , real out " < < src_entr . real_output < < " ! " < < ENDL < < " derived_key: "
< < string_tools : : pod_to_hex ( in_ephemeral . pub ) < < ENDL < < " real output_public_key: "
< < string_tools : : pod_to_hex ( src_entr . outputs [ src_entr . real_output ] . second . dest ) ) ;
LOG_ERROR ( " amount " < < src_entr . amount < < " , rct " < < src_entr . rct ) ;
LOG_ERROR ( " tx pubkey " < < src_entr . real_out_tx_key < < " , real_output_in_tx_index " < < src_entr . real_output_in_tx_index ) ;
return false ;
}
//put key image into tx input
txin_to_key input_to_key ;
input_to_key . amount = src_entr . amount ;
input_to_key . k_image = msout ? rct : : rct2ki ( src_entr . multisig_kLRki . ki ) : img ;
//fill outputs array and use relative offsets
for ( const tx_source_entry : : output_entry & out_entry : src_entr . outputs )
input_to_key . key_offsets . push_back ( out_entry . first ) ;
input_to_key . key_offsets = absolute_output_offsets_to_relative ( input_to_key . key_offsets ) ;
tx . vin . push_back ( input_to_key ) ;
}
if ( shuffle_outs )
{
std : : shuffle ( destinations . begin ( ) , destinations . end ( ) , std : : default_random_engine ( crypto : : rand < unsigned int > ( ) ) ) ;
}
// sort ins by their key image
std : : vector < size_t > ins_order ( sources . size ( ) ) ;
for ( size_t n = 0 ; n < sources . size ( ) ; + + n )
ins_order [ n ] = n ;
std : : sort ( ins_order . begin ( ) , ins_order . end ( ) , [ & ] ( const size_t i0 , const size_t i1 ) {
const txin_to_key & tk0 = boost : : get < txin_to_key > ( tx . vin [ i0 ] ) ;
const txin_to_key & tk1 = boost : : get < txin_to_key > ( tx . vin [ i1 ] ) ;
return memcmp ( & tk0 . k_image , & tk1 . k_image , sizeof ( tk0 . k_image ) ) > 0 ;
} ) ;
tools : : apply_permutation ( ins_order , [ & ] ( size_t i0 , size_t i1 ) {
std : : swap ( tx . vin [ i0 ] , tx . vin [ i1 ] ) ;
std : : swap ( in_contexts [ i0 ] , in_contexts [ i1 ] ) ;
std : : swap ( sources [ i0 ] , sources [ i1 ] ) ;
} ) ;
// figure out if we need to make additional tx pubkeys
size_t num_stdaddresses = 0 ;
size_t num_subaddresses = 0 ;
account_public_address single_dest_subaddress ;
classify_addresses ( destinations , change_addr , num_stdaddresses , num_subaddresses , single_dest_subaddress ) ;
// if this is a single-destination transfer to a subaddress, we set the tx pubkey to R=s*D
if ( num_stdaddresses = = 0 & & num_subaddresses = = 1 )
{
txkey_pub = rct : : rct2pk ( hwdev . scalarmultKey ( rct : : pk2rct ( single_dest_subaddress . m_spend_public_key ) , rct : : sk2rct ( tx_key ) ) ) ;
}
else
{
txkey_pub = rct : : rct2pk ( hwdev . scalarmultBase ( rct : : sk2rct ( tx_key ) ) ) ;
}
remove_field_from_tx_extra ( tx . extra , typeid ( tx_extra_pub_key ) ) ;
add_tx_pub_key_to_extra ( tx , txkey_pub ) ;
std : : vector < crypto : : public_key > additional_tx_public_keys ;
// we don't need to include additional tx keys if:
// - all the destinations are standard addresses
// - there's only one destination which is a subaddress
bool need_additional_txkeys = num_subaddresses > 0 & & ( num_stdaddresses > 0 | | num_subaddresses > 1 ) ;
if ( need_additional_txkeys )
CHECK_AND_ASSERT_MES ( destinations . size ( ) = = additional_tx_keys . size ( ) , false , " Wrong amount of additional tx keys " ) ;
uint64_t summary_outs_money = 0 ;
//fill outputs
size_t output_index = 0 ;
for ( const tx_destination_entry & dst_entr : destinations )
{
CHECK_AND_ASSERT_MES ( dst_entr . amount > 0 | | tx . version > 1 , false , " Destination with wrong amount: " < < dst_entr . amount ) ;
crypto : : public_key out_eph_public_key ;
hwdev . generate_output_ephemeral_keys ( tx . version , sender_account_keys , txkey_pub , tx_key ,
dst_entr , change_addr , output_index ,
need_additional_txkeys , additional_tx_keys ,
additional_tx_public_keys , amount_keys , out_eph_public_key ) ;
tx_out out ;
out . amount = dst_entr . amount ;
txout_to_key tk ;
tk . key = out_eph_public_key ;
out . target = tk ;
tx . vout . push_back ( out ) ;
output_index + + ;
summary_outs_money + = dst_entr . amount ;
}
CHECK_AND_ASSERT_MES ( additional_tx_public_keys . size ( ) = = additional_tx_keys . size ( ) , false , " Internal error creating additional public keys " ) ;
remove_field_from_tx_extra ( tx . extra , typeid ( tx_extra_additional_pub_keys ) ) ;
LOG_PRINT_L2 ( " tx pubkey: " < < txkey_pub ) ;
if ( need_additional_txkeys )
{
LOG_PRINT_L2 ( " additional tx pubkeys: " ) ;
for ( size_t i = 0 ; i < additional_tx_public_keys . size ( ) ; + + i )
LOG_PRINT_L2 ( additional_tx_public_keys [ i ] ) ;
add_additional_tx_pub_keys_to_extra ( tx . extra , additional_tx_public_keys ) ;
}
//check money
if ( summary_outs_money > summary_inputs_money )
{
LOG_ERROR ( " Transaction inputs money ( " < < summary_inputs_money < < " ) less than outputs money ( " < < summary_outs_money < < " ) " ) ;
return false ;
}
// check for watch only wallet
bool zero_secret_key = true ;
for ( size_t i = 0 ; i < sizeof ( sender_account_keys . m_spend_secret_key ) ; + + i )
zero_secret_key & = ( sender_account_keys . m_spend_secret_key . data [ i ] = = 0 ) ;
if ( zero_secret_key )
{
MDEBUG ( " Null secret key, skipping signatures " ) ;
}
if ( tx . version = = 1 )
{
//generate ring signatures
crypto : : hash tx_prefix_hash ;
get_transaction_prefix_hash ( tx , tx_prefix_hash ) ;
std : : stringstream ss_ring_s ;
size_t i = 0 ;
for ( const tx_source_entry & src_entr : sources )
{
ss_ring_s < < " pub_keys: " < < ENDL ;
std : : vector < const crypto : : public_key * > keys_ptrs ;
std : : vector < crypto : : public_key > keys ( src_entr . outputs . size ( ) ) ;
size_t ii = 0 ;
for ( const tx_source_entry : : output_entry & o : src_entr . outputs )
{
keys [ ii ] = rct2pk ( o . second . dest ) ;
keys_ptrs . push_back ( & keys [ ii ] ) ;
ss_ring_s < < o . second . dest < < ENDL ;
+ + ii ;
}
tx . signatures . push_back ( std : : vector < crypto : : signature > ( ) ) ;
std : : vector < crypto : : signature > & sigs = tx . signatures . back ( ) ;
sigs . resize ( src_entr . outputs . size ( ) ) ;
if ( ! zero_secret_key )
crypto : : generate_ring_signature ( tx_prefix_hash , boost : : get < txin_to_key > ( tx . vin [ i ] ) . k_image , keys_ptrs , in_contexts [ i ] . in_ephemeral . sec , src_entr . real_output , sigs . data ( ) ) ;
ss_ring_s < < " signatures: " < < ENDL ;
std : : for_each ( sigs . begin ( ) , sigs . end ( ) , [ & ] ( const crypto : : signature & s ) { ss_ring_s < < s < < ENDL ; } ) ;
ss_ring_s < < " prefix_hash: " < < tx_prefix_hash < < ENDL < < " in_ephemeral_key: " < < in_contexts [ i ] . in_ephemeral . sec < < ENDL < < " real_output: " < < src_entr . real_output < < ENDL ;
i + + ;
}
MCINFO ( " construct_tx " , " transaction_created: " < < get_transaction_hash ( tx ) < < ENDL < < obj_to_json_str ( tx ) < < ENDL < < ss_ring_s . str ( ) ) ;
}
else
{
size_t n_total_outs = sources [ 0 ] . outputs . size ( ) ; // only for non-simple rct
// the non-simple version is slightly smaller, but assumes all real inputs
// are on the same index, so can only be used if there just one ring.
bool use_simple_rct = sources . size ( ) > 1 | | rct_config . range_proof_type ! = rct : : RangeProofBorromean ;
if ( ! use_simple_rct )
{
// non simple ringct requires all real inputs to be at the same index for all inputs
for ( const tx_source_entry & src_entr : sources )
{
if ( src_entr . real_output ! = sources . begin ( ) - > real_output )
{
LOG_ERROR ( " All inputs must have the same index for non-simple ringct " ) ;
return false ;
}
}
// enforce same mixin for all outputs
for ( size_t i = 1 ; i < sources . size ( ) ; + + i ) {
if ( n_total_outs ! = sources [ i ] . outputs . size ( ) ) {
LOG_ERROR ( " Non-simple ringct transaction has varying ring size " ) ;
return false ;
}
}
}
uint64_t amount_in = 0 , amount_out = 0 ;
rct : : ctkeyV inSk ;
inSk . reserve ( sources . size ( ) ) ;
// mixRing indexing is done the other way round for simple
rct : : ctkeyM mixRing ( use_simple_rct ? sources . size ( ) : n_total_outs ) ;
rct : : keyV destinations ;
std : : vector < uint64_t > inamounts , outamounts ;
std : : vector < unsigned int > index ;
std : : vector < rct : : multisig_kLRki > kLRki ;
for ( size_t i = 0 ; i < sources . size ( ) ; + + i )
{
rct : : ctkey ctkey ;
amount_in + = sources [ i ] . amount ;
inamounts . push_back ( sources [ i ] . amount ) ;
index . push_back ( sources [ i ] . real_output ) ;
// inSk: (secret key, mask)
ctkey . dest = rct : : sk2rct ( in_contexts [ i ] . in_ephemeral . sec ) ;
ctkey . mask = sources [ i ] . mask ;
inSk . push_back ( ctkey ) ;
memwipe ( & ctkey , sizeof ( rct : : ctkey ) ) ;
// inPk: (public key, commitment)
// will be done when filling in mixRing
if ( msout )
{
kLRki . push_back ( sources [ i ] . multisig_kLRki ) ;
}
}
for ( size_t i = 0 ; i < tx . vout . size ( ) ; + + i )
{
destinations . push_back ( rct : : pk2rct ( boost : : get < txout_to_key > ( tx . vout [ i ] . target ) . key ) ) ;
outamounts . push_back ( tx . vout [ i ] . amount ) ;
amount_out + = tx . vout [ i ] . amount ;
}
if ( use_simple_rct )
{
// mixRing indexing is done the other way round for simple
for ( size_t i = 0 ; i < sources . size ( ) ; + + i )
{
mixRing [ i ] . resize ( sources [ i ] . outputs . size ( ) ) ;
for ( size_t n = 0 ; n < sources [ i ] . outputs . size ( ) ; + + n )
{
mixRing [ i ] [ n ] = sources [ i ] . outputs [ n ] . second ;
}
}
}
else
{
for ( size_t i = 0 ; i < n_total_outs ; + + i ) // same index assumption
{
mixRing [ i ] . resize ( sources . size ( ) ) ;
for ( size_t n = 0 ; n < sources . size ( ) ; + + n )
{
mixRing [ i ] [ n ] = sources [ n ] . outputs [ i ] . second ;
}
}
}
// fee
if ( ! use_simple_rct & & amount_in > amount_out )
outamounts . push_back ( amount_in - amount_out ) ;
// zero out all amounts to mask rct outputs, real amounts are now encrypted
for ( size_t i = 0 ; i < tx . vin . size ( ) ; + + i )
{
if ( sources [ i ] . rct )
boost : : get < txin_to_key > ( tx . vin [ i ] ) . amount = 0 ;
}
for ( size_t i = 0 ; i < tx . vout . size ( ) ; + + i )
tx . vout [ i ] . amount = 0 ;
crypto : : hash tx_prefix_hash ;
get_transaction_prefix_hash ( tx , tx_prefix_hash ) ;
rct : : ctkeyV outSk ;
if ( use_simple_rct )
tx . rct_signatures = rct : : genRctSimple ( rct : : hash2rct ( tx_prefix_hash ) , inSk , destinations , inamounts , outamounts , amount_in - amount_out , mixRing , amount_keys , msout ? & kLRki : NULL , msout , index , outSk , rct_config , hwdev ) ;
else
tx . rct_signatures = rct : : genRct ( rct : : hash2rct ( tx_prefix_hash ) , inSk , destinations , outamounts , mixRing , amount_keys , msout ? & kLRki [ 0 ] : NULL , msout , sources [ 0 ] . real_output , outSk , rct_config , hwdev ) ; // same index assumption
memwipe ( inSk . data ( ) , inSk . size ( ) * sizeof ( rct : : ctkey ) ) ;
CHECK_AND_ASSERT_MES ( tx . vout . size ( ) = = outSk . size ( ) , false , " outSk size does not match vout " ) ;
MCINFO ( " construct_tx " , " transaction_created: " < < get_transaction_hash ( tx ) < < ENDL < < obj_to_json_str ( tx ) < < ENDL ) ;
}
tx . invalidate_hashes ( ) ;
return true ;
}
//---------------------------------------------------------------
bool construct_tx_and_get_tx_key ( const account_keys & sender_account_keys , const std : : unordered_map < crypto : : public_key , subaddress_index > & subaddresses , std : : vector < tx_source_entry > & sources , std : : vector < tx_destination_entry > & destinations , const boost : : optional < cryptonote : : account_public_address > & change_addr , std : : vector < uint8_t > extra , transaction & tx , uint64_t unlock_time , crypto : : secret_key & tx_key , std : : vector < crypto : : secret_key > & additional_tx_keys , bool rct , const rct : : RCTConfig & rct_config , rct : : multisig_out * msout )
{
hw : : device & hwdev = sender_account_keys . get_device ( ) ;
hwdev . open_tx ( tx_key ) ;
// figure out if we need to make additional tx pubkeys
size_t num_stdaddresses = 0 ;
size_t num_subaddresses = 0 ;
account_public_address single_dest_subaddress ;
classify_addresses ( destinations , change_addr , num_stdaddresses , num_subaddresses , single_dest_subaddress ) ;
bool need_additional_txkeys = num_subaddresses > 0 & & ( num_stdaddresses > 0 | | num_subaddresses > 1 ) ;
if ( need_additional_txkeys )
{
additional_tx_keys . clear ( ) ;
for ( const auto & d : destinations )
additional_tx_keys . push_back ( keypair : : generate ( sender_account_keys . get_device ( ) ) . sec ) ;
}
bool r = construct_tx_with_tx_key ( sender_account_keys , subaddresses , sources , destinations , change_addr , extra , tx , unlock_time , tx_key , additional_tx_keys , rct , rct_config , msout ) ;
hwdev . close_tx ( ) ;
return r ;
}
//---------------------------------------------------------------
bool construct_tx ( const account_keys & sender_account_keys , std : : vector < tx_source_entry > & sources , const std : : vector < tx_destination_entry > & destinations , const boost : : optional < cryptonote : : account_public_address > & change_addr , std : : vector < uint8_t > extra , transaction & tx , uint64_t unlock_time )
{
std : : unordered_map < crypto : : public_key , cryptonote : : subaddress_index > subaddresses ;
subaddresses [ sender_account_keys . m_account_address . m_spend_public_key ] = { 0 , 0 } ;
crypto : : secret_key tx_key ;
std : : vector < crypto : : secret_key > additional_tx_keys ;
std : : vector < tx_destination_entry > destinations_copy = destinations ;
return construct_tx_and_get_tx_key ( sender_account_keys , subaddresses , sources , destinations_copy , change_addr , extra , tx , unlock_time , tx_key , additional_tx_keys , false , { rct : : RangeProofBorromean , 0 } , NULL ) ;
}
//---------------------------------------------------------------
bool generate_genesis_block (
bool generate_genesis_block (
block & bl
block & bl
, std : : string const & genesis_tx
, std : : string const & genesis_tx