Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GRPH-87 - Choice of range proof params in cli wallet reveals transaction magnitude to very narrow range for Blinded transfers #112

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions libraries/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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;


Expand Down Expand Up @@ -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;
Expand Down
89 changes: 89 additions & 0 deletions tests/cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<signed_transaction> 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<std::string> 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<std::string, share_type> to_list = {{"alice",100000000000},
{"bob", 1000000000}};
vector<blind_confirmation> 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<int> rp_mantissabits;
for (auto conf : bconfs) {
for (auto out : conf.trx.operations[0].get<blind_transfer_operation>().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<int>());
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;
}
}