From f1b3ae72b1824c0d5ab22933b54046f21a471717 Mon Sep 17 00:00:00 2001 From: Sandip Patel Date: Wed, 4 Sep 2019 17:21:21 +0530 Subject: [PATCH] Range proof mantissa minimum bit length --- libraries/wallet/wallet.cpp | 13 ++++-- tests/cli/main.cpp | 89 +++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 0df82e7d1..95a7c01cb 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -90,6 +90,10 @@ #endif #define BRAIN_KEY_WORD_COUNT 16 +#define RANGE_PROOF_MANTISSA 49 // Minimum mantissa bits to "hide" in the range proof. + // If this number is set too low, then for large value + // commitments the length of the range proof will hint + // strongly at the value amount that is being hidden. namespace graphene { namespace wallet { @@ -4795,12 +4799,14 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label, if( blind_tr.outputs.size() > 1 ) { - to_out.range_proof = fc::ecc::range_proof_sign( 0, to_out.commitment, blind_factor, nonce, 0, 0, amount.amount.value ); + to_out.range_proof = fc::ecc::range_proof_sign( 0, to_out.commitment, blind_factor, nonce, + 0, RANGE_PROOF_MANTISSA, amount.amount.value ); blind_output change_out; change_out.owner = authority( 1, public_key_type( from_pub_key.child( from_child ) ), 1 ); change_out.commitment = fc::ecc::blind( change_blind_factor, change.amount.value ); - change_out.range_proof = fc::ecc::range_proof_sign( 0, change_out.commitment, change_blind_factor, from_nonce, 0, 0, change.amount.value ); + change_out.range_proof = fc::ecc::range_proof_sign( 0, change_out.commitment, change_blind_factor, from_nonce, + 0, RANGE_PROOF_MANTISSA, change.amount.value ); blind_tr.outputs[1] = change_out; @@ -4908,7 +4914,8 @@ blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name 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 ); + out.range_proof = fc::ecc::range_proof_sign( 0, out.commitment, blind_factor, nonce, + 0, RANGE_PROOF_MANTISSA, amount.amount.value ); blind_confirmation::output conf_output; diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index 82adb1c59..55cfcf214 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -438,3 +438,92 @@ BOOST_FIXTURE_TEST_CASE( cli_vote_for_2_witnesses, cli_fixture ) throw; } } + +/////////////////// +// Test blind transactions and mantissa length of range proofs. +/////////////////// +BOOST_FIXTURE_TEST_CASE( cli_confidential_tx_test, cli_fixture ) +{ + using namespace graphene::wallet; + try { + // we need to increase the default max transaction size to run this test. + this->app1->chain_database()->modify( + this->app1->chain_database()->get_global_properties(), + []( global_property_object& p) { + p.parameters.maximum_transaction_size = 8192; + }); + std::vector import_txs; + + BOOST_TEST_MESSAGE("Importing nathan's balance"); + import_txs = con.wallet_api_ptr->import_balance("nathan", nathan_keys, true); + + unsigned int head_block = 0; + auto & W = *con.wallet_api_ptr; // Wallet alias + + BOOST_TEST_MESSAGE("Creating blind accounts"); + graphene::wallet::brain_key_info bki_nathan = W.suggest_brain_key(); + graphene::wallet::brain_key_info bki_alice = W.suggest_brain_key(); + graphene::wallet::brain_key_info bki_bob = W.suggest_brain_key(); + W.create_blind_account("nathan", bki_nathan.brain_priv_key); + W.create_blind_account("alice", bki_alice.brain_priv_key); + W.create_blind_account("bob", bki_bob.brain_priv_key); + BOOST_CHECK(W.get_blind_accounts().size() == 3); + + // ** Block 1: Import Nathan account: + BOOST_TEST_MESSAGE("Importing nathan key and balance"); + std::vector nathan_keys{"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"}; + W.import_key("nathan", nathan_keys[0]); + W.import_balance("nathan", nathan_keys, true); + generate_block(app1); head_block++; + + // ** Block 2: Nathan will blind 100M CORE token: + BOOST_TEST_MESSAGE("Blinding a large balance"); + W.transfer_to_blind("nathan", GRAPHENE_SYMBOL, {{"nathan","100000000"}}, true); + BOOST_CHECK( W.get_blind_balances("nathan")[0].amount == 10000000000000 ); + generate_block(app1); head_block++; + + // ** Block 3: Nathan will send 1M CORE token to alice and 10K CORE token to bob. We + // then confirm that balances are received, and then analyze the range + // prooofs to make sure the mantissa length does not reveal approximate + // balance (issue #480). + std::map to_list = {{"alice",100000000000}, + {"bob", 1000000000}}; + vector bconfs; + asset_object core_asset = W.get_asset("1.3.0"); + BOOST_TEST_MESSAGE("Sending blind transactions to alice and bob"); + for (auto to : to_list) { + string amount = core_asset.amount_to_string(to.second); + bconfs.push_back(W.blind_transfer("nathan",to.first,amount,core_asset.symbol,true)); + BOOST_CHECK( W.get_blind_balances(to.first)[0].amount == to.second ); + } + BOOST_TEST_MESSAGE("Inspecting range proof mantissa lengths"); + vector rp_mantissabits; + for (auto conf : bconfs) { + for (auto out : conf.trx.operations[0].get().outputs) { + rp_mantissabits.push_back(1+out.range_proof[1]); // 2nd byte encodes mantissa length + } + } + // We are checking the mantissa length of the range proofs for several Pedersen + // commitments of varying magnitude. We don't want the mantissa lengths to give + // away magnitude. Deprecated wallet behavior was to use "just enough" mantissa + // bits to prove range, but this gives away value to within a factor of two. As a + // naive test, we assume that if all mantissa lengths are equal, then they are not + // revealing magnitude. However, future more-sophisticated wallet behavior + // *might* randomize mantissa length to achieve some space savings in the range + // proof. The following test will fail in that case and a more sophisticated test + // will be needed. + auto adjacent_unequal = std::adjacent_find(rp_mantissabits.begin(), + rp_mantissabits.end(), // find unequal adjacent values + std::not_equal_to()); + BOOST_CHECK(adjacent_unequal == rp_mantissabits.end()); + generate_block(app1); head_block++; + + // ** Check head block: + BOOST_TEST_MESSAGE("Check that all expected blocks have processed"); + dynamic_global_property_object dgp = W.get_dynamic_global_properties(); + BOOST_CHECK(dgp.head_block_number == head_block); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} \ No newline at end of file