igneum/sim/results.md
igneum-josh 974f839ece Igneum: design docs, Metal lottery-hash prototype, CUDA test pack, finality simulation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 16:06:01 +01:00

18 KiB

Sustained-mining finality: vote-weight simulation results

Date: 3 October 2026. Simulator: sim/finality_sim.py, seed 7 (seed 11 reproduces every crossing day quoted here). Every number below was produced by the simulator. Nothing is from memory.

The rule as simulated

Each key's vote weight is the sum over the trailing 30 days of its counted blocks, where

counted[d] = min(actual[d], 2 x counted[d-1] + f)

and f is the floor in blocks per day. A checkpoint locks when the signing keys hold two thirds of total weight.

Model assumptions (apply to every table)

Assumption Value
Time step 1 day
Blocks per day 86,400 (1 block/s), Poisson per key with mean 86,400 x hashrate share
Difficulty retargets perfectly, total blocks stay at 86,400/day whatever the hashrate does
Blue blocks every block produced is blue (no DAG, no red blocks, no latency)
Honest network 1,000 keys, Pareto shape 1.0 hashrate: top key 17.1%, top 10 keys 49.8%, bottom 500 keys 8.5%
Warm-up 60 honest days before any event in B to F
Rewards unweighted, a key's reward share is its block share
"Can lock alone" a group's weight is at least 2/3 of total weight (no committee sampling noise on the lock)
"Active total" (E only) a key counts if sampled into at least one committee in 24 h, approximated as Poisson(2,880 checkpoints x committee 100 x weight share) >= 1
Floors tested f in {1, 10, 100, 1000} blocks/day

Days in the B to E tables are counted from the event (burst, doubling or churn), so "+1" is the first full day after it.

A. Steady state

1,000 honest keys, 60 days from zero history.

floor f total weight / full corr(hash, weight) Gini hash / weight top-1 hash / weight top-10 hash / weight bottom-500 hash / weight min / max weight:hash ratio keys under 0.9x
1 100.0% 1.00000 0.761 / 0.762 17.1% / 17.1% 49.8% / 49.9% 8.5% / 8.4% 0.822 / 1.170 21
10 100.0% 1.00000 0.761 / 0.761 17.1% / 17.1% 49.8% / 49.9% 8.5% / 8.5% 0.833 / 1.169 12
100 100.0% 1.00000 0.761 / 0.761 17.1% / 17.1% 49.8% / 49.9% 8.5% / 8.5% 0.833 / 1.169 12
1000 100.0% 1.00000 0.761 / 0.761 17.1% / 17.1% 49.8% / 49.9% 8.5% / 8.5% 0.833 / 1.169 12

Day the network's total weight first reached 99% of the full window (30 x 86,400 = 2,592,000 counted blocks):

floor f day
1 41
10 38
100 35
1000 32

Interpretation. At steady state weight is proportional to hashrate: correlation 1.00000, Gini and top-k shares identical to three figures. The only deviation is Poisson noise on the smallest keys (10 to 20 blocks a day), where a quiet day lowers the next day's cap. At f=1 this leaves 21 of 1,000 keys below 0.9x their hashrate share, at f>=10 it is 12. From zero history the biggest pool (14,774 blocks a day) needs 14 doublings at f=1, so the network holds full weight only from day 41 (day 32 at f=1000). That sits inside the 30-day launch ramp plus 11 days.

B. Rental burst

At day 60 one new key appears with 1.5x the honest hashrate (60% of the network).

Attacker weight share of total:

day after burst f=1 f=10 f=100 f=1000
+1 0.0% 0.0% 0.0% 0.0%
+2 0.0% 0.0% 0.0% 0.2%
+3 0.0% 0.0% 0.0% 0.4%
+5 0.0% 0.0% 0.2% 2.4%
+7 0.0% 0.1% 1.1% 6.7%
+10 0.1% 1.0% 6.9% 13.2%
+14 1.7% 9.0% 16.2% 21.9%
+20 17.3% 24.2% 30.1% 34.9%
+25 31.1% 36.8% 41.8% 45.7%
+30 44.9% 49.5% 53.4% 56.6%
+35 51.6% 55.1% 58.2% 60.0%
+40 56.8% 59.3% 60.0% 60.0%
+46 60.1% 60.0% 60.0% 60.0%
event f=1 f=10 f=100 f=1000 no cap (f=1, cap removed)
crosses 1/3 (honest keys can no longer lock) 26 24 22 20 18
crosses 50% 34 31 29 27 26
crosses 2/3 never never never never never
max share in 46 days 60.1% 60.0% 60.0% 60.0% 60.0%
block-reward share, burst day 1 59.9% 59.9% 59.9% 59.9% 59.9%

Supplementary run, not in the brief: attacker with 3x the honest hashrate (75% of the network), so that a 2/3 crossing exists.

event f=1 f=10 f=100 f=1000 no cap
crosses 1/3 23 21 19 17 14
crosses 50% 27 26 24 23 21
crosses 2/3 34 31 30 29 27
block-reward share, burst day 1 75.0% 75.0% 75.0% 75.0% 75.0%

Interpretation. Rewards are immediate: the renter earns 59.9% of blocks on day one. Vote weight is not: it stays under 2% for the first two weeks at f<=10. A 60% renter never reaches 2/3 because at steady state weight equals hashrate share, so its ceiling is 60%. It does cross 1/3 on day 20 to 26, from which point honest keys cannot lock either. A 60% renter is therefore a liveness attack (finality stalls after about three weeks), not a safety attack. A 75% renter locks alone from day 29 to 34. The "no cap" column shows how the defence splits: the 30-day window alone holds the 75% renter to day 27, the 2x cap adds 2 days (f=1000) to 7 days (f=1) on top.

C. Key splitting

Same attacker, split over K keys. Two variants. C0: K fresh keys appear on the burst day with no history (costs only key creation). C: the brief's scenario, K keys trickle-mine at the floor rate for 30 days before the burst. B is the single-key baseline from above.

Attacker 1.5x honest (60%). Days after the burst until the attacker crosses 50% of total weight:

run f=1 f=10 f=100 f=1000
B: 1 key, no trickle 34 31 29 27
C0: K=100, no trickle 29 27 26 25
C0: K=1,000, no trickle 27 26 26 26
C0: K=10,000, no trickle 27 26 26 26
C0: K=50,000, no trickle 28 26 26 26
C: K=100, 30-day trickle 29 27 25 1
C: K=1,000, 30-day trickle 27 25 1 1
C: K=10,000, 30-day trickle 25 1 1 1
no cap, 1 key 26 26 26 26

Days until the attacker crosses 1/3 (honest keys lose the lock):

run f=1 f=10 f=100 f=1000
B: 1 key 26 24 22 20
C0: K=10,000 19 17 17 17
C: K=10,000, trickle 15 1 1 1

2/3 is never reached by a 60% attacker in any run. Supplementary 75% attacker, days until 2/3:

run f=1 f=10 f=100 f=1000
B: 1 key, no trickle 34 31 30 29
C0: K=100, no trickle 30 29 28 27
C0: K=1,000, no trickle 29 28 27 27
C0: K=10,000, no trickle 28 27 27 27
C0: K=50,000, no trickle 29 27 27 27
C: K=100, 30-day trickle 29 28 27 1
C: K=1,000, 30-day trickle 28 27 1 1
C: K=10,000, 30-day trickle 27 1 1 1
no cap, 1 key 27 27 27 27

What the trickle costs the attacker (days 30 to 60, 60% attacker):

run f=1 f=10 f=100 f=1000
K=100 1 blk/key/day, 0.1% of network, 0.1% weight at burst 10 blk/key/day, 1.2% of network, 1.2% weight 100 blk/key/day, 11.6% of network, 11.5% weight 518 blk/key/day, 60.0% of network, 60.0% weight
K=1,000 1 blk/key/day, 1.2% of network, 1.0% weight 10 blk/key/day, 11.6% of network, 11.5% weight 51.8 blk/key/day, 60.0% of network, 60.0% weight same, trickle = full attack
K=10,000 1 blk/key/day, 11.6% of network, 10.0% weight 5.2 blk/key/day, 60.0% of network, 60.0% weight same, trickle = full attack same, trickle = full attack

Interpretation. The per-key cap is defeated by splitting. The cap's total capacity for fresh keys is K x f counted blocks on day one, then 3Kf, 7Kf and so on. With 10,000 fresh keys at f=1 the ramp lasts 3 days instead of 16 and the 50% crossing moves from day 34 to day 27, one day off the no-cap figure of 26. At f=10, 10,000 keys match the no-cap figure exactly; at f=100, 1,000 keys do; at f=1000, 100 keys do. The 30-day trickle in the brief adds little on top of that: at K=10,000 and f=1 it costs 11.6% of the network for 30 days and buys 2 more days (25 against 27). Where the trickle table reads "60.0% of network", K x f already exceeds the attacker's daily output, so the "trickle" is the full attack started 30 days early and the day-1 figure of 60% is paid for in full, not stolen.

The important reading is the floor row. Against a splitter, time to 50% for a 60% attacker and time to 2/3 for a 75% attacker converge on 26 and 27 days whatever f is. Those days come from the 30-day window, not from the cap. The cap buys 7 to 8 days against an attacker who will not split and 1 to 2 days against one who will.

Supplementary: a presence gate (run with --presence 20, a key's daily cap stays at f until it has counted blocks on 20 of the last 30 days) was tested as a fix. It pushes C0 at K=10,000, f=1 from day 27 to day 40 and pushes the single-key renter past day 46, but the trickle variant is unchanged (the attacker simply pays the gate), and it damages honest growth: in D the new honest cohort never reaches 45% weight within 46 days at f<=10 and the old cohort can lock alone for 39 days instead of 23. Rejected on these numbers.

D. Honest growth shock

At day 60 the honest network doubles: 1,000 new keys with a fresh Pareto draw and the same total hashrate as the old 1,000. The new cohort's hashrate share is 50% from day +1.

New cohort's weight share of total:

day after doubling f=1 f=10 f=100 f=1000
+1 0.0% 0.3% 0.9% 1.4%
+3 0.4% 1.7% 3.4% 4.8%
+5 1.5% 3.9% 6.7% 8.1%
+10 7.6% 12.2% 15.2% 16.5%
+15 16.7% 20.9% 23.6% 24.8%
+20 26.0% 29.7% 32.1% 33.2%
+25 35.2% 38.5% 40.6% 41.5%
+30 44.5% 47.3% 49.1% 49.9%
+35 48.5% 49.7% 50.0% 50.0%
+46 50.0% 50.0% 50.0% 50.0%
event f=1 f=10 f=100 f=1000
new cohort reaches 45% weight (0.9x its hash share) 31 29 28 28
new cohort reaches 49% weight 37 33 30 30
last day old cohort holds 2/3 (can lock alone) 23 22 20 20

Interpretation. New honest miners are under-weighted for the whole window: 28 to 31 days to reach 0.9x their hashrate share, 30 to 37 days to reach 0.98x. The window sets the floor on that (with the cap removed the arithmetic gives old share = 1 - t/60, so the old cohort holds 2/3 for exactly 20 days). The cap adds 0 days at f>=100 and 3 days at f=1. During those 20 to 23 days the old miners can lock checkpoints without any new miner's signature. That is the intended behaviour (a doubling overnight is indistinguishable from a rental burst) and the price is paid by honest newcomers for about three weeks.

E. Churn

At day 60 a random set of keys holding 30% of weight goes offline for good. The remaining keys produce all 86,400 blocks from then on (perfect retarget). Supplementary runs at 35% and 50%.

Live honest weight share, total = every key with weight in the window ("all keys") against total = keys that signed a checkpoint in the last 24 h ("active"). Values were identical across f to one decimal, so one column per churn level is shown.

day after churn 30% offline, all / active 35% offline, all / active 50% offline, all / active
+1 71.0% / 100.0% 66.2% / 100.0% 51.6% / 100.0%
+2 72.0% / 100.0% 67.3% / 100.0% 53.3% / 100.0%
+3 73.0% / 100.0% 68.5% / 100.0% 55.0% / 100.0%
+5 75.0% / 100.0% 70.8% / 100.0% 58.3% / 100.0%
+10 80.0% / 100.0% 76.7% / 100.0% 66.6% / 100.0%
+15 85.0% / 100.0% 82.5% / 100.0% 75.0% / 100.0%
+20 90.0% / 100.0% 88.3% / 100.0% 83.3% / 100.0%
+30 100.0% / 100.0% 100.0% / 100.0% 100.0% / 100.0%
event 30% offline 35% offline 50% offline
offline fraction actually picked (keys) 30.0% (435 keys) 35.0% (529 to 530 keys) 50.0% (473 keys)
live share >= 2/3, all-keys total, first day 1 (never lost) 2 10 to 11
live share >= 2/3, active total, first day 1 1 1
dead weight fully out of the window day 30 day 30 day 30

Interpretation. The brief expected 30% churn to block the lock. It does not: 30% offline leaves 70% to 71% live, above 2/3 from day one, with a 3 to 4 point margin. The threshold is 1/3 offline. At 35% the lock is lost for one day, at 50% for 10 to 11 days. The live share follows 1 - x(30 - t)/30 for offline fraction x and day t, because the survivors inherit the whole block supply and the dead weight decays linearly out of the window, which it leaves completely on day 30. Under the active-24-hour total there is no stall at any churn level: the dead keys drop out of the denominator after one day. The difference is a liveness gain bought with a safety cost the simulation does not model: under the active total, any event that keeps honest keys from signing (eclipse, partition, targeted DoS) shrinks the denominator and lets a smaller faction lock; in a partition both sides see 100% active and both can lock. The all-keys total fails safe (stall) in the same cases.

F. Patient owner

An attacker who owns 51% of the hardware, mines honestly from day 0 and so has full history. Supplementary run at 67%.

Owner's weight share by day:

day 51% owner, f=1 f=10 f=100 f=1000 67% owner, f=1 f=10 f=100 f=1000
15 15.6% 31.0% 38.9% 44.6% 20.7% 43.4% 53.6% 60.2%
30 42.4% 44.0% 45.9% 48.0% 58.0% 59.6% 61.7% 64.0%
45 51.0% 50.9% 50.9% 50.9% 67.1% 67.0% 67.0% 67.0%
60 51.0% 51.0% 51.0% 51.0% 67.1% 67.0% 67.0% 67.0%
90 51.1% 51.0% 51.0% 51.0% 67.1% 67.0% 67.0% 67.0%
event (of 90 days) 51% owner, f=1 f=10 f=100 f=1000 67% owner, f=1 f=10 f=100 f=1000
mean weight share, days 31 to 90 50.0% 50.4% 50.7% 50.9% 66.0% 66.4% 66.7% 66.9%
min / max, days 31 to 90 42.8% / 51.1% 44.5% / 51.1% 46.5% / 51.1% 48.7% / 51.1% 58.5% / 67.2% 60.2% / 67.0% 62.4% / 67.0% 64.7% / 67.0%
days at or above 2/3 (locks alone) 0 0 0 0 47 50 53 57
days above 1/3 (vetoes every lock) 71 74 79 84 74 78 81 85

Interpretation. The residual the design accepts is exactly the hashrate share. A 51% owner holds 51.0% of weight from day 45 onward (51.0% to 51.1% across every f). It can never lock alone, and it can veto every lock from day 7 to 20 onward, for 71 to 84 of the 90 days. A 67% owner locks alone from day 34 to 44 onward. The 2x cap and the floor only decide how fast the owner reaches its share (day 15 reading: 15.6% at f=1 against 44.6% at f=1000); they change nothing about where it lands. Sustained-mining finality therefore defends against rented hashrate, and against owned hashrate it offers the same guarantee as the underlying proof of work: safety needs honest hashrate above 1/3 of the sustained total, liveness needs it above 2/3.

Parameter Recommendation Reason from the runs
Floor f 1 block per key per day Every larger f voids the cap for a smaller key count (f=10: 10,000 keys, f=100: 1,000, f=1000: 100). Honest cost of f=1 is 21 of 1,000 small keys under 0.9x weight, against 12 at f=10 (A).
Daily cap 2x the previous day's counted blocks, keep Cheap for honest growth (adds 0 to 3 days to D) and worth 7 to 8 days against a single-key renter at f=1. Do not count on it against a splitter: it is worth 1 to 2 days there.
Window 30 days, keep This is the actual defence. It holds a 60% renter below 1/3 for 18 to 26 days and below 50% for 26 to 34 days, and a 75% renter below 2/3 for 27 to 34 days, splitting or not. The model's arithmetic says these times scale linearly with the window (a 60-day window doubles them) and that the honest under-weighting in D scales with it too.
Total weight every key with non-zero weight in the window (all keys), not the active-24-hour set Fails safe. The stall it causes needs more than 1/3 of weight to vanish at once (35% offline: 1 day, 50%: 10 to 11 days) and clears within the window. The active-24-hour total removes the stall but lets a partition or an eclipse shrink the denominator, which the simulation cannot price. If liveness under mass churn matters, test a middle ground (drop keys silent for 7 days) before adopting it.
Presence gate do not adopt Tested at 20 of 30 days: slows the no-trickle splitter by 13 days, changes nothing for the trickling splitter, and roughly doubles the time new honest miners wait for weight (D).

What the simulation cannot tell us. It has no network latency, so every block is blue and on time; in the real GHOSTDAG some of an attacker's or a laggard's blocks will be red and not counted, which changes weights in a direction this model cannot predict. It has no DAG, so it cannot see how a 60% attacker's blocks interact with the honest tip or how the Horizen secret-mining penalty bears on withheld blocks. It has no VRF sampling noise: the lock is tested against exact group weights, while the real checkpoint samples a committee, so a faction near 2/3 will sometimes lock and sometimes not, and a faction near 1/3 will sometimes be unable to veto. It assumes difficulty retargets instantly and hashrate is constant within a day. It does not model partitions, eclipse attacks or targeted DoS, which is where the all-keys and active-total definitions differ in safety. And it puts no price on keys: the splitting result assumes 10,000 to 50,000 keys are free to create and to sign with, which the P2P and VRF layers may make expensive in ways this model cannot see.