Skip to content

Commit aa54607

Browse files
committed
cryptonote_core: support intermediate PoW hashes in v17
See docs/BLOCK_HASHING.md
1 parent 389e3ba commit aa54607

15 files changed

+83
-14
lines changed

docs/BLOCK_HASHING.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Block hashing
2+
3+
## Background
4+
5+
In Bitcoin, block IDs and Proof-of-Work (PoW) hashes are calculated the same way, which is why on-chain block IDs always start
6+
with many 0s. However, in Monero, block IDs and PoW hashes are calcuated using different hash functions and slightly different
7+
inputs. This is because the hash functions used for PoW (CryptoNight and RandomX) are much slower to run than other
8+
cryptographic hash functions, specifically Keccak256, which Monero uses for block IDs. By contrast, Bitcoin uses SHA-256 for
9+
both block IDs and PoW. The reason that CryptoNight and RandomX were chosen for PoW in Monero is to protect against
10+
application-specific integrated circuits (ASICs) from dominanting the network hashrate like in Bitcoin. In theory, this
11+
would restore the principle of "1 CPU, 1 vote" outlined in the Satoshi whitepaper, and bring greater decentralization to the
12+
Monero exosystem. This document aims to describe exactly how block IDs and PoW hashes are calculated in the Monero reference
13+
codebase.
14+
15+
## CryptoNight epoch
16+
17+
To be reductive, the underlying design of Cryptonight is to 1) fill a 2MB buffer of memory using a memory-hard function, 2)
18+
perform over a millon random reads on that buffer to permutate some state, and then 3) do a final hash on the state using
19+
a random choice between four independent hash fuctions. There are 4 variants of CryptoNight: v0, v1, v2, and v4. This is the
20+
data flow for how block hashing in the CryptoNight epoch (Monero fork versions v1-v11) is performed:
21+
22+
![CryptoNight data flow](resources/cryptonight-data-flow.png)
23+
24+
## RandomX epoch
25+
26+
Like, CryptoNight, RandomX also uses a memory hard function to fill a large space of memory called a "cache". However, this
27+
space is 256MB, not 2MB. It can be further expanded to 2GB to trade memory usage for compute speed. Unlike CryptoNight's cache,
28+
the RandomX cache is computed only once every 2048 blocks, not once per block. Monero uses a "seed" block ID of a previous block
29+
in the chain to fill the memory space. And to make ASICs even harder, RandomX uses random instruction execution as well. This
30+
is the data flow for how block hashing in the RandomX epoch (Monero fork versions v12-present) is performed:
31+
32+
![RandomX data flow](resources/randomx-data-flow.png)
33+
34+
## RandomX, with commitments, epoch
35+
36+
Because RandomX can be slow to run, especially while building the cache (nearly 1/2 a second on my machine), it inadvertantly
37+
can introduce a Denial-of-Service (DoS) vector. For this reason, RandomX hash commitments can be added, so the PoW can be
38+
partially verified using only Blake2B, a much lighter hash function. For simplicity's sake, we can also remove the number of
39+
transactions in the block from the block hashing blob. This is the data flow for how block hashing works with these changes:
40+
41+
![RandomX commitment data flow](resources/randomx-commitment-data-flow.png)
42+
43+
The way that the hashes are calculated allows us to do partial PoW verification like so:
44+
45+
![Partial RandomX verification data flow](resources/partial-pow-verify-data-flow.png)
46+
47+
A node can be given simply the block header, block content hash, and intermediate PoW hash, and verify that some PoW was done
48+
using only `randomx_calculate_commitment()` (Blake2B undernath). Passing this partial verification requires PoW to done using
49+
Blake2B up to the relevant difficulty. While doing so is much easier than doing the full RandomX PoW, it still requires
50+
significant work. It is very important that we can calculate the block ID from the same information (or a subset thereof)
51+
that we calculate PoW from, since each block header contains the previous block ID. Binding the block headers as such makes
52+
faking intermediate PoW on chains of blocks exponentially harder the longer the chain is.
87.2 KB
Loading
56.8 KB
Loading
108 KB
Loading
102 KB
Loading

src/crypto/hash-ops.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ void rx_seedheights(const uint64_t height, uint64_t *seed_height, uint64_t *next
105105

106106
void rx_set_main_seedhash(const char *seedhash, size_t max_dataset_init_threads);
107107
void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *result_hash);
108+
void rx_commitment(const void *data, size_t length, const void *rx_hash, char *result_commitment);
108109

109110
void rx_set_miner_thread(uint32_t value, size_t max_dataset_init_threads);
110111
uint32_t rx_get_miner_thread(void);

src/crypto/rx-slow-hash.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,10 @@ void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *r
487487
CTHR_RWLOCK_UNLOCK_WRITE(secondary_cache_lock);
488488
}
489489

490+
void rx_commitment(const void *data, size_t length, const void *rx_hash, char *result_commitment) {
491+
randomx_calculate_commitment(data, length, rx_hash, result_commitment);
492+
}
493+
490494
void rx_set_miner_thread(uint32_t value, size_t max_dataset_init_threads) {
491495
miner_thread = value;
492496

src/cryptonote_basic/cryptonote_format_utils.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1439,7 +1439,8 @@ namespace cryptonote
14391439
blobdata blob = t_serializable_object_to_blob(static_cast<block_header>(b));
14401440
crypto::hash tree_root_hash = get_tx_tree_hash(b);
14411441
blob.append(reinterpret_cast<const char*>(&tree_root_hash), sizeof(tree_root_hash));
1442-
blob.append(tools::get_varint_data(b.tx_hashes.size()+1));
1442+
if (b.major_version < HF_VERSION_POW_COMMITMENT)
1443+
blob.append(tools::get_varint_data(b.tx_hashes.size()+1));
14431444
return blob;
14441445
}
14451446
//---------------------------------------------------------------

src/cryptonote_config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@
192192
#define HF_VERSION_BULLETPROOF_PLUS 15
193193
#define HF_VERSION_VIEW_TAGS 15
194194
#define HF_VERSION_2021_SCALING 15
195+
#define HF_VERSION_POW_COMMITMENT 17
195196

196197
#define PER_KB_FEE_QUANTIZATION_DECIMALS 8
197198
#define CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES 2

0 commit comments

Comments
 (0)