Skip to content

Instantly share code, notes, and snippets.

@denniswon
Created March 16, 2026 04:38
Show Gist options
  • Select an option

  • Save denniswon/e3ed1d29b8e110637351a9e08bf93bb7 to your computer and use it in GitHub Desktop.

Select an option

Save denniswon/e3ed1d29b8e110637351a9e08bf93bb7 to your computer and use it in GitHub Desktop.
Vulnerability Report: Missing taskCreatedBlock in Task Hash Enables Time Manipulation
Vulnerability Report: Missing taskCreatedBlock in Task Hash Enables Time Manipulation
Executive Summary
A critical vulnerability exists in the TaskLib.taskHash() function where the taskCreatedBlock field is not included in the computed hash. This allows attackers to manipulate time-sensitive security checks by supplying arbitrary taskCreatedBlock values that still pass task identity verification. The vulnerability enables two distinct attack vectors: premature slashing of operators on source chains and acceptance of late task responses.
Vulnerability Details
Location
File: contracts/src/libraries/TaskLib.sol
Function: taskHash() (Lines 223-241)
Vulnerable Code
solidity
function taskHash(
INewtonProverTaskManager.Task calldata task
) external pure returns (bytes32) {
return keccak256(
abi.encode(
task.taskId,
task.intent,
task.intentSignature,
task.policyClient,
task.wasmArgs,
task.quorumNumbers,
task.quorumThresholdPercentage
)
);
}
Critical Observation: The task.taskCreatedBlock field is conspicuously absent from the hash computation.
Fixed Code
solidity
function taskHash(
INewtonProverTaskManager.Task calldata task
) external pure returns (bytes32) {
return keccak256(
abi.encode(
task.taskId,
task.taskCreatedBlock, // <-- Added
task.intent,
task.intentSignature,
task.policyClient,
task.wasmArgs,
task.quorumNumbers,
task.quorumThresholdPercentage
)
);
}
Technical Analysis
How Task Identity Works
Task Creation: When a task is created, the system stores TaskLib.taskHash(task) in allTaskHashes.
Task Verification: Later operations (responding, challenging) verify task identity by:
Receiving a caller-supplied Task struct
Computing TaskLib.taskHash(task) on the supplied struct
Comparing against the stored hash
The Gap: Since taskCreatedBlock is not part of the hash, a caller can supply a Task with a modified taskCreatedBlock and still pass the identity check.
Downstream Dependencies on taskCreatedBlock
The taskCreatedBlock field controls several critical security mechanisms:
1. Response Deadline Enforcement
solidity
// TaskLib.sanityCheckTaskResponse()
require(
blockNumber < task.taskCreatedBlock + responseWindowBlock,
TaskResponseTooLate(blockNumber, task.taskCreatedBlock, responseWindowBlock)
);
A manipulated taskCreatedBlock can shift the deadline window forward or backward.
2. Challenge Window Verification
solidity
// ChallengeVerifier.sol (Lines 340-349)
require(
block.number > task.taskCreatedBlock + taskResponseWindowBlock,
TaskResponseWindowNotPassed(...)
);
An attacker can supply an older taskCreatedBlock to make this check pass prematurely.
3. Operator Set Reconstruction
solidity
// OperatorVerifierLib.sol (Lines 60-61)
// BLS signature verification reconstructs operator stakes at task.taskCreatedBlock
This determines which operators are considered valid signers for the task.
Exploitation Scenarios
Scenario 1: Premature Operator Slashing (Source Chain)
Attack Vector: Public challengeDirectlyVerifiedAttestation() function
Attack Flow:
A legitimate task is created at block N with taskCreatedBlock = N.
Operators produce a valid aggregated BLS signature for this task.
The task is validated via validateAttestationDirect(), setting isDirectlyVerified = true.
The challenge window is W blocks. Legitimately, challenges should only be possible after block N + W.
Attack: At block N + W/2 (before the window passes):
Attacker calls challengeDirectlyVerifiedAttestation()
Supplies a Task with taskCreatedBlock = N - W/2
The hash check passes (taskCreatedBlock not in hash)
The "window passed" check evaluates: N + W/2 > (N - W/2) + W → N + W/2 > N + W/2 → passes
BLS signature re-verification at block N - W/2 passes if operator membership remained stable.
Result: Operators are unjustly slashed before the legitimate challenge window elapsed.
Preconditions:
Source chain deployment with slashing capabilities enabled
validateAttestationDirect was used (not respondToTask)
Operator membership stable between attacker-chosen block and actual signing block
Attacker can access the signatureData from the direct validation event/storage
Scenario 2: Late Response Acceptance (Trusted Role)
Attack Vector: respondToTask() function (requires onlyTaskGenerator role)
Attack Flow:
A task is created at block N with response window W.
The legitimate response deadline is block N + W.
At block N + W + 100 (well past the deadline):
Trusted generator calls respondToTask()
Supplies a Task with taskCreatedBlock = N + 100
Hash check passes (taskCreatedBlock not in hash)
Deadline check evaluates: N + W + 100 < (N + 100) + W → N + W + 100 < N + W + 100 → fails by 1
With taskCreatedBlock = N + 101: N + W + 100 < N + W + 101 → passes
Result: A late response is accepted, minting an attestation despite the original window having elapsed.
Preconditions:
Requires onlyTaskGenerator role (trusted role)
Task must exist in allTaskHashes
Impact Assessment
Severity: HIGH
Factor Assessment
Impact High - Direct financial loss through unjust operator slashing
Likelihood Medium - Requires specific operational conditions but is publicly exploitable
Attack Complexity Low - Simple parameter manipulation, no special tools required
Privileges Required None for Scenario 1; Trusted role for Scenario 2
Specific Impacts
Financial Loss: Operators lose staked funds through unjust slashing.
Trust Degradation: Operators may lose confidence in the system's fairness.
Protocol Integrity: Time-based security guarantees become meaningless.
Operator Set Manipulation: Attacker can choose operator snapshots from blocks with favorable membership.
Remediation
Primary Fix
Include taskCreatedBlock in the taskHash() computation:
solidity
function taskHash(
INewtonProverTaskManager.Task calldata task
) external pure returns (bytes32) {
return keccak256(
abi.encode(
task.taskId,
task.taskCreatedBlock, // ADDED: Bind taskCreatedBlock to task identity
task.intent,
task.intentSignature,
task.policyClient,
task.wasmArgs,
task.quorumNumbers,
task.quorumThresholdPercentage
)
);
}
Migration Considerations
Existing Tasks: After deploying the fix, existing tasks in allTaskHashes will have hashes computed without taskCreatedBlock. These tasks will fail verification under the new hash formula.
Migration Options:
Hard Cutover: Accept that pending tasks at upgrade time become invalid. Only viable if task lifecycles are short.
Versioned Hashing: Introduce a version field or separate mapping for new tasks.
Dual Verification: Temporarily accept both old and new hash formats during a transition period (adds complexity and temporary risk).
Additional Hardening (Defense in Depth)
Consider adding explicit taskCreatedBlock validation in challenge paths:
solidity
// In challenge functions
require(
task.taskCreatedBlock == storedTask.taskCreatedBlock,
"Task creation block mismatch"
);
This requires storing taskCreatedBlock separately or using the fixed hash approach.
Open Questions for Review
Migration Strategy: What is the expected lifecycle of tasks? Can a hard cutover be tolerated?
Backward Compatibility: Are there external systems or indexers that depend on the current hash format?
Additional Fields: Should any other Task fields be reviewed for inclusion in the hash? The current implementation excludes taskCreatedBlock but includes all other fields.
Event/Storage Impact: If taskCreatedBlock is included in the hash, do any events or off-chain systems need updates to track this change?
Trusted Role Risk: Scenario 2 requires a trusted role. What is the threat model for compromised or malicious task generators?
Verification Steps
After applying the fix:
Unit Test: Create a task, attempt to respond/challenge with modified taskCreatedBlock → should revert with hash mismatch.
Integration Test: Full flow from task creation through response and challenge with consistent taskCreatedBlock.
Fuzzing: Verify that any modification to Task fields (including taskCreatedBlock) produces a different hash.
References
TaskLib.sol Lines 223-241: taskHash() function
TaskLib.sol Lines 191-199: Deadline enforcement in sanityCheckTaskResponse()
ChallengeVerifier.sol Lines 340-349: Challenge window verification
OperatorVerifierLib.sol Lines 60-61: Operator set reconstruction
NewtonProverTaskManagerShared.sol Lines 89-93: respondToTask() entry point
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment