One-liner: Veil Vote is a minimal, audit-friendly commit–reveal voting system built on Solana using Anchor, designed to give DAOs privacy-preserving voting primitives that are easy to run and extend.
Problem statement
Traditional on-chain DAO voting exposes voter choices as soon as votes are cast or tallied. That creates risks:
- Voter coercion and vote-buying when choices can be immediately verified.
- Information cascades and herding because early tallies influence later voters.
- Privacy loss for members who prefer anonymity.
There is a clear need for simple voting primitives that keep vote choices confidential until an intended reveal phase.
Solution overview
Veil Vote implements a commit–reveal scheme on Solana (Anchor):
- Commit phase: Voters submit a cryptographic commitment (SHA-256 hash of choice || secret). The on-chain program stores only the hash and the voter's commitment record.
- Reveal phase: After the voting deadline, voters publish their cleartext choice and secret. The program verifies the hash and tallies votes.
This approach guarantees that votes are:
- Hidden during the commit phase (no plaintext vote on-chain).
- Binding (voters cannot change their vote without breaking the hash match).
Veil Vote is intentionally minimal so it can be inspected and audited within a hackathon timeframe while remaining a solid foundation for later enhancements.
How commit–reveal preserves privacy
- Commitment: Voter computes
commit = SHA256(choice || secret)locally and sendscommiton-chain. Thesecretnever leaves the voter's client until reveal time (and can be stored client-side securely). - Hiding: Because SHA-256 is one-way, the commitment does not reveal
choiceorsecret. - Binding: On reveal, the program recomputes
SHA256(choice || secret)and compares it to the storedcommit. A mismatch fails the reveal.
Security properties:
- Confidentiality (during commit): The choice cannot be recovered from the hash alone.
- Integrity: A revealed choice is verifiably the same choice that produced the commitment.
- Non-repudiation: The commitment ties a signer to a single revealed vote (double-vote protected by PDAs).
Limitations are provided below; these are deliberate trade-offs for hackathon speed and auditability.
Architecture (ASCII diagram)
Client (browser)
├─ generate secret (32 bytes)
├─ compute commit = SHA256(choice || secret)
├─ store secret locally (localStorage by default)
└─ submit commit to Solana program
Solana Program (Anchor)
├─ create_proposal (proposal metadata + deadline)
├─ commit_vote (store commit hash, voter PDA)
└─ reveal_vote (verify SHA256(choice||secret) == commit, tally)
On-chain storage:
- Proposal account (metadata, deadline, tallies)
- VoteCommitment account (voter pubkey, commit hash, revealed flag)
Data flow summary: Client (choice + secret) -> hash -> commit -> on-chain After deadline: Client -> reveal (choice + secret) -> program verifies + tally
How to run locally (quick)
Prerequisites:
- Node.js and npm
- Rust + Anchor (for building the program)
- Solana CLI configured for localnet or devnet
- A Solana wallet (Phantom recommended for browser testing)
Steps (dev flow):
- Build the Solana program
cd c:\Users\pc\Desktop\Veilvote
anchor build- (Optional) Run tests
anchor test- Start local validator or use devnet
Local validator (optional):
solana-test-validator
# in a new terminal, then deploy to localnet
anchor deploy --provider.cluster localnet- Start the frontend
cd app
npm install
npm run dev
# open http://localhost:3000 in your browser- Workflow
- Use the UI to
Create Proposal(admin key or test key) - Connect wallet and
Commit Vote(the UI will generate a secret and compute a hash) - After the deadline,
Reveal Votewith the secret (or use the stored secret in browser)
Notes:
- The frontend utilities live in
app/src/utils— seevote.tsfor commit hashing and localStorage helpers. - To interact programmatically, consult
INTEGRATION_GUIDE.mdfor Anchor/TypeScript examples.
Limitations (honest, judge-friendly)
- Secrets are stored in browser
localStorageby default (convenient for demos). For production, secrets must be stored encrypted or managed off-chain via secure backends or hardware wallets. - This system does not provide voter anonymity beyond the fact that votes are not stored in plaintext on-chain; voters are still identifiable by their signer pubkey when committing and revealing.
- Tallying is public after reveal — if you need fully private tallies, you'll need more advanced cryptography.
- No anti-coercion cryptographic countermeasures (e.g., receipt-freeness) — these are out of scope for this basic design.
- The implementation uses SHA-256 commitments rather than more advanced primitives like Pedersen commitments or ZK proofs; this was a deliberate simplicity choice.
Roadmap to zero-knowledge proofs (how to get stronger privacy)
-
Replace simple SHA-256 commitments with Pedersen commitments where hiding and homomorphic tallying are needed.
-
Add ZK-SNARK or ZK-STARK based revealless verification: voters submit a ZK proof that they voted one of allowed options without revealing which one.
-
Private tallying via homomorphic aggregation or secure multi-party computation:
- Homomorphic commitments + aggregation on-chain and a ZK proof of correct decryption of the final tally.
-
Integrate an identity-preserving privacy layer (e.g., anonymous credentials) to decouple voter identity from the ballot while preventing double-voting.
-
Audit and optimize gas/compute on Solana (ZK-friendly circuits, batching proofs).
This repository is intentionally structured so the commit–reveal core can be replaced or extended with ZK primitives as a next step.
Why this is hackathon-ready
- Minimal, auditable smart contract surface — easy to review in a short time.
- Clear separation of client-side secrets and on-chain state.
- Straightforward UI and developer workflow so judges can test the full flow in minutes.
- A direct path to more advanced privacy features (ZK) without rearchitecting the whole stack.
If you'd like, I can now:
- Add a short demo script that walks through Create → Commit → Reveal using a test key.
- Add a quick encrypted-secret storage example (browser-based) for stronger demo security.
Files to inspect next: programs/veil_vote/src/lib.rs, app/src/utils/vote.ts, APP_SETUP.md, PRIVACY_ANALYSIS.md.
Thank you — ready to help wire the demo or add the ZK prototype next.
A complete Anchor-based Solana program implementing a private, commit-reveal voting system.
- Create Proposals: Initialize voting proposals with title, description, and deadline
- Commit Votes Privately: Voters submit a hash of their vote without revealing the choice
- Reveal Votes: After voting deadline, votes are revealed and validated against commitments
- Vote Tallying: Automatic counting of Yes (1) and No (0) votes
- Security: Hash-based commitment scheme prevents vote manipulation
veil_vote/
├── Anchor.toml # Anchor configuration
├── Cargo.toml # Workspace Cargo.toml
├── programs/
│ └── veil_vote/
│ ├── Cargo.toml # Program dependencies
│ └── src/
│ └── lib.rs # Main program logic
└── tests/ # Test directory
Stores proposal metadata and vote tallies:
proposal_id: Unique identifiertitle: Proposal title (max 100 chars)description: Proposal description (max 500 chars)authority: Creator's/authority's public keyvoting_deadline: Unix timestamp when voting endstotal_votes: Total votes cast and revealedvote_count_yes: Number of Yes votesvote_count_no: Number of No votescreated_at: Creation timestamp
Records each voter's commitment:
proposal_id: Associated proposalvoter: Voter's public keyvote_hash: Keccak256 hash of (voter || vote_choice || secret)committed_at: Commitment timestamprevealed: Whether vote has been revealedvote_choice: Vote choice (0=No, 1=Yes, only valid after reveal)
fn create_proposal(
proposal_id: u64,
title: String,
description: String,
voting_deadline: i64,
)Creates a new proposal. Only the creator can initialize it.
fn commit_vote(vote_hash: [u8; 32])Voter submits a hash commitment of their vote. Must be done before deadline.
fn reveal_vote(vote_choice: u8, secret: [u8; 32])Voter reveals their vote after deadline. Hash is validated against commitment.
The vote hash is computed as:
hash = Keccak256(voter_pubkey || vote_choice || secret)
Where:
voter_pubkey: 32 bytesvote_choice: 1 byte (0 or 1)secret: 32 bytes
anchor buildanchor testUpdate the program ID in declare_id!() and Anchor.toml before deployment.
anchor deployInvalidDeadline: Voting deadline must be in the futureInvalidTitle: Title length must be 1-100 charactersInvalidDescription: Description length must be 1-500 charactersVotingEnded: Cannot commit votes after deadlineAlreadyVoted: Voter has already committedInvalidVoteChoice: Vote must be 0 (No) or 1 (Yes)VoteAlreadyRevealed: Vote has been revealedUnauthorized: Access deniedInvalidReveal: Reveal hash doesn't match commitmentVoteRevealTooEarly: Cannot reveal before voting deadline ends
- Commitment Scheme: Uses cryptographic hash to hide votes until reveal
- Vote Validation: Reveals are validated against original commitments
- Deadline Enforcement: Voting and reveal phases are time-locked
- Signature Verification: All instructions require signer authorization
- No Vote Selling: Secret is kept off-chain to prevent vote coercion
- Add vote delegation
- Implement vote weight (token-weighted voting)
- Add proposal voting for approval
- Implement timelocks for proposals
- Add vote encryption for additional privacy