fix(enroll): bind reward weight to attested hardware, not unsigned request-body device#8013
Open
Vyacheslav-Tomashevskiy wants to merge 1 commit into
Conversation
…quest-body device /epoch/enroll computed the HARDWARE_WEIGHTS reward multiplier from the caller-supplied request-body `device` (family/arch). The enrollment signature covers only (miner_pubkey|miner_id|epoch), so `device` is unsigned and attacker-controlled: a miner that attests on ordinary x86 (verified x86/modern -> 0.8x) can enroll claiming ARM 'arm2' (4.0x) and be recorded with a ~5x inflated epoch weight, draining the fixed per-epoch reward pot from honest miners. check_enrollment_requirements validates only recency/fingerprint_passed/MAC, never arch; the module built to catch this (arch_cross_validation.validate_arch_consistency) has no callers in the server path. Fix: resolve_enroll_weight_device() derives family/arch from the verified device stored at attestation (miner_attest_recent.device_family/arch, written from derive_verified_device against the submitted fingerprint), mirroring resolve_enroll_fingerprint. Request-body device is used only as a legacy fallback when no verified device is stored, so deployed miners are not regressed. Regression test drives the real /epoch/enroll with a valid Ed25519 signature: fails on the old body-trusting code (weight 4.0), passes on the fix (weight 0.8); a legacy-fallback case guards against regressing pre-migration rows.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reward-weight spoofing via unsigned
deviceon/epoch/enrollBug
enroll_epoch()computes theHARDWARE_WEIGHTSreward multiplier from the request-bodydevice:The enrollment signature covers only
miner_pubkey|miner_id|epoch, sodeviceis unsigned and fully caller-controlled. Nothing binds the enroll-claimed arch to the hardware the miner actually attested:check_enrollment_requirements()validates only attestation recency,fingerprint_passed(VM/emulator realness) and MAC churn — never the device arch.arch_cross_validation.validate_arch_consistency()— the module built specifically to catch claimed-arch-vs-fingerprint mismatch — has no callers in the server path (grep: only its own tests + an unwiredrips/module).HARDWARE_WEIGHTSspans0.0005(aarch64 NAS) …4.0(ARMarm2),2.8(consolenes_6502),2.5(x86386) vs0.8for modern x86.Impact (concrete trace)
fingerprint_passed=1, verified device stored asx86/modern(honest weight 0.8).miner_pubkey|miner_id|epochwith their own attestation key — signature verifies."device": {"family":"ARM","arch":"arm2"}→hw_weight = 4.0.anti_double_mining.py:weight = enrolled_weights[miner_id]) and paysshare = int((weight/total_weight) * total_reward_urtc)→ attacker collects a ~5x inflated share of the fixed epoch pot (up to ~8000x versus an honest aarch64 miner), draining honest participants.fingerprint_passedstays 1 throughout because it only validates attestation-hardware realness, not the enroll-claimed arch.Fix
resolve_enroll_weight_device()derivesfamily/archfrom the verified device the node already stored at attestation (miner_attest_recent.device_family/device_arch, written fromderive_verified_device), mirroring the existingresolve_enroll_fingerprintpattern. The request-bodydeviceis consulted only as a legacy fallback when no verified device is stored (pre-migration rows), so deployed miners are not regressed. ~35 lines, localized toenroll_epoch+ one helper.Verification
node/tests/test_enroll_weight_device_spoof.pydrives the real/epoch/enrollendpoint with a valid Ed25519 signature:test_spoofed_device_cannot_inflate_weight— attest verified as x86/modern, enroll body claims ARM/arm2 →hw_weightstays 0.8 (was 4.0). Mutation-checked: revert to the body-trusting line → test FAILS.test_legacy_miner_without_verified_device_falls_back_to_body— no stored verified device → body device still honored (no regression).test_enroll_signature_verification.py/test_block_producer_enroll_weight.pyin my local env are identical with and without this change (missing optional modules like_epoch_eligible_miners/fleet_immune_system) — this PR introduces no new failures.Claiming under rustchain-bounties#71. RTC address:
RTCd1554f0f35576faf01d386a6be1c947f560dd0b7