From 9ca66da09ef5aacfa7db6ee27d49e520696b8fcb Mon Sep 17 00:00:00 2001 From: SimoneMariaRomeo <180769497+SimoneMariaRomeo@users.noreply.github.com> Date: Wed, 13 May 2026 01:32:38 +0700 Subject: [PATCH] Verify mutating challenge proof hashes --- .../src/anti_spoof/mutating_challenge.py | 17 +++- tests/test_mutating_challenge_proof_hash.py | 98 +++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 tests/test_mutating_challenge_proof_hash.py diff --git a/rips/rustchain-core/src/anti_spoof/mutating_challenge.py b/rips/rustchain-core/src/anti_spoof/mutating_challenge.py index c3b83219c..bae514f22 100644 --- a/rips/rustchain-core/src/anti_spoof/mutating_challenge.py +++ b/rips/rustchain-core/src/anti_spoof/mutating_challenge.py @@ -404,9 +404,19 @@ def validate_response( confidence -= 20.0 # 5. Verify proof hash (must have correct round count) - # In production, we'd recompute and verify - - valid = confidence >= 50.0 + proof_ok = True + if not response.proof_hash: + proof_ok = False + failures.append("Missing proof hash") + confidence -= 50.0 + else: + expected_proof = response.compute_proof(challenge, b'') + if not secrets.compare_digest(response.proof_hash, expected_proof): + proof_ok = False + failures.append("Proof hash mismatch") + confidence -= 50.0 + + valid = confidence >= 50.0 and proof_ok # Record result self.round_robin.results_this_round[challenge.target] = valid @@ -531,6 +541,7 @@ def demo_mutating_challenges(): proof_hash=b'', timestamp_ms=int(time.time() * 1000) ) + response.proof_hash = response.compute_proof(challenge, b'') valid, confidence, failures = network.validate_response(response) diff --git a/tests/test_mutating_challenge_proof_hash.py b/tests/test_mutating_challenge_proof_hash.py new file mode 100644 index 000000000..662775482 --- /dev/null +++ b/tests/test_mutating_challenge_proof_hash.py @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: MIT +from __future__ import annotations + +import importlib.util +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +MODULE_PATH = ROOT / "rips" / "rustchain-core" / "src" / "anti_spoof" / "mutating_challenge.py" + + +def _load_module(): + spec = importlib.util.spec_from_file_location("mutating_challenge_under_test", MODULE_PATH) + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +def _hardware_profile(): + return { + "cpu": {"model": "PowerMac3,6"}, + "openfirmware": {"serial_number": "OF123"}, + "gpu": {"device_id": "GPU123"}, + "storage": {"serial": "SSD123"}, + } + + +def _challenge_context(module): + network = module.MutatingChallengeNetwork(["alpha-node", "beta-node"], genesis_seed=b"g" * 32) + for validator in network.validator_failures: + network.register_hardware(validator, _hardware_profile()) + challenge = network.on_new_block(10, b"b" * 32)[0] + challenge.mutation_params.hash_rounds = 2 + return network, challenge + + +def _valid_response(module, network, challenge): + response = module.MutatingResponse( + challenge_id=challenge.challenge_id, + responder=challenge.target, + cache_timing_ticks=challenge.mutation_params.timing_min_ticks + 1, + memory_timing_ticks=45000, + pipeline_timing_ticks=8000, + jitter_variance=challenge.mutation_params.jitter_min_percent, + thermal_celsius=( + challenge.mutation_params.thermal_min_c + + challenge.mutation_params.thermal_max_c + ) + // 2, + serial_value=network._get_serial( + network.validator_hardware[challenge.target], + challenge.mutation_params.serial_type, + ), + proof_hash=b"", + timestamp_ms=challenge.timestamp_ms + 1000, + ) + response.proof_hash = response.compute_proof(challenge, b"") + return response + + +def test_validate_response_accepts_matching_proof_hash(): + module = _load_module() + network, challenge = _challenge_context(module) + response = _valid_response(module, network, challenge) + + valid, confidence, failures = network.validate_response(response) + + assert valid is True + assert confidence == 100.0 + assert failures == [] + + +def test_validate_response_rejects_missing_proof_hash(): + module = _load_module() + network, challenge = _challenge_context(module) + response = _valid_response(module, network, challenge) + response.proof_hash = b"" + + valid, confidence, failures = network.validate_response(response) + + assert valid is False + assert confidence == 50.0 + assert "Missing proof hash" in failures + + +def test_validate_response_rejects_mismatched_proof_hash(): + module = _load_module() + network, challenge = _challenge_context(module) + response = _valid_response(module, network, challenge) + response.proof_hash = b"\x00" * 32 + + valid, confidence, failures = network.validate_response(response) + + assert valid is False + assert confidence == 50.0 + assert "Proof hash mismatch" in failures