Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
if (round.votes[_voteIDs[i]].account != _juror) revert JurorHasToOwnTheVote();
if (hiddenVotes && _getExpectedVoteHash(localDisputeID, localRoundID, _voteIDs[i]) != actualVoteHash)
revert HashDoesNotMatchHiddenVoteCommitment();
if (!_checkJustification(localDisputeID, localRoundID, _voteIDs[i], _justification, _salt))
revert WrongJustification();
if (round.votes[_voteIDs[i]].voted) revert VoteAlreadyCast();
round.votes[_voteIDs[i]].choice = _choice;
round.votes[_voteIDs[i]].voted = true;
Expand Down Expand Up @@ -724,6 +726,18 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
return disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
}

/// @notice Returns true if submitted justification matches. Only used by specific Dispute Kits (eg Shutter).
/// @return Whether justification matches or not.
function _checkJustification(
uint256 /*_localDisputeID*/,
uint256 /*_localRoundID*/,
uint256 /*_voteID*/,
string memory /*_justification*/,
uint256 /*_salt*/
) internal view virtual returns (bool) {
return true;
}

/// @notice Checks that the chosen address satisfies certain conditions for being drawn.
///
/// @dev No need to check the minStake requirement here because of the implicit staking in parent courts.
Expand Down Expand Up @@ -771,4 +785,5 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
error AppealFeeIsAlreadyPaid();
error DisputeNotResolved();
error CoreIsPaused();
error WrongJustification();
}
76 changes: 31 additions & 45 deletions contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
// * Storage * //
// ************************************* //

mapping(uint256 localDisputeID => mapping(uint256 localRoundID => mapping(uint256 voteID => bytes32 recoveryCommitment)))
public recoveryCommitments;
mapping(uint256 localDisputeID => mapping(uint256 localRoundID => mapping(uint256 voteID => bytes32 justificationCommitment)))
public justificationCommitments;

// ************************************* //
// * Transient Storage * //
Expand All @@ -50,15 +50,15 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
/// @dev Emitted when a vote is cast.
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _juror The address of the juror casting the vote commitment.
/// @param _commit The commitment hash.
/// @param _recoveryCommit The commitment hash without the justification.
/// @param _choiceCommit The commitment hash without the justification.
/// @param _justificationCommit The commitment hash for the justification.
/// @param _identity The Shutter identity used for encryption.
/// @param _encryptedVote The Shutter encrypted vote.
event CommitCastShutter(
uint256 indexed _coreDisputeID,
address indexed _juror,
bytes32 indexed _commit,
bytes32 _recoveryCommit,
bytes32 indexed _choiceCommit,
bytes32 _justificationCommit,
bytes32 _identity,
bytes _encryptedVote
);
Expand Down Expand Up @@ -107,30 +107,37 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
///
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @param _voteIDs The IDs of the votes.
/// @param _commit The commitment hash including the justification.
/// @param _recoveryCommit The commitment hash without the justification.
/// @param _choiceCommit The commitment hash without the justification.
/// @param _justificationCommit The commitment hash for justification.
/// @param _identity The Shutter identity used for encryption.
/// @param _encryptedVote The Shutter encrypted vote.
function castCommitShutter(
uint256 _coreDisputeID,
uint256[] calldata _voteIDs,
bytes32 _commit,
bytes32 _recoveryCommit,
bytes32 _choiceCommit,
bytes32 _justificationCommit,
bytes32 _identity,
bytes calldata _encryptedVote
) external {
if (_recoveryCommit == bytes32(0)) revert EmptyRecoveryCommit();
if (_justificationCommit == bytes32(0)) revert EmptyJustificationCommit();

uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
Dispute storage dispute = disputes[localDisputeID];
uint256 localRoundID = dispute.rounds.length - 1;
for (uint256 i = 0; i < _voteIDs.length; i++) {
recoveryCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _recoveryCommit;
justificationCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _justificationCommit;
}

// `_castCommit()` ensures that the caller owns the vote and that dispute is active
_castCommit(_coreDisputeID, _voteIDs, _commit);
emit CommitCastShutter(_coreDisputeID, msg.sender, _commit, _recoveryCommit, _identity, _encryptedVote);
_castCommit(_coreDisputeID, _voteIDs, _choiceCommit);
emit CommitCastShutter(
_coreDisputeID,
msg.sender,
_choiceCommit,
_justificationCommit,
_identity,
_encryptedVote
);
}

/// @notice Version of the `castVote` function designed specifically for Shutter.
Expand Down Expand Up @@ -158,44 +165,23 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
callerIsJuror = false;
}

// ************************************* //
// * Public Views * //
// ************************************* //

/// @notice Computes the hash of a vote using ABI encoding
/// @param _choice The choice being voted for
/// @param _salt A random salt for commitment
/// @param _justification The justification for the vote
/// @return bytes32 The hash of the encoded vote parameters
function hashVote(
uint256 _choice,
uint256 _salt,
string memory _justification
) public view override returns (bytes32) {
if (callerIsJuror) {
// Caller is the juror, hash without `_justification` to facilitate recovery.
return keccak256(abi.encodePacked(_choice, _salt));
} else {
// Caller is not the juror, hash with `_justification`.
bytes32 justificationHash = keccak256(bytes(_justification));
return keccak256(abi.encode(_choice, _salt, justificationHash));
}
}

// ************************************* //
// * Internal * //
// ************************************* //

/// @inheritdoc DisputeKitClassicBase
function _getExpectedVoteHash(
function _checkJustification(
uint256 _localDisputeID,
uint256 _localRoundID,
uint256 _voteID
) internal view override returns (bytes32) {
if (callerIsJuror) {
return recoveryCommitments[_localDisputeID][_localRoundID][_voteID];
uint256 _voteID,
string memory _justification,
uint256 _salt
) internal view override returns (bool) {
if (!callerIsJuror) {
bytes32 justificationCommit = justificationCommitments[_localDisputeID][_localRoundID][_voteID];
return justificationCommit == keccak256(abi.encode(_salt, keccak256(bytes(_justification))));
} else {
return disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
return true;
}
}

Expand Down Expand Up @@ -254,5 +240,5 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
// * Errors * //
// ************************************* //

error EmptyRecoveryCommit();
error EmptyJustificationCommit();
}
82 changes: 32 additions & 50 deletions contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ contract DisputeKitShutter is DisputeKitClassicBase {
// * Storage * //
// ************************************* //

mapping(uint256 localDisputeID => mapping(uint256 localRoundID => mapping(uint256 voteID => bytes32 recoveryCommitment)))
public recoveryCommitments;
mapping(uint256 localDisputeID => mapping(uint256 localRoundID => mapping(uint256 voteID => bytes32 justificationCommitment)))
public justificationCommitments;

// ************************************* //
// * Transient Storage * //
Expand All @@ -34,15 +34,15 @@ contract DisputeKitShutter is DisputeKitClassicBase {
/// @notice Emitted when a vote is cast.
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _juror The address of the juror casting the vote commitment.
/// @param _commit The commitment hash.
/// @param _recoveryCommit The commitment hash without the justification.
/// @param _choiceCommit The commitment hash without the justification.
/// @param _justificationCommit The commitment hash for the justification.
/// @param _identity The Shutter identity used for encryption.
/// @param _encryptedVote The Shutter encrypted vote.
event CommitCastShutter(
uint256 indexed _coreDisputeID,
address indexed _juror,
bytes32 indexed _commit,
bytes32 _recoveryCommit,
bytes32 indexed _choiceCommit,
bytes32 _justificationCommit,
bytes32 _identity,
bytes _encryptedVote
);
Expand Down Expand Up @@ -91,30 +91,37 @@ contract DisputeKitShutter is DisputeKitClassicBase {
///
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @param _voteIDs The IDs of the votes.
/// @param _commit The commitment hash including the justification.
/// @param _recoveryCommit The commitment hash without the justification.
/// @param _choiceCommit The commitment hash without the justification.
/// @param _justificationCommit The commitment hash for justification.
/// @param _identity The Shutter identity used for encryption.
/// @param _encryptedVote The Shutter encrypted vote.
function castCommitShutter(
uint256 _coreDisputeID,
uint256[] calldata _voteIDs,
bytes32 _commit,
bytes32 _recoveryCommit,
bytes32 _choiceCommit,
bytes32 _justificationCommit,
bytes32 _identity,
bytes calldata _encryptedVote
) external {
if (_recoveryCommit == bytes32(0)) revert EmptyRecoveryCommit();
if (_justificationCommit == bytes32(0)) revert EmptyJustificationCommit();

uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
Dispute storage dispute = disputes[localDisputeID];
uint256 localRoundID = dispute.rounds.length - 1;
for (uint256 i = 0; i < _voteIDs.length; i++) {
recoveryCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _recoveryCommit;
justificationCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _justificationCommit;
}

// `_castCommit()` ensures that the caller owns the vote and that dispute is active
_castCommit(_coreDisputeID, _voteIDs, _commit);
emit CommitCastShutter(_coreDisputeID, msg.sender, _commit, _recoveryCommit, _identity, _encryptedVote);
_castCommit(_coreDisputeID, _voteIDs, _choiceCommit);
emit CommitCastShutter(
_coreDisputeID,
msg.sender,
_choiceCommit,
_justificationCommit,
_identity,
_encryptedVote
);
}

/// @notice Version of `castVote` function designed specifically for Shutter.
Expand Down Expand Up @@ -142,54 +149,29 @@ contract DisputeKitShutter is DisputeKitClassicBase {
callerIsJuror = false;
}

// ************************************* //
// * Public Views * //
// ************************************* //

/// @notice Computes the hash of a vote using ABI encoding
/// @param _choice The choice being voted for
/// @param _salt A random salt for commitment
/// @param _justification The justification for the vote
/// @return bytes32 The hash of the encoded vote parameters
function hashVote(
uint256 _choice,
uint256 _salt,
string memory _justification
) public view override returns (bytes32) {
if (callerIsJuror) {
// Caller is the juror, hash without `_justification` to facilitate recovery.
return keccak256(abi.encodePacked(_choice, _salt));
} else {
// Caller is not the juror, hash with `_justification`.
bytes32 justificationHash = keccak256(bytes(_justification));
return keccak256(abi.encode(_choice, _salt, justificationHash));
}
}

// ************************************* //
// * Internal * //
// ************************************* //

/// @notice Returns the expected vote hash for a given vote.
/// @param _localDisputeID The ID of the dispute in the Dispute Kit.
/// @param _localRoundID The ID of the round in the Dispute Kit.
/// @param _voteID The ID of the vote.
/// @return The expected vote hash.
function _getExpectedVoteHash(
/// @inheritdoc DisputeKitClassicBase
function _checkJustification(
uint256 _localDisputeID,
uint256 _localRoundID,
uint256 _voteID
) internal view override returns (bytes32) {
if (callerIsJuror) {
return recoveryCommitments[_localDisputeID][_localRoundID][_voteID];
uint256 _voteID,
string memory _justification,
uint256 _salt
) internal view override returns (bool) {
if (!callerIsJuror) {
bytes32 justificationCommit = justificationCommitments[_localDisputeID][_localRoundID][_voteID];
return justificationCommit == keccak256(abi.encode(_salt, keccak256(bytes(_justification))));
} else {
return disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
return true;
}
}

// ************************************* //
// * Errors * //
// ************************************* //

error EmptyRecoveryCommit();
error EmptyJustificationCommit();
}
Loading
Loading