aere-contracts/contracts/zkverify/risczero/RiscZeroGroth16Verifier.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
Aere Network public source. Everything here can be checked against the live
chain (chain id 2800, https://rpc.aere.network).

Scope note, stated up front rather than buried: consensus on chain 2800 is
classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at
the signature, precompile, account and transport layers. Nothing here makes the
consensus post-quantum, and no document in it should be read as claiming so.
2026-07-20 01:02:37 +03:00

185 lines
8.0 KiB
Solidity

// 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 <https://www.gnu.org/licenses/>.
//
// 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();
}
}
}