Title: feat(dispute): commit-reveal juror voting to prevent vote copying & bribery (#52) - #60
Conversation
I'll approve after addressing two small, testable issues and adding a short doc/changelog entry. Overall this is a clear, well-tested implementation of commit-reveal voting that removes the vote-copying/bribery vector — great work. Merge readiness & risk assessment Positive: Possible improvements (non-blocking, recommended) Add inline code comments (or doc-comments above the relevant functions) documenting the exact hash preimage format: sha256(vote_byte ++ salt), including the required salt length and endianness/concatenation semantics. This makes it explicit for integrators who will prepare salts/off-chain tooling. Notes and reasoning for the above items Salt validation is important: a wrong-length/format salt could lead to surprising behavior (mismatched hashes) or inconsistent client implementations. Rejecting incorrect salts early gives clearer error semantics to off-chain tooling. |
|
Kindly review @meshackyaro |
Replace direct cast_vote with two-phase commit-reveal voting to prevent vote-copying and bribery. Addresses all maintainer review feedback: - Clear JurorCommit after reveal to free storage and prevent reuse - Reorder AlreadyVoted check before NoCommitFound for correct errors - Add detailed hash preimage format documentation (sha256(vote_byte ++ salt)) - Add TODO marker for admin-configurable commit/reveal windows - Add test: same salt across disputes does not collide - Add test: commitment cleared after reveal - Add CHANGELOG.md
Thank you — this is an excellent, well-scoped improvement. What I like
Minor suggestions / next steps (non-blocking)
Final verdict
|
meshackyaro
left a comment
There was a problem hiding this comment.
Thanks — this is an excellent, well-scoped change.
- The commit-reveal design is a clear, well-justified fix for the vote-copying / bribery vector; replacing direct voting with sha256(vote || salt) + reveal is the right approach and is implemented cleanly.
- I appreciate the per-dispute commit/reveal deadlines, the new, focused error variants, and the minimal-but-sufficient event surface (VoteCommitted / VoteRevealed). These make the flow auditable and developer-friendly.
- Tests look thorough: all existing tests adapted and 9 new tests added (62 passing) — that gives me strong confidence in correctness and backwards compatibility of unaffected functionality (staking, slashing, settlement, etc.).
- Storage and API changes are clear (DataKey::JurorCommit, DisputeVoters populated on reveal). Naming and comments are readable and consistent with project style.
- Tradeoffs are well-explained in the PR description — keeping non-reveals unpunished (they simply forfeit their vote) is reasonable for now and keeps complexity down.
Overall: this is a security-forward, well-tested improvement that I'm happy to approve. This is now ready to merge.
Body:
Closes #59
Summary
Replaces direct vote casting (
cast_vote) with a two-phase commit-revealscheme so that no juror can see how anyone else voted before their own
vote is permanently locked on-chain. This eliminates the vote-copying and
bribery vectors that previously made the slashing mechanism ineffective
against rational jurors.
Changes
commit_vote): Jurors submitsha256(vote_byte ++ salt)during a ~1-day commit window. The commitment reveals nothing about the
vote direction.
reveal_vote): After the commit window closes, jurorsdisclose
(vote, salt)during a separate ~1-day reveal window. Thecontract recomputes the hash and rejects mismatches.
resolve_dispute): Only revealed votes are tallied. Ajuror who commits but never reveals simply forfeits their vote — they
are neither counted nor slashed.
DisputeRecordnow carries its owncommit_deadlineandreveal_deadline(ledger sequences computed atraise_disputetime), gating all three voting entrypoints.CommitPhaseEnded,RevealPhaseNotOpen,AlreadyCommitted,NoCommitFound,InvalidReveal,RevealPhaseNotEnded.VoteCommitted(no vote info, just that a commitment exists)and
VoteRevealed(includes vote direction) emitted at each phase.DataKey::JurorCommit(VoteKey)for commitment hashes.DisputeVotersis now populated only byreveal_vote, notcommit_vote.Testing
62 tests passing (
cargo test -p trustflow), including 9 new tests:test_commit_vote_requires_stakeInsufficientStaketest_commit_vote_duplicate_rejectedAlreadyCommittedtest_commit_vote_after_commit_deadline_rejectedCommitPhaseEndedtest_reveal_vote_before_commit_deadline_rejectedRevealPhaseNotOpentest_reveal_vote_wrong_preimage_rejectedInvalidRevealtest_reveal_vote_duplicate_rejectedAlreadyVotedtest_reveal_vote_without_commit_rejectedNoCommitFoundtest_resolve_dispute_before_reveal_deadline_rejectedRevealPhaseNotEndedtest_committed_but_unrevealed_vote_not_countedTradeoffs
never reveals simply loses their vote. An alternative design would slash
non-revealers to incentivise follow-through, but that adds complexity
(jurors might lose their key or go offline) and the current design is
sufficient to prevent the vote-copying attack — which is the actual goal.
17,280 ledgers (~1 day). These could be made admin-configurable per
dispute in a follow-up, but a fixed window is simpler and matches the
expected juror response cadence.
any dispute. This is a pre-existing design choice unrelated to this PR.
Architecture
The voting flow is:
raise_dispute → commit window: ~1 day → reveal window: ~1 day → resolve_dispute
commit_vote() reveal_vote()
Hash format:
sha256(vote_byte ++ salt)wherevote_byteis1u8fordepositor,
0u8for beneficiary. The 32-byte salt is chosen by the juroroff-chain and kept secret until reveal.
Out of scope