DeFiLayer 1Layer 2
14,723 OP
View results
Submission Details
Severity: high
Invalid

Delegated State Proof Replayability Vulnerability

Summary

The Curve Storage Proofs protocol contains a significant vulnerability where proofs lack proper context binding, allowing them to be reused in unintended contexts. This vulnerability enables attackers to repurpose proofs meant for one verification function to be used in another verification function or contract. The absence of context-specific validation creates a security hazard where state updates could be manipulated across different verification contexts, leading to incorrect state updates and potential economic exploitation.

Vulnerability Details

The vulnerability stems from the absence of context binding in the protocol's proof verification system:

  1. Lack of Context Identification: The verification functions in ScrvusdVerifierV1.sol and ScrvusdVerifierV2.sol don't bind proofs to specific contexts or functions:

// ScrvusdVerifierV1.sol:52-59 & 69-74
function verifyScrvusdByBlockHash(
bytes memory _block_header_rlp,
bytes memory _proof_rlp
) external returns (uint256) {
// No context binding or identification
// ...
}
function verifyScrvusdByStateRoot(
uint256 _block_number,
bytes memory _proof_rlp
) external returns (uint256) {
// No context binding or identification
// ...
}
  1. Multiple Verification Functions With Similar Structure: The protocol implements multiple verification functions that accept similar proof formats:

// ScrvusdVerifierV2.sol:22-37
function verifyPeriodByBlockHash(
bytes memory _block_header_rlp,
bytes memory _proof_rlp
) external returns (bool) {
// Similar structure to ScrvusdVerifierV1.verifyScrvusdByBlockHash
// ...
}
  1. Cross-Contract Verification: Different contracts verify different aspects of the protocol state using similar proof formats:

// ScrvusdVerifierV1.sol extracts and updates price parameters
// ScrvusdVerifierV2.sol extracts and updates period parameters
// Both use similar proof structures but for different state variables

Exploitation Scenario:
An attacker could:

  1. Capture a legitimate proof meant for updating price parameters

  2. Modify the proof structure to match that expected by the period verifier

  3. Submit the modified proof to the period verifier

  4. Unintentionally or maliciously update the profit max unlock time with incorrect values

Root Cause:
The fundamental issue is that proofs lack explicit binding to:

  • The specific function they're intended for

  • The type of state update they should trigger

  • The contract that should process them

Impact

Economic Impact:
This vulnerability creates several potential attack vectors:

  1. Parameter Manipulation: Critical parameters like profit_max_unlock_time could be updated with values derived from proofs intended for price updates, potentially creating economic imbalances.

  2. Oracle Poisoning: By replaying proofs in unintended contexts, an attacker could poison oracle data, creating opportunities for arbitrage or manipulation.

  3. Cross-Function State Corruption: State corruption across different verification functions could lead to inconsistent protocol state, affecting pricing and economic calculations.

For a protocol with $10M TVL, the impact could include:

  • Manipulated unlock periods affecting yield distributions

  • Corrupted price data leading to mispriced assets

  • Inconsistent states across different protocol components

  • Potential for repeated exploitation as new proofs are generated

Technical Impact:

  • Breaks the isolation between different verification functions

  • Creates unpredictable interactions between multiple state updates

  • Undermines the security model of delegated proof verification

User Impact:

  • Unexpected parameter changes could affect user yields and expectations

  • Price manipulations could lead to unfair trading conditions

  • Protocol governance might have to intervene to correct manipulated states

This vulnerability is classified as HIGH severity because:

  1. It enables unauthorized or unintended state changes

  2. It affects critical protocol parameters

  3. It could be exploited repeatedly with minimal resources

  4. It breaks fundamental security assumptions about proof context isolation

Tools Used

  • Manual code review focusing on proof verification logic

  • Cross-function proof validation testing

  • Proof modification and replay simulations

  • Context binding analysis across verification functions

  • State integrity verification across different proof contexts

Recommendations

Immediate Mitigations:

  1. Add explicit context parameters to all verification functions:

// Add to all verifier functions
function verifyScrvusdByBlockHash(
bytes memory _block_header_rlp,
bytes memory _proof_rlp,
bytes32 _context // Add context parameter
) external returns (uint256) {
// Validate context
require(_context == keccak256("PRICE_UPDATE"), "Invalid context for price update");
// Rest of the function
}
function verifyPeriodByBlockHash(
bytes memory _block_header_rlp,
bytes memory _proof_rlp,
bytes32 _context // Add context parameter
) external returns (bool) {
// Validate context
require(_context == keccak256("PERIOD_UPDATE"), "Invalid context for period update");
// Rest of the function
}
  1. Implement function-specific nonce tracking:

// Add to verifier contracts
mapping(bytes32 => mapping(bytes32 => bool)) private usedProofs; // context => proofHash => used
function verifyScrvusdByBlockHash(
bytes memory _block_header_rlp,
bytes memory _proof_rlp
) external returns (uint256) {
bytes32 context = keccak256("PRICE_UPDATE");
bytes32 proofHash = keccak256(abi.encodePacked(_block_header_rlp, _proof_rlp));
require(!usedProofs[context][proofHash], "Proof already used for this context");
usedProofs[context][proofHash] = true;
// Rest of the function
}

Long-term Fixes:

  1. Redesign the proof format to include explicit context identification that cannot be modified without invalidating the proof.

  2. Implement distinct proof formats for different verification functions:

struct PriceProof {
bytes32 proofType; // Must be keccak256("PRICE_UPDATE")
bytes blockHeaderRLP;
bytes priceProofRLP;
}
struct PeriodProof {
bytes32 proofType; // Must be keccak256("PERIOD_UPDATE")
bytes blockHeaderRLP;
bytes periodProofRLP;
}
  1. Add cryptographic binding between proofs and their intended context:

// During proof generation (off-chain)
bytes32 contextBoundProof = keccak256(abi.encodePacked(
proofContext,
blockHeaderRLP,
proofRLP
));
// During verification (on-chain)
require(
keccak256(abi.encodePacked("PRICE_UPDATE", _block_header_rlp, _proof_rlp)) == _contextBoundProof,
"Proof not bound to price update context"
);

By implementing these mitigations, the protocol can ensure that proofs are only valid in their intended context, preventing cross-context replay and maintaining the integrity of the state update system.

Updates

Lead Judging Commences

0xnevi Lead Judge
6 months ago
0xnevi Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.