-
-
Notifications
You must be signed in to change notification settings - Fork 198
security: /epoch/enroll allows external reward-weight downgrade #2109
Description
Zero-weight miner reward distortion via explicit /epoch/enroll endpoint
Severity: High
Component: node/rustchain_v2_integrated_v2.2.1_rip200.py (/epoch/enroll), node/sophia_elya_service.py (enroll_epoch)
Labels: bug, rewards, enrollment, security
Summary
The explicit /epoch/enroll endpoint and the enroll_epoch() helper in sophia_elya_service.py both use INSERT OR REPLACE INTO epoch_enroll, allowing any caller to overwrite a legitimate miner's epoch weight with a near-zero value. This causes proportional reward loss for the victim miner for the entire epoch.
Distinction from prior submission
The prior submission (.tmp-issue-attest-overwrite-reward-loss.md) covered two self-inflicted downgrade paths:
miner_attest_recentfingerprint downgrade viaINSERT OR REPLACE→ fixed withON CONFLICT DO UPDATE+MAX(fingerprint_passed).- Auto-enroll weight downgrade via
INSERT OR REPLACE→ fixed withINSERT OR IGNORE. - P2P gossip path → fixed with
ON CONFLICT DO UPDATE+MAX.
This finding is distinct: it covers the explicit /epoch/enroll endpoint which was NOT changed by the prior fix and still uses INSERT OR REPLACE. This creates an external downgrade vector — an attacker (not the victim miner) can call this endpoint to overwrite the victim's weight.
Root Cause
/epoch/enroll endpoint (line ~3075)
c.execute(
"INSERT OR REPLACE INTO epoch_enroll (epoch, miner_pk, weight) VALUES (?, ?, ?)",
(epoch, miner_pk, weight)
)This endpoint accepts miner_pubkey and device from the request body. The weight is recalculated from the provided device data. INSERT OR REPLACE means any call overwrites the existing enrollment for (epoch, miner_pk).
sophia_elya_service.py enroll_epoch() (line ~73)
Same INSERT OR REPLACE pattern in a separate service module.
Exploit Path
- Victim miner attests successfully → auto-enrolled with
weight=2.5(e.g. G4 hardware). - Attacker calls
POST /epoch/enrollwith:miner_pubkey: victim's public keydevice:{}(empty) or any device that resolves to low weight- Or: device data that triggers
fingerprint_failed=True→weight=1e-9
INSERT OR REPLACEoverwrites the victim's enrollment:weightgoes from2.5→1.0(default x86) or1e-9(fingerprint failed).- At epoch settlement, the victim receives ~0% of their expected rewards.
No authentication or ownership proof is required — anyone with a miner's pubkey can do this.
Impact
- Any enrolled miner is vulnerable to weight downgrade by any third party.
- Economic impact: proportional to the victim's weight and epoch pot. A G4 miner (weight 2.5) downgraded to 1e-9 loses ~100% of epoch rewards.
- No exploit complexity: single HTTP POST, no signature or auth required.
- Triggers under normal operation: a miner re-enrolling themselves with different device data also triggers this (self-inflicted variant).
Reproduction
See node/tests/test_attestation_overwrite_reward_loss.py:
test_external_enroll_downgrade_old_behaviour— demonstrates the bugtest_external_enroll_downgrade_fixed— verifies the fix
Fix
Change INSERT OR REPLACE to INSERT OR IGNORE in both enrollment paths. The first enrollment in an epoch wins; subsequent calls are no-ops.
node/rustchain_v2_integrated_v2.2.1_rip200.py — /epoch/enroll
- c.execute(
- "INSERT OR REPLACE INTO epoch_enroll (epoch, miner_pk, weight) VALUES (?, ?, ?)",
- (epoch, miner_pk, weight)
- )
+ c.execute(
+ "INSERT OR IGNORE INTO epoch_enroll (epoch, miner_pk, weight) VALUES (?, ?, ?)",
+ (epoch, miner_pk, weight)
+ )node/sophia_elya_service.py — enroll_epoch()
- c.execute("INSERT OR REPLACE INTO epoch_enroll(epoch, miner_pk, weight) VALUES (?,?,?)", (epoch, miner_pk, float(weight)))
+ c.execute("INSERT OR IGNORE INTO epoch_enroll(epoch, miner_pk, weight) VALUES (?,?,?)", (epoch, miner_pk, float(weight)))Trade-off: first-enrollment-wins
With INSERT OR IGNORE, the first enrollment in an epoch wins. If an attacker enrolls a victim's pubkey first with low weight, the victim's later legitimate enrollment is also blocked. This is acceptable because:
- The attacker would need the victim's pubkey in advance.
- The attacker sacrifices their own rewards by enrolling with low weight.
- This is no worse than the attacker having mined with that pubkey from the start.
- The alternative (allowing re-enrollment) enables the external downgrade attack described above.
A future improvement could add authorization (require the miner to sign the enrollment request) to allow safe re-enrollment.
Files Changed
node/rustchain_v2_integrated_v2.2.1_rip200.py—/epoch/enrollendpointnode/sophia_elya_service.py—enroll_epoch()helpernode/tests/test_attestation_overwrite_reward_loss.py— 3 new tests for external downgrade vector