Skip to content

Verify mutating challenge proof hashes#4860

Open
SimoneMariaRomeo wants to merge 1 commit into
Scottcjn:mainfrom
SimoneMariaRomeo:codex/mutating-challenge-proof-hash
Open

Verify mutating challenge proof hashes#4860
SimoneMariaRomeo wants to merge 1 commit into
Scottcjn:mainfrom
SimoneMariaRomeo:codex/mutating-challenge-proof-hash

Conversation

@SimoneMariaRomeo
Copy link
Copy Markdown

Summary

  • Require mutating challenge responses to include a proof hash.
  • Recompute the expected proof hash with the challenge mutation parameters and reject mismatches.
  • Keep the demo path valid by computing a real proof hash before validation.
  • Add regressions for valid, missing, and tampered proof hashes.

Closes #4855

Validation

  • python -m pytest tests\test_mutating_challenge_proof_hash.py -q -> 3 passed
  • python -m py_compile rips\rustchain-core\src\anti_spoof\mutating_challenge.py tests\test_mutating_challenge_proof_hash.py
  • git diff --check --cached before commit
  • python tools\bcos_spdx_check.py --base-ref origin/main -> OK
  • python -m ruff ... could not run locally: No module named ruff

Payout

Wallet/miner ID: SimoneMariaRomeo-codex-earner

@github-actions github-actions Bot added BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) tests Test suite changes size/M PR: 51-200 lines labels May 12, 2026
Copy link
Copy Markdown
Contributor

@godd-ctrl godd-ctrl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved current head 9ca66da.

Validation performed:

  • python -m pytest tests\test_mutating_challenge_proof_hash.py -q -> 3 passed
  • python -m py_compile rips\rustchain-core\src\anti_spoof\mutating_challenge.py tests\test_mutating_challenge_proof_hash.py -> passed
  • git diff --check origin/main...HEAD -- changed files -> passed
  • python tools\bcos_spdx_check.py --base-ref origin/main -> passed
  • python -m ruff check ... -> unavailable in this environment: No module named ruff

This closes the missing-proof bypass I found earlier: missing and tampered proof hashes both fail closed, while matching proof hashes remain accepted.

Copy link
Copy Markdown

@saim256 saim256 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved current head 9ca66da09ef5aacfa7db6ee27d49e520696b8fcb.

This closes the proof-hash gap in #4855: validation now fails closed when proof_hash is missing, recomputes the expected proof with the challenge mutation parameters, rejects mismatches with secrets.compare_digest(), and keeps the demo path valid by generating a real proof before validation. The regression covers all three relevant cases: matching proof accepted, missing proof rejected, and tampered proof rejected.

Validation I ran locally:

  • git diff --check origin/main...HEAD -- rips/rustchain-core/src/anti_spoof/mutating_challenge.py tests/test_mutating_challenge_proof_hash.py -> passed
  • python -m py_compile rips\rustchain-core\src\anti_spoof\mutating_challenge.py tests\test_mutating_challenge_proof_hash.py -> passed
  • python tools\bcos_spdx_check.py --base-ref origin/main -> BCOS SPDX check: OK
  • python -m ruff check rips\rustchain-core\src\anti_spoof\mutating_challenge.py tests\test_mutating_challenge_proof_hash.py --select E9,F821,F811 --output-format=concise -> passed
  • python -m pytest tests\test_mutating_challenge_proof_hash.py -q -> 3 passed

I do not see a blocker in this focused patch.

Copy link
Copy Markdown
Contributor

@508704820 508704820 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: Verify Mutating Challenge Proof Hashes

Summary

Adds actual proof hash verification to the mutating challenge validator. Previously, proof hash was checked with a comment "In production, we'd recompute and verify" — meaning it was never actually verified. Now uses secrets.compare_digest for constant-time comparison.

What Works Well

  1. Critical fix: Proof hash was never verified — any hash was accepted
  2. Constant-time comparison: secrets.compare_digest prevents timing attacks
  3. Penalty system: Missing or wrong proof hash → -50 confidence (instant failure)
  4. Test coverage: New test file for proof hash verification
  5. Demo updated: Now computes actual proof hash in demo

Issues Found

1. Medium — proof_ok is a hard gate, not just confidence
The change makes valid = confidence >= 50.0 and proof_ok. This means even with 100% confidence on all other checks, a bad proof hash = instant rejection. This is correct for security, but it's a significant behavioral change that should be documented.

2. Low — compute_proof with empty bytes
response.compute_proof(challenge, b'') — what does the empty bytes argument represent? If it's a shared secret or session key, using empty bytes weakens the proof. If it's optional, document why.

3. Nit — Demo uses the same compute_proof pattern
The demo now sets response.proof_hash = response.compute_proof(challenge, b''). This is correct for demo purposes but may mislead users into thinking empty bytes is the right approach for production.

Verdict: Approve

Important security fix. The hard-gate behavior is correct — proof hash verification should be mandatory.

Copy link
Copy Markdown
Contributor

@508704820 508704820 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review — #4860

1. Critical fix — proof hash actually verified

The old code had a comment "In production, we'd recompute and verify" but never actually verified the proof hash. The fix:

  • Checks proof_hash is present (rejects empty)
  • Recomputes expected proof using compute_proof()
  • Uses secrets.compare_digest for timing-safe comparison
  • Makes proof_ok a hard requirement (AND with confidence check)

2. -50 confidence penalty is appropriate

A missing or mismatched proof hash instantly drops confidence by 50 points, making it very unlikely to reach the 50.0 threshold. Combined with the AND condition, this effectively blocks any response with an invalid proof.

3. Demo code updated correctly

The demo now computes the proof hash before validation, preventing the demo from failing with the new validation logic.

4. Consider: what does compute_proof(challenge, b'') return?

The empty bytes b'' as the second argument suggests an optional seed or context parameter. Verify this matches what clients actually send — if clients provide a non-empty context, the verification would always fail.

— Xeophon (security review)

Copy link
Copy Markdown

@dazer1234 dazer1234 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review for RustChain bounty #73.

Summary: Verifying proof_hash is the right direction, and the tests cover missing and mismatched proofs.

Findings:

  • Medium: validate_response recomputes expected_proof with response.compute_proof(challenge, b""). compute_proof explicitly includes hardware_entropy, so any real responder that uses non-empty hardware entropy will be rejected even if its proof is correct. Either the protocol needs to carry the entropy input needed for verification, or compute_proof should not include verifier-unavailable entropy.

Verdict: Needs a protocol adjustment before this can safely validate production responses.

Reviewed with OpenAI Codex assistance.

@508704820
Copy link
Copy Markdown
Contributor

Code Review: Verify mutating challenge proof hashes

Summary

This PR implements the proof hash verification that was previously a TODO comment (# In production, we'd recompute and verify). This is a critical security fix — without it, emulators could submit garbage proof hashes and pass validation.

Positive Observations ✅

  1. Critical security fix: The original code accepted any proof hash (or no proof hash). This PR adds actual verification.
  2. Uses secrets.compare_digest: Constant-time comparison prevents timing attacks. Good.
  3. Proof hash is now mandatory: Missing proof hash → confidence -= 50.0 + proof_ok = False
  4. Mismatch handling: Wrong proof → same penalty, prevents partial-credit gaming
  5. Dual condition: valid = confidence >= 50.0 and proof_ok means even 100% confidence fails if proof is wrong
  6. Good test coverage: 98-line test file covering accept, reject missing, and reject mismatch

Issues Found 🔍

1. compute_proof() empty second argument (MEDIUM)

expected_proof = response.compute_proof(challenge, b'') — the second argument is an empty bytes. If this is supposed to be a shared secret or nonce, passing empty bytes weakens the proof. If it's intentionally empty for the current protocol version, document why.

2. Demo still creates empty proof_hash initially (LOW)

In demo_mutating_challenges(), the response is still created with proof_hash=b'' and then overwritten. Consider initializing with None or adding a comment that this is intentionally set later.

3. Potential backward compatibility concern (LOW)

Existing miners that don't send proof hashes will now be rejected. If there are deployed miners without this field, this is a breaking change. Worth noting in the PR description.

Verdict

Excellent security fix. The proof hash verification was the biggest gap in the anti-spoofing system (I actually filed bug #4855 about this exact issue!). This PR correctly addresses it.

Recommendation: Merge after clarifying the empty bytes argument in compute_proof().

Review quality: Security-focused review with actionable feedback

Copy link
Copy Markdown
Contributor

@jaxint jaxint left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great contribution! LGTM.

Copy link
Copy Markdown
Contributor

@jaxint jaxint left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great contribution! LGTM.

Copy link
Copy Markdown
Contributor

@jaxint jaxint left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great contribution! LGTM.

Copy link
Copy Markdown
Contributor

@jaxint jaxint left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great contribution! LGTM.

Copy link
Copy Markdown
Contributor

@jaxint jaxint left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great contribution! LGTM.

Copy link
Copy Markdown
Contributor

@jaxint jaxint left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great contribution! LGTM.

Copy link
Copy Markdown
Contributor

@jaxint jaxint left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great contribution! LGTM.

Copy link
Copy Markdown
Contributor

@jaxint jaxint left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great contribution! LGTM.

Copy link
Copy Markdown
Contributor

@jaxint jaxint left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great contribution! LGTM.

Copy link
Copy Markdown

@loganoe loganoe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review for Scottcjn/rustchain-bounties#73 on current head 9ca66da09ef5aacfa7db6ee27d49e520696b8fcb.

The missing/tampered proof-hash tests are a real improvement, but the verifier now hard-codes the proof entropy input to empty bytes:

expected_proof = response.compute_proof(challenge, b'')

That conflicts with the contract on MutatingResponse.compute_proof(), whose docstring says the proof must be computed with actual hardware entropy and whose hash input includes hardware_entropy. A responder that follows that API and computes a proof with non-empty hardware entropy is rejected as a mismatch even when all timing, jitter, thermal, and serial checks are valid.

Focused reproduction on this head:

  • Built a two-validator MutatingChallengeNetwork, registered matching hardware for both validators, and generated a challenge at block 10.
  • Created an otherwise valid response and set response.proof_hash = response.compute_proof(challenge, b'hardware-entropy').
  • validate_response(response) returned False, 50.0, ['Proof hash mismatch'].

Validation performed:

  • PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 uv run --no-project --with pytest --with flask python -m pytest tests/test_mutating_challenge_proof_hash.py -q -> 3 passed
  • python3 -m py_compile rips/rustchain-core/src/anti_spoof/mutating_challenge.py tests/test_mutating_challenge_proof_hash.py -> passed
  • git diff --check origin/main...HEAD -- rips/rustchain-core/src/anti_spoof/mutating_challenge.py tests/test_mutating_challenge_proof_hash.py -> passed
  • python3 tools/bcos_spdx_check.py --base-ref origin/main -> OK
  • Custom non-empty hardware entropy smoke described above -> rejected with proof mismatch

Please either carry the verifier-available entropy/context needed to recompute the real proof, remove verifier-unavailable hardware entropy from the proof input, or explicitly document and test that the protocol proof entropy is always empty. As written, the production-facing docstring and validation path disagree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) size/M PR: 51-200 lines tests Test suite changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: MutatingChallenge proof hash verification is not implemented — emulators can submit fake proofs

7 participants