docs/analysis/51-percent.md: what a 51 percent attacker can and cannot do on Igneum, with numbers (the selected-chain race over a 90-s hold, the lock as the reorder bound, the veto at 1/3 of weight and its rental cost, the departure case and the LEAVE item, the p2p surface). docs/analysis/horizon/consensus-security.md: the attack catalogue across GHOSTDAG ordering, the difficulty rule, the finality weight, miner signalling, proof records, the exec layer and p2p, each with the bound and the rental cost at the measured USD 11.7 per GH/s-hour; the pruned-node unwrap class with its sibling list in the sync and IBD flows; fourteen ranked defences, three of them not recommended with the reason. Models and results: sim/horizon/consensus-security/ (ghostdag_sim.py, finality_horizon.py, cost_model.py, signalling.py, result files). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
7.1 KiB
Finality weight attacks over the adversary's share (20, 34, 51, 67, 90 percent)
Generated by sim/horizon/consensus-security/finality_horizon.py on 2026-10-06 19:42 UTC, importing sim/finality_v2.py unchanged; seeds 7,11; rule active/cert + floor 1.00 (a lock needs 66.7% of total); inter-region delay 2.0 s. Model assumptions and limits: sim/results_v2.md (no DAG, perfect retarget, keys free, 2.2% outage).
R. Renter with hash share A from a warm 30-day state, signing every checkpoint (32 days, seed 7)
Formula (spec 03 3.1, results_v2.md B): share(t) = (t/30) A; the veto (1/3) on day 10/A, locking alone (2/3) on day 20/A, never when A < 2/3.
| attacker hash share A | share day 10, sim / formula | share day 20, sim / formula | reaches 1/3 (veto), day sim / formula | reaches 2/3 (locks alone), day sim / formula | stalled checkpoints | conflicting locks |
|---|---|---|---|---|---|---|
| 20% | 6.7% / 6.7% | 13.3% / 13.3% | never / > 32 | never / never | 0 | 0 |
| 34% | 11.3% / 11.3% | 22.6% / 22.7% | 30.0 / 29.4 | never / never | 0 | 0 |
| 51% | 17.0% / 17.0% | 34.0% / 34.0% | 20.0 / 19.6 | never / never | 0 | 0 |
| 67% | 22.4% / 22.3% | 44.7% / 44.7% | 15.0 / 14.9 | 30.0 / 29.9 | 0 | 0 |
| 90% | 30.0% / 30.0% | 60.0% / 60.0% | 12.0 / 11.1 | 23.0 / 22.2 | 0 | 0 |
S. Silent set holding A of weight stops signing and keeps mining, then resumes (seeds 7,11)
| silent weight A | hours | picked | first lock while silent, min | stalled checkpoints | locked while silent | longest gap, min | first lock after resume, min | conflicting locks |
|---|---|---|---|---|---|---|---|---|
| 20% | 1 | 20% | 0 | 0 | 100% | 1 | 0 | 0 |
| 20% | 6 | 20% | 0 | 0 | 100% | 1 | 0 to 0 | 0 |
| 34% | 1 | 34% | never | 122 to 123 | 0% | 60 | 0 | 0 |
| 34% | 6 | 34% | never | 720 to 722 | 0% | 360 | 0 to 0 | 0 |
| 51% | 1 | 51% | never | 122 to 123 | 0% | 60 | 0 | 0 |
| 51% | 6 | 51% | never | 720 to 722 | 0% | 360 | 0 to 0 | 0 |
| 67% | 1 | 67% | never | 122 to 123 | 0% | 60 | 0 | 0 |
| 67% | 6 | 67% | never | 720 to 722 | 0% | 360 | 0 to 0 | 0 |
| 90% | 1 | 83 to 90% | never | 122 to 123 | 0% | 60 | 0 | 0 |
| 90% | 6 | 83 to 90% | never | 720 to 722 | 0% | 360 | 0 to 0 | 0 |
Reading: at A >= 1/3 the pause lasts exactly as long as the silence (the floor needs two thirds of total signing), the first lock comes 0 minutes after the resume, and no row conflicts. A silent set that keeps mining keeps its subsidy, so the pause costs it nothing.
K. Bought keys worth A of the window, attacker mining at 30% of the network, 30 days (seeds 7,11)
Formula (spec 03 3.11.5): share(t) = A (1 - t/30) + 0.30 t/30. Sellers keep their rigs under fresh keys. 'signs' = the buyer votes; 'silent' = it withholds votes (the pause attack with bought weight).
| bought weight A | buyer | picked | peak share | share at day 30 | holds at least 1/3 (veto) | stalled checkpoints of 86400 | conflicting locks |
|---|---|---|---|---|---|---|---|
| 20% | signs | 20% | 30% | 30% | never | 0 | 0 |
| 20% | silent | 20% | 30% | 30% | never | 816 to 1045 | 0 |
| 34% | signs | 34% | 34% | 30% | day 1 to 6 | 0 | 0 |
| 34% | silent | 34% | 34% | 30% | day 1 to 6 | 36334 to 41588 | 0 |
| 51% | signs | 51% | 51% | 30% | day 1 to 24 | 0 | 0 |
| 51% | silent | 51% | 51% | 30% | day 1 to 24 | 74391 to 75691 | 0 |
| 67% | signs | 67% | 66% | 30% | day 1 to 26 | 0 | 0 |
| 67% | silent | 67% | 66% | 30% | day 1 to 26 | 79408 to 79841 | 0 |
| 90% | signs | 83 to 90% | 82 to 89% | 30% | day 1 to 27 | 0 | 0 |
| 90% | silent | 83 to 90% | 82 to 89% | 30% | day 1 to 27 | 81759 to 82557 | 0 |
E. Poisoned eclipse: an attacker holding A of weight feeds a 20% pool a private fork for 2 h and signs both sides (seeds 7,11)
The eclipsed side holds 20% + A of total; the honest side 80% - A. Rule v2 (floor 2/3 of total) as results_v2.md L3; the floor binds whatever the presence window does. A = 90% is not run: the attacker alone is over two thirds and locks alone everywhere, the 20-day public event of spec 03 3.1.
| attacker weight A | eclipsed side holds | conflicting locks | first conflict, min | locks on the eclipsed side (2 h) | locks on the honest side (2 h) | stalls in 3 h after the heal |
|---|---|---|---|---|---|---|
| 20% | 40% | 0 | never | 0 | 141 to 149 | 0 |
| 34% | 54% | 0 | never | 0 | 108 to 110 | 0 |
| 51% | 71% | 67 to 70 | 2 | 172 to 173 | 67 to 70 | 0 |
| 67% | 87% | 30 to 32 | 4 to 5 | 208 to 209 | 30 to 32 | 0 |
| 90% | 110% | not run (attacker alone over 2/3) | - | - | - | - |
P. 50/50 honest partition for 150 min with an equivocator holding A of total reaching both sides (seeds 7,11)
Each side holds (1 - A)/2 + A of total: 60% at A = 20%, 67% at 34%, 75.5% at 51%, 83.5% at 67%, 95% at 90%. Two conflicting certificates need two thirds each, so A >= 1/3 is the bound (spec 03 3.11.2). v2 = the rule as specified with view-local weights; v3 = plus the frozen table (F21).
| equivocator A | each side holds | rule | conflicting locks | first conflict, min | first lock per side, min | every pre-heal lock kept | first lock after heal, min | stalls in 3 h after |
|---|---|---|---|---|---|---|---|---|
| 20% | 60.0% | v2 | 0 | never | never / never | yes | 0 | 0 |
| 20% | 60.0% | v3 | 0 | never | never / never | yes | 0 | 0 |
| 34% | 67.0% | v2 | 21 to 70 | 14 to 77 | 11 to 48 / 0 to 76 | yes | 0 | 0 |
| 34% | 67.0% | v3 | 21 to 69 | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 | 0 |
| 51% | 75.5% | v2 | 299 to 300 | 0 | 0 / 0 | yes | 0 to 0 | 0 |
| 51% | 75.5% | v3 | 299 to 300 | 0 | 0 / 0 | yes | 0 to 0 | 0 |
| 67% | 83.5% | v2 | 301 | 0 | 0 / 0 | yes | 0 to 0 | 0 |
| 67% | 83.5% | v3 | 301 | 0 | 0 / 0 | yes | 0 to 0 | 0 |
| 90% | 95.0% | v2 | 293 to 300 | 0 | 0 to 0 / 0 to 0 | yes | 0 to 0 | 0 |
| 90% | 95.0% | v3 | 293 to 300 | 0 | 0 to 0 / 0 to 0 | yes | 0 to 0 | 0 |
C. Abrupt departure (tonight's pause): keys holding A of weight stop mining AND signing at once; survivors inherit the block supply (31 days, seeds 7,11)
v2 analytic: the survivors hold 1 - A (30 - t)/30 of the sliding table on day t and reach two thirds on day 30 (1 - 1/(3A)) (never for A <= 1/3). v3: the frozen table at the last certified checkpoint holds the departed keys until it expires one window after that checkpoint, so the first lock comes on day 30 whatever A (results_v2.md M4). On the devnet the window is 7,200 DAA (2 h), so divide the days by 360.
| departed weight A | rule | picked | first lock after the departure, days | analytic | stalled checkpoints | live share of total at the end | conflicting locks |
|---|---|---|---|---|---|---|---|
| 20% | v2 | 20% | 0.00 | never | 39 to 213 | 100% | 0 |
| 20% | v3 | 20% | 0.00 | never | 39 to 213 | 100% | 0 |
| 34% | v2 | 34% | 0.77 to 0.85 | 0.6 | 4265 to 4727 | 100% | 0 |
| 34% | v3 | 34% | 30.00 | 30.0 | 86395 to 86472 | 100% | 0 |
| 51% | v2 | 51% | 10.46 to 10.52 | 10.4 | 31312 to 31739 | 100% | 0 |
| 51% | v3 | 51% | 30.00 | 30.0 | 86326 to 86336 | 100% | 0 |
| 67% | v2 | 67% | 15.15 to 15.24 | 15.1 | 44464 to 45047 | 100% | 0 |
| 67% | v3 | 67% | 30.00 | 30.0 | 86361 to 86411 | 100% | 0 |
| 90% | v2 | 83 to 90% | 17.92 to 18.88 | 18.9 | 51897 to 55628 | 100% | 0 |
| 90% | v3 | 83 to 90% | 30.00 to 30.00 | 30.0 | 86340 to 86481 | 100% | 0 |
(592 s in all)