From 87e57590ccbb7b4dbb24716a439f388ddaa9fd4d Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Wed, 15 Oct 2025 02:28:08 +1000 Subject: [PATCH 1/4] fix(ShutterDK): replace recovery with justification commit --- .../dispute-kits/DisputeKitClassicBase.sol | 15 ++ .../dispute-kits/DisputeKitGatedShutter.sol | 76 +++----- .../dispute-kits/DisputeKitShutter.sol | 82 ++++---- .../test/arbitration/dispute-kit-shutter.ts | 181 +++++++++--------- 4 files changed, 168 insertions(+), 186 deletions(-) diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index 7bd0ccebe..f92ac2e88 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -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; @@ -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. @@ -771,4 +785,5 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi error AppealFeeIsAlreadyPaid(); error DisputeNotResolved(); error CoreIsPaused(); + error WrongJustification(); } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol index a4ec2f8dc..0c4b066d6 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol @@ -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 * // @@ -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 ); @@ -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. @@ -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; } } @@ -254,5 +240,5 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase { // * Errors * // // ************************************* // - error EmptyRecoveryCommit(); + error EmptyJustificationCommit(); } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol b/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol index fe2c0eb7b..e78ca6cf9 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol @@ -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 * // @@ -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 ); @@ -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. @@ -142,48 +149,23 @@ 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; } } @@ -191,5 +173,5 @@ contract DisputeKitShutter is DisputeKitClassicBase { // * Errors * // // ************************************* // - error EmptyRecoveryCommit(); + error EmptyJustificationCommit(); } diff --git a/contracts/test/arbitration/dispute-kit-shutter.ts b/contracts/test/arbitration/dispute-kit-shutter.ts index aab7efa58..c739cb49d 100644 --- a/contracts/test/arbitration/dispute-kit-shutter.ts +++ b/contracts/test/arbitration/dispute-kit-shutter.ts @@ -93,18 +93,18 @@ describe("DisputeKitShutter", async () => { ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256", "uint256"], [courtId, minJurors, disputeKitId]); const generateCommitments = (choice: bigint, salt: bigint, justification: string) => { - // Recovery commitment: hash(choice, salt) - no justification - const recoveryCommit = ethers.keccak256( - ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256"], [choice, salt]) + // Choice commitment: hash(choice, salt) + const justificationHash = ethers.keccak256(ethers.toUtf8Bytes(justification)); + // Justification commitment: hash(salt, justificationHash) + const justificationCommit = ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "bytes32"], [salt, justificationHash]) ); - // Full commitment: hash(choice, salt, justificationHash) - const justificationHash = ethers.keccak256(ethers.toUtf8Bytes(justification)); - const fullCommit = ethers.keccak256( - ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256", "bytes32"], [choice, salt, justificationHash]) + const choiceCommit = ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256"], [choice, salt]) ); - return { fullCommit, recoveryCommit }; + return { choiceCommit, justificationCommit }; }; const createDisputeAndDraw = async (courtId: BigNumberish, minJurors: BigNumberish, disputeKitId: number) => { @@ -208,7 +208,7 @@ describe("DisputeKitShutter", async () => { describe("Commit Phase - castCommitShutter()", () => { describe("Successful commits", () => { - it("Should allow juror to commit vote with recovery commitment", async () => { + it("Should allow juror to commit vote with justification commitment", async () => { // Use the court with hidden votes (court ID 2) const disputeId = await createDisputeAndDraw(2, 3, SHUTTER_DK_ID); await advanceToCommitPeriod(disputeId); @@ -216,20 +216,24 @@ describe("DisputeKitShutter", async () => { const voteIDs = await getVoteIDsForJuror(disputeId, juror1); expect(voteIDs.length).to.be.greaterThan(0); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await expect( disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote) + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote) ) .to.emit(disputeKitShutter, "CommitCastShutter") - .withArgs(disputeId, juror1.address, fullCommit, recoveryCommit, identity, encryptedVote); + .withArgs(disputeId, juror1.address, choiceCommit, justificationCommit, identity, encryptedVote); - // Verify recovery commitment was stored + // Verify justification commitment was stored const localDisputeId = await disputeKitShutter.coreDisputeIDToLocal(disputeId); - const storedRecoveryCommit = await disputeKitShutter.recoveryCommitments(localDisputeId, 0, voteIDs[0]); - expect(storedRecoveryCommit).to.equal(recoveryCommit); + const storedJustificationCommit = await disputeKitShutter.justificationCommitments( + localDisputeId, + 0, + voteIDs[0] + ); + expect(storedJustificationCommit).to.equal(justificationCommit); }); it("Should allow juror to update commitment multiple times", async () => { @@ -240,47 +244,55 @@ describe("DisputeKitShutter", async () => { const voteIDs = await getVoteIDsForJuror(disputeId, juror1); // First commitment - const { fullCommit: commit1, recoveryCommit: recovery1 } = generateCommitments(1n, 111n, "First justification"); + const { choiceCommit: commit1, justificationCommit: justification1 } = generateCommitments( + 1n, + 111n, + "First justification" + ); await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, commit1, recovery1, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, commit1, justification1, identity, encryptedVote); // Second commitment (overwrites first) - const { fullCommit: commit2, recoveryCommit: recovery2 } = generateCommitments( + const { choiceCommit: commit2, justificationCommit: justification2 } = generateCommitments( 2n, 222n, "Second justification" ); await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, commit2, recovery2, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, commit2, justification2, identity, encryptedVote); // Verify only the second commitment is stored const localDisputeId = await disputeKitShutter.coreDisputeIDToLocal(disputeId); - const storedRecoveryCommit = await disputeKitShutter.recoveryCommitments(localDisputeId, 0, voteIDs[0]); - expect(storedRecoveryCommit).to.equal(recovery2); + const storedJustificationCommit = await disputeKitShutter.justificationCommitments( + localDisputeId, + 0, + voteIDs[0] + ); + expect(storedJustificationCommit).to.equal(justification2); }); }); describe("Failed commits", () => { - it("Should revert if recovery commitment is empty", async () => { + it("Should revert if justification commitment is empty", async () => { // Use the court with hidden votes (court ID 2) const disputeId = await createDisputeAndDraw(2, 3, SHUTTER_DK_ID); await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit } = generateCommitments(choice, salt, justification); await expect( disputeKitShutter.connect(juror1).castCommitShutter( disputeId, voteIDs, - fullCommit, - ethers.ZeroHash, // Empty recovery commit + choiceCommit, + ethers.ZeroHash, // Empty justification commit identity, encryptedVote ) - ).to.be.revertedWithCustomError(disputeKitShutter, "EmptyRecoveryCommit"); + ).to.be.revertedWithCustomError(disputeKitShutter, "EmptyJustificationCommit"); }); it("Should revert if not in commit period", async () => { @@ -289,12 +301,12 @@ describe("DisputeKitShutter", async () => { // Still in evidence period const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await expect( disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote) + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote) ).to.be.revertedWithCustomError(disputeKitShutter, "NotCommitPeriod"); }); @@ -304,14 +316,14 @@ describe("DisputeKitShutter", async () => { await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await expect( disputeKitShutter.connect(juror2).castCommitShutter( disputeId, voteIDs, // Using juror1's vote IDs - fullCommit, - recoveryCommit, + choiceCommit, + justificationCommit, identity, encryptedVote ) @@ -328,12 +340,12 @@ describe("DisputeKitShutter", async () => { await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); // Juror commits await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote); await advanceToVotePeriod(disputeId); @@ -356,11 +368,11 @@ describe("DisputeKitShutter", async () => { await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote); await advanceToVotePeriod(disputeId); @@ -382,11 +394,11 @@ describe("DisputeKitShutter", async () => { await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote); await advanceToVotePeriod(disputeId); @@ -408,11 +420,11 @@ describe("DisputeKitShutter", async () => { await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote); await advanceToVotePeriod(disputeId); @@ -425,7 +437,7 @@ describe("DisputeKitShutter", async () => { salt, wrongJustification // Wrong justification ) - ).to.be.revertedWithCustomError(disputeKitShutter, "HashDoesNotMatchHiddenVoteCommitment"); + ).to.be.revertedWithCustomError(disputeKitShutter, "WrongJustification"); }); it("Should revert if vote already cast", async () => { @@ -434,11 +446,11 @@ describe("DisputeKitShutter", async () => { await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote); await advanceToVotePeriod(disputeId); @@ -453,20 +465,20 @@ describe("DisputeKitShutter", async () => { }); }); - describe("Recovery Flow - Juror Reveals", () => { - describe("Successful recovery reveals", () => { + describe("Juror Reveals justification", () => { + describe("Successful justification reveals", () => { it("Should allow juror to recover vote without justification", async () => { // Use the court with hidden votes (court ID 2) const disputeId = await createDisputeAndDraw(2, 3, SHUTTER_DK_ID); await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); // Juror commits await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote); await advanceToVotePeriod(disputeId); @@ -490,17 +502,17 @@ describe("DisputeKitShutter", async () => { expect(voteInfo[2]).to.equal(choice); // choice is at index 2 }); - it("Should validate against recovery commitment when juror reveals", async () => { + it("Should validate against justification commitment when juror reveals", async () => { // Use the court with hidden votes (court ID 2) const disputeId = await createDisputeAndDraw(2, 3, SHUTTER_DK_ID); await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote); await advanceToVotePeriod(disputeId); @@ -525,11 +537,11 @@ describe("DisputeKitShutter", async () => { await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote); await advanceToVotePeriod(disputeId); @@ -551,11 +563,11 @@ describe("DisputeKitShutter", async () => { await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote); await advanceToVotePeriod(disputeId); @@ -577,11 +589,11 @@ describe("DisputeKitShutter", async () => { await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote); await advanceToVotePeriod(disputeId); @@ -594,7 +606,7 @@ describe("DisputeKitShutter", async () => { salt, "" // No justification - would work for juror but not for others ) - ).to.be.revertedWithCustomError(disputeKitShutter, "HashDoesNotMatchHiddenVoteCommitment"); + ).to.be.revertedWithCustomError(disputeKitShutter, "WrongJustification"); }); }); }); @@ -605,33 +617,16 @@ describe("DisputeKitShutter", async () => { await advanceToCommitPeriod(disputeId); const voteIDs = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDs, choiceCommit, justificationCommit, identity, encryptedVote); await advanceToVotePeriod(disputeId); - // During castVoteShutter, the contract should use different hash logic - // For juror: hash(choice, salt) - // For non-juror: hash(choice, salt, justificationHash) - - // This is tested implicitly by the recovery flow tests above // The juror can reveal with any justification, while non-juror must provide exact justification }); - - it("Should correctly compute hash for normal flow", async () => { - // Test hashVote function directly - const justificationHash = ethers.keccak256(ethers.toUtf8Bytes(justification)); - const expectedHash = ethers.keccak256( - ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256", "bytes32"], [choice, salt, justificationHash]) - ); - - // When called by non-juror (normal case), should include justification - const computedHash = await disputeKitShutter.hashVote(choice, salt, justification); - expect(computedHash).to.equal(expectedHash); - }); }); describe("Edge Cases and Security", () => { @@ -642,28 +637,32 @@ describe("DisputeKitShutter", async () => { const voteIDsJuror1 = await getVoteIDsForJuror(disputeId, juror1); const voteIDsJuror2 = await getVoteIDsForJuror(disputeId, juror2); - const { fullCommit: commit1, recoveryCommit: recovery1 } = generateCommitments(1n, 111n, "Juror 1 justification"); - const { fullCommit: commit2, recoveryCommit: recovery2 } = generateCommitments(2n, 222n, "Juror 2 justification"); + const { choiceCommit: commit1, justificationCommit: justification1 } = generateCommitments( + 1n, + 111n, + "Juror 1 justification" + ); + const { choiceCommit: commit2, justificationCommit: justification2 } = generateCommitments( + 2n, + 222n, + "Juror 2 justification" + ); // Both jurors commit await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDsJuror1, commit1, recovery1, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDsJuror1, commit1, justification1, identity, encryptedVote); await disputeKitShutter .connect(juror2) - .castCommitShutter(disputeId, voteIDsJuror2, commit2, recovery2, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDsJuror2, commit2, justification2, identity, encryptedVote); await advanceToVotePeriod(disputeId); - // Juror1 uses recovery flow (Shutter failed for them) - await disputeKitShutter.connect(juror1).castVoteShutter( - disputeId, - voteIDsJuror1, - 1n, - 111n, - "Different justification" // Recovery doesn't check this - ); + // Juror1 casts the vote directly (Shutter failed for them) + await disputeKitShutter + .connect(juror1) + .castVoteShutter(disputeId, voteIDsJuror1, 1n, 111n, "Different justification"); // Bot reveals juror2's vote normally await disputeKitShutter.connect(bot).castVoteShutter( @@ -689,16 +688,16 @@ describe("DisputeKitShutter", async () => { await advanceToCommitPeriod(disputeId); const voteIDsJuror1 = await getVoteIDsForJuror(disputeId, juror1); - const { fullCommit, recoveryCommit } = generateCommitments(choice, salt, justification); + const { choiceCommit, justificationCommit } = generateCommitments(choice, salt, justification); await disputeKitShutter .connect(juror1) - .castCommitShutter(disputeId, voteIDsJuror1, fullCommit, recoveryCommit, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDsJuror1, choiceCommit, justificationCommit, identity, encryptedVote); // Juror2 commits with a different choice const differentChoice = 2n; const voteIDsJuror2 = await getVoteIDsForJuror(disputeId, juror2); - const { fullCommit: commit2, recoveryCommit: recovery2 } = generateCommitments( + const { choiceCommit: commit2, justificationCommit: justification2 } = generateCommitments( differentChoice, salt, justification @@ -706,7 +705,7 @@ describe("DisputeKitShutter", async () => { await disputeKitShutter .connect(juror2) - .castCommitShutter(disputeId, voteIDsJuror2, commit2, recovery2, identity, encryptedVote); + .castCommitShutter(disputeId, voteIDsJuror2, commit2, justification2, identity, encryptedVote); await advanceToVotePeriod(disputeId); From 3a930be6248baedc4c0f875bf7566d779b850c00 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 16 Oct 2025 12:12:00 +0100 Subject: [PATCH 2/4] refactor: hidden votes verification logic --- .../dispute-kits/DisputeKitClassicBase.sol | 44 ++++++++----------- .../dispute-kits/DisputeKitGatedShutter.sol | 34 ++++++++++---- .../dispute-kits/DisputeKitShutter.sol | 34 ++++++++++---- 3 files changed, 71 insertions(+), 41 deletions(-) diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index f92ac2e88..b0e82e79e 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -334,15 +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 (!_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; @@ -713,29 +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; - } - - /// @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; + 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[i].commit != actualVoteHash) + revert HashDoesNotMatchHiddenVoteCommitment(); + } } /// @notice Checks that the chosen address satisfies certain conditions for being drawn. @@ -785,5 +780,4 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi error AppealFeeIsAlreadyPaid(); error DisputeNotResolved(); error CoreIsPaused(); - error WrongJustification(); } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol index 0c4b066d6..0e4eb7007 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol @@ -165,23 +165,40 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase { callerIsJuror = false; } + // ************************************* // + // * Public Views * // + // ************************************* // + + /// @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 justification + function hashJustification(uint256 _salt, string memory _justification) public pure returns (bytes32) { + return keccak256(abi.encode(_salt, keccak256(bytes(_justification)))); + } + // ************************************* // // * Internal * // // ************************************* // /// @inheritdoc DisputeKitClassicBase - function _checkJustification( + function _verifyHiddenVoteCommitments( uint256 _localDisputeID, uint256 _localRoundID, - uint256 _voteID, + uint256[] calldata _voteIDs, + uint256 _choice, 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 true; + ) 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 WrongJustification(); } } @@ -241,4 +258,5 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase { // ************************************* // error EmptyJustificationCommit(); + error WrongJustification(); } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol b/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol index e78ca6cf9..8a63f9b68 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol @@ -149,23 +149,40 @@ contract DisputeKitShutter is DisputeKitClassicBase { callerIsJuror = false; } + // ************************************* // + // * Public Views * // + // ************************************* // + + /// @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 justification + function hashJustification(uint256 _salt, string memory _justification) public pure returns (bytes32) { + return keccak256(abi.encode(_salt, keccak256(bytes(_justification)))); + } + // ************************************* // // * Internal * // // ************************************* // /// @inheritdoc DisputeKitClassicBase - function _checkJustification( + function _verifyHiddenVoteCommitments( uint256 _localDisputeID, uint256 _localRoundID, - uint256 _voteID, + uint256[] calldata _voteIDs, + uint256 _choice, 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 true; + ) 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 WrongJustification(); } } @@ -174,4 +191,5 @@ contract DisputeKitShutter is DisputeKitClassicBase { // ************************************* // error EmptyJustificationCommit(); + error WrongJustification(); } From 613b8c35ff758141c45f1a2815e9e3c8ad18706d Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Fri, 17 Oct 2025 12:22:16 +0100 Subject: [PATCH 3/4] fix: vote index error --- .../src/arbitration/dispute-kits/DisputeKitClassicBase.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index b0e82e79e..054dba0b0 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -728,7 +728,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi ) internal view virtual { bytes32 actualVoteHash = hashVote(_choice, _salt, _justification); for (uint256 i = 0; i < _voteIDs.length; i++) { - if (disputes[_localDisputeID].rounds[_localRoundID].votes[i].commit != actualVoteHash) + if (disputes[_localDisputeID].rounds[_localRoundID].votes[_voteIDs[i]].commit != actualVoteHash) revert HashDoesNotMatchHiddenVoteCommitment(); } } From 8ab5d63d12f8ebf2ce464fe17ebb4a03e7199ff1 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Fri, 17 Oct 2025 13:14:18 +0100 Subject: [PATCH 4/4] refactor: errors rename --- .../dispute-kits/DisputeKitClassicBase.sol | 4 ++-- .../dispute-kits/DisputeKitGatedShutter.sol | 4 ++-- .../arbitration/dispute-kits/DisputeKitShutter.sol | 4 ++-- .../helpers/dispute-kit-shutter-common.ts | 14 +++++++------- contracts/test/foundry/KlerosCore_Voting.t.sol | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index 5761302b0..9ed66cdfe 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -729,7 +729,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi 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 HashDoesNotMatchHiddenVoteCommitment(); + revert ChoiceCommitmentMismatch(); } } @@ -773,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(); diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol index f57b9c23b..38b3a66e3 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol @@ -226,7 +226,7 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase { bytes32 actualJustificationHash = hashJustification(_salt, _justification); for (uint256 i = 0; i < _voteIDs.length; i++) { if (justificationCommitments[_localDisputeID][_localRoundID][_voteIDs[i]] != actualJustificationHash) - revert WrongJustification(); + revert JustificationCommitmentMismatch(); } } @@ -287,5 +287,5 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase { error TokenNotSupported(address tokenGate); error EmptyJustificationCommit(); - error WrongJustification(); + error JustificationCommitmentMismatch(); } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol b/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol index 8a63f9b68..53f89d895 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol @@ -182,7 +182,7 @@ contract DisputeKitShutter is DisputeKitClassicBase { bytes32 actualJustificationHash = hashJustification(_salt, _justification); for (uint256 i = 0; i < _voteIDs.length; i++) { if (justificationCommitments[_localDisputeID][_localRoundID][_voteIDs[i]] != actualJustificationHash) - revert WrongJustification(); + revert JustificationCommitmentMismatch(); } } @@ -191,5 +191,5 @@ contract DisputeKitShutter is DisputeKitClassicBase { // ************************************* // error EmptyJustificationCommit(); - error WrongJustification(); + error JustificationCommitmentMismatch(); } diff --git a/contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts b/contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts index 76e4581f3..699968f9c 100644 --- a/contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts +++ b/contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts @@ -502,7 +502,7 @@ export function testNormalFlowBotReveals(context: () => ShutterTestContext) { ctx.salt, ctx.justification ) - ).to.be.revertedWithCustomError(ctx.disputeKit, "HashDoesNotMatchHiddenVoteCommitment"); + ).to.be.revertedWithCustomError(ctx.disputeKit, "ChoiceCommitmentMismatch"); }); it("Should revert if wrong salt provided", async () => { @@ -528,7 +528,7 @@ export function testNormalFlowBotReveals(context: () => ShutterTestContext) { wrongSalt, // Wrong salt ctx.justification ) - ).to.be.revertedWithCustomError(ctx.disputeKit, "HashDoesNotMatchHiddenVoteCommitment"); + ).to.be.revertedWithCustomError(ctx.disputeKit, "ChoiceCommitmentMismatch"); }); it("Should revert if wrong justification provided", async () => { @@ -554,7 +554,7 @@ export function testNormalFlowBotReveals(context: () => ShutterTestContext) { ctx.salt, wrongJustification // Wrong justification ) - ).to.be.revertedWithCustomError(ctx.disputeKit, "WrongJustification"); + ).to.be.revertedWithCustomError(ctx.disputeKit, "JustificationCommitmentMismatch"); }); it("Should revert if vote already cast", async () => { @@ -675,7 +675,7 @@ export function testRecoveryFlowJurorReveals(context: () => ShutterTestContext) ctx.salt, "" ) - ).to.be.revertedWithCustomError(ctx.disputeKit, "HashDoesNotMatchHiddenVoteCommitment"); + ).to.be.revertedWithCustomError(ctx.disputeKit, "ChoiceCommitmentMismatch"); }); it("Should revert if wrong salt in recovery", async () => { @@ -701,7 +701,7 @@ export function testRecoveryFlowJurorReveals(context: () => ShutterTestContext) wrongSalt, // Wrong salt "" ) - ).to.be.revertedWithCustomError(ctx.disputeKit, "HashDoesNotMatchHiddenVoteCommitment"); + ).to.be.revertedWithCustomError(ctx.disputeKit, "ChoiceCommitmentMismatch"); }); it("Should revert if non-juror tries to reveal without correct full commitment", async () => { @@ -727,7 +727,7 @@ export function testRecoveryFlowJurorReveals(context: () => ShutterTestContext) ctx.salt, "" // No justification - would work for juror but not for others ) - ).to.be.revertedWithCustomError(ctx.disputeKit, "WrongJustification"); + ).to.be.revertedWithCustomError(ctx.disputeKit, "JustificationCommitmentMismatch"); }); }); }); @@ -846,7 +846,7 @@ export function testEdgeCasesAndSecurity(context: () => ShutterTestContext) { ctx.salt, ctx.justification ) - ).to.be.revertedWithCustomError(ctx.disputeKit, "HashDoesNotMatchHiddenVoteCommitment"); + ).to.be.revertedWithCustomError(ctx.disputeKit, "ChoiceCommitmentMismatch"); }); }); } diff --git a/contracts/test/foundry/KlerosCore_Voting.t.sol b/contracts/test/foundry/KlerosCore_Voting.t.sol index 689df3a7d..5a7b4c091 100644 --- a/contracts/test/foundry/KlerosCore_Voting.t.sol +++ b/contracts/test/foundry/KlerosCore_Voting.t.sol @@ -114,11 +114,11 @@ contract KlerosCore_VotingTest is KlerosCore_TestBase { // Check the require with the wrong choice and then with the wrong salt vm.prank(staker1); - vm.expectRevert(DisputeKitClassicBase.HashDoesNotMatchHiddenVoteCommitment.selector); + vm.expectRevert(DisputeKitClassicBase.ChoiceCommitmentMismatch.selector); disputeKit.castVote(disputeID, voteIDs, 2, salt, "XYZ"); vm.prank(staker1); - vm.expectRevert(DisputeKitClassicBase.HashDoesNotMatchHiddenVoteCommitment.selector); + vm.expectRevert(DisputeKitClassicBase.ChoiceCommitmentMismatch.selector); disputeKit.castVote(disputeID, voteIDs, YES, salt - 1, "XYZ"); vm.prank(staker1);