// Copyright 2024 RISC Zero, Inc. // // The RiscZeroGroth16Verifier is a free software: you can redistribute it // and/or modify it under the terms of the GNU General Public License as // published by the Free Software Foundation, either version 3 of the License, // or (at your option) any later version. // // The RiscZeroGroth16Verifier is distributed in the hope that it will be // useful, but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General // Public License for more details. // // You should have received a copy of the GNU General Public License along with // the RiscZeroGroth16Verifier. If not, see . // // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.9; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {ControlID} from "./ControlID.sol"; import {Groth16Verifier} from "./Groth16Verifier.sol"; import { ExitCode, IRiscZeroVerifier, Output, OutputLib, Receipt, ReceiptClaim, ReceiptClaimLib, SystemExitCode, VerificationFailed } from "./IRiscZeroVerifier.sol"; import {StructHash} from "./StructHash.sol"; import {reverseByteOrderUint256, reverseByteOrderUint32} from "./Util.sol"; import {IRiscZeroSelectable} from "./IRiscZeroSelectable.sol"; /// @notice A Groth16 seal over the claimed receipt claim. struct Seal { uint256[2] a; uint256[2][2] b; uint256[2] c; } /// @notice Error raised when this verifier receives a receipt with a selector that does not match /// its own. The selector value is calculated from the verifier parameters, and so this /// usually indicates a mismatch between the version of the prover and this verifier. error SelectorMismatch(bytes4 received, bytes4 expected); /// @notice Groth16 verifier contract for RISC Zero receipts of execution. contract RiscZeroGroth16Verifier is IRiscZeroVerifier, IRiscZeroSelectable, Groth16Verifier { using ReceiptClaimLib for ReceiptClaim; using OutputLib for Output; using SafeCast for uint256; /// @notice Semantic version of the RISC Zero system of which this contract is part. /// @dev This is set to be equal to the version of the risc0-zkvm crate. string public constant VERSION = "5.0.0-rc.1"; /// @notice Control root hash binding the set of circuits in the RISC Zero system. /// @dev This value controls what set of recursion programs (e.g. lift, join, resolve), and /// therefore what version of the zkVM circuit, will be accepted by this contract. Each /// instance of this verifier contract will accept a single release of the RISC Zero circuits. /// /// New releases of RISC Zero's zkVM require updating these values. These values can be /// calculated from the [risc0 monorepo][1] using: `cargo xtask bootstrap`. /// /// [1]: https://github.com/risc0/risc0 bytes16 public immutable CONTROL_ROOT_0; bytes16 public immutable CONTROL_ROOT_1; bytes32 public immutable BN254_CONTROL_ID; /// @notice A short key attached to the seal to select the correct verifier implementation. /// @dev The selector is taken from the hash of the verifier parameters including the Groth16 /// verification key and the control IDs that commit to the RISC Zero circuits. If two /// receipts have different selectors (i.e. different verifier parameters), then it can /// generally be assumed that they need distinct verifier implementations. This is used as /// part of the RISC Zero versioning mechanism. /// /// A selector is not intended to be collision resistant, in that it is possible to find /// two preimages that result in the same selector. This is acceptable since it's purpose /// to a route a request among a set of trusted verifiers, and to make errors of sending a /// receipt to a mismatching verifiers easier to debug. It is analogous to the ABI /// function selectors. bytes4 public immutable SELECTOR; /// @notice Identifier for the Groth16 verification key encoded into the base contract. /// @dev This value is computed at compile time. function verifier_key_digest() internal pure returns (bytes32) { bytes32[] memory ic_digests = new bytes32[](6); ic_digests[0] = sha256(abi.encodePacked(IC0x, IC0y)); ic_digests[1] = sha256(abi.encodePacked(IC1x, IC1y)); ic_digests[2] = sha256(abi.encodePacked(IC2x, IC2y)); ic_digests[3] = sha256(abi.encodePacked(IC3x, IC3y)); ic_digests[4] = sha256(abi.encodePacked(IC4x, IC4y)); ic_digests[5] = sha256(abi.encodePacked(IC5x, IC5y)); return sha256( abi.encodePacked( // tag sha256("risc0_groth16.VerifyingKey"), // down sha256(abi.encodePacked(alphax, alphay)), sha256(abi.encodePacked(betax1, betax2, betay1, betay2)), sha256(abi.encodePacked(gammax1, gammax2, gammay1, gammay2)), sha256(abi.encodePacked(deltax1, deltax2, deltay1, deltay2)), StructHash.taggedList(sha256("risc0_groth16.VerifyingKey.IC"), ic_digests), // down length uint16(5) << 8 ) ); } constructor(bytes32 control_root, bytes32 bn254_control_id) { (CONTROL_ROOT_0, CONTROL_ROOT_1) = splitDigest(control_root); BN254_CONTROL_ID = bn254_control_id; SELECTOR = bytes4( sha256( abi.encodePacked( // tag sha256("risc0.Groth16ReceiptVerifierParameters"), // down control_root, reverseByteOrderUint256(uint256(bn254_control_id)), verifier_key_digest(), // down length uint16(3) << 8 ) ) ); } /// @notice splits a digest into two 128-bit halves to use as public signal inputs. /// @dev RISC Zero's Circom verifier circuit takes each of two hash digests in two 128-bit /// chunks. These values can be derived from the digest by splitting the digest in half and /// then reversing the bytes of each. function splitDigest(bytes32 digest) internal pure returns (bytes16, bytes16) { uint256 reversed = reverseByteOrderUint256(uint256(digest)); return (bytes16(uint128(reversed)), bytes16(uint128(reversed >> 128))); } /// @inheritdoc IRiscZeroVerifier function verify(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) external view { _verifyIntegrity(seal, ReceiptClaimLib.ok(imageId, journalDigest).digest()); } /// @inheritdoc IRiscZeroVerifier function verifyIntegrity(Receipt calldata receipt) external view { return _verifyIntegrity(receipt.seal, receipt.claimDigest); } /// @notice internal implementation of verifyIntegrity, factored to avoid copying calldata bytes to memory. function _verifyIntegrity(bytes calldata seal, bytes32 claimDigest) internal view { // Check that the seal has a matching selector. Mismatch generally indicates that the // prover and this verifier are using different parameters, and so the verification // will not succeed. if (SELECTOR != bytes4(seal[:4])) { revert SelectorMismatch({received: bytes4(seal[:4]), expected: SELECTOR}); } // Run the Groth16 verify procedure. (bytes16 claim0, bytes16 claim1) = splitDigest(claimDigest); Seal memory decodedSeal = abi.decode(seal[4:], (Seal)); bool verified = this.verifyProof( decodedSeal.a, decodedSeal.b, decodedSeal.c, [ uint256(uint128(CONTROL_ROOT_0)), uint256(uint128(CONTROL_ROOT_1)), uint256(uint128(claim0)), uint256(uint128(claim1)), uint256(BN254_CONTROL_ID) ] ); // Revert is verification failed. if (!verified) { revert VerificationFailed(); } } }