@@ -30,6 +30,19 @@ interface IBalanceHolderERC1155 {
3030contract DisputeKitGatedShutter is DisputeKitClassicBase {
3131 string public constant override version = "0.13.0 " ;
3232
33+ // ************************************* //
34+ // * Storage * //
35+ // ************************************* //
36+
37+ mapping (uint256 localDisputeID = > mapping (uint256 localRoundID = > mapping (uint256 voteID = > bytes32 recoveryCommitment )))
38+ public recoveryCommitments;
39+
40+ // ************************************* //
41+ // * Transient Storage * //
42+ // ************************************* //
43+
44+ bool transient callerIsJuror;
45+
3346 // ************************************* //
3447 // * Events * //
3548 // ************************************* //
@@ -38,12 +51,14 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
3851 /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
3952 /// @param _juror The address of the juror casting the vote commitment.
4053 /// @param _commit The commitment hash.
54+ /// @param _recoveryCommit The commitment hash without the justification.
4155 /// @param _identity The Shutter identity used for encryption.
4256 /// @param _encryptedVote The Shutter encrypted vote.
4357 event CommitCastShutter (
4458 uint256 indexed _coreDisputeID ,
4559 address indexed _juror ,
4660 bytes32 indexed _commit ,
61+ bytes32 _recoveryCommit ,
4762 bytes32 _identity ,
4863 bytes _encryptedVote
4964 );
@@ -96,17 +111,29 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
96111 /// @param _coreDisputeID The ID of the dispute in Kleros Core.
97112 /// @param _voteIDs The IDs of the votes.
98113 /// @param _commit The commitment hash including the justification.
114+ /// @param _recoveryCommit The commitment hash without the justification.
99115 /// @param _identity The Shutter identity used for encryption.
100116 /// @param _encryptedVote The Shutter encrypted vote.
101117 function castCommitShutter (
102118 uint256 _coreDisputeID ,
103119 uint256 [] calldata _voteIDs ,
104120 bytes32 _commit ,
121+ bytes32 _recoveryCommit ,
105122 bytes32 _identity ,
106123 bytes calldata _encryptedVote
107124 ) external notJumped (_coreDisputeID) {
125+ if (_recoveryCommit == bytes32 (0 )) revert EmptyRecoveryCommit ();
126+
127+ uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
128+ Dispute storage dispute = disputes[localDisputeID];
129+ uint256 localRoundID = dispute.rounds.length - 1 ;
130+ for (uint256 i = 0 ; i < _voteIDs.length ; i++ ) {
131+ recoveryCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _recoveryCommit;
132+ }
133+
134+ // `_castCommit()` ensures that the caller owns the vote
108135 _castCommit (_coreDisputeID, _voteIDs, _commit);
109- emit CommitCastShutter (_coreDisputeID, msg .sender , _commit, _identity, _encryptedVote);
136+ emit CommitCastShutter (_coreDisputeID, msg .sender , _commit, _recoveryCommit, _identity, _encryptedVote);
110137 }
111138
112139 function castVoteShutter (
@@ -119,8 +146,12 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
119146 Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
120147 address juror = dispute.rounds[dispute.rounds.length - 1 ].votes[_voteIDs[0 ]].account;
121148
122- // _castVote() ensures that all the _voteIDs do belong to `juror`
149+ callerIsJuror = juror == msg .sender ;
150+
151+ // `_castVote()` ensures that all the `_voteIDs` do belong to `juror`
123152 _castVote (_coreDisputeID, _voteIDs, _choice, _salt, _justification, juror);
153+
154+ callerIsJuror = false ;
124155 }
125156
126157 // ************************************* //
@@ -138,15 +169,38 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
138169 uint256 _choice ,
139170 uint256 _salt ,
140171 string memory _justification
141- ) public pure override returns (bytes32 ) {
142- bytes32 justificationHash = keccak256 (bytes (_justification));
143- return keccak256 (abi.encode (_choice, _salt, justificationHash));
172+ ) public view override returns (bytes32 ) {
173+ if (callerIsJuror) {
174+ // Caller is the juror, hash without `_justification` to facilitate recovery.
175+ return keccak256 (abi.encodePacked (_choice, _salt));
176+ } else {
177+ // Caller is not the juror, hash with `_justification`.
178+ bytes32 justificationHash = keccak256 (bytes (_justification));
179+ return keccak256 (abi.encode (_choice, _salt, justificationHash));
180+ }
144181 }
145182
146183 // ************************************* //
147184 // * Internal * //
148185 // ************************************* //
149186
187+ /// @dev Returns the expected vote hash for a given vote.
188+ /// @param _localDisputeID The ID of the dispute in the Dispute Kit.
189+ /// @param _localRoundID The ID of the round in the Dispute Kit.
190+ /// @param _voteID The ID of the vote.
191+ /// @return The expected vote hash.
192+ function _getExpectedVoteHash (
193+ uint256 _localDisputeID ,
194+ uint256 _localRoundID ,
195+ uint256 _voteID
196+ ) internal view override returns (bytes32 ) {
197+ if (callerIsJuror) {
198+ return recoveryCommitments[_localDisputeID][_localRoundID][_voteID];
199+ } else {
200+ return disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
201+ }
202+ }
203+
150204 /// @dev Extracts token gating information from the extra data.
151205 /// @param _extraData The extra data bytes array with the following encoding:
152206 /// - bytes 0-31: uint96 courtID, not used here
@@ -197,4 +251,10 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
197251 return IBalanceHolder (tokenGate).balanceOf (_juror) > 0 ;
198252 }
199253 }
254+
255+ // ************************************* //
256+ // * Errors * //
257+ // ************************************* //
258+
259+ error EmptyRecoveryCommit ();
200260}
0 commit comments