# ZKP #3 — Vote Reveal Proof

> **Status: Needs review by Dev**

## ZKP #3 — Vote Reveal Proof

This ZKP opens a single encrypted share from a registered vote commitment, revealing the El Gamal ciphertext for homomorphic accumulation — without revealing the plaintext amount or which vote commitment the share came from.

### Public inputs

* `share_nullifier` — prevents double-counting
* `enc_share_c1_x` — x-coordinate of the El Gamal C1 ciphertext
* `enc_share_c1_y` — y-coordinate of the El Gamal C1 ciphertext
* `enc_share_c2_x` — x-coordinate of the El Gamal C2 ciphertext
* `enc_share_c2_y` — y-coordinate of the El Gamal C2 ciphertext
* `proposal_id` — which proposal
* `vote_decision` — the voter's choice
* `vote_comm_tree_root` — root of the vote commitment tree
* `voting_round_id`

Note: `vote_comm_tree_anchor_height` is used by the chain's ante handler to look up the correct tree root, but is not itself a circuit public input. Both coordinates of each ciphertext point are public inputs — the y-coordinates bind the proof to the exact curve point, preventing sign-malleability attacks where an adversary negates the ElGamal ciphertext without invalidating the share commitment.

### Witness (private inputs)

* `vote_commitment` — the VC being opened (hidden)
* `vote_comm_tree_path, vote_comm_tree_position` — Merkle proof for VC membership
* `shares_hash` — hash of all 16 blinded share commitments (`share_comm_i = H(blind_i, c1_i_x, c2_i_x, c1_i_y, c2_i_y)`)
* `share_index` — which of the 16 shares (0..15)
* `share_comm_0, ..., share_comm_15` — all 16 blinded share commitments (to recompute `shares_hash`)
* `blind` — the blind factor for the revealed share (at position `share_index`)

### Conditions

#### Vote commitment membership (conditions 1-2)

1. [**Merkle Tree Membership**](https://valargroup.gitbook.io/shielded-vote-docs/circuit-components/merkle-tree-membership) — VC exists in vote commitment tree, without revealing which one
2. [**Vote Commitment Integrity**](https://valargroup.gitbook.io/shielded-vote-docs/circuit-components/vote-commitment-integrity) — `vote_commitment = H(DOMAIN_VC, voting_round_id, shares_hash, proposal_id, vote_decision)`

#### Share opening (conditions 3-4)

3. [**Shares Hash Integrity**](https://valargroup.gitbook.io/shielded-vote-docs/circuit-components/shares-hash-integrity) — Recomputes `shares_hash` from witness share commitments (the server receives blinded commitments, not raw ciphertexts/blinds of other shares)
4. [**Share Membership**](https://github.com/z-cale/shielded-vote-book/blob/main/circuits/share-membership.md) — `Poseidon(blind, enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y) = share_comms[share_index]` (the commitment derived from the public ciphertext and witness blind matches the committed set)

#### Nullifier (condition 5)

5. [**Share Nullifier**](https://valargroup.gitbook.io/shielded-vote-docs/circuit-components/share-nullifier) — `share_nullifier = H("share spend", vote_commitment, share_index, blind)`

### Out-of-circuit checks

* `share_nullifier` not seen before
* `vote_comm_tree_root` matches published root
* `proposal_id` is valid
* `voting_round_id` matches active round

### Who runs this

This ZKP is constructed by the **submission server**, not the client. The client sends the share data as a payload; the server computes the proof and submits it at a randomized delay. See [Server-Delegated Share Submission](https://valargroup.gitbook.io/shielded-vote-docs/delegation/server-delegated-shares).
