@ -4625,6 +4625,447 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr
return count ;
}
bool wallet2 : : light_wallet_login ( bool & new_address )
{
MDEBUG ( " Light wallet login request " ) ;
m_light_wallet_connected = false ;
cryptonote : : COMMAND_RPC_LOGIN : : request request ;
cryptonote : : COMMAND_RPC_LOGIN : : response response ;
request . address = get_account ( ) . get_public_address_str ( m_testnet ) ;
request . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
// Always create account if it doesnt exist.
request . create_account = true ;
m_daemon_rpc_mutex . lock ( ) ;
bool connected = epee : : net_utils : : invoke_http_json ( " /login " , request , response , m_http_client , rpc_timeout , " POST " ) ;
m_daemon_rpc_mutex . unlock ( ) ;
// MyMonero doesn't send any status message. OpenMonero does.
m_light_wallet_connected = connected & & ( response . status . empty ( ) | | response . status = = " success " ) ;
new_address = response . new_address ;
MDEBUG ( " Status: " < < response . status ) ;
MDEBUG ( " Reason: " < < response . reason ) ;
MDEBUG ( " New wallet: " < < response . new_address ) ;
if ( m_light_wallet_connected )
{
// Clear old data on successfull login.
// m_transfers.clear();
// m_payments.clear();
// m_unconfirmed_payments.clear();
}
return m_light_wallet_connected ;
}
bool wallet2 : : light_wallet_import_wallet_request ( cryptonote : : COMMAND_RPC_IMPORT_WALLET_REQUEST : : response & response )
{
MDEBUG ( " Light wallet import wallet request " ) ;
cryptonote : : COMMAND_RPC_IMPORT_WALLET_REQUEST : : request oreq ;
oreq . address = get_account ( ) . get_public_address_str ( m_testnet ) ;
oreq . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = epee : : net_utils : : invoke_http_json ( " /import_wallet_request " , oreq , response , m_http_client , rpc_timeout , " POST " ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " import_wallet_request " ) ;
return true ;
}
void wallet2 : : light_wallet_get_unspent_outs ( )
{
MDEBUG ( " Getting unspent outs " ) ;
cryptonote : : COMMAND_RPC_GET_UNSPENT_OUTS : : request oreq ;
cryptonote : : COMMAND_RPC_GET_UNSPENT_OUTS : : response ores ;
oreq . amount = " 0 " ;
oreq . address = get_account ( ) . get_public_address_str ( m_testnet ) ;
oreq . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
// openMonero specific
oreq . dust_threshold = boost : : lexical_cast < std : : string > ( : : config : : DEFAULT_DUST_THRESHOLD ) ;
// below are required by openMonero api - but are not used.
oreq . mixin = 0 ;
oreq . use_dust = true ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = epee : : net_utils : : invoke_http_json ( " /get_unspent_outs " , oreq , ores , m_http_client , rpc_timeout , " POST " ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " get_unspent_outs " ) ;
THROW_WALLET_EXCEPTION_IF ( ores . status = = " error " , error : : wallet_internal_error , ores . reason ) ;
m_light_wallet_per_kb_fee = ores . per_kb_fee ;
std : : unordered_map < crypto : : hash , bool > transfers_txs ;
for ( const auto & t : m_transfers )
transfers_txs . emplace ( t . m_txid , t . m_spent ) ;
MDEBUG ( " FOUND " < < ores . outputs . size ( ) < < " outputs " ) ;
// return if no outputs found
if ( ores . outputs . empty ( ) )
return ;
// Clear old outputs
m_transfers . clear ( ) ;
for ( const auto & o : ores . outputs ) {
bool spent = false ;
bool add_transfer = true ;
crypto : : key_image unspent_key_image ;
crypto : : public_key tx_public_key = AUTO_VAL_INIT ( tx_public_key ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , o . tx_pub_key ) , error : : wallet_internal_error , " Invalid tx_pub_key field " ) ;
string_tools : : hex_to_pod ( o . tx_pub_key , tx_public_key ) ;
for ( const std : : string & ski : o . spend_key_images ) {
spent = false ;
// Check if key image is ours
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , ski ) , error : : wallet_internal_error , " Invalid key image " ) ;
string_tools : : hex_to_pod ( ski , unspent_key_image ) ;
if ( light_wallet_key_image_is_ours ( unspent_key_image , tx_public_key , o . index ) ) {
MTRACE ( " Output " < < o . public_key < < " is spent. Key image: " < < ski ) ;
spent = true ;
break ;
} {
MTRACE ( " Unspent output found. " < < o . public_key ) ;
}
}
// Check if tx already exists in m_transfers.
crypto : : hash txid ;
crypto : : public_key tx_pub_key ;
crypto : : public_key public_key ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , o . tx_hash ) , error : : wallet_internal_error , " Invalid tx_hash field " ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , o . public_key ) , error : : wallet_internal_error , " Invalid public_key field " ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , o . tx_pub_key ) , error : : wallet_internal_error , " Invalid tx_pub_key field " ) ;
string_tools : : hex_to_pod ( o . tx_hash , txid ) ;
string_tools : : hex_to_pod ( o . public_key , public_key ) ;
string_tools : : hex_to_pod ( o . tx_pub_key , tx_pub_key ) ;
for ( auto & t : m_transfers ) {
if ( t . get_public_key ( ) = = public_key ) {
t . m_spent = spent ;
add_transfer = false ;
break ;
}
}
if ( ! add_transfer )
continue ;
m_transfers . push_back ( boost : : value_initialized < transfer_details > ( ) ) ;
transfer_details & td = m_transfers . back ( ) ;
td . m_block_height = o . height ;
td . m_global_output_index = o . global_index ;
td . m_txid = txid ;
// Add to extra
add_tx_pub_key_to_extra ( td . m_tx , tx_pub_key ) ;
td . m_key_image = unspent_key_image ;
td . m_key_image_known = ! m_watch_only ;
td . m_amount = o . amount ;
td . m_pk_index = 0 ;
td . m_internal_output_index = o . index ;
td . m_spent = spent ;
tx_out txout ;
txout . target = txout_to_key ( public_key ) ;
txout . amount = td . m_amount ;
td . m_tx . vout . resize ( td . m_internal_output_index + 1 ) ;
td . m_tx . vout [ td . m_internal_output_index ] = txout ;
// Add unlock time and coinbase bool got from get_address_txs api call
std : : unordered_map < crypto : : hash , address_tx > : : const_iterator found = m_light_wallet_address_txs . find ( txid ) ;
THROW_WALLET_EXCEPTION_IF ( found = = m_light_wallet_address_txs . end ( ) , error : : wallet_internal_error , " Lightwallet: tx not found in m_light_wallet_address_txs " ) ;
bool miner_tx = found - > second . m_coinbase ;
td . m_tx . unlock_time = found - > second . m_unlock_time ;
if ( ! o . rct . empty ( ) )
{
// Coinbase tx's
if ( miner_tx )
{
td . m_mask = rct : : identity ( ) ;
}
else
{
// rct txs
// decrypt rct mask, calculate commit hash and compare against blockchain commit hash
rct : : key rct_commit ;
light_wallet_parse_rct_str ( o . rct , tx_pub_key , td . m_internal_output_index , td . m_mask , rct_commit , true ) ;
bool valid_commit = ( rct_commit = = rct : : commit ( td . amount ( ) , td . m_mask ) ) ;
if ( ! valid_commit )
{
MDEBUG ( " output index: " < < o . global_index ) ;
MDEBUG ( " mask: " + string_tools : : pod_to_hex ( td . m_mask ) ) ;
MDEBUG ( " calculated commit: " + string_tools : : pod_to_hex ( rct : : commit ( td . amount ( ) , td . m_mask ) ) ) ;
MDEBUG ( " expected commit: " + string_tools : : pod_to_hex ( rct_commit ) ) ;
MDEBUG ( " amount: " < < td . amount ( ) ) ;
}
THROW_WALLET_EXCEPTION_IF ( ! valid_commit , error : : wallet_internal_error , " Lightwallet: rct commit hash mismatch! " ) ;
}
td . m_rct = true ;
}
else
{
td . m_mask = rct : : identity ( ) ;
td . m_rct = false ;
}
if ( ! spent )
set_unspent ( m_transfers . size ( ) - 1 ) ;
m_key_images [ td . m_key_image ] = m_transfers . size ( ) - 1 ;
m_pub_keys [ td . get_public_key ( ) ] = m_transfers . size ( ) - 1 ;
}
}
bool wallet2 : : light_wallet_get_address_info ( cryptonote : : COMMAND_RPC_GET_ADDRESS_INFO : : response & response )
{
MTRACE ( __FUNCTION__ ) ;
cryptonote : : COMMAND_RPC_GET_ADDRESS_INFO : : request request ;
request . address = get_account ( ) . get_public_address_str ( m_testnet ) ;
request . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = epee : : net_utils : : invoke_http_json ( " /get_address_info " , request , response , m_http_client , rpc_timeout , " POST " ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " get_address_info " ) ;
// TODO: Validate result
return true ;
}
void wallet2 : : light_wallet_get_address_txs ( )
{
MDEBUG ( " Refreshing light wallet " ) ;
cryptonote : : COMMAND_RPC_GET_ADDRESS_TXS : : request ireq ;
cryptonote : : COMMAND_RPC_GET_ADDRESS_TXS : : response ires ;
ireq . address = get_account ( ) . get_public_address_str ( m_testnet ) ;
ireq . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = epee : : net_utils : : invoke_http_json ( " /get_address_txs " , ireq , ires , m_http_client , rpc_timeout , " POST " ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " get_address_txs " ) ;
//OpenMonero sends status=success, Mymonero doesn't.
THROW_WALLET_EXCEPTION_IF ( ( ! ires . status . empty ( ) & & ires . status ! = " success " ) , error : : no_connection_to_daemon , " get_address_txs " ) ;
// Abort if no transactions
if ( ires . transactions . empty ( ) )
return ;
// Create searchable vectors
std : : vector < crypto : : hash > payments_txs ;
for ( const auto & p : m_payments )
payments_txs . push_back ( p . second . m_tx_hash ) ;
std : : vector < crypto : : hash > unconfirmed_payments_txs ;
for ( const auto & up : m_unconfirmed_payments )
unconfirmed_payments_txs . push_back ( up . second . m_tx_hash ) ;
// for balance calculation
uint64_t wallet_total_sent = 0 ;
uint64_t wallet_total_unlocked_sent = 0 ;
// txs in pool
std : : vector < crypto : : hash > pool_txs ;
for ( const auto & t : ires . transactions ) {
const uint64_t total_received = t . total_received ;
uint64_t total_sent = t . total_sent ;
// Check key images - subtract fake outputs from total_sent
for ( const auto & so : t . spent_outputs )
{
crypto : : public_key tx_public_key ;
crypto : : key_image key_image ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , so . tx_pub_key ) , error : : wallet_internal_error , " Invalid tx_pub_key field " ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , so . key_image ) , error : : wallet_internal_error , " Invalid key_image field " ) ;
string_tools : : hex_to_pod ( so . tx_pub_key , tx_public_key ) ;
string_tools : : hex_to_pod ( so . key_image , key_image ) ;
if ( ! light_wallet_key_image_is_ours ( key_image , tx_public_key , so . out_index ) ) {
THROW_WALLET_EXCEPTION_IF ( so . amount > t . total_sent , error : : wallet_internal_error , " Lightwallet: total sent is negative! " ) ;
total_sent - = so . amount ;
}
}
// Do not add tx if empty.
if ( total_sent = = 0 & & total_received = = 0 )
continue ;
crypto : : hash payment_id = null_hash ;
crypto : : hash tx_hash ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , t . payment_id ) , error : : wallet_internal_error , " Invalid payment_id field " ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , t . hash ) , error : : wallet_internal_error , " Invalid hash field " ) ;
string_tools : : hex_to_pod ( t . payment_id , payment_id ) ;
string_tools : : hex_to_pod ( t . hash , tx_hash ) ;
// lightwallet specific info
bool incoming = ( total_received > total_sent ) ;
address_tx address_tx ;
address_tx . m_tx_hash = tx_hash ;
address_tx . m_incoming = incoming ;
address_tx . m_amount = incoming ? total_received - total_sent : total_sent - total_received ;
address_tx . m_block_height = t . height ;
address_tx . m_unlock_time = t . unlock_time ;
address_tx . m_timestamp = t . timestamp ;
address_tx . m_coinbase = t . coinbase ;
address_tx . m_mempool = t . mempool ;
m_light_wallet_address_txs . emplace ( tx_hash , address_tx ) ;
// populate data needed for history (m_payments, m_unconfirmed_payments, m_confirmed_txs)
// INCOMING transfers
if ( total_received > total_sent ) {
payment_details payment ;
payment . m_tx_hash = tx_hash ;
payment . m_amount = total_received - total_sent ;
payment . m_block_height = t . height ;
payment . m_unlock_time = t . unlock_time ;
payment . m_timestamp = t . timestamp ;
if ( t . mempool ) {
if ( std : : find ( unconfirmed_payments_txs . begin ( ) , unconfirmed_payments_txs . end ( ) , tx_hash ) = = unconfirmed_payments_txs . end ( ) ) {
pool_txs . push_back ( tx_hash ) ;
m_unconfirmed_payments . emplace ( tx_hash , payment ) ;
if ( 0 ! = m_callback ) {
cryptonote : : transaction dummy_tx ;
m_callback - > on_unconfirmed_money_received ( t . height , payment . m_tx_hash , dummy_tx , payment . m_amount ) ;
}
}
} else {
if ( std : : find ( payments_txs . begin ( ) , payments_txs . end ( ) , tx_hash ) = = payments_txs . end ( ) ) {
m_payments . emplace ( tx_hash , payment ) ;
if ( 0 ! = m_callback ) {
cryptonote : : transaction dummy_tx ;
m_callback - > on_money_received ( t . height , payment . m_tx_hash , dummy_tx , payment . m_amount ) ;
}
}
}
// Outgoing transfers
} else {
uint64_t amount_sent = total_sent - total_received ;
cryptonote : : transaction dummy_tx ; // not used by light wallet
// increase wallet total sent
wallet_total_sent + = total_sent ;
if ( t . mempool )
{
// Handled by add_unconfirmed_tx in commit_tx
// If sent from another wallet instance we need to add it
if ( m_unconfirmed_txs . find ( tx_hash ) = = m_unconfirmed_txs . end ( ) )
{
unconfirmed_transfer_details utd ;
utd . m_amount_in = amount_sent ;
utd . m_amount_out = amount_sent ;
utd . m_change = 0 ;
utd . m_payment_id = payment_id ;
utd . m_timestamp = t . timestamp ;
utd . m_state = wallet2 : : unconfirmed_transfer_details : : pending ;
m_unconfirmed_txs . emplace ( tx_hash , utd ) ;
}
}
else
{
// Only add if new
auto confirmed_tx = m_confirmed_txs . find ( tx_hash ) ;
if ( confirmed_tx = = m_confirmed_txs . end ( ) ) {
// tx is added to m_unconfirmed_txs - move to confirmed
if ( m_unconfirmed_txs . find ( tx_hash ) ! = m_unconfirmed_txs . end ( ) )
{
process_unconfirmed ( tx_hash , dummy_tx , t . height ) ;
}
// Tx sent by another wallet instance
else
{
confirmed_transfer_details ctd ;
ctd . m_amount_in = amount_sent ;
ctd . m_amount_out = amount_sent ;
ctd . m_change = 0 ;
ctd . m_payment_id = payment_id ;
ctd . m_block_height = t . height ;
ctd . m_timestamp = t . timestamp ;
m_confirmed_txs . emplace ( tx_hash , ctd ) ;
}
if ( 0 ! = m_callback )
{
m_callback - > on_money_spent ( t . height , tx_hash , dummy_tx , amount_sent , dummy_tx ) ;
}
}
// If not new - check the amount and update if necessary.
// when sending a tx to same wallet the receiving amount has to be credited
else
{
if ( confirmed_tx - > second . m_amount_in ! = amount_sent | | confirmed_tx - > second . m_amount_out ! = amount_sent )
{
MDEBUG ( " Adjusting amount sent/received for tx: < " + t . hash + " >. Is tx sent to own wallet? " < < print_money ( amount_sent ) < < " != " < < print_money ( confirmed_tx - > second . m_amount_in ) ) ;
confirmed_tx - > second . m_amount_in = amount_sent ;
confirmed_tx - > second . m_amount_out = amount_sent ;
confirmed_tx - > second . m_change = 0 ;
}
}
}
}
}
// TODO: purge old unconfirmed_txs
remove_obsolete_pool_txs ( pool_txs ) ;
// Calculate wallet balance
m_light_wallet_balance = ires . total_received - wallet_total_sent ;
// MyMonero doesnt send unlocked balance
if ( ires . total_received_unlocked > 0 )
m_light_wallet_unlocked_balance = ires . total_received_unlocked - wallet_total_sent ;
else
m_light_wallet_unlocked_balance = m_light_wallet_balance ;
}
bool wallet2 : : light_wallet_parse_rct_str ( const std : : string & rct_string , const crypto : : public_key & tx_pub_key , uint64_t internal_output_index , rct : : key & decrypted_mask , rct : : key & rct_commit , bool decrypt ) const
{
// 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 rct_commit_str = rct_string . substr ( 0 , 64 ) ;
std : : string encrypted_mask_str = rct_string . substr ( 64 , 64 ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , rct_commit_str ) , error : : wallet_internal_error , " Invalid rct commit hash: " + rct_commit_str ) ;
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 ( rct_commit_str , rct_commit ) ;
string_tools : : hex_to_pod ( encrypted_mask_str , encrypted_mask ) ;
if ( decrypt ) {
// Decrypt the mask
crypto : : key_derivation derivation ;
generate_key_derivation ( tx_pub_key , get_account ( ) . get_keys ( ) . m_view_secret_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 wallet2 : : light_wallet_key_image_is_ours ( const crypto : : key_image & key_image , const crypto : : public_key & tx_public_key , uint64_t out_index )
{
// Lookup key image from cache
std : : map < uint64_t , crypto : : key_image > index_keyimage_map ;
std : : unordered_map < crypto : : public_key , std : : map < uint64_t , crypto : : key_image > > : : const_iterator found_pub_key = m_key_image_cache . find ( tx_public_key ) ;
if ( found_pub_key ! = m_key_image_cache . end ( ) ) {
// pub key found. key image for index cached?
index_keyimage_map = found_pub_key - > second ;
std : : map < uint64_t , crypto : : key_image > : : const_iterator index_found = index_keyimage_map . find ( out_index ) ;
if ( index_found ! = index_keyimage_map . end ( ) )
return key_image = = index_found - > second ;
}
// Not in cache - calculate key image
crypto : : key_image calculated_key_image ;
cryptonote : : keypair in_ephemeral ;
cryptonote : : generate_key_image_helper ( get_account ( ) . get_keys ( ) , tx_public_key , out_index , in_ephemeral , calculated_key_image ) ;
index_keyimage_map . emplace ( out_index , calculated_key_image ) ;
m_key_image_cache . emplace ( tx_public_key , index_keyimage_map ) ;
return key_image = = calculated_key_image ;
}
// Another implementation of transaction creation that is hopefully better
// While there is anything left to pay, it goes through random outputs and tries
// to fill the next destination/amount. If it fully fills it, it will use the