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.13.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 );
@@ -80,17 +95,29 @@ contract DisputeKitShutter is DisputeKitClassicBase {
8095 /// @param _coreDisputeID The ID of the dispute in Kleros Core.
8196 /// @param _voteIDs The IDs of the votes.
8297 /// @param _commit The commitment hash including the justification.
98+ /// @param _recoveryCommit The commitment hash without the justification.
8399 /// @param _identity The Shutter identity used for encryption.
84100 /// @param _encryptedVote The Shutter encrypted vote.
85101 function castCommitShutter (
86102 uint256 _coreDisputeID ,
87103 uint256 [] calldata _voteIDs ,
88104 bytes32 _commit ,
105+ bytes32 _recoveryCommit ,
89106 bytes32 _identity ,
90107 bytes calldata _encryptedVote
91108 ) external notJumped (_coreDisputeID) {
109+ if (_recoveryCommit == bytes32 (0 )) revert EmptyRecoveryCommit ();
110+
111+ uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
112+ Dispute storage dispute = disputes[localDisputeID];
113+ uint256 localRoundID = dispute.rounds.length - 1 ;
114+ for (uint256 i = 0 ; i < _voteIDs.length ; i++ ) {
115+ recoveryCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _recoveryCommit;
116+ }
117+
118+ // `_castCommit()` ensures that the caller owns the vote
92119 _castCommit (_coreDisputeID, _voteIDs, _commit);
93- emit CommitCastShutter (_coreDisputeID, msg .sender , _commit, _identity, _encryptedVote);
120+ emit CommitCastShutter (_coreDisputeID, msg .sender , _commit, _recoveryCommit, _identity, _encryptedVote);
94121 }
95122
96123 function castVoteShutter (
@@ -103,8 +130,12 @@ contract DisputeKitShutter is DisputeKitClassicBase {
103130 Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
104131 address juror = dispute.rounds[dispute.rounds.length - 1 ].votes[_voteIDs[0 ]].account;
105132
106- // _castVote() ensures that all the _voteIDs do belong to `juror`
133+ callerIsJuror = juror == msg .sender ;
134+
135+ // `_castVote()` ensures that all the `_voteIDs` do belong to `juror`
107136 _castVote (_coreDisputeID, _voteIDs, _choice, _salt, _justification, juror);
137+
138+ callerIsJuror = false ;
108139 }
109140
110141 // ************************************* //
@@ -122,8 +153,37 @@ contract DisputeKitShutter is DisputeKitClassicBase {
122153 uint256 _choice ,
123154 uint256 _salt ,
124155 string memory _justification
125- ) public pure override returns (bytes32 ) {
126- bytes32 justificationHash = keccak256 (bytes (_justification));
127- return keccak256 (abi.encode (_choice, _salt, justificationHash));
156+ ) public view override returns (bytes32 ) {
157+ if (callerIsJuror) {
158+ // Caller is the juror, hash without `_justification` to facilitate recovery.
159+ return keccak256 (abi.encodePacked (_choice, _salt));
160+ } else {
161+ // Caller is not the juror, hash with `_justification`.
162+ bytes32 justificationHash = keccak256 (bytes (_justification));
163+ return keccak256 (abi.encode (_choice, _salt, justificationHash));
164+ }
165+ }
166+
167+ /// @dev Returns the expected vote hash for a given vote.
168+ /// @param _localDisputeID The ID of the dispute in the Dispute Kit.
169+ /// @param _localRoundID The ID of the round in the Dispute Kit.
170+ /// @param _voteID The ID of the vote.
171+ /// @return The expected vote hash.
172+ function getExpectedVoteHash (
173+ uint256 _localDisputeID ,
174+ uint256 _localRoundID ,
175+ uint256 _voteID
176+ ) internal view override returns (bytes32 ) {
177+ if (callerIsJuror) {
178+ return recoveryCommitments[_localDisputeID][_localRoundID][_voteID];
179+ } else {
180+ return disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
181+ }
128182 }
183+
184+ // ************************************* //
185+ // * Errors * //
186+ // ************************************* //
187+
188+ error EmptyRecoveryCommit ();
129189}
0 commit comments