2017-02-21 17:38:18 +00:00
// Copyright (c) 2014-2017, The Monero Project
2017-01-26 15:07:23 +00:00
//
// 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.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
# include "include_base_utils.h"
using namespace epee ;
# include "cryptonote_tx_utils.h"
# include "cryptonote_config.h"
# include "cryptonote_basic/miner.h"
# include "crypto/crypto.h"
# include "crypto/hash.h"
# include "ringct/rctSigs.h"
namespace cryptonote
{
//---------------------------------------------------------------
bool construct_miner_tx ( size_t height , size_t median_size , uint64_t already_generated_coins , size_t current_block_size , uint64_t fee , const account_public_address & miner_address , transaction & tx , const blobdata & extra_nonce , size_t max_outs , uint8_t hard_fork_version ) {
tx . vin . clear ( ) ;
tx . vout . clear ( ) ;
tx . extra . clear ( ) ;
keypair txkey = keypair : : generate ( ) ;
add_tx_pub_key_to_extra ( tx , txkey . pub ) ;
if ( ! extra_nonce . empty ( ) )
if ( ! add_extra_nonce_to_tx_extra ( tx . extra , extra_nonce ) )
return false ;
txin_gen in ;
in . height = height ;
uint64_t block_reward ;
if ( ! get_block_reward ( median_size , current_block_size , already_generated_coins , block_reward , hard_fork_version ) )
{
LOG_PRINT_L0 ( " Block is too big " ) ;
return false ;
}
# if defined(DEBUG_CREATE_BLOCK_TEMPLATE)
LOG_PRINT_L1 ( " Creating block template: reward " < < block_reward < <
" , fee " < < fee ) ;
# endif
block_reward + = fee ;
// from hard fork 2, we cut out the low significant digits. This makes the tx smaller, and
// keeps the paid amount almost the same. The unpaid remainder gets pushed back to the
// emission schedule
// from hard fork 4, we use a single "dusty" output. This makes the tx even smaller,
// and avoids the quantization. These outputs will be added as rct outputs with identity
// masks, to they can be used as rct inputs.
if ( hard_fork_version > = 2 & & hard_fork_version < 4 ) {
block_reward = block_reward - block_reward % : : config : : BASE_REWARD_CLAMP_THRESHOLD ;
}
std : : vector < uint64_t > out_amounts ;
decompose_amount_into_digits ( block_reward , hard_fork_version > = 2 ? 0 : : : config : : DEFAULT_DUST_THRESHOLD ,
[ & out_amounts ] ( uint64_t a_chunk ) { out_amounts . push_back ( a_chunk ) ; } ,
[ & out_amounts ] ( uint64_t a_dust ) { out_amounts . push_back ( a_dust ) ; } ) ;
CHECK_AND_ASSERT_MES ( 1 < = max_outs , false , " max_out must be non-zero " ) ;
if ( height = = 0 | | hard_fork_version > = 4 )
{
// the genesis block was not decomposed, for unknown reasons
while ( max_outs < out_amounts . size ( ) )
{
//out_amounts[out_amounts.size() - 2] += out_amounts.back();
//out_amounts.resize(out_amounts.size() - 1);
out_amounts [ 1 ] + = out_amounts [ 0 ] ;
for ( size_t n = 1 ; n < out_amounts . size ( ) ; + + n )
out_amounts [ n - 1 ] = out_amounts [ n ] ;
out_amounts . resize ( out_amounts . size ( ) - 1 ) ;
}
}
else
{
CHECK_AND_ASSERT_MES ( max_outs > = out_amounts . size ( ) , false , " max_out exceeded " ) ;
}
uint64_t summary_amounts = 0 ;
for ( size_t no = 0 ; no < out_amounts . size ( ) ; no + + )
{
crypto : : key_derivation derivation = AUTO_VAL_INIT ( derivation ) ; ;
crypto : : public_key out_eph_public_key = AUTO_VAL_INIT ( out_eph_public_key ) ;
bool r = crypto : : generate_key_derivation ( miner_address . m_view_public_key , txkey . sec , derivation ) ;
CHECK_AND_ASSERT_MES ( r , false , " while creating outs: failed to generate_key_derivation( " < < miner_address . m_view_public_key < < " , " < < txkey . sec < < " ) " ) ;
r = crypto : : derive_public_key ( derivation , no , miner_address . m_spend_public_key , out_eph_public_key ) ;
CHECK_AND_ASSERT_MES ( r , false , " while creating outs: failed to derive_public_key( " < < derivation < < " , " < < no < < " , " < < miner_address . m_spend_public_key < < " ) " ) ;
txout_to_key tk ;
tk . key = out_eph_public_key ;
tx_out out ;
summary_amounts + = out . amount = out_amounts [ no ] ;
out . target = tk ;
tx . vout . push_back ( out ) ;
}
CHECK_AND_ASSERT_MES ( summary_amounts = = block_reward , false , " Failed to construct miner tx, summary_amounts = " < < summary_amounts < < " not equal block_reward = " < < block_reward ) ;
if ( hard_fork_version > = 4 )
tx . version = 2 ;
else
tx . version = 1 ;
//lock
tx . unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW ;
tx . vin . push_back ( in ) ;
//LOG_PRINT("MINER_TX generated ok, block_reward=" << print_money(block_reward) << "(" << print_money(block_reward - fee) << "+" << print_money(fee)
// << "), current_block_size=" << current_block_size << ", already_generated_coins=" << already_generated_coins << ", tx_id=" << get_transaction_hash(tx), LOG_LEVEL_2);
return true ;
}
//---------------------------------------------------------------
crypto : : public_key get_destination_view_key_pub ( const std : : vector < tx_destination_entry > & destinations , const account_keys & sender_keys )
{
if ( destinations . empty ( ) )
return null_pkey ;
for ( size_t n = 1 ; n < destinations . size ( ) ; + + n )
{
if ( ! memcmp ( & destinations [ n ] . addr , & sender_keys . m_account_address , sizeof ( destinations [ 0 ] . addr ) ) )
continue ;
if ( destinations [ n ] . amount = = 0 )
continue ;
if ( memcmp ( & destinations [ n ] . addr , & destinations [ 0 ] . addr , sizeof ( destinations [ 0 ] . addr ) ) )
return null_pkey ;
}
return destinations [ 0 ] . addr . m_view_public_key ;
}
//---------------------------------------------------------------
bool construct_tx_and_get_tx_key ( const account_keys & sender_account_keys , const std : : vector < tx_source_entry > & sources , const std : : vector < tx_destination_entry > & destinations , std : : vector < uint8_t > extra , transaction & tx , uint64_t unlock_time , crypto : : secret_key & tx_key , bool rct )
{
std : : vector < rct : : key > amount_keys ;
tx . set_null ( ) ;
amount_keys . clear ( ) ;
tx . version = rct ? 2 : 1 ;
tx . unlock_time = unlock_time ;
tx . extra = extra ;
keypair txkey = keypair : : generate ( ) ;
remove_field_from_tx_extra ( tx . extra , typeid ( tx_extra_pub_key ) ) ;
add_tx_pub_key_to_extra ( tx , txkey . pub ) ;
tx_key = txkey . sec ;
// 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 ) )
{
tx_extra_nonce extra_nonce ;
if ( find_tx_extra_field_by_type ( tx_extra_fields , extra_nonce ) )
{
crypto : : hash8 payment_id = null_hash8 ;
if ( get_encrypted_payment_id_from_tx_extra_nonce ( extra_nonce . nonce , payment_id ) )
{
LOG_PRINT_L2 ( " Encrypting payment id " < < payment_id ) ;
crypto : : public_key view_key_pub = get_destination_view_key_pub ( destinations , sender_account_keys ) ;
if ( view_key_pub = = null_pkey )
{
LOG_ERROR ( " Destinations have to have exactly one output to support encrypted payment ids " ) ;
return false ;
}
if ( ! encrypt_payment_id ( payment_id , view_key_pub , txkey . sec ) )
{
LOG_ERROR ( " Failed to encrypt payment id " ) ;
return false ;
}
std : : string extra_nonce ;
set_encrypted_payment_id_to_tx_extra_nonce ( extra_nonce , payment_id ) ;
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_id ) ;
}
}
}
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 ;
if ( ! generate_key_image_helper ( sender_account_keys , src_entr . real_out_tx_key , src_entr . real_output_in_tx_index , in_ephemeral , img ) )
return false ;
//check that derivated key is equal with real output key
if ( ! ( 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 ) ) ;
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 = 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 ) ;
}
// "Shuffle" outs
std : : vector < tx_destination_entry > shuffled_dsts ( destinations ) ;
std : : sort ( shuffled_dsts . begin ( ) , shuffled_dsts . end ( ) , [ ] ( const tx_destination_entry & de1 , const tx_destination_entry & de2 ) { return de1 . amount < de2 . amount ; } ) ;
uint64_t summary_outs_money = 0 ;
//fill outputs
size_t output_index = 0 ;
for ( const tx_destination_entry & dst_entr : shuffled_dsts )
{
CHECK_AND_ASSERT_MES ( dst_entr . amount > 0 | | tx . version > 1 , false , " Destination with wrong amount: " < < dst_entr . amount ) ;
crypto : : key_derivation derivation ;
crypto : : public_key out_eph_public_key ;
bool r = crypto : : generate_key_derivation ( dst_entr . addr . m_view_public_key , txkey . sec , derivation ) ;
CHECK_AND_ASSERT_MES ( r , false , " at creation outs: failed to generate_key_derivation( " < < dst_entr . addr . m_view_public_key < < " , " < < txkey . sec < < " ) " ) ;
if ( tx . version > 1 )
{
crypto : : secret_key scalar1 ;
crypto : : derivation_to_scalar ( derivation , output_index , scalar1 ) ;
amount_keys . push_back ( rct : : sk2rct ( scalar1 ) ) ;
}
r = crypto : : derive_public_key ( derivation , output_index , dst_entr . addr . m_spend_public_key , out_eph_public_key ) ;
CHECK_AND_ASSERT_MES ( r , false , " at creation outs: failed to derive_public_key( " < < derivation < < " , " < < output_index < < " , " < < dst_entr . addr . m_spend_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 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 ; } ) ;
2017-02-14 19:14:41 +00:00
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 ;
2017-01-26 15:07:23 +00:00
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 ;
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 mixin " ) ;
return false ;
}
}
}
uint64_t amount_in = 0 , amount_out = 0 ;
rct : : ctkeyV inSk ;
// 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 ;
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 ) ;
// inPk: (public key, commitment)
// will be done when filling in mixRing
}
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 , index , outSk ) ;
else
tx . rct_signatures = rct : : genRct ( rct : : hash2rct ( tx_prefix_hash ) , inSk , destinations , outamounts , mixRing , amount_keys , sources [ 0 ] . real_output , outSk ) ; // same index assumption
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 ) ;
}
return true ;
}
//---------------------------------------------------------------
bool construct_tx ( const account_keys & sender_account_keys , const std : : vector < tx_source_entry > & sources , const std : : vector < tx_destination_entry > & destinations , std : : vector < uint8_t > extra , transaction & tx , uint64_t unlock_time )
{
crypto : : secret_key tx_key ;
return construct_tx_and_get_tx_key ( sender_account_keys , sources , destinations , extra , tx , unlock_time , tx_key ) ;
}
//---------------------------------------------------------------
bool generate_genesis_block (
block & bl
, std : : string const & genesis_tx
, uint32_t nonce
)
{
//genesis block
bl = boost : : value_initialized < block > ( ) ;
account_public_address ac = boost : : value_initialized < account_public_address > ( ) ;
std : : vector < size_t > sz ;
construct_miner_tx ( 0 , 0 , 0 , 0 , 0 , ac , bl . miner_tx ) ; // zero fee in genesis
blobdata txb = tx_to_blob ( bl . miner_tx ) ;
std : : string hex_tx_represent = string_tools : : buff_to_hex_nodelimer ( txb ) ;
std : : string genesis_coinbase_tx_hex = config : : GENESIS_TX ;
blobdata tx_bl ;
string_tools : : parse_hexstr_to_binbuff ( genesis_coinbase_tx_hex , tx_bl ) ;
bool r = parse_and_validate_tx_from_blob ( tx_bl , bl . miner_tx ) ;
CHECK_AND_ASSERT_MES ( r , false , " failed to parse coinbase tx from hard coded blob " ) ;
bl . major_version = CURRENT_BLOCK_MAJOR_VERSION ;
bl . minor_version = CURRENT_BLOCK_MINOR_VERSION ;
bl . timestamp = 0 ;
bl . nonce = nonce ;
miner : : find_nonce_for_given_block ( bl , 1 , 0 ) ;
return true ;
}
//---------------------------------------------------------------
}