Skip to content

Commit 0c6cdd4

Browse files
authored
Merge pull request #1117 from christophersanborn/480-rp-mantissa-and-tests
Range proof mantissa minimum bit length
2 parents dc38201 + 31a09ce commit 0c6cdd4

File tree

2 files changed

+105
-4
lines changed

2 files changed

+105
-4
lines changed

libraries/wallet/wallet.cpp

+10-4
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ namespace fc {
8585
}
8686

8787
#define BRAIN_KEY_WORD_COUNT 16
88+
#define RANGE_PROOF_MANTISSA 49 // Minimum mantissa bits to "hide" in the range proof.
89+
// If this number is set too low, then for large value
90+
// commitments the length of the range proof will hint
91+
// strongly at the value amount that is being hidden.
8892

8993
namespace graphene { namespace wallet {
9094

@@ -4274,12 +4278,14 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label,
42744278

42754279
if( blind_tr.outputs.size() > 1 )
42764280
{
4277-
to_out.range_proof = fc::ecc::range_proof_sign( 0, to_out.commitment, blind_factor, nonce, 0, 0, amount.amount.value );
4281+
to_out.range_proof = fc::ecc::range_proof_sign( 0, to_out.commitment, blind_factor, nonce,
4282+
0, RANGE_PROOF_MANTISSA, amount.amount.value );
42784283

42794284
blind_output change_out;
42804285
change_out.owner = authority( 1, public_key_type( from_pub_key.child( from_child ) ), 1 );
42814286
change_out.commitment = fc::ecc::blind( change_blind_factor, change.amount.value );
4282-
change_out.range_proof = fc::ecc::range_proof_sign( 0, change_out.commitment, change_blind_factor, from_nonce, 0, 0, change.amount.value );
4287+
change_out.range_proof = fc::ecc::range_proof_sign( 0, change_out.commitment, change_blind_factor, from_nonce,
4288+
0, RANGE_PROOF_MANTISSA, change.amount.value );
42834289
blind_tr.outputs[1] = change_out;
42844290

42854291

@@ -4387,8 +4393,8 @@ blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name
43874393
out.owner = authority( 1, public_key_type( to_pub_key.child( child ) ), 1 );
43884394
out.commitment = fc::ecc::blind( blind_factor, amount.amount.value );
43894395
if( to_amounts.size() > 1 )
4390-
out.range_proof = fc::ecc::range_proof_sign( 0, out.commitment, blind_factor, nonce, 0, 0, amount.amount.value );
4391-
4396+
out.range_proof = fc::ecc::range_proof_sign( 0, out.commitment, blind_factor, nonce,
4397+
0, RANGE_PROOF_MANTISSA, amount.amount.value );
43924398

43934399
blind_confirmation::output conf_output;
43944400
conf_output.label = item.first;

tests/cli/main.cpp

+95
Original file line numberDiff line numberDiff line change
@@ -388,3 +388,98 @@ BOOST_AUTO_TEST_CASE( cli_set_voting_proxy )
388388
}
389389
app1->shutdown();
390390
}
391+
392+
///////////////////
393+
// Test blind transactions and mantissa length of range proofs.
394+
///////////////////
395+
BOOST_AUTO_TEST_CASE( cli_confidential_tx_test )
396+
{
397+
using namespace graphene::chain;
398+
using namespace graphene::app;
399+
using namespace graphene::wallet;
400+
std::shared_ptr<graphene::app::application> app1;
401+
try {
402+
403+
// ** Start a Graphene chain and API server:
404+
fc::temp_directory app_dir( graphene::utilities::temp_directory_path() );
405+
int server_port_number;
406+
app1 = start_application(app_dir, server_port_number);
407+
unsigned int head_block = 0;
408+
409+
// ** Connect a Wallet to the API server, and generate three BLIND accounts:
410+
client_connection con(app1, app_dir, server_port_number);
411+
auto & W = *con.wallet_api_ptr; // Wallet alias
412+
BOOST_TEST_MESSAGE("Setting wallet password");
413+
W.set_password("supersecret");
414+
W.unlock("supersecret");
415+
BOOST_TEST_MESSAGE("Creating blind accounts");
416+
graphene::wallet::brain_key_info bki_nathan = W.suggest_brain_key();
417+
graphene::wallet::brain_key_info bki_alice = W.suggest_brain_key();
418+
graphene::wallet::brain_key_info bki_bob = W.suggest_brain_key();
419+
W.create_blind_account("nathan", bki_nathan.brain_priv_key);
420+
W.create_blind_account("alice", bki_alice.brain_priv_key);
421+
W.create_blind_account("bob", bki_bob.brain_priv_key);
422+
BOOST_CHECK(W.get_blind_accounts().size() == 3);
423+
424+
// ** Block 1: Import Nathan account:
425+
BOOST_TEST_MESSAGE("Importing nathan key and balance");
426+
std::vector<std::string> nathan_keys{"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"};
427+
W.import_key("nathan", nathan_keys[0]);
428+
W.import_balance("nathan", nathan_keys, true);
429+
generate_block(app1); head_block++;
430+
431+
// ** Block 2: Nathan will blind 100M BTS:
432+
BOOST_TEST_MESSAGE("Blinding a large balance");
433+
W.transfer_to_blind("nathan", "BTS", {{"nathan","100000000"}}, true);
434+
BOOST_CHECK( W.get_blind_balances("nathan")[0].amount == 10000000000000 );
435+
generate_block(app1); head_block++;
436+
437+
// ** Block 3: Nathan will send 1M BTS to alice and 10K BTS to bob. We
438+
// then confirm that balances are received, and then analyze the range
439+
// prooofs to make sure the mantissa length does not reveal approximate
440+
// balance (issue #480).
441+
std::map<std::string, share_type> to_list = {{"alice",100000000000},
442+
{"bob", 1000000000}};
443+
vector<blind_confirmation> bconfs;
444+
asset_object core_asset = W.get_asset("1.3.0");
445+
BOOST_TEST_MESSAGE("Sending blind transactions to alice and bob");
446+
for (auto to : to_list) {
447+
string amount = core_asset.amount_to_string(to.second);
448+
bconfs.push_back(W.blind_transfer("nathan",to.first,amount,core_asset.symbol,true));
449+
BOOST_CHECK( W.get_blind_balances(to.first)[0].amount == to.second );
450+
}
451+
BOOST_TEST_MESSAGE("Inspecting range proof mantissa lengths");
452+
vector<int> rp_mantissabits;
453+
for (auto conf : bconfs) {
454+
for (auto out : conf.trx.operations[0].get<blind_transfer_operation>().outputs) {
455+
rp_mantissabits.push_back(1+out.range_proof[1]); // 2nd byte encodes mantissa length
456+
}
457+
}
458+
// We are checking the mantissa length of the range proofs for several Pedersen
459+
// commitments of varying magnitude. We don't want the mantissa lengths to give
460+
// away magnitude. Deprecated wallet behavior was to use "just enough" mantissa
461+
// bits to prove range, but this gives away value to within a factor of two. As a
462+
// naive test, we assume that if all mantissa lengths are equal, then they are not
463+
// revealing magnitude. However, future more-sophisticated wallet behavior
464+
// *might* randomize mantissa length to achieve some space savings in the range
465+
// proof. The following test will fail in that case and a more sophisticated test
466+
// will be needed.
467+
auto adjacent_unequal = std::adjacent_find(rp_mantissabits.begin(),
468+
/* find unequal adjacent values */ rp_mantissabits.end(),
469+
std::not_equal_to<int>());
470+
BOOST_CHECK(adjacent_unequal == rp_mantissabits.end());
471+
generate_block(app1); head_block++;
472+
473+
// ** Check head block:
474+
BOOST_TEST_MESSAGE("Check that all expected blocks have processed");
475+
dynamic_global_property_object dgp = W.get_dynamic_global_properties();
476+
BOOST_CHECK(dgp.head_block_number == head_block);
477+
478+
// wait for everything to finish up
479+
fc::usleep(fc::seconds(1));
480+
} catch( fc::exception& e ) {
481+
edump((e.to_detail_string()));
482+
throw;
483+
}
484+
app1->shutdown();
485+
}

0 commit comments

Comments
 (0)