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.
214 lines
9.3 KiB
Solidity
214 lines
9.3 KiB
Solidity
// 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.9;
|
|
|
|
import {reverseByteOrderUint32} from "./Util.sol";
|
|
|
|
/// @notice A receipt attesting to a claim using the RISC Zero proof system.
|
|
/// @dev A receipt contains two parts: a seal and a claim.
|
|
///
|
|
/// The seal is a zero-knowledge proof attesting to knowledge of a witness for the claim. The claim
|
|
/// is a set of public outputs, and for zkVM execution is the hash of a `ReceiptClaim` struct.
|
|
///
|
|
/// IMPORTANT: The `claimDigest` field must be a hash computed by the caller for verification to
|
|
/// have meaningful guarantees. Treat this similar to verifying an ECDSA signature, in that hashing
|
|
/// is a key operation in verification. The most common way to calculate this hash is to use the
|
|
/// `ReceiptClaimLib.ok(imageId, journalDigest).digest()` for successful executions.
|
|
struct Receipt {
|
|
bytes seal;
|
|
bytes32 claimDigest;
|
|
}
|
|
|
|
/// @notice Public claims about a zkVM guest execution, such as the journal committed to by the guest.
|
|
/// @dev Also includes important information such as the exit code and the starting and ending system
|
|
/// state (i.e. the state of memory). `ReceiptClaim` is a "Merkle-ized struct" supporting
|
|
/// partial openings of the underlying fields from a hash commitment to the full structure.
|
|
struct ReceiptClaim {
|
|
/// @notice Digest of the SystemState just before execution has begun.
|
|
bytes32 preStateDigest;
|
|
/// @notice Digest of the SystemState just after execution has completed.
|
|
bytes32 postStateDigest;
|
|
/// @notice The exit code for the execution.
|
|
ExitCode exitCode;
|
|
/// @notice A digest of the input to the guest.
|
|
/// @dev This field is currently unused and must be set to the zero digest.
|
|
bytes32 input;
|
|
/// @notice Digest of the Output of the guest, including the journal
|
|
/// and assumptions set during execution.
|
|
bytes32 output;
|
|
}
|
|
|
|
library ReceiptClaimLib {
|
|
using OutputLib for Output;
|
|
using SystemStateLib for SystemState;
|
|
|
|
bytes32 constant TAG_DIGEST = sha256("risc0.ReceiptClaim");
|
|
|
|
// Define a constant to ensure hashing is done at compile time. Can't use the
|
|
// SystemStateLib.digest method here because the Solidity compiler complains.
|
|
bytes32 constant SYSTEM_STATE_ZERO_DIGEST = 0xa3acc27117418996340b84e5a90f3ef4c49d22c79e44aad822ec9c313e1eb8e2;
|
|
|
|
/// @notice Construct a ReceiptClaim from the given imageId and journalDigest.
|
|
/// Returned ReceiptClaim will represent a successful execution of the zkVM, running
|
|
/// the program committed by imageId and resulting in the journal specified by
|
|
/// journalDigest.
|
|
/// @param imageId The identifier for the guest program.
|
|
/// @param journalDigest The SHA-256 digest of the journal bytes.
|
|
/// @dev Input hash and postStateDigest are set to all-zeros (i.e. no committed input, or
|
|
/// final memory state), the exit code is (Halted, 0), and there are no assumptions
|
|
/// (i.e. the receipt is unconditional).
|
|
function ok(bytes32 imageId, bytes32 journalDigest) internal pure returns (ReceiptClaim memory) {
|
|
return ReceiptClaim(
|
|
imageId,
|
|
SYSTEM_STATE_ZERO_DIGEST,
|
|
ExitCode(SystemExitCode.Halted, 0),
|
|
bytes32(0),
|
|
Output(journalDigest, bytes32(0)).digest()
|
|
);
|
|
}
|
|
|
|
function digest(ReceiptClaim memory claim) internal pure returns (bytes32) {
|
|
return sha256(
|
|
abi.encodePacked(
|
|
TAG_DIGEST,
|
|
// down
|
|
claim.input,
|
|
claim.preStateDigest,
|
|
claim.postStateDigest,
|
|
claim.output,
|
|
// data
|
|
uint32(claim.exitCode.system) << 24,
|
|
uint32(claim.exitCode.user) << 24,
|
|
// down.length
|
|
uint16(4) << 8
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/// @notice Commitment to the memory state and program counter (pc) of the zkVM.
|
|
/// @dev The "pre" and "post" fields of the ReceiptClaim are digests of the system state at the
|
|
/// start and stop of execution. Programs are loaded into the zkVM by creating a memory image
|
|
/// of the loaded program, and creating a system state for initializing the zkVM. This is
|
|
/// known as the "image ID".
|
|
struct SystemState {
|
|
/// @notice Program counter.
|
|
uint32 pc;
|
|
/// @notice Root hash of a Merkle tree which confirms the integrity of the memory image.
|
|
bytes32 merkle_root;
|
|
}
|
|
|
|
library SystemStateLib {
|
|
bytes32 constant TAG_DIGEST = sha256("risc0.SystemState");
|
|
|
|
function digest(SystemState memory state) internal pure returns (bytes32) {
|
|
return sha256(
|
|
abi.encodePacked(
|
|
TAG_DIGEST,
|
|
// down
|
|
state.merkle_root,
|
|
// data
|
|
reverseByteOrderUint32(state.pc),
|
|
// down.length
|
|
uint16(1) << 8
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/// @notice Exit condition indicated by the zkVM at the end of the guest execution.
|
|
/// @dev Exit codes have a "system" part and a "user" part. Semantically, the system part is set to
|
|
/// indicate the type of exit (e.g. halt, pause, or system split) and is directly controlled by the
|
|
/// zkVM. The user part is an exit code, similar to exit codes used in Linux, chosen by the guest
|
|
/// program to indicate additional information (e.g. 0 to indicate success or 1 to indicate an
|
|
/// error).
|
|
struct ExitCode {
|
|
SystemExitCode system;
|
|
uint8 user;
|
|
}
|
|
|
|
/// @notice Exit condition indicated by the zkVM at the end of the execution covered by this proof.
|
|
/// @dev
|
|
/// `Halted` indicates normal termination of a program with an interior exit code returned from the
|
|
/// guest program. A halted program cannot be resumed.
|
|
///
|
|
/// `Paused` indicates the execution ended in a paused state with an interior exit code set by the
|
|
/// guest program. A paused program can be resumed such that execution picks up where it left
|
|
/// of, with the same memory state.
|
|
///
|
|
/// `SystemSplit` indicates the execution ended on a host-initiated system split. System split is
|
|
/// mechanism by which the host can temporarily stop execution of the execution ended in a system
|
|
/// split has no output and no conclusions can be drawn about whether the program will eventually
|
|
/// halt. System split is used in continuations to split execution into individually provable segments.
|
|
enum SystemExitCode {
|
|
Halted,
|
|
Paused,
|
|
SystemSplit
|
|
}
|
|
|
|
/// @notice Output field in the `ReceiptClaim`, committing to a claimed journal and assumptions list.
|
|
struct Output {
|
|
/// @notice Digest of the journal committed to by the guest execution.
|
|
bytes32 journalDigest;
|
|
/// @notice Digest of the ordered list of `ReceiptClaim` digests corresponding to the
|
|
/// calls to `env::verify` and `env::verify_integrity`.
|
|
/// @dev Verifying the integrity of a `Receipt` corresponding to a `ReceiptClaim` with a
|
|
/// non-empty assumptions list does not guarantee unconditionally any of the claims over the
|
|
/// guest execution (i.e. if the assumptions list is non-empty, then the journal digest cannot
|
|
/// be trusted to correspond to a genuine execution). The claims can be checked by additional
|
|
/// verifying a `Receipt` for every digest in the assumptions list.
|
|
bytes32 assumptionsDigest;
|
|
}
|
|
|
|
library OutputLib {
|
|
bytes32 constant TAG_DIGEST = sha256("risc0.Output");
|
|
|
|
function digest(Output memory output) internal pure returns (bytes32) {
|
|
return sha256(
|
|
abi.encodePacked(
|
|
TAG_DIGEST,
|
|
// down
|
|
output.journalDigest,
|
|
output.assumptionsDigest,
|
|
// down.length
|
|
uint16(2) << 8
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/// @notice Error raised when cryptographic verification of the zero-knowledge proof fails.
|
|
error VerificationFailed();
|
|
|
|
/// @notice Verifier interface for RISC Zero receipts of execution.
|
|
interface IRiscZeroVerifier {
|
|
/// @notice Verify that the given seal is a valid RISC Zero proof of execution with the
|
|
/// given image ID and journal digest. Reverts on failure.
|
|
/// @dev This method additionally ensures that the input hash is all-zeros (i.e. no
|
|
/// committed input), the exit code is (Halted, 0), and there are no assumptions (i.e. the
|
|
/// receipt is unconditional).
|
|
/// @param seal The encoded cryptographic proof (i.e. SNARK).
|
|
/// @param imageId The identifier for the guest program.
|
|
/// @param journalDigest The SHA-256 digest of the journal bytes.
|
|
function verify(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) external view;
|
|
|
|
/// @notice Verify that the given receipt is a valid RISC Zero receipt, ensuring the `seal` is
|
|
/// valid a cryptographic proof of the execution with the given `claim`. Reverts on failure.
|
|
/// @param receipt The receipt to be verified.
|
|
function verifyIntegrity(Receipt calldata receipt) external view;
|
|
}
|