Skip to content

Commit 8af1ba0

Browse files
fix(ShutterDK): tie recovery commit
1 parent decd3e7 commit 8af1ba0

File tree

3 files changed

+43
-36
lines changed

3 files changed

+43
-36
lines changed

contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,15 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
107107
///
108108
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
109109
/// @param _voteIDs The IDs of the votes.
110-
/// @param _commit The commitment hash including the justification.
111110
/// @param _recoveryCommit The commitment hash without the justification.
111+
/// @param _justification Justification of the choice.
112112
/// @param _identity The Shutter identity used for encryption.
113113
/// @param _encryptedVote The Shutter encrypted vote.
114114
function castCommitShutter(
115115
uint256 _coreDisputeID,
116116
uint256[] calldata _voteIDs,
117-
bytes32 _commit,
118117
bytes32 _recoveryCommit,
118+
string memory _justification,
119119
bytes32 _identity,
120120
bytes calldata _encryptedVote
121121
) external notJumped(_coreDisputeID) {
@@ -127,10 +127,12 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
127127
for (uint256 i = 0; i < _voteIDs.length; i++) {
128128
recoveryCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _recoveryCommit;
129129
}
130-
130+
// Construct Shutter commit out of recovery commit + justification.
131+
bytes32 justificationHash = keccak256(bytes(_justification));
132+
bytes32 commit = keccak256(abi.encode(_recoveryCommit, justificationHash));
131133
// `_castCommit()` ensures that the caller owns the vote
132-
_castCommit(_coreDisputeID, _voteIDs, _commit);
133-
emit CommitCastShutter(_coreDisputeID, msg.sender, _commit, _recoveryCommit, _identity, _encryptedVote);
134+
_castCommit(_coreDisputeID, _voteIDs, commit);
135+
emit CommitCastShutter(_coreDisputeID, msg.sender, commit, _recoveryCommit, _identity, _encryptedVote);
134136
}
135137

136138
/// @notice Version of the `castVote` function designed specifically for Shutter.
@@ -178,7 +180,7 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
178180
} else {
179181
// Caller is not the juror, hash with `_justification`.
180182
bytes32 justificationHash = keccak256(bytes(_justification));
181-
return keccak256(abi.encode(_choice, _salt, justificationHash));
183+
return keccak256(abi.encode(keccak256(abi.encodePacked(_choice, _salt)), justificationHash));
182184
}
183185
}
184186

contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,15 @@ contract DisputeKitShutter is DisputeKitClassicBase {
9191
///
9292
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
9393
/// @param _voteIDs The IDs of the votes.
94-
/// @param _commit The commitment hash including the justification.
9594
/// @param _recoveryCommit The commitment hash without the justification.
95+
/// @param _justification Justification of the choice.
9696
/// @param _identity The Shutter identity used for encryption.
9797
/// @param _encryptedVote The Shutter encrypted vote.
9898
function castCommitShutter(
9999
uint256 _coreDisputeID,
100100
uint256[] calldata _voteIDs,
101-
bytes32 _commit,
102101
bytes32 _recoveryCommit,
102+
string memory _justification,
103103
bytes32 _identity,
104104
bytes calldata _encryptedVote
105105
) external notJumped(_coreDisputeID) {
@@ -111,10 +111,12 @@ contract DisputeKitShutter is DisputeKitClassicBase {
111111
for (uint256 i = 0; i < _voteIDs.length; i++) {
112112
recoveryCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _recoveryCommit;
113113
}
114-
114+
// Construct Shutter commit out of recovery commit + justification.
115+
bytes32 justificationHash = keccak256(bytes(_justification));
116+
bytes32 commit = keccak256(abi.encode(_recoveryCommit, justificationHash));
115117
// `_castCommit()` ensures that the caller owns the vote
116-
_castCommit(_coreDisputeID, _voteIDs, _commit);
117-
emit CommitCastShutter(_coreDisputeID, msg.sender, _commit, _recoveryCommit, _identity, _encryptedVote);
118+
_castCommit(_coreDisputeID, _voteIDs, commit);
119+
emit CommitCastShutter(_coreDisputeID, msg.sender, commit, _recoveryCommit, _identity, _encryptedVote);
118120
}
119121

120122
/// @notice Version of `castVote` function designed specifically for Shutter.
@@ -162,7 +164,7 @@ contract DisputeKitShutter is DisputeKitClassicBase {
162164
} else {
163165
// Caller is not the juror, hash with `_justification`.
164166
bytes32 justificationHash = keccak256(bytes(_justification));
165-
return keccak256(abi.encode(_choice, _salt, justificationHash));
167+
return keccak256(abi.encode(keccak256(abi.encodePacked(_choice, _salt)), justificationHash));
166168
}
167169
}
168170

contracts/test/arbitration/dispute-kit-shutter.ts

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ describe("DisputeKitShutter", async () => {
101101
// Full commitment: hash(choice, salt, justificationHash)
102102
const justificationHash = ethers.keccak256(ethers.toUtf8Bytes(justification));
103103
const fullCommit = ethers.keccak256(
104-
ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256", "bytes32"], [choice, salt, justificationHash])
104+
ethers.AbiCoder.defaultAbiCoder().encode(["bytes32", "bytes32"], [recoveryCommit, justificationHash])
105105
);
106106

107107
return { fullCommit, recoveryCommit };
@@ -221,7 +221,7 @@ describe("DisputeKitShutter", async () => {
221221
await expect(
222222
disputeKitShutter
223223
.connect(juror1)
224-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote)
224+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote)
225225
)
226226
.to.emit(disputeKitShutter, "CommitCastShutter")
227227
.withArgs(disputeId, juror1.address, fullCommit, recoveryCommit, identity, encryptedVote);
@@ -243,7 +243,7 @@ describe("DisputeKitShutter", async () => {
243243
const { fullCommit: commit1, recoveryCommit: recovery1 } = generateCommitments(1n, 111n, "First justification");
244244
await disputeKitShutter
245245
.connect(juror1)
246-
.castCommitShutter(disputeId, voteIDs, commit1, recovery1, identity, encryptedVote);
246+
.castCommitShutter(disputeId, voteIDs, recovery1, "First justification", identity, encryptedVote);
247247

248248
// Second commitment (overwrites first)
249249
const { fullCommit: commit2, recoveryCommit: recovery2 } = generateCommitments(
@@ -253,7 +253,7 @@ describe("DisputeKitShutter", async () => {
253253
);
254254
await disputeKitShutter
255255
.connect(juror1)
256-
.castCommitShutter(disputeId, voteIDs, commit2, recovery2, identity, encryptedVote);
256+
.castCommitShutter(disputeId, voteIDs, recovery2, "Second justification", identity, encryptedVote);
257257

258258
// Verify only the second commitment is stored
259259
const localDisputeId = await disputeKitShutter.coreDisputeIDToLocal(disputeId);
@@ -269,14 +269,13 @@ describe("DisputeKitShutter", async () => {
269269
await advanceToCommitPeriod(disputeId);
270270

271271
const voteIDs = await getVoteIDsForJuror(disputeId, juror1);
272-
const { fullCommit } = generateCommitments(choice, salt, justification);
273272

274273
await expect(
275274
disputeKitShutter.connect(juror1).castCommitShutter(
276275
disputeId,
277276
voteIDs,
278-
fullCommit,
279277
ethers.ZeroHash, // Empty recovery commit
278+
justification,
280279
identity,
281280
encryptedVote
282281
)
@@ -294,7 +293,7 @@ describe("DisputeKitShutter", async () => {
294293
await expect(
295294
disputeKitShutter
296295
.connect(juror1)
297-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote)
296+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote)
298297
).to.be.revertedWithCustomError(disputeKitShutter, "NotCommitPeriod");
299298
});
300299

@@ -310,8 +309,8 @@ describe("DisputeKitShutter", async () => {
310309
disputeKitShutter.connect(juror2).castCommitShutter(
311310
disputeId,
312311
voteIDs, // Using juror1's vote IDs
313-
fullCommit,
314312
recoveryCommit,
313+
justification,
315314
identity,
316315
encryptedVote
317316
)
@@ -333,7 +332,7 @@ describe("DisputeKitShutter", async () => {
333332
// Juror commits
334333
await disputeKitShutter
335334
.connect(juror1)
336-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote);
335+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote);
337336

338337
await advanceToVotePeriod(disputeId);
339338

@@ -360,7 +359,7 @@ describe("DisputeKitShutter", async () => {
360359

361360
await disputeKitShutter
362361
.connect(juror1)
363-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote);
362+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote);
364363

365364
await advanceToVotePeriod(disputeId);
366365

@@ -386,7 +385,7 @@ describe("DisputeKitShutter", async () => {
386385

387386
await disputeKitShutter
388387
.connect(juror1)
389-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote);
388+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote);
390389

391390
await advanceToVotePeriod(disputeId);
392391

@@ -412,7 +411,7 @@ describe("DisputeKitShutter", async () => {
412411

413412
await disputeKitShutter
414413
.connect(juror1)
415-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote);
414+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote);
416415

417416
await advanceToVotePeriod(disputeId);
418417

@@ -438,7 +437,7 @@ describe("DisputeKitShutter", async () => {
438437

439438
await disputeKitShutter
440439
.connect(juror1)
441-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote);
440+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote);
442441

443442
await advanceToVotePeriod(disputeId);
444443

@@ -466,7 +465,7 @@ describe("DisputeKitShutter", async () => {
466465
// Juror commits
467466
await disputeKitShutter
468467
.connect(juror1)
469-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote);
468+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote);
470469

471470
await advanceToVotePeriod(disputeId);
472471

@@ -500,7 +499,7 @@ describe("DisputeKitShutter", async () => {
500499

501500
await disputeKitShutter
502501
.connect(juror1)
503-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote);
502+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote);
504503

505504
await advanceToVotePeriod(disputeId);
506505

@@ -529,7 +528,7 @@ describe("DisputeKitShutter", async () => {
529528

530529
await disputeKitShutter
531530
.connect(juror1)
532-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote);
531+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote);
533532

534533
await advanceToVotePeriod(disputeId);
535534

@@ -555,7 +554,7 @@ describe("DisputeKitShutter", async () => {
555554

556555
await disputeKitShutter
557556
.connect(juror1)
558-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote);
557+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote);
559558

560559
await advanceToVotePeriod(disputeId);
561560

@@ -581,7 +580,7 @@ describe("DisputeKitShutter", async () => {
581580

582581
await disputeKitShutter
583582
.connect(juror1)
584-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote);
583+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote);
585584

586585
await advanceToVotePeriod(disputeId);
587586

@@ -609,7 +608,7 @@ describe("DisputeKitShutter", async () => {
609608

610609
await disputeKitShutter
611610
.connect(juror1)
612-
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, identity, encryptedVote);
611+
.castCommitShutter(disputeId, voteIDs, recoveryCommit, justification, identity, encryptedVote);
613612

614613
await advanceToVotePeriod(disputeId);
615614

@@ -624,8 +623,12 @@ describe("DisputeKitShutter", async () => {
624623
it("Should correctly compute hash for normal flow", async () => {
625624
// Test hashVote function directly
626625
const justificationHash = ethers.keccak256(ethers.toUtf8Bytes(justification));
626+
const recoveryCommit = ethers.keccak256(
627+
ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256"], [choice, salt])
628+
);
629+
627630
const expectedHash = ethers.keccak256(
628-
ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256", "bytes32"], [choice, salt, justificationHash])
631+
ethers.AbiCoder.defaultAbiCoder().encode(["bytes32", "bytes32"], [recoveryCommit, justificationHash])
629632
);
630633

631634
// When called by non-juror (normal case), should include justification
@@ -648,11 +651,11 @@ describe("DisputeKitShutter", async () => {
648651
// Both jurors commit
649652
await disputeKitShutter
650653
.connect(juror1)
651-
.castCommitShutter(disputeId, voteIDsJuror1, commit1, recovery1, identity, encryptedVote);
654+
.castCommitShutter(disputeId, voteIDsJuror1, recovery1, "Juror 1 justification", identity, encryptedVote);
652655

653656
await disputeKitShutter
654657
.connect(juror2)
655-
.castCommitShutter(disputeId, voteIDsJuror2, commit2, recovery2, identity, encryptedVote);
658+
.castCommitShutter(disputeId, voteIDsJuror2, recovery2, "Juror 2 justification", identity, encryptedVote);
656659

657660
await advanceToVotePeriod(disputeId);
658661

@@ -693,7 +696,7 @@ describe("DisputeKitShutter", async () => {
693696

694697
await disputeKitShutter
695698
.connect(juror1)
696-
.castCommitShutter(disputeId, voteIDsJuror1, fullCommit, recoveryCommit, identity, encryptedVote);
699+
.castCommitShutter(disputeId, voteIDsJuror1, recoveryCommit, justification, identity, encryptedVote);
697700

698701
// Juror2 commits with a different choice
699702
const differentChoice = 2n;
@@ -706,7 +709,7 @@ describe("DisputeKitShutter", async () => {
706709

707710
await disputeKitShutter
708711
.connect(juror2)
709-
.castCommitShutter(disputeId, voteIDsJuror2, commit2, recovery2, identity, encryptedVote);
712+
.castCommitShutter(disputeId, voteIDsJuror2, recovery2, justification, identity, encryptedVote);
710713

711714
await advanceToVotePeriod(disputeId);
712715

0 commit comments

Comments
 (0)