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

Proof Construction Malleability Attack

Summary

The Curve Storage Proofs protocol contains a vulnerability where the proof validation process fails to enforce semantic integrity of proof elements. While the protocol verifies the technical structure of proofs, it lacks validation of semantic relationships between parameters, allowing technically valid but semantically corrupted proofs to pass verification. This vulnerability enables an attacker to construct malicious proofs with impossible or inconsistent parameter combinations, potentially leading to incorrect price calculations, economic exploitation, and undermining of the cross-chain oracle system.

Vulnerability Details

The vulnerability emerges from the lack of semantic validation in the proof verification process:

  1. Minimal Technical Validation Without Semantic Checks: The verification process in ScrvusdVerifierV1.sol validates only the basic structure of proofs without enforcing semantic consistency:

// ScrvusdVerifierV1.sol:83-105
function _extractParametersFromProof(
bytes32 stateRoot,
bytes memory proofRlp
) internal view returns (uint256[PARAM_CNT] memory) {
RLPReader.RLPItem[] memory proofs = proofRlp.toRlpItem().toList();
require(proofs.length == PROOF_CNT, "Invalid number of proofs");
// Extract account proof
Verifier.Account memory account = Verifier.extractAccountFromProof(
SCRVUSD_HASH,
stateRoot,
proofs[0].toList()
);
require(account.exists, "scrvUSD account does not exist");
// Extract slot values without semantic validation
uint256[PARAM_CNT] memory params;
for (uint256 i = 1; i < PROOF_CNT; i++) {
Verifier.SlotValue memory slot = Verifier.extractSlotValueFromProof(
keccak256(abi.encode(PARAM_SLOTS[i])),
account.storageRoot,
proofs[i].toList()
);
// Slots might not exist, but typically we just read them.
params[i - 1] = slot.value;
}
// No validation of semantic relationships between parameters
return params;
}
  1. Critical Economic Parameter Relationships: The protocol relies on several invariants that should be enforced but aren't:

// Invariants that should be true but aren't enforced:
// 1. total_supply >= balance_of_self (cannot have more balance than total supply)
// 2. If full_profit_unlock_date > 0, then profit_unlocking_rate should be > 0
// 3. If profit_unlocking_rate > 0, then full_profit_unlock_date > last_profit_update
  1. Oracle Accepts Parameters Without Validation: The oracle contract blindly accepts parameters without validating economic invariants:

# ScrvusdOracleV2.vy:304-311
self.price_params = PriceParams(
total_debt=_parameters[0],
total_idle=_parameters[1],
total_supply=_parameters[2],
full_profit_unlock_date=_parameters[3],
profit_unlocking_rate=_parameters[4],
last_profit_update=_parameters[5],
balance_of_self=_parameters[6],
)

Exploitation Scenario:
An attacker with the ability to construct manipulated proofs could:

  1. Create a proof with balance_of_self > total_supply (an impossible state)

  2. Create a proof with extremely high profit_unlocking_rate but very low balance_of_self

  3. Create a proof with full_profit_unlock_date < last_profit_update (impossible timeline)

  4. Submit these proofs to manipulate price calculations in advantageous ways

Root Cause:
The fundamental issue is the lack of semantic validation of extracted parameters. The protocol verifies that proofs are technically valid against a state root but fails to enforce economic and logical invariants that must hold for the protocol to function correctly.

Impact

Economic Impact:
Semantic inconsistencies in parameters can lead to:

  1. Manipulated Price Calculations: The raw price calculation becomes vulnerable to manipulation:

# ScrvusdOracleV2.vy:284-285
def _raw_price(ts: uint256, parameters_ts: uint256) -> uint256:
parameters: PriceParams = self._obtain_price_params(parameters_ts)
return self._total_assets(parameters) * 10**18 // self._total_supply(parameters, ts)
  1. Extreme Value Attacks: Parameters could be pushed to extreme values while maintaining technical validity:

    • Setting balance_of_self > total_supply could cause underflow in supply calculations

    • Setting impossibly high profit_unlocking_rate could manipulate price projections

    • Creating inconsistent time relationships could break unlocking calculations

  2. Protocol-Breaking Parameter Combinations: Certain combinations could break core economic assumptions:

# ScrvusdOracleV2.vy:247-248
def _total_supply(p: PriceParams, ts: uint256) -> uint256:
# This calculation assumes total_supply >= unlocked_shares
return p.total_supply - self._unlocked_shares(...)

In a protocol with $10M TVL, parameter manipulation could lead to:

  • Extreme price deviations (potentially >50% in edge cases)

  • Complete breakdown of economic calculations

  • Opportunity for repeated exploitation until patched

Technical Impact:

  • Breaks fundamental economic invariants of the protocol

  • Creates potential for division by zero or arithmetic errors

  • Undermines the reliability of cross-chain price information

User Impact:

  • Users could face extremely manipulated prices

  • Arbitrage opportunities could drain value from liquidity pools

  • Economic calculations throughout the ecosystem could break

This vulnerability is classified as MEDIUM-HIGH severity because:

  1. It requires sophisticated proof manipulation capabilities

  2. The impact can be extreme in certain parameter combinations

  3. It fundamentally breaks the economic security model of the protocol

  4. It creates direct economic exploitation opportunities

Tools Used

  • Manual code review focusing on parameter validation logic

  • Economic invariant analysis

  • Proof manipulation simulations with semantically impossible parameters

  • Edge case testing of economic calculations with inconsistent parameters

  • Invariant property testing across state transitions

Recommendations

Immediate Mitigations:

  1. Add comprehensive semantic validation to the parameter extraction process:

function _extractParametersFromProof(
bytes32 stateRoot,
bytes memory proofRlp
) internal view returns (uint256[PARAM_CNT] memory) {
// Existing extraction code
// Add semantic validation
uint256[PARAM_CNT] memory params = // existing extraction logic
// Validate basic economic invariants
require(params[2] >= params[6], "total_supply < balance_of_self");
// Validate time-based parameters
if (params[3] > 0) { // full_profit_unlock_date > 0
require(params[3] > params[5], "Unlock date before last update");
require(params[4] > 0, "Unlocking rate must be > 0 with unlock date");
} else {
require(params[4] == 0, "Unlocking rate must be 0 without unlock date");
}
// Validate value bounds
require(params[0] + params[1] > 0, "Total assets cannot be zero");
require(params[2] > 0, "Total supply cannot be zero");
return params;
}
  1. Add sanity checks to the oracle when applying parameters:

@external
def update_price(
_parameters: uint256[ALL_PARAM_CNT], _ts: uint256, _block_number: uint256
) -> uint256:
# Existing checks
# Add sanity checks
assert _parameters[2] >= _parameters[6], "Supply < balance" # total_supply >= balance_of_self
assert _parameters[0] + _parameters[1] > 0, "No assets" # total_debt + total_idle > 0
# Continue with update

Long-term Fixes:

  1. Implement a comprehensive economic model verification layer:

    • Define a formal model of valid protocol states and transitions

    • Validate all proofs against this model before acceptance

    • Include cryptographic verification of economic invariants

  2. Add parameter bounds and relationship constraints to the protocol specification:

    • Document all required economic invariants

    • Implement validation for all invariants in both on-chain and off-chain components

    • Create automated testing to verify invariants are maintained across updates

  3. Redesign the proof format to include explicit invariant attestations:

    • Include cryptographic commitments to economic invariants

    • Require proofs to demonstrate invariant preservation

    • Reject proofs that violate critical economic relationships

By implementing these mitigations, the protocol can ensure that proofs maintain semantic integrity, preventing manipulation through technically valid but economically impossible parameter combinations.

Updates

Lead Judging Commences

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

[invalid] finding-missing-proof-content-validation

- See [here]([https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle)](https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle) on how it is used to verify storage variable - All state roots and proofs must be verified by the OOS `StateProofVerifier` inherited as `Verifier` (where the price values and params are extracted), so there is no proof that manipulating timestamp/inputs can affect a price update - It is assumed that the OOS prover will provide accurate data and the OOS verifier will verify the prices/max unlock time to be within an appropriate bound/values - There is a account existance check in L96 of `ScrvusdVerifierV1.sol`, in which the params for price updates are extracted from

Support

FAQs

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