-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathChessProofVerifier.sol
More file actions
144 lines (125 loc) · 5.56 KB
/
ChessProofVerifier.sol
File metadata and controls
144 lines (125 loc) · 5.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title ChessProofVerifier
* @notice On-chain wrapper for RISC Zero ZK proof verification of chess games.
*
* Architecture
* ────────────
* Off-chain (Rust / RISC Zero guest program):
* 1. Build the chess-guest program (see chess-guest/ directory)
* 2. Feed it the uint16[] moves array as private input
* 3. Guest validates the full game using shakmaty
* 4. Guest commits (outcome: uint8, movesHash: bytes32) as public journal
* 5. Submit proof receipt to Bonsai or self-hosted prover
*
* On-chain (this contract):
* 1. Receive the proof receipt + journal
* 2. Forward to RISC Zero verifier contract (already deployed on major networks)
* 3. Decode (outcome, movesHash) from the journal
* 4. Call ChessWager.settleFromVerifier() to settle the game
*
* Gas comparison:
* - submitGame() with 60-move game: ~500,000 gas
* - verifyAndSettle() with ZK proof: ~3,000-5,000 gas
*
* @dev The IMAGE_ID is derived deterministically from the compiled chess-guest
* binary. It is set at deployment and cannot change.
*
* RISC Zero verifier addresses (as of 2025):
* Mainnet: 0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319
* Sepolia: 0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187
* Base: 0x0b144e07A0826182176AB3e27021fA07F9EDE7Aa
* Arbitrum: 0x0b144e07A0826182176AB3e27021fA07F9EDE7Aa
*/
contract ChessProofVerifier is Ownable, ReentrancyGuard {
// =========================================================================
// Interfaces
// =========================================================================
/// @notice RISC Zero on-chain verifier interface.
interface IRiscZeroVerifier {
/**
* @notice Verify a RISC Zero STARK proof.
* @param seal The proof seal (encoded STARK).
* @param imageId The guest image ID — identifies which program was run.
* @param journalHash SHA-256 hash of the public journal outputs.
*/
function verify(
bytes calldata seal,
bytes32 imageId,
bytes32 journalHash
) external view;
}
/// @notice ChessWager settlement interface — settleFromVerifier is the ZK path.
interface IChessWager {
function settleFromVerifier(
uint256 gameId,
uint8 outcome,
bytes32 movesHash
) external;
}
// =========================================================================
// State
// =========================================================================
IRiscZeroVerifier public immutable verifier;
IChessWager public immutable wager;
/// @notice Image ID of the compiled chess-guest binary (set at deploy).
bytes32 public immutable IMAGE_ID;
// =========================================================================
// Events
// =========================================================================
event ProofVerified(
uint256 indexed gameId,
uint8 outcome,
bytes32 movesHash
);
// =========================================================================
// Constructor
// =========================================================================
/**
* @param _verifier RISC Zero on-chain verifier contract address.
* @param _wager ChessWager contract address.
* @param _imageId IMAGE_ID of the chess-guest binary (from build output).
*/
constructor(
address _verifier,
address _wager,
bytes32 _imageId
) Ownable() {
require(_verifier != address(0), "ChessProofVerifier: zero verifier");
require(_wager != address(0), "ChessProofVerifier: zero wager");
require(_imageId != bytes32(0), "ChessProofVerifier: zero imageId");
verifier = IRiscZeroVerifier(_verifier);
wager = IChessWager(_wager);
IMAGE_ID = _imageId;
}
// =========================================================================
// Core function
// =========================================================================
/**
* @notice Verify a ZK proof of game validity and settle the wager.
*
* The journal must be ABI-encoded as: abi.encode(uint256 gameId, uint8 outcome, bytes32 movesHash)
* The proof must have been generated by the chess-guest program for the
* same IMAGE_ID that was set at deployment.
*
* @param seal RISC Zero STARK proof seal.
* @param journal ABI-encoded public outputs: (gameId, outcome, movesHash).
*/
function verifyAndSettle(
bytes calldata seal,
bytes calldata journal
) external nonReentrant {
// 1. Verify the ZK proof against the committed journal
verifier.verify(seal, IMAGE_ID, sha256(journal));
// 2. Decode public journal outputs
(uint256 gameId, uint8 outcome, bytes32 movesHash) =
abi.decode(journal, (uint256, uint8, bytes32));
require(outcome >= 1 && outcome <= 3, "ChessProofVerifier: invalid outcome in journal");
emit ProofVerified(gameId, outcome, movesHash);
// 3. Settle the game in ChessWager
wager.settleFromVerifier(gameId, outcome, movesHash);
}
}