diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index d751c31a..5539ba4c 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -684,6 +685,13 @@ namespace graphene { namespace app { result.reserve( impacted.size() ); for( auto& item : impacted ) result.emplace_back(item); break; + } case impl_blinded_balance_object_type:{ + const auto& aobj = dynamic_cast(obj); + assert( aobj != nullptr ); + result.reserve( aobj->owner.account_auths.size() ); + for( const auto& a : aobj->owner.account_auths ) + result.push_back( a.first ); + break; } case impl_block_summary_object_type:{ } case impl_account_transaction_history_object_type:{ } case impl_witness_schedule_object_type: { @@ -1088,5 +1096,18 @@ namespace graphene { namespace app { _db.get_global_properties().parameters.max_authority_depth ); return true; } + vector database_api::get_blinded_balances( const flat_set& commitments )const + { + vector result; result.reserve(commitments.size()); + const auto& bal_idx = _db.get_index_type(); + const auto& by_commitment_idx = bal_idx.indices().get(); + for( const auto& c : commitments ) + { + auto itr = by_commitment_idx.find( c ); + if( itr != by_commitment_idx.end() ) + result.push_back( *itr ); + } + return result; + } } } // graphene::app diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index 6039a85e..66ce7c31 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -319,6 +320,12 @@ namespace graphene { namespace app { bool verify_authority( const signed_transaction& trx )const; + /** + * @return the set of blinded balance objects by commitment ID + */ + vector get_blinded_balances( const flat_set& commitments )const; + + private: /** called every time a block is applied to report the objects that were changed */ void on_objects_changed(const vector& ids); diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 3ad4183a..029c5877 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -21,6 +21,7 @@ add_library( graphene_chain protocol/transaction.cpp protocol/block.cpp protocol/fee_schedule.cpp + protocol/confidential.cpp pts_address.cpp @@ -37,6 +38,7 @@ add_library( graphene_chain vesting_balance_evaluator.cpp withdraw_permission_evaluator.cpp worker_evaluator.cpp + confidential_evaluator.cpp account_object.cpp asset_object.cpp diff --git a/libraries/chain/confidential_evaluator.cpp b/libraries/chain/confidential_evaluator.cpp new file mode 100644 index 00000000..4e293846 --- /dev/null +++ b/libraries/chain/confidential_evaluator.cpp @@ -0,0 +1,137 @@ +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result transfer_to_blind_evaluator::do_evaluate( const transfer_to_blind_operation& o ) +{ try { + const auto& d = db(); + + const auto& atype = o.amount.asset_id(db()); + FC_ASSERT( atype.allow_confidential() ); + FC_ASSERT( !atype.is_transfer_restricted() ); + FC_ASSERT( !atype.enforce_white_list() ); + + for( const auto& out : o.outputs ) + { + for( const auto& a : out.owner.account_auths ) + a.first(d); // verify all accounts exist and are valid + } + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + + +void_result transfer_to_blind_evaluator::do_apply( const transfer_to_blind_operation& o ) +{ try { + db().adjust_balance( o.from, -o.amount ); + + const auto& add = o.amount.asset_id(db()).dynamic_asset_data_id(db()); // verify fee is a legit asset + db().modify( add, [&]( asset_dynamic_data_object& obj ){ + obj.confidential_supply += o.amount.amount; + FC_ASSERT( obj.confidential_supply >= 0 ); + }); + for( const auto& out : o.outputs ) + { + db().create( [&]( blinded_balance_object& obj ){ + obj.asset_id = o.amount.asset_id; + obj.owner = out.owner; + obj.commitment = out.commitment; + }); + } + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + + +void_result transfer_from_blind_evaluator::do_evaluate( const transfer_from_blind_operation& o ) +{ try { + const auto& d = db(); + o.fee.asset_id(d); // verify fee is a legit asset + const auto& bbi = d.get_index_type(); + const auto& cidx = bbi.indices().get(); + for( const auto& in : o.inputs ) + { + auto itr = cidx.find( in.commitment ); + FC_ASSERT( itr != cidx.end() ); + FC_ASSERT( itr->asset_id == o.fee.asset_id ); + FC_ASSERT( itr->owner == in.owner ); + } + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + +void_result transfer_from_blind_evaluator::do_apply( const transfer_from_blind_operation& o ) +{ try { + db().adjust_balance( o.fee_payer(), o.fee ); + db().adjust_balance( o.to, o.amount ); + const auto& bbi = db().get_index_type(); + const auto& cidx = bbi.indices().get(); + for( const auto& in : o.inputs ) + { + auto itr = cidx.find( in.commitment ); + FC_ASSERT( itr != cidx.end() ); + db().remove( *itr ); + } + const auto& add = o.amount.asset_id(db()).dynamic_asset_data_id(db()); // verify fee is a legit asset + db().modify( add, [&]( asset_dynamic_data_object& obj ){ + obj.confidential_supply -= o.amount.amount + o.fee.amount; + FC_ASSERT( obj.confidential_supply >= 0 ); + }); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + + + + + +void_result blind_transfer_evaluator::do_evaluate( const blind_transfer_operation& o ) +{ try { + const auto& d = db(); + o.fee.asset_id(db()); // verify fee is a legit asset + const auto& bbi = db().get_index_type(); + const auto& cidx = bbi.indices().get(); + for( const auto& out : o.outputs ) + { + for( const auto& a : out.owner.account_auths ) + a.first(d); // verify all accounts exist and are valid + } + for( const auto& in : o.inputs ) + { + auto itr = cidx.find( in.commitment ); + GRAPHENE_ASSERT( itr != cidx.end(), blind_transfer_unknown_commitment, "", ("commitment",in.commitment) ); + FC_ASSERT( itr->asset_id == o.fee.asset_id ); + FC_ASSERT( itr->owner == in.owner ); + } + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + +void_result blind_transfer_evaluator::do_apply( const blind_transfer_operation& o ) +{ try { + db().adjust_balance( o.fee_payer(), o.fee ); // deposit the fee to the temp account + const auto& bbi = db().get_index_type(); + const auto& cidx = bbi.indices().get(); + for( const auto& in : o.inputs ) + { + auto itr = cidx.find( in.commitment ); + GRAPHENE_ASSERT( itr != cidx.end(), blind_transfer_unknown_commitment, "", ("commitment",in.commitment) ); + db().remove( *itr ); + } + for( const auto& out : o.outputs ) + { + db().create( [&]( blinded_balance_object& obj ){ + obj.asset_id = o.fee.asset_id; + obj.owner = out.owner; + obj.commitment = out.commitment; + }); + } + const auto& add = o.fee.asset_id(db()).dynamic_asset_data_id(db()); + db().modify( add, [&]( asset_dynamic_data_object& obj ){ + obj.confidential_supply -= o.fee.amount; + FC_ASSERT( obj.confidential_supply >= 0 ); + }); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + + +} } // graphene::chain diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index dcd014de..3cbd7dcc 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include @@ -150,6 +151,9 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -177,6 +181,7 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); //Implementation object indexes add_index< primary_index >(); diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 01376bc5..1e763ff4 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -56,6 +56,7 @@ namespace graphene { namespace chain { /// The number of shares currently in existence share_type current_supply; + share_type confidential_supply; ///< total asset held in confidential balances share_type accumulated_fees; ///< fees accumulate to be paid out over time share_type fee_pool; ///< in core asset }; @@ -91,6 +92,7 @@ namespace graphene { namespace chain { /// @return true if this asset may only be transferred to/from the issuer or market orders bool is_transfer_restricted()const { return options.flags & transfer_restricted; } bool can_override()const { return options.flags & override_authority; } + bool allow_confidential()const { return !(options.flags & asset_issuer_permission_flags::disable_confidential); } /// Helper function to get an asset object with the given amount in this asset's type asset amount(share_type a)const { return asset(a, id); } @@ -234,7 +236,7 @@ namespace graphene { namespace chain { } } // graphene::chain FC_REFLECT_DERIVED( graphene::chain::asset_dynamic_data_object, (graphene::db::object), - (current_supply)(accumulated_fees)(fee_pool) ) + (current_supply)(confidential_supply)(accumulated_fees)(fee_pool) ) FC_REFLECT_DERIVED( graphene::chain::asset_bitasset_data_object, (graphene::db::object), (feeds) diff --git a/libraries/chain/include/graphene/chain/confidential_evaluator.hpp b/libraries/chain/include/graphene/chain/confidential_evaluator.hpp new file mode 100644 index 00000000..b22ac23b --- /dev/null +++ b/libraries/chain/include/graphene/chain/confidential_evaluator.hpp @@ -0,0 +1,71 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + +/** + * @class blinded_balance_object + * @brief tracks a blinded balance commitment + * @ingroup object + * @ingroup protocol + */ +class blinded_balance_object : public graphene::db::abstract_object +{ + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_blinded_balance_object_type; + + fc::ecc::commitment_type commitment; + asset_id_type asset_id; + authority owner; +}; + +struct by_asset; +struct by_owner; +struct by_commitment; + +/** + * @ingroup object_index + */ +typedef multi_index_container< + blinded_balance_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, member > + > +> blinded_balance_object_multi_index_type; +typedef generic_index blinded_balance_index; + + +class transfer_to_blind_evaluator : public evaluator +{ + public: + typedef transfer_to_blind_operation operation_type; + + void_result do_evaluate( const transfer_to_blind_operation& o ); + void_result do_apply( const transfer_to_blind_operation& o ) ; +}; + +class transfer_from_blind_evaluator : public evaluator +{ + public: + typedef transfer_from_blind_operation operation_type; + + void_result do_evaluate( const transfer_from_blind_operation& o ); + void_result do_apply( const transfer_from_blind_operation& o ) ; +}; + +class blind_transfer_evaluator : public evaluator +{ + public: + typedef blind_transfer_operation operation_type; + + void_result do_evaluate( const blind_transfer_operation& o ); + void_result do_apply( const blind_transfer_operation& o ) ; +}; + +} } // namespace graphene::chain + +FC_REFLECT( graphene::chain::blinded_balance_object, (commitment)(asset_id)(owner) ) diff --git a/libraries/chain/include/graphene/chain/exceptions.hpp b/libraries/chain/include/graphene/chain/exceptions.hpp index 4d8a7af4..0e474548 100644 --- a/libraries/chain/include/graphene/chain/exceptions.hpp +++ b/libraries/chain/include/graphene/chain/exceptions.hpp @@ -143,6 +143,10 @@ namespace graphene { namespace chain { GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( not_permitted, override_transfer, 1, "not permitted" ) + + GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( blind_transfer ); + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( unknown_commitment, blind_transfer, 1, "Attempting to claim an unknown prior commitment" ); + /* FC_DECLARE_DERIVED_EXCEPTION( addition_overflow, graphene::chain::chain_exception, 30002, "addition overflow" ) FC_DECLARE_DERIVED_EXCEPTION( subtraction_overflow, graphene::chain::chain_exception, 30003, "subtraction overflow" ) diff --git a/libraries/chain/include/graphene/chain/protocol/authority.hpp b/libraries/chain/include/graphene/chain/protocol/authority.hpp index f00009a6..28e7c7c2 100644 --- a/libraries/chain/include/graphene/chain/protocol/authority.hpp +++ b/libraries/chain/include/graphene/chain/protocol/authority.hpp @@ -83,6 +83,14 @@ namespace graphene { namespace chain { result.push_back(k.first); return result; } + + friend bool operator == ( const authority& a, const authority& b ) + { + return (a.weight_threshold == b.weight_threshold) && + (a.account_auths == b.account_auths) && + (a.key_auths == b.key_auths) && + (a.address_auths == b.address_auths); + } uint32_t num_auths()const { return account_auths.size() + key_auths.size() + address_auths.size(); } void clear() { account_auths.clear(); key_auths.clear(); } diff --git a/libraries/chain/include/graphene/chain/protocol/confidential.hpp b/libraries/chain/include/graphene/chain/protocol/confidential.hpp new file mode 100644 index 00000000..2fc37555 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/confidential.hpp @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2015, Cryptonomex, Inc. + * All rights reserved. + */ + +#pragma once +#include + +namespace graphene { namespace chain { + +using fc::ecc::blind_factor_type; + +/** + * @defgroup stealth Stealth Transfer + * @brief Operations related to stealth transfer of value + * + * Stealth Transfers enable users to maintain their finanical privacy against even + * though all transactions are public. Every account has three balances: + * + * 1. Public Balance - everyone can see the balance changes and the parties involved + * 2. Blinded Balance - everyone can see who is transacting but not the amounts involved + * 3. Stealth Balance - both the amounts and parties involved are obscured + * + * Account owners may set a flag that allows their account to receive(or not) transfers of these kinds + * Asset issuers can enable or disable the use of each of these types of accounts. + * + * Using the "temp account" which has no permissions required, users can transfer a + * stealth balance to the temp account and then use the temp account to register a new + * account. In this way users can use stealth funds to create anonymous accounts with which + * they can perform other actions that are not compatible with blinded balances (such as market orders) + * + * @section referral_program Referral Progam + * + * Stealth transfers that do not specify any account id cannot pay referral fees so 100% of the + * transaction fee is paid to the network. + * + * @section transaction_fees Fees + * + * Stealth transfers can have an arbitrarylly large size and therefore the transaction fee for + * stealth transfers is based purley on the data size of the transaction. + */ +///@{ + +/** + * @ingroup stealth + * This data is encrypted and stored in the + * encrypted memo portion of the blind output. + */ +struct blind_memo +{ + account_id_type from; + share_type amount; + string message; + /** set to the first 4 bytes of the shared secret + * used to encrypt the memo. Used to verify that + * decryption was successful. + */ + uint32_t check= 0; +}; + +/** + * @ingroup stealth + */ +struct blind_input +{ + fc::ecc::commitment_type commitment; + /** provided to maintain the invariant that all authority + * required by an operation is explicit in the operation. Must + * match blinded_balance_id->owner + */ + authority owner; +}; + +/** + * When sending a stealth tranfer we assume users are unable to scan + * the full blockchain; therefore, payments require confirmation data + * to be passed out of band. We assume this out-of-band channel is + * not secure and therefore the contents of the confirmation must be + * encrypted. + */ +struct stealth_confirmation +{ + struct memo_data + { + public_key_type from; + asset amount; + fc::ecc::commitment_type commitment; + uint32_t check = 0; + }; + + /** + * Packs *this then encodes as base58 encoded string. + */ + operator string()const; + /** + * Unpacks from a base58 string + */ + stealth_confirmation( const std::string& base58 ); + stealth_confirmation(){} + + public_key_type one_time_key; + vector encrypted_memo; +}; + +/** + * @class blind_output + * @brief Defines data required to create a new blind commitment + * @ingroup stealth + * + * The blinded output that must be proven to be greater than 0 + */ +struct blind_output +{ + fc::ecc::commitment_type commitment; + /** only required if there is more than one blind output */ + range_proof_type range_proof; + authority owner; + optional stealth_memo; +}; + + +/** + * @class transfer_to_blind_operation + * @ingroup stealth + * @brief Converts public account balance to a blinded or stealth balance + */ +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; + }; + + + asset fee; + asset amount; + account_id_type from; + blind_factor_type blinding_factor; + vector outputs; + + account_id_type fee_payer()const { return from; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& )const; + + void get_impacted_accounts( flat_set& i )const + { + i.insert(from); + for( const auto& out : outputs ) + add_authority_accounts( i, out.owner ); + } +}; + +/** + * @ingroup stealth + * @brief Converts blinded/stealth balance to a public account balance + */ +struct transfer_from_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 + }; + + asset fee; + asset amount; + account_id_type to; + blind_factor_type blinding_factor; + vector inputs; + + account_id_type fee_payer()const { return GRAPHENE_TEMP_ACCOUNT; } + void validate()const; + + void get_impacted_accounts( flat_set& i )const + { + i.insert(to); + for( const auto& in : inputs ) + add_authority_accounts( i, in.owner ); + } + void get_required_authorities( vector& a )const + { + for( const auto& in : inputs ) + a.push_back( in.owner ); + } +}; + +/** + * @ingroup stealth + * @brief Transfers from blind to blind + * + * There are two ways to transfer value while maintaining privacy: + * 1. account to account with amount kept secret + * 2. stealth transfers with amount sender/receiver kept secret + * + * When doing account to account transfers, everyone with access to the + * memo key can see the amounts, but they will not have access to the funds. + * + * When using stealth transfers the same key is used for control and reading + * the memo. + * + * This operation is more expensive than a normal transfer and has + * a fee proportional to the size of the operation. + * + * All assets in a blind transfer must be of the same type: fee.asset_id + * The fee_payer is the temp account and can be funded from the blinded values. + * + * Using this operation you can transfer from an account and/or blinded balances + * to an account and/or blinded balances. + * + * Stealth Transfers: + * + * Assuming Receiver has key pair R,r and has shared public key R with Sender + * Assuming Sender has key pair S,s + * Generate one time key pair O,o as s.child(nonce) where nonce can be inferred from transaction + * Calculate secret V = o*R + * blinding_factor = sha256(V) + * memo is encrypted via aes of V + * owner = R.child(sha256(blinding_factor)) + * + * Sender gives Receiver output ID to complete the payment. + * + * This process can also be used to send money to a cold wallet without having to + * pre-register any accounts. + * + * Outputs are assigned the same IDs as the inputs until no more input IDs are available, + * in which case a the return value will be the *first* ID allocated for an output. Additional + * output IDs are allocated sequentially thereafter. If there are fewer outputs than inputs + * then the input IDs are freed and never used again. + */ +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; + vector inputs; + vector outputs; + + /** graphene TEMP account */ + account_id_type fee_payer()const; + void validate()const; + share_type calculate_fee( const fee_parameters_type& k )const; + + void get_impacted_accounts( flat_set& i )const + { + for( const auto& in : inputs ) + add_authority_accounts( i, in.owner ); + for( const auto& out : outputs ) + add_authority_accounts( i, out.owner ); + } + void get_required_authorities( vector& a )const + { + for( const auto& in : inputs ) + a.push_back( in.owner ); + } +}; + +///@} endgroup stealth +} } // graphene::chain + +FC_REFLECT( graphene::chain::stealth_confirmation, + (one_time_key)(encrypted_memo) ) + +FC_REFLECT( graphene::chain::stealth_confirmation::memo_data, + (from)(amount)(commitment)(check) ); + +FC_REFLECT( graphene::chain::blind_memo, + (from)(amount)(message)(check) ) +FC_REFLECT( graphene::chain::blind_input, + (commitment)(owner) ) +FC_REFLECT( graphene::chain::blind_output, + (commitment)(range_proof)(owner)(stealth_memo) ) +FC_REFLECT( graphene::chain::transfer_to_blind_operation, + (fee)(amount)(from)(blinding_factor)(outputs) ) +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_from_blind_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::blind_transfer_operation::fee_parameters_type, (fee)(price_per_output)(price_per_kb) ) + diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index dac09e6a..25fff129 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -59,7 +60,10 @@ namespace graphene { namespace chain { custom_operation, assert_operation, balance_claim_operation, - override_transfer_operation + override_transfer_operation, + transfer_to_blind_operation, + blind_transfer_operation, + transfer_from_blind_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index f78b93cf..8ef2af12 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -84,10 +84,11 @@ namespace graphene { namespace chain { override_authority = 0x04, /**< issuer may transfer asset back to himself */ transfer_restricted = 0x08, /**< require the issuer to be one party to every transfer */ disable_force_settle = 0x10, /**< disable force settling */ - global_settle = 0x20 /**< allow the bitasset issuer to force a global settling -- this may be set in permissions, but not flags */ + global_settle = 0x20, /**< allow the bitasset issuer to force a global settling -- this may be set in permissions, but not flags */ + disable_confidential = 0x40 /**< allow the asset to be used with confidential transactions */ }; - const static uint32_t ASSET_ISSUER_PERMISSION_MASK = charge_market_fee|white_list|override_authority|transfer_restricted|disable_force_settle|global_settle; - const static uint32_t UIA_ASSET_ISSUER_PERMISSION_MASK = charge_market_fee|white_list|override_authority|transfer_restricted; + const static uint32_t ASSET_ISSUER_PERMISSION_MASK = charge_market_fee|white_list|override_authority|transfer_restricted|disable_force_settle|global_settle|disable_confidential; + const static uint32_t UIA_ASSET_ISSUER_PERMISSION_MASK = charge_market_fee|white_list|override_authority|transfer_restricted|disable_confidential; enum reserved_spaces { @@ -138,7 +139,8 @@ namespace graphene { namespace chain { impl_transaction_object_type, impl_block_summary_object_type, impl_account_transaction_history_object_type, - impl_witness_schedule_object_type + impl_witness_schedule_object_type, + impl_blinded_balance_object_type }; enum meta_info_object_type @@ -386,6 +388,7 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, (impl_block_summary_object_type) (impl_account_transaction_history_object_type) (impl_witness_schedule_object_type) + (impl_blinded_balance_object_type) ) FC_REFLECT_ENUM( graphene::chain::meta_info_object_type, (meta_account_object_type)(meta_asset_object_type) ) @@ -417,4 +420,4 @@ FC_REFLECT_TYPENAME( graphene::chain::account_transaction_history_id_type ) FC_REFLECT_TYPENAME( graphene::chain::witness_schedule_id_type ) FC_REFLECT( graphene::chain::void_t, ) -FC_REFLECT_ENUM( graphene::chain::asset_issuer_permission_flags, (charge_market_fee)(white_list)(transfer_restricted)(override_authority)(disable_force_settle)(global_settle) ) +FC_REFLECT_ENUM( graphene::chain::asset_issuer_permission_flags, (charge_market_fee)(white_list)(transfer_restricted)(override_authority)(disable_force_settle)(global_settle)(disable_confidential) ) diff --git a/libraries/chain/protocol/confidential.cpp b/libraries/chain/protocol/confidential.cpp new file mode 100644 index 00000000..7e4df25f --- /dev/null +++ b/libraries/chain/protocol/confidential.cpp @@ -0,0 +1,122 @@ +#include +#include +#include + +namespace graphene { namespace chain { + +void transfer_to_blind_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0 ); + FC_ASSERT( amount.amount > 0 ); + + vector in; + vector out(outputs.size()); + int64_t net_public = amount.amount.value; + for( uint32_t i = 0; i < out.size(); ++i ) + { + out[i] = outputs[i].commitment; + /// require all outputs to be sorted prevents duplicates AND prevents implementations + /// from accidentally leaking information by how they arrange commitments. + if( i > 0 ) FC_ASSERT( out[i-1] < out[i], "all outputs must be sorted by commitment id" ); + FC_ASSERT( !outputs[i].owner.is_impossible() ); + } + FC_ASSERT( out.size(), "there must be at least one output" ); + + auto public_c = fc::ecc::blind(blinding_factor,net_public); + + FC_ASSERT( fc::ecc::verify_sum( {public_c}, out, 0 ), "", ("net_public",net_public) ); + + if( outputs.size() > 1 ) + { + for( auto out : outputs ) + { + auto info = fc::ecc::range_get_info( out.range_proof ); + FC_ASSERT( info.max_value <= GRAPHENE_MAX_SHARE_SUPPLY ); + } + } +} + +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 ); +} + + +void transfer_from_blind_operation::validate()const +{ + FC_ASSERT( amount.amount > 0 ); + FC_ASSERT( fee.amount >= 0 ); + FC_ASSERT( inputs.size() > 0 ); + FC_ASSERT( amount.asset_id == fee.asset_id ); + + + vector in(inputs.size()); + vector out; + int64_t net_public = fee.amount.value + amount.amount.value; + out.push_back( fc::ecc::blind( blinding_factor, net_public ) ); + for( uint32_t i = 0; i < in.size(); ++i ) + { + in[i] = inputs[i].commitment; + /// by requiring all inputs to be sorted we also prevent duplicate commitments on the input + if( i > 0 ) FC_ASSERT( in[i-1] < in[i], "all inputs must be sorted by commitment id" ); + } + FC_ASSERT( in.size(), "there must be at least one input" ); + FC_ASSERT( fc::ecc::verify_sum( in, out, 0 ) ); +} + + +/** + * If fee_payer = temp_account_id, then the fee is paid by the surplus balance of inputs-outputs and + * 100% of the fee goes to the network. + */ +account_id_type blind_transfer_operation::fee_payer()const +{ + return GRAPHENE_TEMP_ACCOUNT; +} + + +/** + * This method can be computationally intensive because it verifies that input commitments - output commitments add up to 0 + */ +void blind_transfer_operation::validate()const +{ try { + vector in(inputs.size()); + vector out(outputs.size()); + int64_t net_public = fee.amount.value;//from_amount.value - to_amount.value; + for( uint32_t i = 0; i < in.size(); ++i ) + { + in[i] = inputs[i].commitment; + /// by requiring all inputs to be sorted we also prevent duplicate commitments on the input + if( i > 0 ) FC_ASSERT( in[i-1] < in[i] ); + } + for( uint32_t i = 0; i < out.size(); ++i ) + { + out[i] = outputs[i].commitment; + if( i > 0 ) FC_ASSERT( out[i-1] < out[i] ); + FC_ASSERT( !outputs[i].owner.is_impossible() ); + } + FC_ASSERT( in.size(), "there must be at least one input" ); + FC_ASSERT( fc::ecc::verify_sum( in, out, net_public ), "", ("net_public", net_public) ); + + if( outputs.size() > 1 ) + { + for( auto out : outputs ) + { + auto info = fc::ecc::range_get_info( out.range_proof ); + FC_ASSERT( info.max_value <= GRAPHENE_MAX_SHARE_SUPPLY ); + } + } +} FC_CAPTURE_AND_RETHROW( (*this) ) } + +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 );; +} + + + + + + + +} } // graphene::chain diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index b7075ab4..296292bc 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -192,7 +192,7 @@ void database_fixture::verify_asset_supplies( const database& db ) } BOOST_CHECK_EQUAL( core_in_orders.value , reported_core_in_orders.value ); - BOOST_CHECK_EQUAL( total_balances[asset_id_type()].value , core_asset_data.current_supply.value ); + BOOST_CHECK_EQUAL( total_balances[asset_id_type()].value , core_asset_data.current_supply.value - core_asset_data.confidential_supply.value); // wlog("*** End asset supply verification ***"); } diff --git a/tests/tests/confidential_tests.cpp b/tests/tests/confidential_tests.cpp new file mode 100644 index 00000000..dba74eda --- /dev/null +++ b/tests/tests/confidential_tests.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2015, Cryptonomex, Inc. + * All rights reserved. + * + * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and + * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, + * are permitted until September 8, 2015, provided that the following conditions are met: + * + * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. + * + * 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. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; + +BOOST_FIXTURE_TEST_SUITE( confidential_tests, database_fixture ) +BOOST_AUTO_TEST_CASE( confidential_test ) +{ try { + ACTORS( (dan)(nathan) ) + const asset_object& core = asset_id_type()(db); + + transfer(account_id_type()(db), dan, core.amount(1000000)); + + transfer_to_blind_operation to_blind; + to_blind.amount = core.amount(1000); + to_blind.from = dan.id; + + auto owner1_key = fc::ecc::private_key::generate(); + auto owner1_pub = owner1_key.get_public_key(); + auto owner2_key = fc::ecc::private_key::generate(); + auto owner2_pub = owner2_key.get_public_key(); + + blind_output out1, out2; + out1.owner = authority( 1, public_key_type(owner1_pub), 1 ); + out2.owner = authority( 1, public_key_type(owner2_pub), 1 ); + + + auto InB1 = fc::sha256::hash("InB1"); + auto InB2 = fc::sha256::hash("InB2"); + auto OutB = fc::sha256::hash("InB2"); + auto nonce1 = fc::sha256::hash("nonce"); + auto nonce2 = fc::sha256::hash("nonce2"); + + out1.commitment = fc::ecc::blind(InB1,250); + out1.range_proof = fc::ecc::range_proof_sign( 0, out1.commitment, InB1, nonce1, 0, 0, 250 ); + + out2.commitment = fc::ecc::blind(InB2,750); + out2.range_proof = fc::ecc::range_proof_sign( 0, out2.commitment, InB1, nonce2, 0, 0, 750 ); + + to_blind.blinding_factor = fc::ecc::blind_sum( {InB1,InB2}, 2 ); + to_blind.outputs = {out2,out1}; + + trx.operations = {to_blind}; + trx.sign( dan_private_key ); + db.push_transaction(trx); + trx.signatures.clear(); + + BOOST_TEST_MESSAGE( "Transfering from blind to blind with change address" ); + auto Out3B = fc::sha256::hash("Out3B"); + auto Out4B = fc::ecc::blind_sum( {InB2,Out3B}, 1 ); // add InB2 - Out3b + blind_output out3, out4; + out3.commitment = fc::ecc::blind(Out3B,300); + out3.range_proof = fc::ecc::range_proof_sign( 0, out3.commitment, InB1, nonce1, 0, 0, 300 ); + out4.commitment = fc::ecc::blind(Out4B,750-300-10); + out4.range_proof = fc::ecc::range_proof_sign( 0, out3.commitment, InB1, nonce1, 0, 0, 750-300-10 ); + + + blind_transfer_operation blind_tr; + blind_tr.fee = core.amount(10); + blind_tr.inputs.push_back( {out2.commitment, out2.owner} ); + blind_tr.outputs = {out3,out4}; + blind_tr.validate(); + trx.operations = {blind_tr}; + trx.sign( owner2_key ); + db.push_transaction(trx); + + BOOST_TEST_MESSAGE( "Attempting to double spend the same commitments" ); + blind_tr.fee = core.amount(11); + + Out4B = fc::ecc::blind_sum( {InB2,Out3B}, 1 ); // add InB2 - Out3b + out4.commitment = fc::ecc::blind(Out4B,750-300-11); + auto out4_amount = 750-300-10; + out4.range_proof = fc::ecc::range_proof_sign( 0, out3.commitment, InB1, nonce1, 0, 0, 750-300-11 ); + blind_tr.outputs = {out4,out3}; + trx.operations = {blind_tr}; + BOOST_REQUIRE_THROW( db.push_transaction(trx, ~0), graphene::chain::blind_transfer_unknown_commitment ); + + + BOOST_TEST_MESSAGE( "Transfering from blind to nathan public" ); + out4.commitment = fc::ecc::blind(Out4B,750-300-10); + + transfer_from_blind_operation from_blind; + from_blind.fee = core.amount(10); + from_blind.to = nathan.id; + from_blind.amount = core.amount( out4_amount - 10 ); + from_blind.blinding_factor = Out4B; + from_blind.inputs.push_back( {out4.commitment, out4.owner} ); + trx.operations = {from_blind}; + trx.signatures.clear(); + db.push_transaction(trx); + + BOOST_REQUIRE_EQUAL( get_balance( nathan, core ), 750-300-10-10 ); + +} FC_LOG_AND_RETHROW() } + + + +BOOST_AUTO_TEST_SUITE_END()