From 57ca0c0e39f79948e378029623ea81e599dfac66 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Thu, 23 Jul 2015 18:46:06 -0400 Subject: [PATCH] Wallet Support for Some Confidential Transactions Support public account to blinded balance Support receiving blinded balance given confirmation Implement but not test blind to blind transfers. --- docs | 2 +- libraries/app/include/graphene/app/api.hpp | 1 + .../graphene/chain/protocol/confidential.hpp | 18 +- libraries/chain/protocol/confidential.cpp | 21 +- libraries/fc | 2 +- .../wallet/include/graphene/wallet/wallet.hpp | 34 +- libraries/wallet/wallet.cpp | 377 +++++++++++++++++- 7 files changed, 435 insertions(+), 20 deletions(-) diff --git a/docs b/docs index 66a8825f..97435c1a 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 66a8825f937af9b810b63a3015521e29027ac5d4 +Subproject commit 97435c1a622e41e0a5fc1be72aaadea62e1b7adb diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index 66ce7c31..703e2480 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -537,6 +537,7 @@ FC_API(graphene::app::database_api, (get_balance_objects) (get_required_signatures) (verify_authority) + (get_blinded_balances) ) FC_API(graphene::app::history_api, (get_account_history) diff --git a/libraries/chain/include/graphene/chain/protocol/confidential.hpp b/libraries/chain/include/graphene/chain/protocol/confidential.hpp index 2fc37555..3374909f 100644 --- a/libraries/chain/include/graphene/chain/protocol/confidential.hpp +++ b/libraries/chain/include/graphene/chain/protocol/confidential.hpp @@ -82,8 +82,9 @@ struct stealth_confirmation { struct memo_data { - public_key_type from; + optional from; asset amount; + fc::sha256 blinding_factor; fc::ecc::commitment_type commitment; uint32_t check = 0; }; @@ -98,8 +99,9 @@ struct stealth_confirmation stealth_confirmation( const std::string& base58 ); stealth_confirmation(){} - public_key_type one_time_key; - vector encrypted_memo; + public_key_type one_time_key; + optional to; + vector encrypted_memo; }; /** @@ -129,7 +131,6 @@ struct transfer_to_blind_operation : public base_operation struct fee_parameters_type { uint64_t fee = 5*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account uint32_t price_per_output = 5*GRAPHENE_BLOCKCHAIN_PRECISION; - uint32_t price_per_kb = 5*GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -231,7 +232,6 @@ struct blind_transfer_operation : public base_operation struct fee_parameters_type { uint64_t fee = 5*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account uint32_t price_per_output = 5*GRAPHENE_BLOCKCHAIN_PRECISION; - uint32_t price_per_kb = 5*GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -261,10 +261,10 @@ struct blind_transfer_operation : public base_operation } } // graphene::chain FC_REFLECT( graphene::chain::stealth_confirmation, - (one_time_key)(encrypted_memo) ) + (one_time_key)(to)(encrypted_memo) ) FC_REFLECT( graphene::chain::stealth_confirmation::memo_data, - (from)(amount)(commitment)(check) ); + (from)(amount)(blinding_factor)(commitment)(check) ); FC_REFLECT( graphene::chain::blind_memo, (from)(amount)(message)(check) ) @@ -278,7 +278,7 @@ FC_REFLECT( graphene::chain::transfer_from_blind_operation, (fee)(amount)(to)(blinding_factor)(inputs) ) FC_REFLECT( graphene::chain::blind_transfer_operation, (fee)(inputs)(outputs) ) -FC_REFLECT( graphene::chain::transfer_to_blind_operation::fee_parameters_type, (fee)(price_per_output)(price_per_kb) ) +FC_REFLECT( graphene::chain::transfer_to_blind_operation::fee_parameters_type, (fee)(price_per_output) ) FC_REFLECT( graphene::chain::transfer_from_blind_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::chain::blind_transfer_operation::fee_parameters_type, (fee)(price_per_output)(price_per_kb) ) +FC_REFLECT( graphene::chain::blind_transfer_operation::fee_parameters_type, (fee)(price_per_output) ) diff --git a/libraries/chain/protocol/confidential.cpp b/libraries/chain/protocol/confidential.cpp index 7e4df25f..8fa286f3 100644 --- a/libraries/chain/protocol/confidential.cpp +++ b/libraries/chain/protocol/confidential.cpp @@ -2,6 +2,9 @@ #include #include +#include +#include + namespace graphene { namespace chain { void transfer_to_blind_operation::validate()const @@ -38,7 +41,7 @@ void transfer_to_blind_operation::validate()const share_type transfer_to_blind_operation::calculate_fee( const fee_parameters_type& k )const { - return k.fee + outputs.size() * k.price_per_output + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kb ); + return k.fee + outputs.size() * k.price_per_output; } @@ -110,12 +113,26 @@ void blind_transfer_operation::validate()const share_type blind_transfer_operation::calculate_fee( const fee_parameters_type& k )const { - return k.fee + outputs.size() * k.price_per_output + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kb );; + return k.fee + outputs.size() * k.price_per_output; } +/** + * Packs *this then encodes as base58 encoded string. + */ +stealth_confirmation::operator string()const +{ + return fc::to_base58( fc::raw::pack( *this ) ); +} +/** + * Unpacks from a base58 string + */ +stealth_confirmation::stealth_confirmation( const std::string& base58 ) +{ + *this = fc::raw::unpack( fc::from_base58( base58 ) ); +} diff --git a/libraries/fc b/libraries/fc index 2808c02c..d76a15ec 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 2808c02cc775e923a7cc235d3f0ab336fce56a28 +Subproject commit d76a15ec896dddb021ef8aeb9f77bba34dadd27b diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 37955a2f..bc141142 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -66,7 +66,7 @@ struct blind_confirmation struct output { string label; - string pub_key; + public_key_type pub_key; stealth_confirmation::memo_data decrypted_memo; stealth_confirmation confirmation; string confirmation_receipt; @@ -82,7 +82,9 @@ struct blind_balance public_key_type from; ///< the account this balance came from public_key_type to; ///< the account this balance is logically associated with public_key_type one_time_key; ///< used to derive the authority key and blinding factor + fc::sha256 blinding_factor; fc::ecc::commitment_type commitment; + bool used = false; }; struct key_label @@ -589,9 +591,14 @@ class wallet_api /** These methods are used for stealth transfers */ ///@{ /** - * This method can be used + * This method can be used to set the label for a public key + * + * @note No two keys can have the same label. + * + * @return true if the label was set, otherwise false */ - void set_key_label( public_key_type, string label ); + bool set_key_label( public_key_type, string label ); + string get_key_label( public_key_type )const; /** * Generates a new blind account for the given brain key and assigns it the given label. @@ -602,7 +609,7 @@ class wallet_api * @return the total balance of all blinded commitments that can be claimed by the * given account key or label */ - asset get_blind_balance( string key_or_label )const; + vector get_blind_balances( string key_or_label ); /** @return all blind accounts */ map get_blind_accounts()const; /** @return all blind accounts for which this wallet has the private key */ @@ -626,12 +633,18 @@ class wallet_api * Transfers a public balance from @from to one or more blinded balances using a * stealth transfer. */ - blind_confirmation transfer_to_blind( string from, + blind_confirmation transfer_to_blind( string from_account_id_or_name, string asset_symbol, /** map from key or label to amount */ map to_amounts, bool broadcast = false ); + blind_confirmation blind_transfer( string from_key_or_label, + string to_key_or_label, + string amount, + string symbol, + bool broadcast = false ); + /** Place a limit order attempting to sell one asset for another. * * Buying and selling are the same operation on Graphene; if you want to buy BTS @@ -1122,7 +1135,7 @@ class wallet_api } } FC_REFLECT( graphene::wallet::key_label, (label)(key) ) -FC_REFLECT( graphene::wallet::blind_balance, (amount)(from)(to)(one_time_key)(commitment) ) +FC_REFLECT( graphene::wallet::blind_balance, (amount)(from)(to)(one_time_key)(commitment)(used) ) FC_REFLECT( graphene::wallet::blind_confirmation::output, (label)(pub_key)(decrypted_memo)(confirmation)(confirmation_receipt) ) FC_REFLECT( graphene::wallet::blind_confirmation, (trx)(outputs) ) @@ -1223,4 +1236,13 @@ FC_API( graphene::wallet::wallet_api, (dbg_make_uia) (dbg_make_mia) (flood_network) + (set_key_label) + (get_key_label) + (get_public_key) + (get_blind_accounts) + (get_my_blind_accounts) + (get_blind_balances) + (create_blind_account) + (transfer_to_blind) + (receive_blind_transfer) ) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index cfaffb66..0615a0f6 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1908,6 +1908,9 @@ void operation_printer::operator()(const asset_create_operation& op) const } }}} + + + namespace graphene { namespace wallet { wallet_api::wallet_api(fc::api rapi) @@ -2573,7 +2576,379 @@ signed_transaction wallet_api::borrow_asset(string seller_name, string amount_to FC_ASSERT(!is_locked()); return my->borrow_asset(seller_name, amount_to_sell, asset_symbol, amount_of_collateral, broadcast); } -} } + +string wallet_api::get_key_label( public_key_type key )const +{ + auto key_itr = my->_wallet.labeled_keys.get().find(key); + if( key_itr != my->_wallet.labeled_keys.get().end() ) + return key_itr->label; + return string(); +} + +public_key_type wallet_api::get_public_key( string label )const +{ + auto key_itr = my->_wallet.labeled_keys.get().find(label); + if( key_itr != my->_wallet.labeled_keys.get().end() ) + return key_itr->key; + return public_key_type(); +} + +bool wallet_api::set_key_label( public_key_type key, string label ) +{ + auto result = my->_wallet.labeled_keys.insert( key_label{label,key} ); + if( result.second ) return true; + + auto key_itr = my->_wallet.labeled_keys.get().find(key); + auto label_itr = my->_wallet.labeled_keys.get().find(label); + if( label_itr == my->_wallet.labeled_keys.get().end() ) + { + if( key_itr != my->_wallet.labeled_keys.get().end() ) + return my->_wallet.labeled_keys.get().modify( key_itr, [&]( key_label& obj ){ obj.label = label; } ); + } + return false; +} +map wallet_api::get_blind_accounts()const +{ + map result; + for( const auto& item : my->_wallet.labeled_keys ) + result[item.label] = item.key; + return result; +} +map wallet_api::get_my_blind_accounts()const +{ + FC_ASSERT( !is_locked() ); + map result; + for( const auto& item : my->_wallet.labeled_keys ) + { + if( my->_keys.find(item.key) != my->_keys.end() ) + result[item.label] = item.key; + } + return result; +} + +public_key_type wallet_api::create_blind_account( string label, string brain_key ) +{ + FC_ASSERT( !is_locked() ); + + auto label_itr = my->_wallet.labeled_keys.get().find(label); + if( label_itr != my->_wallet.labeled_keys.get().end() ) + FC_ASSERT( !"Key with label already exists" ); + brain_key = fc::trim_and_normalize_spaces( brain_key ); + auto secret = fc::sha256::hash( brain_key.c_str(), brain_key.size() ); + auto priv_key = fc::ecc::private_key::regenerate( secret ); + public_key_type pub_key = priv_key.get_public_key(); + + FC_ASSERT( set_key_label( pub_key, label ) ); + + my->_keys[pub_key] = graphene::utilities::key_to_wif( priv_key ); + + save_wallet_file(); + return pub_key; +} + +vector wallet_api::get_blind_balances( string key_or_label ) +{ + vector result; + + auto pub_key = get_public_key( key_or_label ); + if( pub_key == public_key_type() ) + { + try { + pub_key = variant(key_or_label).as(); + } catch ( ... ) + { + FC_ASSERT( false, "Unknown key or label '${v}'", ("v",key_or_label) ); + } + } + + for( auto& asset_itr : my->_wallet.blinded_balances ) + { + 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); + } + return result; +} + +blind_confirmation wallet_api::blind_transfer( string from_key_or_label, + string to_key_or_label, + string amount_in, + string symbol, + bool broadcast ) +{ try { + FC_ASSERT( !is_locked() ); + blind_confirmation conf; + public_key_type from_key = get_public_key(from_key_or_label); + public_key_type to_key = get_public_key(to_key_or_label); + + fc::optional asset_obj = get_asset(symbol); + FC_ASSERT(asset_obj.valid(), "Could not find asset matching ${asset}", ("asset", symbol)); + + blind_transfer_operation blind_tr; + blind_tr.outputs.resize(2); + + auto fees = my->_remote_db->get_global_properties().parameters.current_fees; + + auto amount = asset_obj->amount_from_string(amount_in); + + 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() ) + { + 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 + { + // 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; + } + } + } + } + + FC_ASSERT( total_amount >= amount+blind_tr.fee, "Insufficent Balance", ("available",total_amount)("amount",amount)("fee",blind_tr.fee) ); + + auto one_time_key = fc::ecc::private_key::generate(); + auto secret = one_time_key.get_shared_secret( to_key ); + auto child = fc::sha256::hash( secret ); + auto nonce = fc::sha256::hash( one_time_key.get_secret() ); + auto blind_factor = fc::sha256::hash( child ); + + auto from_secret = one_time_key.get_shared_secret( from_key ); + auto from_child = fc::sha256::hash( from_secret ); + auto from_nonce = fc::sha256::hash( nonce ); + + auto change = total_amount - amount - blind_tr.fee; + fc::sha256 change_blind_factor; + fc::sha256 to_blind_factor; + if( change.amount > 0 ) + { + blinding_factors.push_back( blind_factor ); + change_blind_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() - 1 ); + } + else // change == 0 + { + blind_tr.outputs.resize(1); + blind_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() ); + blinding_factors.push_back( blind_factor ); + } + fc::ecc::public_key from_pub_key = from_key; + fc::ecc::public_key to_pub_key = to_key; + + blind_output out; + out.owner = authority( 1, public_key_type( from_pub_key.child( from_child ) ), 1 ); + out.commitment = fc::ecc::blind( change_blind_factor, amount.amount.value ); + + blind_confirmation confirm; + + if( blind_tr.outputs.size() > 1 ) + { + out.range_proof = fc::ecc::range_proof_sign( 0, out.commitment, blind_factor, nonce, 0, 0, amount.amount.value ); + + blind_output change_out; + change_out.owner = authority( 1, public_key_type( to_pub_key.child( child ) ), 1 ); + change_out.commitment = fc::ecc::blind( change_blind_factor, change.amount.value ); + change_out.range_proof = fc::ecc::range_proof_sign( 0, out.commitment, change_blind_factor, from_nonce, 0, 0, change.amount.value ); + blind_tr.outputs[1] = change_out; + + + blind_confirmation::output conf_output; + conf_output.label = from_key_or_label; + conf_output.pub_key = from_key; + conf_output.decrypted_memo.from = from_key; + conf_output.decrypted_memo.amount = change; + conf_output.decrypted_memo.blinding_factor = change_blind_factor; + conf_output.decrypted_memo.commitment = change_out.commitment; + conf_output.decrypted_memo.check = from_secret._hash[0]; + 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( secret, fc::raw::pack( conf_output.decrypted_memo ) ); + conf_output.confirmation_receipt = conf_output.confirmation; + + confirm.outputs.push_back( conf_output ); + } + blind_tr.outputs[0] = out; + + blind_confirmation::output conf_output; + conf_output.label = to_key_or_label; + conf_output.pub_key = to_key; + conf_output.decrypted_memo.from = from_key; + conf_output.decrypted_memo.amount = amount; + conf_output.decrypted_memo.blinding_factor = blind_factor; + conf_output.decrypted_memo.commitment = out.commitment; + conf_output.decrypted_memo.check = secret._hash[0]; + 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.confirmation_receipt = conf_output.confirmation; + + confirm.outputs.push_back( conf_output ); + + /** 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; } ); + + confirm.trx.validate(); + confirm.trx = sign_transaction(confirm.trx, broadcast); + + return conf; +} FC_CAPTURE_AND_RETHROW( (from_key_or_label)(to_key_or_label)(amount_in)(symbol)(broadcast) ) } + + + +/** + * Transfers a public balance from @from to one or more blinded balances using a + * stealth transfer. + */ +blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name, + string asset_symbol, + /** map from key or label to amount */ + map to_amounts, + bool broadcast ) +{ try { + FC_ASSERT( !is_locked() ); + + blind_confirmation confirm; + account_object from_account = my->get_account(from_account_id_or_name); + + fc::optional asset_obj = get_asset(asset_symbol); + FC_ASSERT(asset_obj, "Could not find asset matching ${asset}", ("asset", asset_symbol)); + + transfer_to_blind_operation bop; + bop.from = from_account.id; + + vector blinding_factors; + + asset total_amount = asset_obj->amount(0); + + for( auto item : to_amounts ) + { + auto one_time_key = fc::ecc::private_key::generate(); + auto to_key = get_public_key( item.first ); + auto secret = one_time_key.get_shared_secret( to_key ); + auto child = fc::sha256::hash( secret ); + auto nonce = fc::sha256::hash( one_time_key.get_secret() ); + auto blind_factor = fc::sha256::hash( child ); + + blinding_factors.push_back( blind_factor ); + + auto amount = asset_obj->amount_from_string(item.second); + total_amount += amount; + + + fc::ecc::public_key to_pub_key = to_key; + blind_output out; + out.owner = authority( 1, public_key_type( to_pub_key.child( child ) ), 1 ); + out.commitment = fc::ecc::blind( blind_factor, amount.amount.value ); + if( to_amounts.size() > 1 ) + out.range_proof = fc::ecc::range_proof_sign( 0, out.commitment, blind_factor, nonce, 0, 0, amount.amount.value ); + + + blind_confirmation::output conf_output; + conf_output.label = item.first; + conf_output.pub_key = to_key; + conf_output.decrypted_memo.amount = amount; + conf_output.decrypted_memo.blinding_factor = blind_factor; + conf_output.decrypted_memo.commitment = out.commitment; + conf_output.decrypted_memo.check = secret._hash[0]; + 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.confirmation_receipt = conf_output.confirmation; + + confirm.outputs.push_back( conf_output ); + + bop.outputs.push_back(out); + } + bop.amount = total_amount; + bop.blinding_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() ); + + /** commitments must be in sorted order */ + std::sort( bop.outputs.begin(), bop.outputs.end(), + [&]( const blind_output& a, const blind_output& b ){ return a.commitment < b.commitment; } ); + + confirm.trx.operations.push_back( bop ); + 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); + 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 ) +{ + FC_ASSERT( !is_locked() ); + stealth_confirmation conf(confirmation_receipt); + FC_ASSERT( conf.to ); + + 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 ); + + auto secret = to_priv_key->get_shared_secret( conf.one_time_key ); + auto child = fc::sha256::hash( secret ); + //auto blind_factor = fc::sha256::hash( child ); + + auto plain_memo = fc::aes_decrypt( secret, conf.encrypted_memo ); + auto memo = fc::raw::unpack( plain_memo ); + + auto bbal = my->_remote_db->get_blinded_balances( {memo.commitment} ); + FC_ASSERT( bbal.size(), "commitment not found in blockchain", ("memo",memo) ); + + blind_balance bal; + bal.amount = memo.amount; + bal.to = *conf.to; + if( memo.from ) bal.from = *memo.from; + bal.one_time_key = conf.one_time_key; + bal.blinding_factor = memo.blinding_factor; + bal.commitment = memo.commitment; + bal.used = false; + + // 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 ); + + return memo; +} + +} } // graphene::wallet void fc::to_variant(const account_multi_index_type& accts, fc::variant& vo) {