aboutsummaryrefslogtreecommitdiff
path: root/src/blockchain_db
diff options
context:
space:
mode:
authorjeffro256 <jeffro256@tutanota.com>2023-07-16 11:56:36 -0500
committerjeffro256 <jeffro256@tutanota.com>2023-08-01 17:25:25 -0500
commitb0bf49a65a38ceb1acfbc8e17f40e63383ac140d (patch)
tree87870e4054ca4940f94e3c733c38bca570e2f21c /src/blockchain_db
parentMerge pull request #8919 (diff)
downloadmonero-b0bf49a65a38ceb1acfbc8e17f40e63383ac140d.tar.xz
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>
Diffstat (limited to 'src/blockchain_db')
-rw-r--r--src/blockchain_db/blockchain_db.h15
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp52
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h2
-rw-r--r--src/blockchain_db/testdb.h1
4 files changed, 70 insertions, 0 deletions
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index 835d6dcad..f3e962c81 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -1306,6 +1306,21 @@ public:
virtual bool get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<cryptonote::blobdata> &bd) const = 0;
/**
+ * @brief Get all txids in the database (chain and pool) that match a certain nbits txid template
+ *
+ * To be more specific, for all `dbtxid` txids in the database, return `dbtxid` if
+ * `0 == cryptonote::compare_hash32_reversed_nbits(txid_template, dbtxid, nbits)`.
+ *
+ * @param txid_template the transaction id template
+ * @param nbits number of bits to compare against in the template
+ * @param max_num_txs The maximum number of txids to match, if we hit this limit, throw early
+ * @return std::vector<crypto::hash> the list of all matching txids
+ *
+ * @throw TX_EXISTS if the number of txids that match exceed `max_num_txs`
+ */
+ virtual std::vector<crypto::hash> get_txids_loose(const crypto::hash& txid_template, std::uint32_t nbits, uint64_t max_num_txs = 0) = 0;
+
+ /**
* @brief fetches a variable number of blocks and transactions from the given height, in canonical blockchain order
*
* The subclass should return the blocks and transactions stored from the one with the given
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 4178c862b..2c015faee 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -3144,6 +3144,58 @@ bool BlockchainLMDB::get_pruned_tx_blobs_from(const crypto::hash& h, size_t coun
return true;
}
+std::vector<crypto::hash> BlockchainLMDB::get_txids_loose(const crypto::hash& txid_template, std::uint32_t bits, uint64_t max_num_txs)
+{
+ LOG_PRINT_L3("BlockchainLMDB::" << __func__);
+ check_open();
+
+ std::vector<crypto::hash> matching_hashes;
+
+ TXN_PREFIX_RDONLY(); // Start a read-only transaction
+ RCURSOR(tx_indices); // Open cursors to the tx_indices and txpool_meta databases
+ RCURSOR(txpool_meta);
+
+ // Search on-chain and pool transactions together, starting with on-chain txs
+ MDB_cursor* cursor = m_cur_tx_indices;
+ MDB_val k = zerokval; // tx_indicies DB uses a dummy key
+ MDB_val_set(v, txid_template); // tx_indicies DB indexes data values by crypto::hash value on front
+ MDB_cursor_op op = MDB_GET_BOTH_RANGE; // Set the cursor to the first key/value pair >= the given key
+ bool doing_chain = true; // this variable tells us whether we are processing chain or pool txs
+ while (1)
+ {
+ const int get_result = mdb_cursor_get(cursor, &k, &v, op);
+ op = doing_chain ? MDB_NEXT_DUP : MDB_NEXT; // Set the cursor to the next key/value pair
+ if (get_result && get_result != MDB_NOTFOUND)
+ throw0(DB_ERROR(lmdb_error("DB error attempting to fetch txid range", get_result).c_str()));
+
+ // In tx_indicies, the hash is stored at the data, in txpool_meta at the key
+ const crypto::hash* const p_dbtxid = (const crypto::hash*)(doing_chain ? v.mv_data : k.mv_data);
+
+ // Check if we reached the end of a DB or the hashes no longer match the template
+ if (get_result == MDB_NOTFOUND || compare_hash32_reversed_nbits(txid_template, *p_dbtxid, bits))
+ {
+ if (doing_chain) // done with chain processing, switch to pool processing
+ {
+ k.mv_size = sizeof(crypto::hash); // txpool_meta DB is indexed using crypto::hash as keys
+ k.mv_data = (void*) txid_template.data;
+ cursor = m_cur_txpool_meta; // switch databases
+ op = MDB_SET_RANGE; // Set the cursor to the first key >= the given key
+ doing_chain = false;
+ continue;
+ }
+ break; // if we get to this point, then we finished pool processing and we are done
+ }
+ else if (matching_hashes.size() >= max_num_txs && max_num_txs != 0)
+ throw0(TX_EXISTS("number of tx hashes in template range exceeds maximum"));
+
+ matching_hashes.push_back(*p_dbtxid);
+ }
+
+ TXN_POSTFIX_RDONLY(); // End the read-only transaction
+
+ return matching_hashes;
+}
+
bool BlockchainLMDB::get_blocks_from(uint64_t start_height, size_t min_block_count, size_t max_block_count, size_t max_tx_count, size_t max_size, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata>>>>& blocks, bool pruned, bool skip_coinbase, bool get_miner_tx_hash) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index c352458b4..95e7b2aa4 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -262,6 +262,8 @@ public:
virtual bool get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const;
virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const;
+ virtual std::vector<crypto::hash> get_txids_loose(const crypto::hash& h, std::uint32_t bits, uint64_t max_num_txs = 0);
+
virtual uint64_t get_tx_count() const;
virtual std::vector<transaction> get_tx_list(const std::vector<crypto::hash>& hlist) const;
diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h
index 946f26270..a27183b2c 100644
--- a/src/blockchain_db/testdb.h
+++ b/src/blockchain_db/testdb.h
@@ -71,6 +71,7 @@ public:
virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; }
virtual bool get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<cryptonote::blobdata> &bd) const override { return false; }
virtual bool get_blocks_from(uint64_t start_height, size_t min_block_count, size_t max_block_count, size_t max_tx_count, size_t max_size, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata>>>>& blocks, bool pruned, bool skip_coinbase, bool get_miner_tx_hash) const override { return false; }
+ virtual std::vector<crypto::hash> get_txids_loose(const crypto::hash& h, std::uint32_t bits, uint64_t max_num_txs = 0) override { return {}; }
virtual bool get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; }
virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const override { return false; }
virtual uint64_t get_block_height(const crypto::hash& h) const override { return 0; }