blockchain_db: add k-anonymity to txid fetching

Read more about k-anonymity [here](https://en.wikipedia.org/wiki/K-anonymity). We implement this feature in the monero daemon for transactions
by providing a "Txid Template", which is simply a txid with all but `num_matching_bits` bits zeroed out, and the number `num_matching_bits`. We add an operation to `BlockchainLMDB` called
`get_txids_loose` which takes a txid template and returns all txids in the database (chain and mempool) that satisfy that template. Thus, a client can
ask about a specific transaction from a daemon without revealing the exact transaction they are inquiring about. The client can control the statistical
chance that other TXIDs (besides the one in question) match the txid template sent to the daemon up to a power of 2. For example, if a client sets their `num_matching_bits`
to 5, then statistically any txid has a 1/(2^5) chance to match. With `num_matching_bits`=10, there is a 1/(2^10) chance, so on and so forth.

Co-authored-by: ACK-J <60232273+ACK-J@users.noreply.github.com>
This commit is contained in:
jeffro256 2023-07-16 11:56:36 -05:00
parent 00fd416a99
commit b0bf49a65a
No known key found for this signature in database
GPG key ID: 6F79797A6E392442
13 changed files with 770 additions and 1 deletions

View file

@ -3533,6 +3533,82 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_txids_loose(const COMMAND_RPC_GET_TXIDS_LOOSE::request& req, COMMAND_RPC_GET_TXIDS_LOOSE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx)
{
RPC_TRACKER(get_txids_loose);
// Maybe don't use bootstrap since this endpoint is meant to retreive TXIDs w/ k-anonymity,
// so shunting this request to a random node seems counterproductive.
#if BYTE_ORDER == LITTLE_ENDIAN
const uint64_t max_num_txids = RESTRICTED_SPENT_KEY_IMAGES_COUNT * (m_restricted ? 1 : 10);
// Sanity check parameters
if (req.num_matching_bits > 256)
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "There are only 256 bits in a hash, you gave too many";
return false;
}
// Attempt to guess when bit count is too low before fetching, within a certain margin of error
const uint64_t num_txs_ever = m_core.get_blockchain_storage().get_db().get_tx_count();
const uint64_t num_expected_fetch = (num_txs_ever >> std::min((int) req.num_matching_bits, 63));
const uint64_t max_num_txids_with_margin = 2 * max_num_txids;
if (num_expected_fetch > max_num_txids_with_margin)
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "Trying to search with too few matching bits, detected before fetching";
return false;
}
// Convert txid template to a crypto::hash
crypto::hash search_hash;
if (!epee::string_tools::hex_to_pod(req.txid_template, search_hash))
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "Could not decode hex txid";
return false;
}
// Check that txid template is zeroed correctly for number of given matchign bits
else if (search_hash != make_hash32_loose_template(req.num_matching_bits, search_hash))
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "Txid template is not zeroed correctly for number of bits. You could be leaking true txid!";
return false;
}
try
{
// Do the DB fetch
const auto txids = m_core.get_blockchain_storage().get_db().get_txids_loose(search_hash, req.num_matching_bits, max_num_txids);
// Fill out response form
for (const auto& txid : txids)
res.txids.emplace_back(epee::string_tools::pod_to_hex(txid));
}
catch (const TX_EXISTS&)
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "Trying to search with too few matching bits";
return false;
}
catch (const std::exception& e)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = std::string("Error during get_txids_loose: ") + e.what();
return false;
}
res.status = CORE_RPC_STATUS_OK;
return true;
#else // BYTE_ORDER == BIG_ENDIAN
// BlockchainLMDB::compare_hash32 has different key ordering (thus different txid templates) on BE systems
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Due to implementation details, this feature is not available on big-endian daemons";
return false;
#endif
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_rpc_access_submit_nonce(const COMMAND_RPC_ACCESS_SUBMIT_NONCE::request& req, COMMAND_RPC_ACCESS_SUBMIT_NONCE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx)
{
RPC_TRACKER(rpc_access_submit_nonce);