CVE-2025-52884
Description
RISC Zero is a zero-knowledge verifiable general computing platform, with Ethereum integration. The risc0-ethereum repository contains Solidity verifier contracts, Steel EVM view call library, and supporting code. Prior to versions 2.1.1 and 2.2.0, the Steel.validateCommitment Solidity library function will return true for a crafted commitment with a digest value of zero. This violates the semantics of validateCommitment, as this does not commitment to a block that is in the current chain. Because the digest is zero, it does not correspond to any block and there exist no known openings. As a result, this commitment will never be produced by a correct zkVM guest using Steel and leveraging this bug to compromise the soundness of a program using Steel would require a separate bug or misuse of the Steel library, which is expected to be used to validate the root of state opening proofs. A fix has been released as part of risc0-ethereum 2.1.1 and 2.2.0. Users for the Steel Solidity library versions 2.1.0 or earlier should ensure they are using Steel.validateCommitment in tandem with zkVM proof verification of a Steel program, as shown in the ERC-20 counter example, and documentation. This is the correct usage of Steel, and users following this pattern are not at risk, and do not need to take action. Users not verifying a zkVM proof of a Steel program should update their application to do so, as this is incorrect usage of Steel.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
risc0-ethereum-contractscrates.io | < 2.1.1 | 2.1.1 |
Patches
33bbac859c713WEB3-458: Update the Steel beacon block commit validation to always revert on invalid timestamps (#605)
2 files changed · +157 −6
contracts/src/steel/Steel.sol+18 −6 modified@@ -30,12 +30,15 @@ library Steel { } /// @notice The version of the Commitment is incorrect. + /// @dev Error signature: 0xbb2b2291 error InvalidCommitmentVersion(); /// @notice The Commitment is too old and can no longer be validated. + /// @dev Error signature: 0xcfef9a95 error CommitmentTooOld(); /// @notice The consensus slot (version 2) commitment is not supported. + /// @dev Error signature: 0x13d71698 error ConsensusSlotCommitmentNotSupported(); /// @notice Validates if the provided Commitment matches the block hash of the given block number. @@ -60,20 +63,19 @@ library Steel { /// @param blockHash The block hash to validate. /// @return True if the block's block hash matches the block hash, false otherwise. function validateBlockCommitment(uint256 blockNumber, bytes32 blockHash) internal view returns (bool) { - if (block.number - blockNumber > 256) { + // NOTE: blockhash opcode returns all zeroes if the block number is too far in the past. + bytes32 blockHashResult = blockhash(blockNumber); + if (blockHashResult == bytes32(0)) { revert CommitmentTooOld(); } - return blockHash == blockhash(blockNumber); + return blockHash == blockHashResult; } /// @notice Validates if the provided beacon commitment matches the block root of the given timestamp. /// @param timestamp The timestamp to compare against. /// @param blockRoot The block root to validate. /// @return True if the block's block root matches the block root, false otherwise. function validateBeaconCommitment(uint256 timestamp, bytes32 blockRoot) internal view returns (bool) { - if (block.timestamp - timestamp > 12 * 8191) { - revert CommitmentTooOld(); - } return blockRoot == Beacon.parentBlockRoot(timestamp); } } @@ -84,12 +86,22 @@ library Beacon { /// @dev https://eips.ethereum.org/EIPS/eip-4788 address internal constant BEACON_ROOTS_ADDRESS = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02; + /// @notice Call to the EIP-4788 beacon roots contract failed due to an invalid block timestamp. + /// @dev A block timestamp is invalid if it does not correspond to a stored block on the + /// EIP-4788 contract. This can happen if the timestamp is too old, and the corresponding + /// block has been evicted from the cache, if the timestamp corresponds to a + /// slot with no block, or if the timestamp does not correspond to any slot at all. + /// @dev Error signature: 0x4d0b0a41 + error InvalidBlockTimestamp(); + /// @notice Find the root of the Beacon block corresponding to the parent of the execution block with the given timestamp. - /// @return root Returns the corresponding Beacon block root or null, if no such block exists. + /// @return root Returns the corresponding Beacon block root or reverts, if no such block exists. function parentBlockRoot(uint256 timestamp) internal view returns (bytes32 root) { (bool success, bytes memory result) = BEACON_ROOTS_ADDRESS.staticcall(abi.encode(timestamp)); if (success) { return abi.decode(result, (bytes32)); + } else { + revert InvalidBlockTimestamp(); } } }
contracts/src/test/Steel.t.sol+139 −0 added@@ -0,0 +1,139 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.17; + +import {Test} from "forge-std/Test.sol"; + +import {Steel, Beacon, Encoding} from "../steel/Steel.sol"; + +contract SteelVerifier { + function validateCommitment(Steel.Commitment memory commitment) external view returns (bool) { + return Steel.validateCommitment(commitment); + } +} + +contract SteelTest is Test { + SteelVerifier internal verifier; + + function setUp() public { + verifier = new SteelVerifier(); + } + + function createCommitment(uint240 claimID, uint16 version, bytes32 digest) + internal + pure + returns (Steel.Commitment memory) + { + return + Steel.Commitment({id: Encoding.encodeVersionedID(claimID, version), digest: digest, configID: bytes32(0)}); + } + + function test_ValidateCommitment_V0_Block_Success() public { + vm.roll(block.number + 10); + uint256 targetBlockNumber = block.number - 5; + bytes32 targetBlockHash = blockhash(targetBlockNumber); + assertTrue(targetBlockHash != bytes32(0), "Test setup: blockhash(target) is zero"); + + Steel.Commitment memory c = createCommitment(uint240(targetBlockNumber), 0, targetBlockHash); + assertTrue(verifier.validateCommitment(c), "V0 valid block commitment failed"); + } + + function test_ValidateCommitment_V0_Block_WrongHash() public { + vm.roll(block.number + 10); + uint256 targetBlockNumber = block.number - 5; + bytes32 wrongHash = keccak256(abi.encodePacked("wrong_hash")); + assertTrue(blockhash(targetBlockNumber) != bytes32(0), "Test setup: blockhash(target) is zero"); + + Steel.Commitment memory c = createCommitment(uint240(targetBlockNumber), 0, wrongHash); + assertFalse(verifier.validateCommitment(c), "V0 wrong block hash should be invalid"); + } + + function test_ValidateCommitment_V0_Block_TooOld() public { + vm.roll(block.number + 300); + uint256 oldBlockNumber = 1; + bytes32 someHash = keccak256(abi.encodePacked("some_hash")); + + Steel.Commitment memory c = createCommitment(uint240(oldBlockNumber), 0, someHash); + vm.expectPartialRevert(Steel.CommitmentTooOld.selector); + verifier.validateCommitment(c); + } + + function test_ValidateCommitment_V1_Beacon_Success() public { + uint256 timestamp = 1700000000; + bytes32 expectedRoot = keccak256(abi.encodePacked("beacon_root_v1")); + + // Mock the call to Beacon.BEACON_ROOTS_ADDRESS + vm.mockCall(Beacon.BEACON_ROOTS_ADDRESS, abi.encode(timestamp), abi.encode(expectedRoot)); + + Steel.Commitment memory c = createCommitment( + uint240(timestamp), // claimID is timestamp for V1 + 1, + expectedRoot + ); + assertTrue(verifier.validateCommitment(c), "V1 valid beacon commitment failed"); + } + + function test_ValidateCommitment_V1_Beacon_WrongRoot() public { + uint256 timestamp = 1700000000; + bytes32 correctRoot = keccak256(abi.encodePacked("beacon_root_v1")); + bytes32 wrongRootInCommitment = keccak256(abi.encodePacked("wrong_root")); + + // Mock the call to Beacon.BEACON_ROOTS_ADDRESS to return the correctRoot + vm.mockCall(Beacon.BEACON_ROOTS_ADDRESS, abi.encode(timestamp), abi.encode(correctRoot)); + + Steel.Commitment memory c = createCommitment( + uint240(timestamp), + 1, + wrongRootInCommitment // Commitment has the wrong root + ); + assertFalse(verifier.validateCommitment(c), "V1 wrong beacon root should be invalid"); + } + + function test_ValidateCommitment_V1_Beacon_InvalidTimestamp() public { + uint256 invalidTimestamp = 1700000001; + + // Mock the call to Beacon.BEACON_ROOTS_ADDRESS to revert + vm.mockCallRevert( + Beacon.BEACON_ROOTS_ADDRESS, + abi.encode(invalidTimestamp), + bytes("Mocked EIP-4788 Revert for invalid timestamp") + ); + + Steel.Commitment memory c = + createCommitment(uint240(invalidTimestamp), 1, keccak256(abi.encodePacked("any_root"))); + vm.expectRevert(Beacon.InvalidBlockTimestamp.selector); + verifier.validateCommitment(c); + } + + function test_ValidateCommitment_V2_Reverts_ConsensusSlotNotSupported() public { + uint240 claimID = 999; + Steel.Commitment memory c = createCommitment(claimID, 2, keccak256(abi.encodePacked("any_digest_v2"))); + vm.expectRevert(Steel.ConsensusSlotCommitmentNotSupported.selector); + verifier.validateCommitment(c); + } + + function test_ValidateCommitment_V3_Reverts_InvalidCommitmentVersion() public { + uint240 claimID = 1000; + Steel.Commitment memory c = createCommitment( + claimID, + 3, // Any version > 2 not explicitly handled + keccak256(abi.encodePacked("any_digest_v3")) + ); + vm.expectRevert(Steel.InvalidCommitmentVersion.selector); + verifier.validateCommitment(c); + } +}
0e450a773165382d76a80400Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-gjv3-89hh-9xq2ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-52884ghsaADVISORY
- docs.beboundless.xyz/developers/steel/how-it-worksnvdWEB
- github.com/risc0/risc0-ethereum/blob/ff0cb9253a87945b653b825711b8b5075f8b7545/examples/erc20-counter/contracts/src/Counter.solnvdWEB
- github.com/risc0/risc0-ethereum/commit/3bbac859c7132b21ba5fdf2d47f1dd52e7e73d98nvdWEB
- github.com/risc0/risc0-ethereum/pull/605nvdWEB
- github.com/risc0/risc0-ethereum/releases/tag/v2.1.1nvdWEB
- github.com/risc0/risc0-ethereum/releases/tag/v2.2.0nvdWEB
- github.com/risc0/risc0-ethereum/security/advisories/GHSA-gjv3-89hh-9xq2nvdWEB
News mentions
0No linked articles in our index yet.