diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 25812456..566c6f23 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -69,6 +69,7 @@ struct blind_confirmation public_key_type pub_key; stealth_confirmation::memo_data decrypted_memo; stealth_confirmation confirmation; + authority auth; string confirmation_receipt; }; @@ -87,6 +88,41 @@ struct blind_balance bool used = false; }; +struct blind_receipt +{ + std::pair from_date()const { return std::make_pair(from_key,date); } + std::pair to_date()const { return std::make_pair(to_key,date); } + std::tuple to_asset_used()const { return std::make_tuple(to_key,amount.asset_id,used); } + const commitment_type& commitment()const { return data.commitment; } + + fc::time_point date; + public_key_type from_key; + string from_label; + public_key_type to_key; + string to_label; + asset amount; + string memo; + authority control_authority; + stealth_confirmation::memo_data data; + bool used = false; + stealth_confirmation conf; +}; + +struct by_from; +struct by_to; +struct by_to_asset_used; +struct by_commitment; + +typedef multi_index_container< blind_receipt, + indexed_by< + ordered_unique< tag, const_mem_fun< blind_receipt, const commitment_type&, &blind_receipt::commitment > >, + ordered_unique< tag, const_mem_fun< blind_receipt, std::pair, &blind_receipt::to_date > >, + ordered_non_unique< tag, const_mem_fun< blind_receipt, std::tuple, &blind_receipt::to_asset_used > >, + ordered_unique< tag, const_mem_fun< blind_receipt, std::pair, &blind_receipt::from_date > > + > +> blind_receipt_index_type; + + struct key_label { string label; @@ -145,7 +181,7 @@ struct wallet_data map pending_witness_registrations; key_label_index_type labeled_keys; - map > > blinded_balances; + blind_receipt_index_type blind_receipts; string ws_server = "ws://localhost:8090"; string ws_user; @@ -618,6 +654,10 @@ class wallet_api public_key_type get_public_key( string label )const; ///@} + /** + * @return all blind receipts to/form a particular account + */ + vector blind_history( string key_or_account ); /** * Given a confirmation receipt, this method will parse it for a blinded balance and confirm @@ -627,7 +667,7 @@ class wallet_api * @param opt_from - if not empty and the sender is a unknown public key, then the unknown public key will be given the label opt_from * @param confirmation_receipt - a base58 encoded stealth confirmation */ - stealth_confirmation::memo_data receive_blind_transfer( string confirmation_receipt, string opt_from ); + blind_receipt receive_blind_transfer( string confirmation_receipt, string opt_from, string opt_memo ); /** * Transfers a public balance from @from to one or more blinded balances using a @@ -639,6 +679,19 @@ class wallet_api map to_amounts, bool broadcast = false ); + /** + * Transfers funds from a set of blinded balances to a public account balance. + */ + blind_confirmation transfer_from_blind( + string from_blind_account_key_or_label, + string to_account_id_or_name, + string amount, + string asset_symbol, + bool broadcast = false ); + + /** + * Used to transfer from one set of blinded balances to another + */ blind_confirmation blind_transfer( string from_key_or_label, string to_key_or_label, string amount, @@ -1124,6 +1177,18 @@ class wallet_api void dbg_make_mia(string creator, string symbol); void flood_network(string prefix, uint32_t number_of_transactions); + + /** + * Used to transfer from one set of blinded balances to another + */ + blind_confirmation blind_transfer_help( string from_key_or_label, + string to_key_or_label, + string amount, + string symbol, + bool broadcast = false, + bool to_temp = false ); + + std::map> get_result_formatters() const; @@ -1136,7 +1201,7 @@ class wallet_api FC_REFLECT( graphene::wallet::key_label, (label)(key) ) FC_REFLECT( graphene::wallet::blind_balance, (amount)(from)(to)(one_time_key)(blinding_factor)(commitment)(used) ) -FC_REFLECT( graphene::wallet::blind_confirmation::output, (label)(pub_key)(decrypted_memo)(confirmation)(confirmation_receipt) ) +FC_REFLECT( graphene::wallet::blind_confirmation::output, (label)(pub_key)(decrypted_memo)(confirmation)(auth)(confirmation_receipt) ) FC_REFLECT( graphene::wallet::blind_confirmation, (trx)(outputs) ) FC_REFLECT( graphene::wallet::plain_keys, (keys)(checksum) ) @@ -1147,7 +1212,7 @@ FC_REFLECT( graphene::wallet::wallet_data, (extra_keys) (pending_account_registrations)(pending_witness_registrations) (labeled_keys) - (blinded_balances) + (blind_receipts) (ws_server) (ws_user) (ws_password) @@ -1163,6 +1228,9 @@ FC_REFLECT( graphene::wallet::exported_account_keys, (account_name)(encrypted_pr FC_REFLECT( graphene::wallet::exported_keys, (password_checksum)(account_keys) ) +FC_REFLECT( graphene::wallet::blind_receipt, + (date)(from_key)(from_label)(to_key)(to_label)(amount)(memo)(control_authority)(data)(used)(conf) ) + FC_API( graphene::wallet::wallet_api, (help) (gethelp) @@ -1244,6 +1312,9 @@ FC_API( graphene::wallet::wallet_api, (get_blind_balances) (create_blind_account) (transfer_to_blind) + (transfer_from_blind) (blind_transfer) + (blind_history) (receive_blind_transfer) ) + diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 85821a2a..dbec6b36 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -85,19 +85,13 @@ public: result(r) {} typedef void result_type; + template - void operator()(const T& op)const - { - //balance_accumulator acc; - //op.get_balance_delta( acc, result ); - string op_name = fc::get_typename::name(); - if( op_name.find_last_of(':') != string::npos ) - op_name.erase(0, op_name.find_last_of(':')+1); - out << op_name <<" "; - // out << "balance delta: " << fc::json::to_string(acc.balance) <<" "; - out << fc::json::to_string(op.fee_payer()) << " fee: " << fc::json::to_string(op.fee); - } + void operator()(const T& op)const; + void operator()(const transfer_operation& op)const; + void operator()(const transfer_from_blind_operation& op)const; + void operator()(const transfer_to_blind_operation& op)const; void operator()(const account_create_operation& op)const; void operator()(const account_update_operation& op)const; void operator()(const asset_create_operation& op)const; @@ -396,11 +390,14 @@ public: void encrypt_keys() { - plain_keys data; - data.keys = _keys; - data.checksum = _checksum; - auto plain_txt = fc::raw::pack(data); - _wallet.cipher_keys = fc::aes_encrypt( data.checksum, plain_txt ); + if( !is_locked() ) + { + plain_keys data; + data.keys = _keys; + data.checksum = _checksum; + auto plain_txt = fc::raw::pack(data); + _wallet.cipher_keys = fc::aes_encrypt( data.checksum, plain_txt ); + } } bool copy_wallet_file( string destination_filename ) @@ -1755,6 +1752,70 @@ public: return ss.str(); }; + m["get_blind_balances"] = [this](variant result, const fc::variants& a) + { + auto r = result.as>(); + vector asset_recs; + std::transform(r.begin(), r.end(), std::back_inserter(asset_recs), [this](const asset& a) { + return get_asset(a.asset_id); + }); + + std::stringstream ss; + for( unsigned i = 0; i < asset_recs.size(); ++i ) + ss << asset_recs[i].amount_to_pretty_string(r[i]) << "\n"; + + return ss.str(); + }; + m["transfer_to_blind"] = [this](variant result, const fc::variants& a) + { + auto r = result.as(); + std::stringstream ss; + r.trx.operations[0].visit( operation_printer( ss, *this, operation_result() ) ); + ss << "\n"; + for( const auto& out : r.outputs ) + { + asset_object a = get_asset( out.decrypted_memo.amount.asset_id ); + ss << a.amount_to_pretty_string( out.decrypted_memo.amount ) << " to " << out.label << "\n\t receipt: " << out.confirmation_receipt <<"\n\n"; + } + return ss.str(); + }; + m["blind_transfer"] = [this](variant result, const fc::variants& a) + { + auto r = result.as(); + std::stringstream ss; + r.trx.operations[0].visit( operation_printer( ss, *this, operation_result() ) ); + ss << "\n"; + for( const auto& out : r.outputs ) + { + asset_object a = get_asset( out.decrypted_memo.amount.asset_id ); + ss << a.amount_to_pretty_string( out.decrypted_memo.amount ) << " to " << out.label << "\n\t receipt: " << out.confirmation_receipt <<"\n\n"; + } + return ss.str(); + }; + m["receive_blind_transfer"] = [this](variant result, const fc::variants& a) + { + auto r = result.as(); + std::stringstream ss; + asset_object as = get_asset( r.amount.asset_id ); + ss << as.amount_to_pretty_string( r.amount ) << " " << r.from_label << " => " << r.to_label << " " << r.memo <<"\n"; + return ss.str(); + }; + m["blind_history"] = [this](variant result, const fc::variants& a) + { + auto records = result.as>(); + std::stringstream ss; + ss << "WHEN " + << " " << "AMOUNT" << " " << "FROM" << " => " << "TO" << " " << "MEMO" <<"\n"; + ss << "====================================================================================\n"; + for( auto& r : records ) + { + asset_object as = get_asset( r.amount.asset_id ); + ss << fc::get_approximate_relative_time_string( r.date ) + << " " << as.amount_to_pretty_string( r.amount ) << " " << r.from_label << " => " << r.to_label << " " << r.memo <<"\n"; + } + return ss.str(); + }; + return m; } @@ -1859,6 +1920,39 @@ void operation_printer::fee(const asset& a)const { out << " (Fee: " << wallet.get_asset(a.asset_id).amount_to_pretty_string(a) << ")"; } +template +void operation_printer::operator()(const T& op)const +{ + //balance_accumulator acc; + //op.get_balance_delta( acc, result ); + auto a = wallet.get_asset( op.fee.asset_id ); + auto payer = wallet.get_account( op.fee_payer() ); + + string op_name = fc::get_typename::name(); + if( op_name.find_last_of(':') != string::npos ) + op_name.erase(0, op_name.find_last_of(':')+1); + out << op_name <<" "; + // out << "balance delta: " << fc::json::to_string(acc.balance) <<" "; + out << payer.name << " fee: " << a.amount_to_pretty_string( op.fee ); +} +void operation_printer::operator()(const transfer_from_blind_operation& op)const +{ + auto a = wallet.get_asset( op.fee.asset_id ); + auto receiver = wallet.get_account( op.to ); + + out << receiver.name + << " received " << a.amount_to_pretty_string( op.amount ) << " from blinded balance"; +} +void operation_printer::operator()(const transfer_to_blind_operation& op)const +{ + auto fa = wallet.get_asset( op.fee.asset_id ); + auto a = wallet.get_asset( op.amount.asset_id ); + auto sender = wallet.get_account( op.from ); + + out << sender.name + << " sent " << a.amount_to_pretty_string( op.amount ) << " to " << op.outputs.size() << " blinded balance" << (op.outputs.size()>1?"s":"") + << " fee: " << fa.amount_to_pretty_string( op.fee ); +} void operation_printer::operator()(const transfer_operation& op) const { out << "Transfer " << wallet.get_asset(op.amount.asset_id).amount_to_pretty_string(op.amount) @@ -2587,6 +2681,8 @@ string wallet_api::get_key_label( public_key_type key )const public_key_type wallet_api::get_public_key( string label )const { + try { return fc::variant(label).as(); } catch ( ... ){} + auto key_itr = my->_wallet.labeled_keys.get().find(label); if( key_itr != my->_wallet.labeled_keys.get().end() ) return key_itr->key; @@ -2649,47 +2745,91 @@ public_key_type wallet_api::create_blind_account( string label, string brain_ vector wallet_api::get_blind_balances( string key_or_label ) { vector result; + map balances; + + vector used; auto pub_key = get_public_key( key_or_label ); - if( pub_key == public_key_type() ) + auto& to_asset_used_idx = my->_wallet.blind_receipts.get(); + auto start = to_asset_used_idx.lower_bound( std::make_tuple(pub_key,0,false) ); + auto end = to_asset_used_idx.lower_bound( std::make_tuple(pub_key,uint32_t(0xffffffff),true) ); + while( start != end ) { - try { - pub_key = variant(key_or_label).as(); - } catch ( ... ) + if( !start->used ) { - FC_ASSERT( false, "Unknown key or label '${v}'", ("v",key_or_label) ); + auto answer = my->_remote_db->get_blinded_balances( {start->commitment()} ); + if( answer.size() ) + balances[start->amount.asset_id] += start->amount.amount; + else + used.push_back( start->commitment() ); } + ++start; } - - for( auto& asset_itr : my->_wallet.blinded_balances ) + for( const auto& u : used ) { - asset balance( 0, asset_itr.first ); - auto owner_itr = asset_itr.second.find( pub_key ); - if( owner_itr != asset_itr.second.end() ) - { - for( auto& bb : owner_itr->second ) - { - if( !bb.used ) - { - auto result = my->_remote_db->get_blinded_balances( {bb.commitment} ); - if( result.size() == 0 ) - bb.used = true; - else - balance += bb.amount; - } - } - } - if( balance.amount > 0 ) - result.push_back(balance); + auto itr = my->_wallet.blind_receipts.get().find( u ); + my->_wallet.blind_receipts.modify( itr, []( blind_receipt& r ){ r.used = true; } ); } + for( auto item : balances ) + result.push_back( asset( item.second, item.first ) ); return result; } +blind_confirmation wallet_api::transfer_from_blind( string from_blind_account_key_or_label, + string to_account_id_or_name, + string amount_in, + string symbol, + bool broadcast ) +{ try { + transfer_from_blind_operation from_blind; + + + auto fees = my->_remote_db->get_global_properties().parameters.current_fees; + fc::optional asset_obj = get_asset(symbol); + FC_ASSERT(asset_obj.valid(), "Could not find asset matching ${asset}", ("asset", symbol)); + auto amount = asset_obj->amount_from_string(amount_in); + + from_blind.fee = fees->calculate_fee( from_blind, asset_obj->options.core_exchange_rate ); + + auto blind_in = asset_obj->amount_to_string( from_blind.fee + amount ); + + + auto conf = blind_transfer_help( from_blind_account_key_or_label, + from_blind_account_key_or_label, + blind_in, symbol, false, true ); + FC_ASSERT( conf.outputs.size() > 0 ); + + auto to_account = my->get_account( to_account_id_or_name ); + from_blind.to = to_account.id; + from_blind.amount = amount; + from_blind.blinding_factor = conf.outputs.back().decrypted_memo.blinding_factor; + from_blind.inputs.push_back( {conf.outputs.back().decrypted_memo.commitment, authority() } ); + from_blind.fee = fees->calculate_fee( from_blind, asset_obj->options.core_exchange_rate ); + + idump( (from_blind) ); + conf.trx.operations.push_back(from_blind); + ilog( "about to validate" ); + conf.trx.validate(); + ilog( "about to broadcast" ); + conf.trx = sign_transaction( conf.trx, broadcast ); + + return conf; +} FC_CAPTURE_AND_RETHROW( (from_blind_account_key_or_label)(to_account_id_or_name)(amount_in)(symbol) ) } + blind_confirmation wallet_api::blind_transfer( string from_key_or_label, string to_key_or_label, string amount_in, string symbol, bool broadcast ) +{ + return blind_transfer_help( from_key_or_label, to_key_or_label, amount_in, symbol, broadcast, false ); +} +blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label, + string to_key_or_label, + string amount_in, + string symbol, + bool broadcast, + bool to_temp ) { blind_confirmation confirm; try { @@ -2710,39 +2850,40 @@ blind_confirmation wallet_api::blind_transfer( string from_key_or_label, asset total_amount = asset_obj->amount(0); - auto& asset_itr = my->_wallet.blinded_balances[amount.asset_id]; - vector blinding_factors; //auto from_priv_key = my->get_private_key( from_key ); blind_tr.fee = fees->calculate_fee( blind_tr, asset_obj->options.core_exchange_rate ); - auto owner_itr = asset_itr.find( from_key ); - if( owner_itr != asset_itr.end() ) + vector used; + + auto& to_asset_used_idx = my->_wallet.blind_receipts.get(); + auto start = to_asset_used_idx.lower_bound( std::make_tuple(from_key,amount.asset_id,false) ); + auto end = to_asset_used_idx.lower_bound( std::make_tuple(from_key,amount.asset_id,true) ); + while( start != end ) { - for( auto& bb : owner_itr->second ) + auto result = my->_remote_db->get_blinded_balances( {start->commitment() } ); + if( result.size() == 0 ) { - if( !bb.used ) - { - auto result = my->_remote_db->get_blinded_balances( {bb.commitment} ); - if( result.size() == 0 ) - bb.used = true; - else - { - // ADD THIS TO THE INPUTS! - blind_tr.inputs.push_back( {bb.commitment,result.front().owner} ); - - blinding_factors.push_back( bb.blinding_factor ); - - total_amount += bb.amount; - if( total_amount >= amount + blind_tr.fee ) - break; - } - } + used.push_back( start->commitment() ); } + else + { + blind_tr.inputs.push_back({start->commitment(), start->control_authority}); + blinding_factors.push_back( start->data.blinding_factor ); + total_amount += start->amount; + + if( total_amount >= amount + blind_tr.fee ) + break; + } + ++start; + } + for( const auto& u : used ) + { + auto itr = my->_wallet.blind_receipts.get().find( u ); + my->_wallet.blind_receipts.modify( itr, []( blind_receipt& r ){ r.used = true; } ); } - wdump( ("input")(blinding_factors) ); FC_ASSERT( total_amount >= amount+blind_tr.fee, "Insufficent Balance", ("available",total_amount)("amount",amount)("fee",blind_tr.fee) ); @@ -2770,6 +2911,7 @@ blind_confirmation wallet_api::blind_transfer( string from_key_or_label, { blind_tr.outputs.resize(1); blind_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() ); + idump(("to_sum_blind_factor")(blind_factor) ); blinding_factors.push_back( blind_factor ); idump(("nochange to_blind_factor")(blind_factor) ); } @@ -2777,8 +2919,9 @@ blind_confirmation wallet_api::blind_transfer( string from_key_or_label, fc::ecc::public_key to_pub_key = to_key; blind_output to_out; - to_out.owner = authority( 1, public_key_type( to_pub_key.child( child ) ), 1 ); + to_out.owner = to_temp ? authority() : authority( 1, public_key_type( to_pub_key.child( child ) ), 1 ); to_out.commitment = fc::ecc::blind( blind_factor, amount.amount.value ); + idump(("to_out.blind")(blind_factor)(to_out.commitment) ); if( blind_tr.outputs.size() > 1 ) @@ -2803,6 +2946,7 @@ blind_confirmation wallet_api::blind_transfer( string from_key_or_label, conf_output.confirmation.one_time_key = one_time_key.get_public_key(); conf_output.confirmation.to = from_key; conf_output.confirmation.encrypted_memo = fc::aes_encrypt( from_secret, fc::raw::pack( conf_output.decrypted_memo ) ); + conf_output.auth = change_out.owner; conf_output.confirmation_receipt = conf_output.confirmation; confirm.outputs.push_back( conf_output ); @@ -2820,6 +2964,7 @@ blind_confirmation wallet_api::blind_transfer( string from_key_or_label, conf_output.confirmation.one_time_key = one_time_key.get_public_key(); conf_output.confirmation.to = to_key; conf_output.confirmation.encrypted_memo = fc::aes_encrypt( secret, fc::raw::pack( conf_output.decrypted_memo ) ); + conf_output.auth = to_out.owner; conf_output.confirmation_receipt = conf_output.confirmation; confirm.outputs.push_back( conf_output ); @@ -2827,14 +2972,19 @@ blind_confirmation wallet_api::blind_transfer( string from_key_or_label, /** commitments must be in sorted order */ std::sort( blind_tr.outputs.begin(), blind_tr.outputs.end(), [&]( const blind_output& a, const blind_output& b ){ return a.commitment < b.commitment; } ); + std::sort( blind_tr.inputs.begin(), blind_tr.inputs.end(), + [&]( const blind_input& a, const blind_input& b ){ return a.commitment < b.commitment; } ); confirm.trx.operations.emplace_back( std::move(blind_tr) ); ilog( "validate before" ); confirm.trx.validate(); confirm.trx = sign_transaction(confirm.trx, broadcast); - ilog( "validate after signing" ); - confirm.trx.validate(); - + + if( broadcast ) + { + for( const auto& out : confirm.outputs ) + try { receive_blind_transfer( out.confirmation_receipt, from_key_or_label, "" ); } catch ( ... ){} + } return confirm; } FC_CAPTURE_AND_RETHROW( (from_key_or_label)(to_key_or_label)(amount_in)(symbol)(broadcast)(confirm) ) } @@ -2916,17 +3066,29 @@ blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name my->set_operation_fees( confirm.trx, my->_remote_db->get_global_properties().parameters.current_fees); confirm.trx.validate(); confirm.trx = sign_transaction(confirm.trx, broadcast); + + if( broadcast ) + { + for( const auto& out : confirm.outputs ) + try { receive_blind_transfer( out.confirmation_receipt, "@"+from_account.name, "from @"+from_account.name ); } catch ( ... ){} + } + return confirm; } FC_CAPTURE_AND_RETHROW( (from_account_id_or_name)(asset_symbol)(to_amounts) ) } -stealth_confirmation::memo_data wallet_api::receive_blind_transfer( string confirmation_receipt, string opt_from ) +blind_receipt wallet_api::receive_blind_transfer( string confirmation_receipt, string opt_from, string opt_memo ) { FC_ASSERT( !is_locked() ); stealth_confirmation conf(confirmation_receipt); FC_ASSERT( conf.to ); + blind_receipt result; + result.conf = conf; + auto to_priv_key_itr = my->_keys.find( *conf.to ); FC_ASSERT( to_priv_key_itr != my->_keys.end(), "No private key for receiver", ("conf",conf) ); + + auto to_priv_key = wif_to_key( to_priv_key_itr->second ); FC_ASSERT( to_priv_key ); @@ -2938,6 +3100,30 @@ stealth_confirmation::memo_data wallet_api::receive_blind_transfer( string confi auto plain_memo = fc::aes_decrypt( secret, conf.encrypted_memo ); auto memo = fc::raw::unpack( plain_memo ); + + + result.to_key = *conf.to; + result.to_label = get_key_label( result.to_key ); + if( memo.from ) + { + result.from_key = *memo.from; + result.from_label = get_key_label( result.from_key ); + if( result.from_label == string() ) + { + result.from_label = opt_from; + set_key_label( result.from_key, result.from_label ); + } + } + else + { + result.from_label = opt_from; + } + result.amount = memo.amount; + result.memo = opt_memo; + + // confirm the amount matches the commitment (verify the blinding factor) + auto commtiment_test = fc::ecc::blind( memo.blinding_factor, memo.amount.amount.value ); + FC_ASSERT( fc::ecc::verify_sum( {commtiment_test}, {memo.commitment}, 0 ) ); auto bbal = my->_remote_db->get_blinded_balances( {memo.commitment} ); FC_ASSERT( bbal.size(), "commitment not found in blockchain", ("memo",memo) ); @@ -2951,21 +3137,42 @@ stealth_confirmation::memo_data wallet_api::receive_blind_transfer( string confi bal.commitment = memo.commitment; bal.used = false; + result.control_authority = bbal.front().owner; + result.data = memo; + + auto child_key_itr = bbal.front().owner.key_auths.find( child_priv_key.get_public_key() ); if( child_key_itr != bbal.front().owner.key_auths.end() ) my->_keys[child_key_itr->first] = key_to_wif( child_priv_key ); - // TODO: confirm the amount matches the commitment (verify the blinding factor - // TODO: make sure the same blind balance isn't included twice + // my->_wallet.blinded_balances[memo.amount.asset_id][bal.to].push_back( bal ); - my->_wallet.blinded_balances[memo.amount.asset_id][bal.to].push_back( bal ); + result.date = fc::time_point::now(); + my->_wallet.blind_receipts.insert( result ); my->_keys[child_priv_key.get_public_key()] = key_to_wif( child_priv_key ); save_wallet_file(); - return memo; + return result; +} + +vector wallet_api::blind_history( string key_or_account ) +{ + vector result; + auto pub_key = get_public_key( key_or_account ); + + if( pub_key == public_key_type() ) + return vector(); + + for( auto& r : my->_wallet.blind_receipts ) + { + if( r.from_key == pub_key || r.to_key == pub_key ) + result.push_back( r ); + } + std::sort( result.begin(), result.end(), [&]( const blind_receipt& a, const blind_receipt& b ){ return a.date > b.date; } ); + return result; } } } // graphene::wallet