Skip to content

Instantly share code, notes, and snippets.

@Rucknium
Created September 11, 2024 17:32
Show Gist options
  • Save Rucknium/da1e57b1864aca477dfa3b4e02e86e26 to your computer and use it in GitHub Desktop.
Save Rucknium/da1e57b1864aca477dfa3b4e02e86e26 to your computer and use it in GitHub Desktop.

Monero double spend attack success probability

Below is a table of the attack success probability for different values of the adversary's hashpower share and the number of blocks that the victim waits for "full confirmation" of a transaction.

The computations are based on Theorem 1 of Grunspan & Perez-Marco (2018). "Double spend races." It is assumed that the attacker has a one-block advantage at the start of the attack.

Theorem 1 is


Let $0<q<1/2$, respectively $p=1-q$, be the relative hash power of the group of the attackers, respectively of honest miners. After $z$ blocks have been validated by the honest miners, the probability of success of the attackers is

$$P(z)=I_{4pq}(z,1/2)$$

where $I_{x}(a,b)$ is the regularized incomplete beta function


See https://miningpoolstats.stream/monero for current hashpower share of the major mining pools.

Columns are the hashpower share of the adversary. Rows are the number of mined blocks that the victim waits before considering a transaction "confirmed".

0.05 0.1 0.2 0.3 0.4 0.45
1 10.00000 20.00000 40.00000 60.00000 80.00000 90.00000
2 1.45000 5.60000 20.80000 43.20000 70.40000 85.05000
3 0.23162 1.71200 11.58400 32.61600 63.48800 81.37463
4 0.03872 0.54560 6.66880 25.20720 57.95840 78.34244
5 0.00664 0.17818 3.91629 19.76173 53.31354 75.71581
6 0.00116 0.05914 2.33084 15.64496 49.30037 73.37548
7 0.00021 0.01986 1.40071 12.47504 45.76879 71.25164
8 0.00004 0.00672 0.84795 10.00251 42.62064 69.29921
9 0.00001 0.00229 0.51629 8.05539 39.78730 67.48712
10 0.00000 0.00079 0.31582 6.51067 37.21840 65.79282
11 0.00000 0.00027 0.19394 5.27799 34.87557 64.19932
12 0.00000 0.00009 0.11948 4.28960 32.72869 62.69347
13 0.00000 0.00003 0.07381 3.49395 30.75355 61.26479
14 0.00000 0.00001 0.04571 2.85131 28.93035 59.90480
15 0.00000 0.00000 0.02836 2.33077 27.24259 58.60650
16 0.00000 0.00000 0.01763 1.90809 25.67635 57.36402
17 0.00000 0.00000 0.01098 1.56413 24.21974 56.17240
18 0.00000 0.00000 0.00685 1.28371 22.86252 55.02740
19 0.00000 0.00000 0.00427 1.05470 21.59579 53.92534
20 0.00000 0.00000 0.00267 0.86739 20.41173 52.86301
21 0.00000 0.00000 0.00167 0.71398 19.30344 51.83759
22 0.00000 0.00000 0.00105 0.58819 18.26482 50.84660
23 0.00000 0.00000 0.00066 0.48492 17.29041 49.88782
24 0.00000 0.00000 0.00041 0.40007 16.37531 48.95926
25 0.00000 0.00000 0.00026 0.33027 15.51511 48.05913
26 0.00000 0.00000 0.00016 0.27282 14.70584 47.18583
27 0.00000 0.00000 0.00010 0.22548 13.94388 46.33789
28 0.00000 0.00000 0.00006 0.18646 13.22594 45.51397
29 0.00000 0.00000 0.00004 0.15427 12.54903 44.71286
30 0.00000 0.00000 0.00003 0.12769 11.91040 43.93344

R code to reproduce the table:

# install.packages("zipfR")
# install.packages("knitr")

# Rows: 1 to 30 number of blocks to wait
# Column: hashpower share: 5%, 10%, 20%, 30%, 40%, 45%

# Based on Theorem 1 of Grunspan & Perez-Marco (2018). "Double spend races."

n.blocks <- 30
hashpower.share <- c(0.05, 0.10, 0.20, 0.30, 0.40, 0.45)

blocks <- 1:n.blocks
results <- matrix(0, nrow = n.blocks, ncol = length(hashpower.share))

for (i in seq_along(hashpower.share)) {
  q = hashpower.share[i]
  p = 1 - q
  results[, i] <- zipfR::Rbeta(x = 4*p*q, a = blocks, b = 1/2)
}

results <- 100 * results # Convert to percentage
rownames(results) <- as.character(blocks)

comparison.block <- 10
results.comparison <- results

for (j in seq_len(ncol(results.comparison))) {
  divisor <- results[blocks == comparison.block, j]
  results.comparison[, j] <- results.comparison[, j] / divisor
}


knitr::kable(results, format = "pipe", row.names = TRUE,
  col.names = hashpower.share, digits = 5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment