Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 20 additions & 11 deletions contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -334,13 +334,13 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
{
(uint96 courtID, , , , ) = core.disputes(_coreDisputeID);
(, bool hiddenVotes, , , , ) = core.courts(courtID);
bytes32 actualVoteHash = hashVote(_choice, _salt, _justification);
if (hiddenVotes) {
_verifyHiddenVoteCommitments(localDisputeID, localRoundID, _voteIDs, _choice, _justification, _salt);
}

// Save the votes.
for (uint256 i = 0; i < _voteIDs.length; i++) {
if (round.votes[_voteIDs[i]].account != _juror) revert JurorHasToOwnTheVote();
if (hiddenVotes && _getExpectedVoteHash(localDisputeID, localRoundID, _voteIDs[i]) != actualVoteHash)
revert HashDoesNotMatchHiddenVoteCommitment();
if (round.votes[_voteIDs[i]].voted) revert VoteAlreadyCast();
round.votes[_voteIDs[i]].choice = _choice;
round.votes[_voteIDs[i]].voted = true;
Expand Down Expand Up @@ -711,17 +711,26 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
// * Internal * //
// ************************************* //

/// @notice Returns the expected vote hash for a given vote.
/// @notice Verifies that revealed choice and justification match the hidden vote commitments.
/// @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(
/// @param _voteIDs The IDs of the votes.
/// @param _choice The choice.
/// @param _justification The justification.
/// @param _salt The salt.
function _verifyHiddenVoteCommitments(
uint256 _localDisputeID,
uint256 _localRoundID,
uint256 _voteID
) internal view virtual returns (bytes32) {
return disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
uint256[] calldata _voteIDs,
uint256 _choice,
string memory _justification,
uint256 _salt
) internal view virtual {
bytes32 actualVoteHash = hashVote(_choice, _salt, _justification);
for (uint256 i = 0; i < _voteIDs.length; i++) {
if (disputes[_localDisputeID].rounds[_localRoundID].votes[_voteIDs[i]].commit != actualVoteHash)
revert ChoiceCommitmentMismatch();
}
}

/// @notice Checks that the chosen address satisfies certain conditions for being drawn.
Expand Down Expand Up @@ -764,7 +773,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
error NotVotePeriod();
error EmptyVoteIDs();
error ChoiceOutOfBounds();
error HashDoesNotMatchHiddenVoteCommitment();
error ChoiceCommitmentMismatch();
error VoteAlreadyCast();
error NotAppealPeriod();
error NotAppealPeriodForLoser();
Expand Down
80 changes: 42 additions & 38 deletions contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
// ************************************* //

mapping(address token => bool supported) public supportedTokens; // Whether the token is supported or not.
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 @@ -53,15 +53,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 @@ -135,30 +135,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 @@ -190,40 +197,36 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
// * Public Views * //
// ************************************* //

/// @notice Computes the hash of a vote using ABI encoding
/// @param _choice The choice being voted for
/// @notice Computes the hash of a justification using ABI encoding
/// @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));
}
/// @return bytes32 The hash of the encoded justification
function hashJustification(uint256 _salt, string memory _justification) public pure returns (bytes32) {
return keccak256(abi.encode(_salt, keccak256(bytes(_justification))));
}

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

/// @inheritdoc DisputeKitClassicBase
function _getExpectedVoteHash(
function _verifyHiddenVoteCommitments(
uint256 _localDisputeID,
uint256 _localRoundID,
uint256 _voteID
) internal view override returns (bytes32) {
if (callerIsJuror) {
return recoveryCommitments[_localDisputeID][_localRoundID][_voteID];
} else {
return disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
uint256[] calldata _voteIDs,
uint256 _choice,
string memory _justification,
uint256 _salt
) internal view override {
super._verifyHiddenVoteCommitments(_localDisputeID, _localRoundID, _voteIDs, _choice, _justification, _salt);

// The juror is allowed to reveal without verifying the justification commitment for recovery purposes.
if (callerIsJuror) return;

bytes32 actualJustificationHash = hashJustification(_salt, _justification);
for (uint256 i = 0; i < _voteIDs.length; i++) {
if (justificationCommitments[_localDisputeID][_localRoundID][_voteIDs[i]] != actualJustificationHash)
revert JustificationCommitmentMismatch();
}
}

Expand Down Expand Up @@ -283,5 +286,6 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
// ************************************* //

error TokenNotSupported(address tokenGate);
error EmptyRecoveryCommit();
error EmptyJustificationCommit();
error JustificationCommitmentMismatch();
}
86 changes: 43 additions & 43 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 @@ -146,50 +153,43 @@ contract DisputeKitShutter is DisputeKitClassicBase {
// * Public Views * //
// ************************************* //

/// @notice Computes the hash of a vote using ABI encoding
/// @param _choice The choice being voted for
/// @notice Computes the hash of a justification using ABI encoding
/// @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));
}
/// @return bytes32 The hash of the encoded justification
function hashJustification(uint256 _salt, string memory _justification) public pure returns (bytes32) {
return keccak256(abi.encode(_salt, keccak256(bytes(_justification))));
}

// ************************************* //
// * 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 _verifyHiddenVoteCommitments(
uint256 _localDisputeID,
uint256 _localRoundID,
uint256 _voteID
) internal view override returns (bytes32) {
if (callerIsJuror) {
return recoveryCommitments[_localDisputeID][_localRoundID][_voteID];
} else {
return disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
uint256[] calldata _voteIDs,
uint256 _choice,
string memory _justification,
uint256 _salt
) internal view override {
super._verifyHiddenVoteCommitments(_localDisputeID, _localRoundID, _voteIDs, _choice, _justification, _salt);

// The juror is allowed to reveal without verifying the justification commitment for recovery purposes.
if (callerIsJuror) return;

bytes32 actualJustificationHash = hashJustification(_salt, _justification);
for (uint256 i = 0; i < _voteIDs.length; i++) {
if (justificationCommitments[_localDisputeID][_localRoundID][_voteIDs[i]] != actualJustificationHash)
revert JustificationCommitmentMismatch();
}
}

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

error EmptyRecoveryCommit();
error EmptyJustificationCommit();
error JustificationCommitmentMismatch();
}
2 changes: 0 additions & 2 deletions contracts/test/arbitration/dispute-kit-gated-shutter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
testCommitPhase,
testNormalFlowBotReveals,
testRecoveryFlowJurorReveals,
testHashFunctionBehavior,
testEdgeCasesAndSecurity,
ShutterTestContext,
} from "./helpers/dispute-kit-shutter-common";
Expand Down Expand Up @@ -68,7 +67,6 @@ describe("DisputeKitGatedShutter", async () => {
testCommitPhase(() => shutterContext);
testNormalFlowBotReveals(() => shutterContext);
testRecoveryFlowJurorReveals(() => shutterContext);
testHashFunctionBehavior(() => shutterContext);
testEdgeCasesAndSecurity(() => shutterContext);
});
});
2 changes: 0 additions & 2 deletions contracts/test/arbitration/dispute-kit-shutter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
testCommitPhase,
testNormalFlowBotReveals,
testRecoveryFlowJurorReveals,
testHashFunctionBehavior,
testEdgeCasesAndSecurity,
ShutterTestContext,
} from "./helpers/dispute-kit-shutter-common";
Expand Down Expand Up @@ -33,6 +32,5 @@ describe("DisputeKitShutter", async () => {
testCommitPhase(() => context);
testNormalFlowBotReveals(() => context);
testRecoveryFlowJurorReveals(() => context);
testHashFunctionBehavior(() => context);
testEdgeCasesAndSecurity(() => context);
});
Loading
Loading