@ -7947,6 +7947,251 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account
return false ;
}
std : : string wallet2 : : get_reserve_proof ( const boost : : optional < std : : pair < uint32_t , uint64_t > > & account_minreserve , const std : : string & message )
{
THROW_WALLET_EXCEPTION_IF ( m_watch_only | | m_multisig , error : : wallet_internal_error , " Reserve proof can only be generated by a full wallet " ) ;
THROW_WALLET_EXCEPTION_IF ( balance_all ( ) = = 0 , error : : wallet_internal_error , " Zero balance " ) ;
THROW_WALLET_EXCEPTION_IF ( account_minreserve & & balance ( account_minreserve - > first ) < account_minreserve - > second , error : : wallet_internal_error ,
" Not enough balance in this account for the requested minimum reserve amount " ) ;
// determine which outputs to include in the proof
std : : vector < size_t > selected_transfers ;
for ( size_t i = 0 ; i < m_transfers . size ( ) ; + + i )
{
const transfer_details & td = m_transfers [ i ] ;
if ( ! td . m_spent & & ( ! account_minreserve | | account_minreserve - > first = = td . m_subaddr_index . major ) )
selected_transfers . push_back ( i ) ;
}
if ( account_minreserve )
{
// minimize the number of outputs included in the proof, by only picking the N largest outputs that can cover the requested min reserve amount
std : : sort ( selected_transfers . begin ( ) , selected_transfers . end ( ) , [ & ] ( const size_t a , const size_t b )
{ return m_transfers [ a ] . amount ( ) > m_transfers [ b ] . amount ( ) ; } ) ;
while ( selected_transfers . size ( ) > = 2 & & m_transfers [ selected_transfers [ 1 ] ] . amount ( ) > = account_minreserve - > second )
selected_transfers . erase ( selected_transfers . begin ( ) ) ;
size_t sz = 0 ;
uint64_t total = 0 ;
while ( total < account_minreserve - > second )
{
total + = m_transfers [ selected_transfers [ sz ] ] . amount ( ) ;
+ + sz ;
}
selected_transfers . resize ( sz ) ;
}
// compute signature prefix hash
std : : string prefix_data = message ;
prefix_data . append ( ( const char * ) & m_account . get_keys ( ) . m_account_address , sizeof ( cryptonote : : account_public_address ) ) ;
for ( size_t i = 0 ; i < selected_transfers . size ( ) ; + + i )
{
prefix_data . append ( ( const char * ) & m_transfers [ selected_transfers [ i ] ] . m_key_image , sizeof ( crypto : : key_image ) ) ;
}
crypto : : hash prefix_hash ;
crypto : : cn_fast_hash ( prefix_data . data ( ) , prefix_data . size ( ) , prefix_hash ) ;
// generate proof entries
std : : vector < reserve_proof_entry > proofs ( selected_transfers . size ( ) ) ;
std : : unordered_set < cryptonote : : subaddress_index > subaddr_indices = { { 0 , 0 } } ;
for ( size_t i = 0 ; i < selected_transfers . size ( ) ; + + i )
{
const transfer_details & td = m_transfers [ selected_transfers [ i ] ] ;
reserve_proof_entry & proof = proofs [ i ] ;
proof . txid = td . m_txid ;
proof . index_in_tx = td . m_internal_output_index ;
proof . key_image = td . m_key_image ;
subaddr_indices . insert ( td . m_subaddr_index ) ;
// get tx pub key
const crypto : : public_key tx_pub_key = get_tx_pub_key_from_extra ( td . m_tx , td . m_pk_index ) ;
THROW_WALLET_EXCEPTION_IF ( tx_pub_key = = crypto : : null_pkey , error : : wallet_internal_error , " The tx public key isn't found " ) ;
const std : : vector < crypto : : public_key > additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra ( td . m_tx ) ;
// determine which tx pub key was used for deriving the output key
const crypto : : public_key * tx_pub_key_used = & tx_pub_key ;
for ( int i = 0 ; i < 2 ; + + i )
{
proof . shared_secret = rct : : rct2pk ( rct : : scalarmultKey ( rct : : pk2rct ( * tx_pub_key_used ) , rct : : sk2rct ( m_account . get_keys ( ) . m_view_secret_key ) ) ) ;
crypto : : key_derivation derivation ;
THROW_WALLET_EXCEPTION_IF ( ! crypto : : generate_key_derivation ( proof . shared_secret , rct : : rct2sk ( rct : : I ) , derivation ) ,
error : : wallet_internal_error , " Failed to generate key derivation " ) ;
crypto : : public_key subaddress_spendkey ;
THROW_WALLET_EXCEPTION_IF ( ! derive_subaddress_public_key ( td . get_public_key ( ) , derivation , proof . index_in_tx , subaddress_spendkey ) ,
error : : wallet_internal_error , " Failed to derive subaddress public key " ) ;
if ( m_subaddresses . count ( subaddress_spendkey ) = = 1 )
break ;
THROW_WALLET_EXCEPTION_IF ( additional_tx_pub_keys . empty ( ) , error : : wallet_internal_error ,
" Normal tx pub key doesn't derive the expected output, while the additional tx pub keys are empty " ) ;
THROW_WALLET_EXCEPTION_IF ( i = = 1 , error : : wallet_internal_error ,
" Neither normal tx pub key nor additional tx pub key derive the expected output key " ) ;
tx_pub_key_used = & additional_tx_pub_keys [ proof . index_in_tx ] ;
}
// generate signature for shared secret
crypto : : generate_tx_proof ( prefix_hash , m_account . get_keys ( ) . m_account_address . m_view_public_key , * tx_pub_key_used , boost : : none , proof . shared_secret , m_account . get_keys ( ) . m_view_secret_key , proof . shared_secret_sig ) ;
// derive ephemeral secret key
crypto : : key_image ki ;
cryptonote : : keypair ephemeral ;
const bool r = cryptonote : : generate_key_image_helper ( m_account . get_keys ( ) , m_subaddresses , td . get_public_key ( ) , tx_pub_key , additional_tx_pub_keys , td . m_internal_output_index , ephemeral , ki ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : wallet_internal_error , " Failed to generate key image " ) ;
THROW_WALLET_EXCEPTION_IF ( ephemeral . pub ! = td . get_public_key ( ) , error : : wallet_internal_error , " Derived public key doesn't agree with the stored one " ) ;
// generate signature for key image
const std : : vector < const crypto : : public_key * > pubs = { & ephemeral . pub } ;
crypto : : generate_ring_signature ( prefix_hash , td . m_key_image , & pubs [ 0 ] , 1 , ephemeral . sec , 0 , & proof . key_image_sig ) ;
}
// collect all subaddress spend keys that received those outputs and generate their signatures
std : : unordered_map < crypto : : public_key , crypto : : signature > subaddr_spendkeys ;
for ( const cryptonote : : subaddress_index & index : subaddr_indices )
{
crypto : : secret_key subaddr_spend_skey = m_account . get_keys ( ) . m_spend_secret_key ;
if ( ! index . is_zero ( ) )
{
crypto : : secret_key m = cryptonote : : get_subaddress_secret_key ( m_account . get_keys ( ) . m_view_secret_key , index ) ;
crypto : : secret_key tmp = subaddr_spend_skey ;
sc_add ( ( unsigned char * ) & subaddr_spend_skey , ( unsigned char * ) & m , ( unsigned char * ) & tmp ) ;
}
crypto : : public_key subaddr_spend_pkey ;
secret_key_to_public_key ( subaddr_spend_skey , subaddr_spend_pkey ) ;
crypto : : generate_signature ( prefix_hash , subaddr_spend_pkey , subaddr_spend_skey , subaddr_spendkeys [ subaddr_spend_pkey ] ) ;
}
// serialize & encode
std : : ostringstream oss ;
boost : : archive : : portable_binary_oarchive ar ( oss ) ;
ar < < proofs < < subaddr_spendkeys ;
return " ReserveProofV1 " + tools : : base58 : : encode ( oss . str ( ) ) ;
}
bool wallet2 : : check_reserve_proof ( const cryptonote : : account_public_address & address , const std : : string & message , const std : : string & sig_str , uint64_t & total , uint64_t & spent )
{
uint32_t rpc_version ;
THROW_WALLET_EXCEPTION_IF ( ! check_connection ( & rpc_version ) , error : : wallet_internal_error , " Failed to connect to daemon: " + get_daemon_address ( ) ) ;
THROW_WALLET_EXCEPTION_IF ( rpc_version < MAKE_CORE_RPC_VERSION ( 1 , 0 ) , error : : wallet_internal_error , " Daemon RPC version is too old " ) ;
static constexpr char header [ ] = " ReserveProofV1 " ;
THROW_WALLET_EXCEPTION_IF ( ! boost : : string_ref { sig_str } . starts_with ( header ) , error : : wallet_internal_error ,
" Signature header check error " ) ;
std : : string sig_decoded ;
THROW_WALLET_EXCEPTION_IF ( ! tools : : base58 : : decode ( sig_str . substr ( std : : strlen ( header ) ) , sig_decoded ) , error : : wallet_internal_error ,
" Signature decoding error " ) ;
std : : istringstream iss ( sig_decoded ) ;
boost : : archive : : portable_binary_iarchive ar ( iss ) ;
std : : vector < reserve_proof_entry > proofs ;
std : : unordered_map < crypto : : public_key , crypto : : signature > subaddr_spendkeys ;
ar > > proofs > > subaddr_spendkeys ;
THROW_WALLET_EXCEPTION_IF ( subaddr_spendkeys . count ( address . m_spend_public_key ) = = 0 , error : : wallet_internal_error ,
" The given address isn't found in the proof " ) ;
// compute signature prefix hash
std : : string prefix_data = message ;
prefix_data . append ( ( const char * ) & address , sizeof ( cryptonote : : account_public_address ) ) ;
for ( size_t i = 0 ; i < proofs . size ( ) ; + + i )
{
prefix_data . append ( ( const char * ) & proofs [ i ] . key_image , sizeof ( crypto : : key_image ) ) ;
}
crypto : : hash prefix_hash ;
crypto : : cn_fast_hash ( prefix_data . data ( ) , prefix_data . size ( ) , prefix_hash ) ;
// fetch txes from daemon
COMMAND_RPC_GET_TRANSACTIONS : : request gettx_req ;
COMMAND_RPC_GET_TRANSACTIONS : : response gettx_res ;
for ( size_t i = 0 ; i < proofs . size ( ) ; + + i )
gettx_req . txs_hashes . push_back ( epee : : string_tools : : pod_to_hex ( proofs [ i ] . txid ) ) ;
m_daemon_rpc_mutex . lock ( ) ;
bool ok = net_utils : : invoke_http_json ( " /gettransactions " , gettx_req , gettx_res , m_http_client ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! ok | | gettx_res . txs . size ( ) ! = proofs . size ( ) ,
error : : wallet_internal_error , " Failed to get transaction from daemon " ) ;
// check spent status
COMMAND_RPC_IS_KEY_IMAGE_SPENT : : request kispent_req ;
COMMAND_RPC_IS_KEY_IMAGE_SPENT : : response kispent_res ;
for ( size_t i = 0 ; i < proofs . size ( ) ; + + i )
kispent_req . key_images . push_back ( epee : : string_tools : : pod_to_hex ( proofs [ i ] . key_image ) ) ;
m_daemon_rpc_mutex . lock ( ) ;
ok = epee : : net_utils : : invoke_http_json ( " /is_key_image_spent " , kispent_req , kispent_res , m_http_client , rpc_timeout ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! ok | | kispent_res . spent_status . size ( ) ! = proofs . size ( ) ,
error : : wallet_internal_error , " Failed to get key image spent status from daemon " ) ;
total = spent = 0 ;
for ( size_t i = 0 ; i < proofs . size ( ) ; + + i )
{
const reserve_proof_entry & proof = proofs [ i ] ;
THROW_WALLET_EXCEPTION_IF ( gettx_res . txs [ i ] . in_pool , error : : wallet_internal_error , " Tx is unconfirmed " ) ;
cryptonote : : blobdata tx_data ;
ok = string_tools : : parse_hexstr_to_binbuff ( gettx_res . txs [ i ] . as_hex , tx_data ) ;
THROW_WALLET_EXCEPTION_IF ( ! ok , error : : wallet_internal_error , " Failed to parse transaction from daemon " ) ;
crypto : : hash tx_hash , tx_prefix_hash ;
cryptonote : : transaction tx ;
THROW_WALLET_EXCEPTION_IF ( ! cryptonote : : parse_and_validate_tx_from_blob ( tx_data , tx , tx_hash , tx_prefix_hash ) , error : : wallet_internal_error ,
" Failed to validate transaction from daemon " ) ;
THROW_WALLET_EXCEPTION_IF ( tx_hash ! = proof . txid , error : : wallet_internal_error , " Failed to get the right transaction from daemon " ) ;
THROW_WALLET_EXCEPTION_IF ( proof . index_in_tx > = tx . vout . size ( ) , error : : wallet_internal_error , " index_in_tx is out of bound " ) ;
const cryptonote : : txout_to_key * const out_key = boost : : get < cryptonote : : txout_to_key > ( std : : addressof ( tx . vout [ proof . index_in_tx ] . target ) ) ;
THROW_WALLET_EXCEPTION_IF ( ! out_key , error : : wallet_internal_error , " Output key wasn't found " )
// get tx pub key
const crypto : : public_key tx_pub_key = get_tx_pub_key_from_extra ( tx ) ;
THROW_WALLET_EXCEPTION_IF ( tx_pub_key = = crypto : : null_pkey , error : : wallet_internal_error , " The tx public key isn't found " ) ;
const std : : vector < crypto : : public_key > additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra ( tx ) ;
// check singature for shared secret
ok = crypto : : check_tx_proof ( prefix_hash , address . m_view_public_key , tx_pub_key , boost : : none , proof . shared_secret , proof . shared_secret_sig ) ;
if ( ! ok & & additional_tx_pub_keys . size ( ) = = tx . vout . size ( ) )
ok = crypto : : check_tx_proof ( prefix_hash , address . m_view_public_key , additional_tx_pub_keys [ proof . index_in_tx ] , boost : : none , proof . shared_secret , proof . shared_secret_sig ) ;
if ( ! ok )
return false ;
// check signature for key image
const std : : vector < const crypto : : public_key * > pubs = { & out_key - > key } ;
ok = crypto : : check_ring_signature ( prefix_hash , proof . key_image , & pubs [ 0 ] , 1 , & proof . key_image_sig ) ;
if ( ! ok )
return false ;
// check if the address really received the fund
crypto : : key_derivation derivation ;
THROW_WALLET_EXCEPTION_IF ( ! crypto : : generate_key_derivation ( proof . shared_secret , rct : : rct2sk ( rct : : I ) , derivation ) , error : : wallet_internal_error , " Failed to generate key derivation " ) ;
crypto : : public_key subaddr_spendkey ;
crypto : : derive_subaddress_public_key ( out_key - > key , derivation , proof . index_in_tx , subaddr_spendkey ) ;
THROW_WALLET_EXCEPTION_IF ( subaddr_spendkeys . count ( subaddr_spendkey ) = = 0 , error : : wallet_internal_error ,
" The address doesn't seem to have received the fund " ) ;
// check amount
uint64_t amount = tx . vout [ proof . index_in_tx ] . amount ;
if ( amount = = 0 )
{
// decode rct
crypto : : secret_key shared_secret ;
crypto : : derivation_to_scalar ( derivation , proof . index_in_tx , shared_secret ) ;
rct : : ecdhTuple ecdh_info = tx . rct_signatures . ecdhInfo [ proof . index_in_tx ] ;
rct : : ecdhDecode ( ecdh_info , rct : : sk2rct ( shared_secret ) ) ;
amount = rct : : h2d ( ecdh_info . amount ) ;
}
total + = amount ;
if ( kispent_res . spent_status [ i ] )
spent + = amount ;
}
// check signatures for all subaddress spend keys
for ( const auto & i : subaddr_spendkeys )
{
if ( ! crypto : : check_signature ( prefix_hash , i . first , i . second ) )
return false ;
}
return true ;
}
std : : string wallet2 : : get_wallet_file ( ) const
{
return m_wallet_file ;