/epoch/enroll lacks signature verification / ownership proof
Severity
Medium-High — Unauthorized enrollment enables miner_id hijacking and enrollment race attacks.
Summary
The POST /epoch/enroll endpoint accepts an arbitrary miner_pubkey in the request body without any cryptographic proof that the caller owns or controls that pubkey. Any caller who knows a pubkey with a recent attestation can enroll it — including hijacking the miner_id mapping via INSERT OR REPLACE INTO miner_header_keys.
Affected Code
node/rustchain_v2_integrated_v2.2.1_rip200.py — enroll_epoch() handler (line ~3027)
rustchain-miner/src/miner.rs — enroll() method (line ~270)
Exploit Scenario
Attack 1: Miner ID Hijacking
- Victim completes attestation:
POST /attest/submit → server records miner_attest_recent with miner = RTC_VICTIM
- Attacker calls
POST /epoch/enroll with:
{
"miner_pubkey": "RTC_VICTIM",
"miner_id": "attacker_controlled_id",
"device": {"family": "x86_64", "arch": "default"}
}
- Server executes
INSERT OR REPLACE INTO miner_header_keys (miner_id, pubkey_hex) VALUES ('attacker_controlled_id', 'RTC_VICTIM')
- Victim's block submissions using their original
miner_id may fail or be attributed to the attacker's mapping.
Attack 2: Enrollment Race (First-Come-First-Served)
- Victim attests but hasn't enrolled yet.
- Attacker enrolls victim's pubkey with a low-weight device (e.g., default x86 at 1.0x instead of victim's actual PowerPC G4 at 2.5x).
INSERT OR IGNORE INTO epoch_enroll means the victim's legitimate high-weight enrollment is silently ignored.
- Victim earns at 1.0x weight instead of 2.5x for the entire epoch.
Note: Attack 2 is partially mitigated by the existing INSERT OR IGNORE fix, but the race window remains if the attacker enrolls first.
Root Cause
The enrollment endpoint verifies that the miner_pubkey has a recent attestation (via check_enrollment_requirements), but does not verify that the caller is the same entity that performed the attestation. There is no signature or ownership proof on the enrollment request.
Contrast with Prior Fixes
/attest/submit signature verification (prior fix, test_attest_signature_verification.py): Verifies Ed25519 signatures on attestation reports. This proves the attester controls the signing keypair. Already fixed.
INSERT OR IGNORE on epoch_enroll (prior fix, test_attestation_overwrite_reward_loss.py): Prevents weight downgrade attacks from repeated enrollment calls. Already fixed.
- This finding: The enrollment endpoint itself has no signature verification. An attacker doesn't need to replay or modify an existing enrollment — they can submit a fresh enrollment for any pubkey with a recent attestation. Not previously addressed.
Fix
Require Ed25519 signatures on enrollment requests, verified against the signing pubkey stored during the miner's most recent attestation:
- Server-side: Store the Ed25519
public_key from attestation in miner_attest_recent.signing_pubkey. On enrollment, verify the signature against this stored key.
- Client-side (Rust miner): Generate the Ed25519 keypair once at startup and reuse it for both attestation and enrollment signatures.
- Backward compatibility: Unsigned enrollment requests are still accepted (warn-only) to allow legacy miners to continue working while operators upgrade.
Reproduction
# Without the fix, any caller can enroll any pubkey with a recent attestation:
curl -X POST http://localhost:8099/epoch/enroll \
-H "Content-Type: application/json" \
-d '{"miner_pubkey": "RTC_VICTIM", "miner_id": "attacker_id", "device": {"family": "x86_64", "arch": "default"}}'
# Returns: {"ok": true, "epoch": N, "weight": 1.0, "miner_pk": "RTC_VICTIM", "miner_id": "attacker_id"}
References
- Prior fix:
/attest/submit signature verification — tests/test_attest_signature_verification.py
- Prior fix:
INSERT OR IGNORE on epoch_enroll — tests/test_attestation_overwrite_reward_loss.py
- New tests:
tests/test_enroll_signature_verification.py
/epoch/enroll lacks signature verification / ownership proof
Severity
Medium-High — Unauthorized enrollment enables miner_id hijacking and enrollment race attacks.
Summary
The
POST /epoch/enrollendpoint accepts an arbitraryminer_pubkeyin the request body without any cryptographic proof that the caller owns or controls that pubkey. Any caller who knows a pubkey with a recent attestation can enroll it — including hijacking theminer_idmapping viaINSERT OR REPLACE INTO miner_header_keys.Affected Code
node/rustchain_v2_integrated_v2.2.1_rip200.py—enroll_epoch()handler (line ~3027)rustchain-miner/src/miner.rs—enroll()method (line ~270)Exploit Scenario
Attack 1: Miner ID Hijacking
POST /attest/submit→ server recordsminer_attest_recentwithminer = RTC_VICTIMPOST /epoch/enrollwith:{ "miner_pubkey": "RTC_VICTIM", "miner_id": "attacker_controlled_id", "device": {"family": "x86_64", "arch": "default"} }INSERT OR REPLACE INTO miner_header_keys (miner_id, pubkey_hex) VALUES ('attacker_controlled_id', 'RTC_VICTIM')miner_idmay fail or be attributed to the attacker's mapping.Attack 2: Enrollment Race (First-Come-First-Served)
INSERT OR IGNORE INTO epoch_enrollmeans the victim's legitimate high-weight enrollment is silently ignored.Note: Attack 2 is partially mitigated by the existing
INSERT OR IGNOREfix, but the race window remains if the attacker enrolls first.Root Cause
The enrollment endpoint verifies that the
miner_pubkeyhas a recent attestation (viacheck_enrollment_requirements), but does not verify that the caller is the same entity that performed the attestation. There is no signature or ownership proof on the enrollment request.Contrast with Prior Fixes
/attest/submitsignature verification (prior fix,test_attest_signature_verification.py): Verifies Ed25519 signatures on attestation reports. This proves the attester controls the signing keypair. Already fixed.INSERT OR IGNOREonepoch_enroll(prior fix,test_attestation_overwrite_reward_loss.py): Prevents weight downgrade attacks from repeated enrollment calls. Already fixed.Fix
Require Ed25519 signatures on enrollment requests, verified against the signing pubkey stored during the miner's most recent attestation:
public_keyfrom attestation inminer_attest_recent.signing_pubkey. On enrollment, verify the signature against this stored key.Reproduction
References
/attest/submitsignature verification —tests/test_attest_signature_verification.pyINSERT OR IGNOREonepoch_enroll—tests/test_attestation_overwrite_reward_loss.pytests/test_enroll_signature_verification.py