VYPR
Low severityNVD Advisory· Published Jun 24, 2025· Updated Apr 15, 2026

CVE-2025-52884

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.

PackageAffected versionsPatched versions
risc0-ethereum-contractscrates.io
< 2.1.12.1.1

Patches

3
3bbac859c713

WEB3-458: Update the Steel beacon block commit validation to always revert on invalid timestamps (#605)

https://github.com/risc0/risc0-ethereumVictor Snyder-GrafJun 2, 2025via ghsa
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);
    +    }
    +}
    

Vulnerability 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

News mentions

0

No linked articles in our index yet.