11// SPDX-License-Identifier: MIT
22
3- pragma solidity ^ 0.8.24 ;
3+ pragma solidity ^ 0.8.28 ;
44
55import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol " ;
66
@@ -14,6 +14,19 @@ import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol";
1414contract DisputeKitShutter is DisputeKitClassicBase {
1515 string public constant override version = "0.12.0 " ;
1616
17+ // ************************************* //
18+ // * Storage * //
19+ // ************************************* //
20+
21+ mapping (uint256 localDisputeID = > mapping (uint256 localRoundID = > mapping (uint256 voteID = > bytes32 recoveryCommitment )))
22+ public recoveryCommitments;
23+
24+ // ************************************* //
25+ // * Transient Storage * //
26+ // ************************************* //
27+
28+ bool transient callerIsJuror;
29+
1730 // ************************************* //
1831 // * Events * //
1932 // ************************************* //
@@ -22,12 +35,14 @@ contract DisputeKitShutter is DisputeKitClassicBase {
2235 /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
2336 /// @param _juror The address of the juror casting the vote commitment.
2437 /// @param _commit The commitment hash.
38+ /// @param _recoveryCommit The commitment hash without the justification.
2539 /// @param _identity The Shutter identity used for encryption.
2640 /// @param _encryptedVote The Shutter encrypted vote.
2741 event CommitCastShutter (
2842 uint256 indexed _coreDisputeID ,
2943 address indexed _juror ,
3044 bytes32 indexed _commit ,
45+ bytes32 _recoveryCommit ,
3146 bytes32 _identity ,
3247 bytes _encryptedVote
3348 );
@@ -74,17 +89,29 @@ contract DisputeKitShutter is DisputeKitClassicBase {
7489 /// @param _coreDisputeID The ID of the dispute in Kleros Core.
7590 /// @param _voteIDs The IDs of the votes.
7691 /// @param _commit The commitment hash including the justification.
92+ /// @param _recoveryCommit The commitment hash without the justification.
7793 /// @param _identity The Shutter identity used for encryption.
7894 /// @param _encryptedVote The Shutter encrypted vote.
7995 function castCommitShutter (
8096 uint256 _coreDisputeID ,
8197 uint256 [] calldata _voteIDs ,
8298 bytes32 _commit ,
99+ bytes32 _recoveryCommit ,
83100 bytes32 _identity ,
84101 bytes calldata _encryptedVote
85102 ) external notJumped (_coreDisputeID) {
103+ if (_recoveryCommit == bytes32 (0 )) revert EmptyRecoveryCommit ();
104+
105+ uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
106+ Dispute storage dispute = disputes[localDisputeID];
107+ uint256 localRoundID = dispute.rounds.length - 1 ;
108+ for (uint256 i = 0 ; i < _voteIDs.length ; i++ ) {
109+ recoveryCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _recoveryCommit;
110+ }
111+
112+ // `_castCommit()` ensures that the caller owns the vote
86113 _castCommit (_coreDisputeID, _voteIDs, _commit);
87- emit CommitCastShutter (_coreDisputeID, msg .sender , _commit, _identity, _encryptedVote);
114+ emit CommitCastShutter (_coreDisputeID, msg .sender , _commit, _recoveryCommit, _identity, _encryptedVote);
88115 }
89116
90117 function castVoteShutter (
@@ -97,8 +124,12 @@ contract DisputeKitShutter is DisputeKitClassicBase {
97124 Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
98125 address juror = dispute.rounds[dispute.rounds.length - 1 ].votes[_voteIDs[0 ]].account;
99126
100- // _castVote() ensures that all the _voteIDs do belong to `juror`
127+ callerIsJuror = juror == msg .sender ;
128+
129+ // `_castVote()` ensures that all the `_voteIDs` do belong to `juror`
101130 _castVote (_coreDisputeID, _voteIDs, _choice, _salt, _justification, juror);
131+
132+ callerIsJuror = false ;
102133 }
103134
104135 // ************************************* //
@@ -116,8 +147,37 @@ contract DisputeKitShutter is DisputeKitClassicBase {
116147 uint256 _choice ,
117148 uint256 _salt ,
118149 string memory _justification
119- ) public pure override returns (bytes32 ) {
120- bytes32 justificationHash = keccak256 (bytes (_justification));
121- return keccak256 (abi.encode (_choice, _salt, justificationHash));
150+ ) public view override returns (bytes32 ) {
151+ if (callerIsJuror) {
152+ // Caller is the juror, hash without `_justification` to facilitate recovery.
153+ return keccak256 (abi.encodePacked (_choice, _salt));
154+ } else {
155+ // Caller is not the juror, hash with `_justification`.
156+ bytes32 justificationHash = keccak256 (bytes (_justification));
157+ return keccak256 (abi.encode (_choice, _salt, justificationHash));
158+ }
159+ }
160+
161+ /// @dev Returns the expected vote hash for a given vote.
162+ /// @param _localDisputeID The ID of the dispute in the Dispute Kit.
163+ /// @param _localRoundID The ID of the round in the Dispute Kit.
164+ /// @param _voteID The ID of the vote.
165+ /// @return The expected vote hash.
166+ function getExpectedVoteHash (
167+ uint256 _localDisputeID ,
168+ uint256 _localRoundID ,
169+ uint256 _voteID
170+ ) internal view override returns (bytes32 ) {
171+ if (callerIsJuror) {
172+ return recoveryCommitments[_localDisputeID][_localRoundID][_voteID];
173+ } else {
174+ return disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
175+ }
122176 }
177+
178+ // ************************************* //
179+ // * Errors * //
180+ // ************************************* //
181+
182+ error EmptyRecoveryCommit ();
123183}
0 commit comments